repowise 0.1.82 → 0.1.83
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 +1547 -762
- 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,819 @@ 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();
|
|
679
1423
|
for (const repoId of allRepoIds) {
|
|
680
1424
|
if (!state.repos[repoId]) {
|
|
681
1425
|
state.repos[repoId] = { lastSyncTimestamp: now, lastSyncCommitSha: null };
|
|
682
1426
|
}
|
|
683
1427
|
}
|
|
1428
|
+
await saveState(state);
|
|
1429
|
+
const packageName = true ? "repowise" : "repowise";
|
|
1430
|
+
let currentVersion = "";
|
|
1431
|
+
try {
|
|
1432
|
+
const selfDir = dirname4(fileURLToPath2(import.meta.url));
|
|
1433
|
+
const pkgJsonPath = join12(selfDir, "..", "..", "package.json");
|
|
1434
|
+
const pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf-8"));
|
|
1435
|
+
currentVersion = pkgJson.version;
|
|
1436
|
+
} catch (err) {
|
|
1437
|
+
console.log(`[auto-update] Version detection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1438
|
+
}
|
|
684
1439
|
let pollIntervalMs = 5e3;
|
|
685
1440
|
let pollCycleCount = 0;
|
|
686
1441
|
const RECONCILE_EVERY_N_CYCLES = 60;
|
|
@@ -700,10 +1455,24 @@ async function startListener(options) {
|
|
|
700
1455
|
console.log("Shutting down...");
|
|
701
1456
|
stop();
|
|
702
1457
|
await saveState(state);
|
|
703
|
-
|
|
1458
|
+
if (releaseLock) {
|
|
1459
|
+
try {
|
|
1460
|
+
await releaseLock();
|
|
1461
|
+
} catch {
|
|
1462
|
+
}
|
|
1463
|
+
releaseLock = null;
|
|
1464
|
+
}
|
|
704
1465
|
};
|
|
705
|
-
process.on("SIGTERM", () =>
|
|
706
|
-
|
|
1466
|
+
process.on("SIGTERM", () => {
|
|
1467
|
+
shutdown().catch((err) => {
|
|
1468
|
+
console.error("Shutdown error:", err);
|
|
1469
|
+
}).finally(() => process.exit(0));
|
|
1470
|
+
});
|
|
1471
|
+
process.on("SIGINT", () => {
|
|
1472
|
+
shutdown().catch((err) => {
|
|
1473
|
+
console.error("Shutdown error:", err);
|
|
1474
|
+
}).finally(() => process.exit(0));
|
|
1475
|
+
});
|
|
707
1476
|
while (running) {
|
|
708
1477
|
let anyAuthError = null;
|
|
709
1478
|
let authErrorGroup = null;
|
|
@@ -711,6 +1480,7 @@ async function startListener(options) {
|
|
|
711
1480
|
let connectionLostNotified = false;
|
|
712
1481
|
pollCycleCount++;
|
|
713
1482
|
const shouldReconcile = pollCycleCount % RECONCILE_EVERY_N_CYCLES === 0;
|
|
1483
|
+
let latestCliVersion;
|
|
714
1484
|
for (const group of groups) {
|
|
715
1485
|
if (!running)
|
|
716
1486
|
break;
|
|
@@ -725,9 +1495,14 @@ async function startListener(options) {
|
|
|
725
1495
|
const response = await group.pollClient.poll(group.repoIds, sinceTimestamp, {
|
|
726
1496
|
includeActiveRepos: shouldReconcile
|
|
727
1497
|
});
|
|
1498
|
+
if (response.latestCliVersion) {
|
|
1499
|
+
latestCliVersion = response.latestCliVersion;
|
|
1500
|
+
}
|
|
728
1501
|
if (response.activeRepos && response.activeRepos.length > 0) {
|
|
729
1502
|
try {
|
|
730
|
-
const result = reconcileRepos(config2.repos, response.activeRepos, state, group.apiUrl
|
|
1503
|
+
const result = reconcileRepos(config2.repos, response.activeRepos, state, group.apiUrl, {
|
|
1504
|
+
autoDiscover: config2.autoDiscoverRepos
|
|
1505
|
+
});
|
|
731
1506
|
if (result.updated) {
|
|
732
1507
|
config2.repos = result.repos;
|
|
733
1508
|
await saveListenerConfig(config2);
|
|
@@ -742,6 +1517,19 @@ async function startListener(options) {
|
|
|
742
1517
|
for (const change of result.changes) {
|
|
743
1518
|
console.log(`[reconcile] ${change}`);
|
|
744
1519
|
}
|
|
1520
|
+
if (result.addedRepos.length > 0) {
|
|
1521
|
+
for (const addedRepo of result.addedRepos) {
|
|
1522
|
+
const apiUrl = addedRepo.apiUrl ?? config2.defaultApiUrl;
|
|
1523
|
+
try {
|
|
1524
|
+
const fetchResult = await fetchContextFromServer(addedRepo.repoId, addedRepo.localPath, apiUrl);
|
|
1525
|
+
if (fetchResult.success) {
|
|
1526
|
+
console.log(`[self-heal] Downloaded ${fetchResult.updatedFiles.length} context files for ${addedRepo.repoId}`);
|
|
1527
|
+
}
|
|
1528
|
+
} catch (fetchErr) {
|
|
1529
|
+
console.warn(`[self-heal] Context download failed for ${addedRepo.repoId}:`, fetchErr instanceof Error ? fetchErr.message : String(fetchErr));
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
745
1533
|
}
|
|
746
1534
|
} catch (reconcileErr) {
|
|
747
1535
|
console.warn("Repo reconciliation failed \u2014 will retry next cycle", reconcileErr instanceof Error ? reconcileErr.message : String(reconcileErr));
|
|
@@ -783,6 +1571,49 @@ async function startListener(options) {
|
|
|
783
1571
|
console.error(`Poll failed for ${group.apiUrl} (attempt ${group.offline.attemptCount}): ${message}. Retrying in ${Math.round(delay2 / 1e3)}s`);
|
|
784
1572
|
}
|
|
785
1573
|
}
|
|
1574
|
+
if (shouldReconcile) {
|
|
1575
|
+
try {
|
|
1576
|
+
const staleDirty = await checkStaleContext(config2.repos, state, groups);
|
|
1577
|
+
if (staleDirty) {
|
|
1578
|
+
await saveState(state);
|
|
1579
|
+
}
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
console.warn("[self-heal] Stale context check failed:", err instanceof Error ? err.message : String(err));
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
if (latestCliVersion && currentVersion && !config2.noAutoUpdate && (state.crashCount ?? 0) < CRASH_LOOP_THRESHOLD) {
|
|
1585
|
+
if (latestCliVersion !== state.lastUpdateTargetVersion) {
|
|
1586
|
+
state.updateFailCount = 0;
|
|
1587
|
+
state.lastUpdateAttemptAt = void 0;
|
|
1588
|
+
state.lastUpdateTargetVersion = latestCliVersion;
|
|
1589
|
+
}
|
|
1590
|
+
const backoffMs = Math.min(5 * 60 * 1e3 * Math.pow(2, state.updateFailCount ?? 0), 60 * 60 * 1e3);
|
|
1591
|
+
const lastAttempt = state.lastUpdateAttemptAt ? new Date(state.lastUpdateAttemptAt).getTime() : 0;
|
|
1592
|
+
if (Date.now() - lastAttempt >= backoffMs) {
|
|
1593
|
+
const listenerStatus = await getListenerStatus();
|
|
1594
|
+
state.lastUpdateAttemptAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1595
|
+
const updateResult = await installUpdate(currentVersion, packageName, latestCliVersion);
|
|
1596
|
+
if (updateResult.updated) {
|
|
1597
|
+
state.updateFailCount = 0;
|
|
1598
|
+
await saveState(state);
|
|
1599
|
+
if (listenerStatus.serviceInstalled) {
|
|
1600
|
+
console.log("[auto-update] Restarting with new version...");
|
|
1601
|
+
await shutdown();
|
|
1602
|
+
process.exit(0);
|
|
1603
|
+
} else {
|
|
1604
|
+
console.log("[auto-update] Spawning new listener with updated version...");
|
|
1605
|
+
await shutdown();
|
|
1606
|
+
await startBackground();
|
|
1607
|
+
process.exit(0);
|
|
1608
|
+
}
|
|
1609
|
+
} else if (updateResult.error) {
|
|
1610
|
+
state.updateFailCount = (state.updateFailCount ?? 0) + 1;
|
|
1611
|
+
await saveState(state);
|
|
1612
|
+
} else {
|
|
1613
|
+
await saveState(state);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
786
1617
|
if (anyAuthError) {
|
|
787
1618
|
let recovered = false;
|
|
788
1619
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
@@ -812,8 +1643,19 @@ async function startListener(options) {
|
|
|
812
1643
|
} catch {
|
|
813
1644
|
}
|
|
814
1645
|
}
|
|
1646
|
+
const credentialsPath = join12(getConfigDir(), "credentials.json");
|
|
1647
|
+
let credentialsChanged = false;
|
|
1648
|
+
let watcher = null;
|
|
1649
|
+
try {
|
|
1650
|
+
const fs = await import("fs");
|
|
1651
|
+
watcher = fs.watch(credentialsPath, () => {
|
|
1652
|
+
credentialsChanged = true;
|
|
1653
|
+
});
|
|
1654
|
+
} catch {
|
|
1655
|
+
}
|
|
815
1656
|
while (running) {
|
|
816
|
-
await sleep(
|
|
1657
|
+
await sleep(credentialsChanged ? 0 : 1e4);
|
|
1658
|
+
credentialsChanged = false;
|
|
817
1659
|
if (!running)
|
|
818
1660
|
break;
|
|
819
1661
|
const fresh = await getValidCredentials({ forceRefresh: true });
|
|
@@ -822,452 +1664,170 @@ async function startListener(options) {
|
|
|
822
1664
|
break;
|
|
823
1665
|
}
|
|
824
1666
|
}
|
|
1667
|
+
if (watcher)
|
|
1668
|
+
watcher.close();
|
|
825
1669
|
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));
|
|
1670
|
+
}
|
|
1671
|
+
pollIntervalMs = minPollInterval;
|
|
1672
|
+
await sleep(pollIntervalMs);
|
|
1001
1673
|
}
|
|
1002
|
-
if (
|
|
1674
|
+
if (releaseLock) {
|
|
1003
1675
|
try {
|
|
1004
|
-
|
|
1676
|
+
await releaseLock();
|
|
1005
1677
|
} catch {
|
|
1006
1678
|
}
|
|
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"] };
|
|
1679
|
+
releaseLock = null;
|
|
1045
1680
|
}
|
|
1046
1681
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
resolve(String(stdout ?? ""));
|
|
1055
|
-
});
|
|
1682
|
+
var isDirectRun = process.argv[1]?.endsWith("main.js") || process.argv[1]?.endsWith("main.ts");
|
|
1683
|
+
if (isDirectRun) {
|
|
1684
|
+
startListener().catch((err) => {
|
|
1685
|
+
console.error("Listener fatal error:", err);
|
|
1686
|
+
process.exitCode = 1;
|
|
1056
1687
|
});
|
|
1057
1688
|
}
|
|
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
|
-
}
|
|
1689
|
+
|
|
1690
|
+
// src/lib/env.ts
|
|
1691
|
+
import { homedir as homedir4 } from "os";
|
|
1692
|
+
import { join as join13 } from "path";
|
|
1693
|
+
var IS_STAGING2 = true ? false : false;
|
|
1694
|
+
var PRODUCTION = {
|
|
1695
|
+
apiUrl: "https://api.repowise.ai",
|
|
1696
|
+
cognitoDomain: "auth.repowise.ai",
|
|
1697
|
+
cognitoClientId: "50so1fkmjbqt1ufnsmbhsjbtst",
|
|
1698
|
+
cognitoRegion: "us-east-1",
|
|
1699
|
+
customDomain: true
|
|
1700
|
+
};
|
|
1701
|
+
var STAGING = {
|
|
1702
|
+
apiUrl: "https://staging-api.repowise.ai",
|
|
1703
|
+
cognitoDomain: "auth-staging.repowise.ai",
|
|
1704
|
+
cognitoClientId: "7h0l0dhjcb1v5erer0gaclv0q6",
|
|
1705
|
+
cognitoRegion: "us-east-1",
|
|
1706
|
+
customDomain: true
|
|
1707
|
+
};
|
|
1708
|
+
function getEnvConfig() {
|
|
1709
|
+
return IS_STAGING2 ? STAGING : PRODUCTION;
|
|
1112
1710
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
const stdout = await exec("launchctl", ["list"]);
|
|
1116
|
-
return stdout.includes(PLIST_LABEL);
|
|
1117
|
-
} catch {
|
|
1118
|
-
return false;
|
|
1119
|
-
}
|
|
1711
|
+
function getConfigDir2() {
|
|
1712
|
+
return join13(homedir4(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
|
|
1120
1713
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
return join9(homedir7(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
|
|
1714
|
+
function getPackageName() {
|
|
1715
|
+
return true ? "repowise" : "repowise";
|
|
1124
1716
|
}
|
|
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
1717
|
|
|
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")}
|
|
1718
|
+
// src/lib/welcome.ts
|
|
1719
|
+
import chalk from "chalk";
|
|
1141
1720
|
|
|
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
|
-
}
|
|
1721
|
+
// src/lib/config.ts
|
|
1722
|
+
import { readFile as readFile6, writeFile as writeFile8, mkdir as mkdir8, rename as rename3, unlink as unlink6 } from "fs/promises";
|
|
1723
|
+
import { join as join14 } from "path";
|
|
1724
|
+
import lockfile3 from "proper-lockfile";
|
|
1725
|
+
async function getConfig() {
|
|
1166
1726
|
try {
|
|
1167
|
-
await
|
|
1727
|
+
const data = await readFile6(join14(getConfigDir2(), "config.json"), "utf-8");
|
|
1728
|
+
return JSON.parse(data);
|
|
1168
1729
|
} catch {
|
|
1730
|
+
return {};
|
|
1169
1731
|
}
|
|
1170
1732
|
}
|
|
1171
|
-
async function
|
|
1733
|
+
async function saveConfig(config2) {
|
|
1734
|
+
const dir = getConfigDir2();
|
|
1735
|
+
const path = join14(dir, "config.json");
|
|
1736
|
+
await mkdir8(dir, { recursive: true });
|
|
1737
|
+
const tmpPath = path + ".tmp";
|
|
1172
1738
|
try {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
} catch {
|
|
1176
|
-
|
|
1739
|
+
await writeFile8(tmpPath, JSON.stringify(config2, null, 2));
|
|
1740
|
+
await rename3(tmpPath, path);
|
|
1741
|
+
} catch (err) {
|
|
1742
|
+
try {
|
|
1743
|
+
await unlink6(tmpPath);
|
|
1744
|
+
} catch {
|
|
1745
|
+
}
|
|
1746
|
+
throw err;
|
|
1177
1747
|
}
|
|
1178
1748
|
}
|
|
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
|
-
}
|
|
1749
|
+
async function mergeAndSaveConfig(updates) {
|
|
1750
|
+
const dir = getConfigDir2();
|
|
1751
|
+
const path = join14(dir, "config.json");
|
|
1752
|
+
await mkdir8(dir, { recursive: true });
|
|
1203
1753
|
try {
|
|
1204
|
-
await
|
|
1754
|
+
await writeFile8(path, "", { flag: "a" });
|
|
1205
1755
|
} catch {
|
|
1206
1756
|
}
|
|
1207
|
-
|
|
1208
|
-
async function win32IsInstalled() {
|
|
1757
|
+
let release = null;
|
|
1209
1758
|
try {
|
|
1210
|
-
await
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1759
|
+
release = await lockfile3.lock(path, { stale: 1e4, retries: 3, realpath: false });
|
|
1760
|
+
let raw = {};
|
|
1761
|
+
try {
|
|
1762
|
+
raw = JSON.parse(await readFile6(path, "utf-8"));
|
|
1763
|
+
} catch {
|
|
1764
|
+
}
|
|
1765
|
+
const merged = { ...raw, ...updates };
|
|
1766
|
+
if (updates.repos) {
|
|
1767
|
+
const existingRepos = Array.isArray(raw["repos"]) ? raw["repos"] : [];
|
|
1768
|
+
const updatedRepoIds = new Set(updates.repos.map((r) => r.repoId));
|
|
1769
|
+
merged.repos = [
|
|
1770
|
+
...existingRepos.filter((r) => !updatedRepoIds.has(r.repoId)),
|
|
1771
|
+
...updates.repos
|
|
1772
|
+
];
|
|
1773
|
+
}
|
|
1774
|
+
await saveConfig(merged);
|
|
1775
|
+
} finally {
|
|
1776
|
+
if (release) {
|
|
1777
|
+
try {
|
|
1778
|
+
await release();
|
|
1779
|
+
} catch {
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1229
1782
|
}
|
|
1230
1783
|
}
|
|
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
|
-
}
|
|
1784
|
+
|
|
1785
|
+
// src/lib/welcome.ts
|
|
1786
|
+
var W = 41;
|
|
1787
|
+
function row(styled, visible) {
|
|
1788
|
+
return chalk.cyan(" \u2502") + styled + " ".repeat(W - visible) + chalk.cyan("\u2502");
|
|
1245
1789
|
}
|
|
1246
|
-
async function
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1790
|
+
async function showWelcome(currentVersion) {
|
|
1791
|
+
try {
|
|
1792
|
+
const config2 = await getConfig();
|
|
1793
|
+
if (config2.lastSeenVersion === currentVersion) {
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
const isUpgrade = !!config2.lastSeenVersion;
|
|
1797
|
+
const tag = isUpgrade ? "updated" : "installed";
|
|
1798
|
+
const titleText = `RepoWise v${currentVersion}`;
|
|
1799
|
+
const titleStyled = " " + chalk.bold(titleText) + chalk.green(` \u2713 ${tag}`);
|
|
1800
|
+
const titleVisible = 3 + titleText.length + 3 + tag.length;
|
|
1801
|
+
const border = "\u2500".repeat(W);
|
|
1802
|
+
console.log("");
|
|
1803
|
+
console.log(chalk.cyan(` \u256D${border}\u256E`));
|
|
1804
|
+
console.log(row("", 0));
|
|
1805
|
+
console.log(row(titleStyled, titleVisible));
|
|
1806
|
+
console.log(row("", 0));
|
|
1807
|
+
console.log(row(" " + chalk.dim("Get started:"), 15));
|
|
1808
|
+
console.log(row(" $ " + chalk.bold("repowise create"), 22));
|
|
1809
|
+
console.log(row("", 0));
|
|
1810
|
+
console.log(row(" " + chalk.dim("Thank you for using RepoWise!"), 32));
|
|
1811
|
+
console.log(row(" " + chalk.dim("https://repowise.ai"), 22));
|
|
1812
|
+
console.log(row("", 0));
|
|
1813
|
+
console.log(chalk.cyan(` \u2570${border}\u256F`));
|
|
1814
|
+
console.log("");
|
|
1815
|
+
await mergeAndSaveConfig({ lastSeenVersion: currentVersion });
|
|
1816
|
+
} catch {
|
|
1256
1817
|
}
|
|
1257
1818
|
}
|
|
1258
1819
|
|
|
1259
1820
|
// src/commands/create.ts
|
|
1821
|
+
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1822
|
+
import { dirname as dirname6, join as join18 } from "path";
|
|
1260
1823
|
import chalk5 from "chalk";
|
|
1261
1824
|
import ora from "ora";
|
|
1262
1825
|
|
|
1263
1826
|
// src/lib/auth.ts
|
|
1264
1827
|
import { createHash, randomBytes } from "crypto";
|
|
1265
|
-
import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, chmod as chmod4, unlink as
|
|
1828
|
+
import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, chmod as chmod4, unlink as unlink7 } from "fs/promises";
|
|
1266
1829
|
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");
|
|
1830
|
+
import { join as join15 } from "path";
|
|
1271
1831
|
var CLI_CALLBACK_PORT = 19876;
|
|
1272
1832
|
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
1273
1833
|
function getCognitoConfigForStorage() {
|
|
@@ -1433,7 +1993,8 @@ async function refreshTokens2(refreshToken) {
|
|
|
1433
1993
|
}
|
|
1434
1994
|
async function getStoredCredentials2() {
|
|
1435
1995
|
try {
|
|
1436
|
-
const
|
|
1996
|
+
const credPath = join15(getConfigDir2(), "credentials.json");
|
|
1997
|
+
const data = await readFile7(credPath, "utf-8");
|
|
1437
1998
|
return JSON.parse(data);
|
|
1438
1999
|
} catch (err) {
|
|
1439
2000
|
if (err.code === "ENOENT" || err instanceof SyntaxError) {
|
|
@@ -1443,13 +2004,15 @@ async function getStoredCredentials2() {
|
|
|
1443
2004
|
}
|
|
1444
2005
|
}
|
|
1445
2006
|
async function storeCredentials2(credentials) {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
await
|
|
2007
|
+
const dir = getConfigDir2();
|
|
2008
|
+
const credPath = join15(dir, "credentials.json");
|
|
2009
|
+
await mkdir9(dir, { recursive: true, mode: 448 });
|
|
2010
|
+
await writeFile9(credPath, JSON.stringify(credentials, null, 2));
|
|
2011
|
+
await chmod4(credPath, 384);
|
|
1449
2012
|
}
|
|
1450
2013
|
async function clearCredentials() {
|
|
1451
2014
|
try {
|
|
1452
|
-
await
|
|
2015
|
+
await unlink7(join15(getConfigDir2(), "credentials.json"));
|
|
1453
2016
|
} catch (err) {
|
|
1454
2017
|
if (err.code !== "ENOENT") throw err;
|
|
1455
2018
|
}
|
|
@@ -1641,7 +2204,7 @@ async function selectAiTools() {
|
|
|
1641
2204
|
|
|
1642
2205
|
// src/lib/ai-tools.ts
|
|
1643
2206
|
import { readFile as readFile8, writeFile as writeFile10, mkdir as mkdir10, readdir } from "fs/promises";
|
|
1644
|
-
import { join as
|
|
2207
|
+
import { join as join16, dirname as dirname5 } from "path";
|
|
1645
2208
|
var AI_TOOL_CONFIG = {
|
|
1646
2209
|
cursor: {
|
|
1647
2210
|
label: "Cursor",
|
|
@@ -1770,8 +2333,8 @@ function generateReference(tool, repoName, contextFolder, contextFiles) {
|
|
|
1770
2333
|
}
|
|
1771
2334
|
async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
|
|
1772
2335
|
const config2 = AI_TOOL_CONFIG[tool];
|
|
1773
|
-
const fullPath =
|
|
1774
|
-
const dir =
|
|
2336
|
+
const fullPath = join16(repoRoot, config2.filePath);
|
|
2337
|
+
const dir = dirname5(fullPath);
|
|
1775
2338
|
if (dir !== repoRoot) {
|
|
1776
2339
|
await mkdir10(dir, { recursive: true });
|
|
1777
2340
|
}
|
|
@@ -1799,14 +2362,14 @@ async function updateToolConfig(repoRoot, tool, repoName, contextFolder, context
|
|
|
1799
2362
|
return { created };
|
|
1800
2363
|
}
|
|
1801
2364
|
async function scanLocalContextFiles(repoRoot, contextFolder) {
|
|
1802
|
-
const folderPath =
|
|
2365
|
+
const folderPath = join16(repoRoot, contextFolder);
|
|
1803
2366
|
try {
|
|
1804
2367
|
const entries = await readdir(folderPath, { withFileTypes: true, recursive: true });
|
|
1805
2368
|
const results = [];
|
|
1806
2369
|
for (const entry of entries) {
|
|
1807
2370
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
1808
2371
|
const parentDir = entry.parentPath ?? folderPath;
|
|
1809
|
-
const fullPath =
|
|
2372
|
+
const fullPath = join16(parentDir, entry.name);
|
|
1810
2373
|
const relFromContext = fullPath.slice(folderPath.length + 1);
|
|
1811
2374
|
results.push({
|
|
1812
2375
|
fileName: relFromContext,
|
|
@@ -1822,9 +2385,9 @@ async function scanLocalContextFiles(repoRoot, contextFolder) {
|
|
|
1822
2385
|
|
|
1823
2386
|
// src/lib/gitignore.ts
|
|
1824
2387
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
1825
|
-
import { join as
|
|
2388
|
+
import { join as join17 } from "path";
|
|
1826
2389
|
function ensureGitignore(repoRoot, entry) {
|
|
1827
|
-
const gitignorePath =
|
|
2390
|
+
const gitignorePath = join17(repoRoot, ".gitignore");
|
|
1828
2391
|
if (existsSync(gitignorePath)) {
|
|
1829
2392
|
const content = readFileSync(gitignorePath, "utf-8");
|
|
1830
2393
|
const lines = content.split("\n").map((l) => l.trim());
|
|
@@ -1838,6 +2401,24 @@ function ensureGitignore(repoRoot, entry) {
|
|
|
1838
2401
|
}
|
|
1839
2402
|
}
|
|
1840
2403
|
|
|
2404
|
+
// src/lib/git.ts
|
|
2405
|
+
import { execSync } from "child_process";
|
|
2406
|
+
function detectRepoRoot() {
|
|
2407
|
+
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
2408
|
+
}
|
|
2409
|
+
function detectRepoName(repoRoot) {
|
|
2410
|
+
try {
|
|
2411
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
2412
|
+
encoding: "utf-8",
|
|
2413
|
+
cwd: repoRoot
|
|
2414
|
+
}).trim();
|
|
2415
|
+
const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
|
|
2416
|
+
if (match?.[1]) return match[1];
|
|
2417
|
+
} catch {
|
|
2418
|
+
}
|
|
2419
|
+
return repoRoot.split("/").pop() ?? "unknown";
|
|
2420
|
+
}
|
|
2421
|
+
|
|
1841
2422
|
// src/lib/interview-handler.ts
|
|
1842
2423
|
import chalk3 from "chalk";
|
|
1843
2424
|
import { input } from "@inquirer/prompts";
|
|
@@ -2081,7 +2662,9 @@ var ProgressRenderer = class {
|
|
|
2081
2662
|
spinner.stop();
|
|
2082
2663
|
console.log(chalk4.cyan.bold(" \u2500\u2500 Context Generation \u2500\u2500"));
|
|
2083
2664
|
console.log(
|
|
2084
|
-
chalk4.cyan(
|
|
2665
|
+
chalk4.cyan(
|
|
2666
|
+
" \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!"
|
|
2667
|
+
)
|
|
2085
2668
|
);
|
|
2086
2669
|
console.log("");
|
|
2087
2670
|
spinner.start();
|
|
@@ -2247,21 +2830,6 @@ var ProgressRenderer = class {
|
|
|
2247
2830
|
};
|
|
2248
2831
|
|
|
2249
2832
|
// 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
2833
|
function formatElapsed(ms) {
|
|
2266
2834
|
const totalSeconds = Math.round(ms / 1e3);
|
|
2267
2835
|
const minutes = Math.floor(totalSeconds / 60);
|
|
@@ -2354,12 +2922,18 @@ async function create() {
|
|
|
2354
2922
|
console.log(
|
|
2355
2923
|
chalk5.cyan(
|
|
2356
2924
|
`
|
|
2357
|
-
|
|
2358
|
-
|
|
2925
|
+
If you're a team member, run: ${chalk5.bold("repowise member")}
|
|
2926
|
+
This will download context and configure your AI tools.
|
|
2927
|
+
`
|
|
2928
|
+
)
|
|
2929
|
+
);
|
|
2930
|
+
console.log(
|
|
2931
|
+
chalk5.dim(
|
|
2932
|
+
` To trigger a new full scan, visit: https://app.repowise.ai/repos/${repoId}
|
|
2933
|
+
To sync recent changes, use: repowise sync
|
|
2359
2934
|
`
|
|
2360
2935
|
)
|
|
2361
2936
|
);
|
|
2362
|
-
console.log(chalk5.dim(" To sync recent changes, use: repowise sync\n"));
|
|
2363
2937
|
process.exitCode = 1;
|
|
2364
2938
|
return;
|
|
2365
2939
|
}
|
|
@@ -2487,7 +3061,7 @@ async function create() {
|
|
|
2487
3061
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
2488
3062
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
2489
3063
|
if (files.length > 0) {
|
|
2490
|
-
const contextDir =
|
|
3064
|
+
const contextDir = join18(repoRoot, DEFAULT_CONTEXT_FOLDER);
|
|
2491
3065
|
mkdirSync(contextDir, { recursive: true });
|
|
2492
3066
|
let downloadedCount = 0;
|
|
2493
3067
|
let failedCount = 0;
|
|
@@ -2501,8 +3075,8 @@ async function create() {
|
|
|
2501
3075
|
const response = await fetch(presignedUrl);
|
|
2502
3076
|
if (response.ok) {
|
|
2503
3077
|
const content = await response.text();
|
|
2504
|
-
const filePath =
|
|
2505
|
-
mkdirSync(
|
|
3078
|
+
const filePath = join18(contextDir, file.fileName);
|
|
3079
|
+
mkdirSync(dirname6(filePath), { recursive: true });
|
|
2506
3080
|
writeFileSync2(filePath, content, "utf-8");
|
|
2507
3081
|
downloadedCount++;
|
|
2508
3082
|
} else {
|
|
@@ -2563,33 +3137,27 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
2563
3137
|
spinner.succeed("AI tools configured");
|
|
2564
3138
|
console.log(chalk5.dim(results.join("\n")));
|
|
2565
3139
|
}
|
|
2566
|
-
const
|
|
2567
|
-
const existingRepos = existingConfig.repos ?? [];
|
|
2568
|
-
const updatedRepos = existingRepos.filter((r) => r.repoId !== repoId);
|
|
3140
|
+
const updatedRepos = [];
|
|
2569
3141
|
if (repoRoot) {
|
|
2570
3142
|
const repoEntry = {
|
|
2571
3143
|
repoId,
|
|
2572
|
-
localPath: repoRoot
|
|
3144
|
+
localPath: repoRoot,
|
|
3145
|
+
apiUrl: getEnvConfig().apiUrl
|
|
2573
3146
|
};
|
|
2574
|
-
if (isStagingMode()) {
|
|
2575
|
-
repoEntry.apiUrl = getEnvConfig().apiUrl;
|
|
2576
|
-
}
|
|
2577
3147
|
if (repoPlatform) repoEntry.platform = repoPlatform;
|
|
2578
3148
|
if (repoExternalId) repoEntry.externalId = repoExternalId;
|
|
2579
3149
|
updatedRepos.push(repoEntry);
|
|
2580
3150
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
contextFolder,
|
|
2585
|
-
repos: updatedRepos
|
|
2586
|
-
});
|
|
3151
|
+
const configUpdate = { aiTools: tools, contextFolder };
|
|
3152
|
+
if (updatedRepos.length > 0) configUpdate.repos = updatedRepos;
|
|
3153
|
+
await mergeAndSaveConfig(configUpdate);
|
|
2587
3154
|
let listenerRunning = false;
|
|
2588
3155
|
try {
|
|
2589
|
-
await
|
|
2590
|
-
await startBackground();
|
|
3156
|
+
await ensureListenerRunning();
|
|
2591
3157
|
listenerRunning = true;
|
|
2592
3158
|
} catch {
|
|
3159
|
+
}
|
|
3160
|
+
if (!listenerRunning) {
|
|
2593
3161
|
console.log(
|
|
2594
3162
|
chalk5.yellow(
|
|
2595
3163
|
"Warning: Could not start listener automatically. Run the following to enable it:"
|
|
@@ -2626,11 +3194,255 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
2626
3194
|
}
|
|
2627
3195
|
}
|
|
2628
3196
|
|
|
2629
|
-
// src/commands/
|
|
3197
|
+
// src/commands/member.ts
|
|
3198
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3199
|
+
import { dirname as dirname7, join as join19 } from "path";
|
|
2630
3200
|
import chalk6 from "chalk";
|
|
2631
3201
|
import ora2 from "ora";
|
|
3202
|
+
var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
|
|
3203
|
+
function formatElapsed2(ms) {
|
|
3204
|
+
const totalSeconds = Math.round(ms / 1e3);
|
|
3205
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
3206
|
+
const seconds = totalSeconds % 60;
|
|
3207
|
+
if (minutes === 0) return `${seconds}s`;
|
|
3208
|
+
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
3209
|
+
}
|
|
3210
|
+
async function member() {
|
|
3211
|
+
const startTime = Date.now();
|
|
3212
|
+
const spinner = ora2("Checking authentication...").start();
|
|
3213
|
+
try {
|
|
3214
|
+
let credentials = await getValidCredentials2();
|
|
3215
|
+
if (!credentials) {
|
|
3216
|
+
spinner.info(chalk6.yellow("Not logged in. Opening browser to authenticate..."));
|
|
3217
|
+
credentials = await performLogin();
|
|
3218
|
+
const { email } = decodeIdToken(credentials.idToken);
|
|
3219
|
+
spinner.succeed(chalk6.green(`Authenticated as ${chalk6.bold(email)}`));
|
|
3220
|
+
} else {
|
|
3221
|
+
spinner.succeed("Authenticated");
|
|
3222
|
+
}
|
|
3223
|
+
let repoId;
|
|
3224
|
+
let repoName;
|
|
3225
|
+
let repoRoot;
|
|
3226
|
+
let repoPlatform;
|
|
3227
|
+
let repoExternalId;
|
|
3228
|
+
spinner.start("Checking for pending repository...");
|
|
3229
|
+
try {
|
|
3230
|
+
const pending = await apiRequest("/v1/onboarding/pending");
|
|
3231
|
+
if (pending?.repoId) {
|
|
3232
|
+
repoId = pending.repoId;
|
|
3233
|
+
repoName = pending.repoName;
|
|
3234
|
+
spinner.succeed(`Found pending repository: ${chalk6.bold(repoName)}`);
|
|
3235
|
+
apiRequest("/v1/onboarding/pending", { method: "DELETE" }).catch(() => {
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
} catch {
|
|
3239
|
+
}
|
|
3240
|
+
if (!repoId) {
|
|
3241
|
+
spinner.text = "Detecting repository...";
|
|
3242
|
+
try {
|
|
3243
|
+
repoRoot = detectRepoRoot();
|
|
3244
|
+
repoName = detectRepoName(repoRoot);
|
|
3245
|
+
spinner.succeed(`Repository: ${chalk6.bold(repoName)}`);
|
|
3246
|
+
} catch {
|
|
3247
|
+
spinner.fail(
|
|
3248
|
+
chalk6.red("Not in a git repository. Run this command from your project directory.")
|
|
3249
|
+
);
|
|
3250
|
+
process.exitCode = 1;
|
|
3251
|
+
return;
|
|
3252
|
+
}
|
|
3253
|
+
} else {
|
|
3254
|
+
try {
|
|
3255
|
+
repoRoot = detectRepoRoot();
|
|
3256
|
+
const localRepoName = detectRepoName(repoRoot);
|
|
3257
|
+
if (repoName && localRepoName !== repoName && !repoName.endsWith(`/${localRepoName}`)) {
|
|
3258
|
+
spinner.warn(
|
|
3259
|
+
chalk6.yellow(
|
|
3260
|
+
`Pending repo is ${chalk6.bold(repoName)} but you're in ${chalk6.bold(localRepoName)}.
|
|
3261
|
+
Make sure you're in the right project directory, or the context files will be saved here.`
|
|
3262
|
+
)
|
|
3263
|
+
);
|
|
3264
|
+
}
|
|
3265
|
+
} catch {
|
|
3266
|
+
spinner.fail(
|
|
3267
|
+
chalk6.red(
|
|
3268
|
+
"Please navigate to your project directory first, then run `repowise member` again."
|
|
3269
|
+
)
|
|
3270
|
+
);
|
|
3271
|
+
process.exitCode = 1;
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
if (!repoId || !repoPlatform) {
|
|
3276
|
+
spinner.start("Looking up repository...");
|
|
3277
|
+
try {
|
|
3278
|
+
const repos = await apiRequest("/v1/repos");
|
|
3279
|
+
const match = repos.find(
|
|
3280
|
+
(r) => r.repoId === repoId || r.name === repoName || r.fullName.endsWith(`/${repoName}`)
|
|
3281
|
+
);
|
|
3282
|
+
if (match) {
|
|
3283
|
+
repoId = match.repoId;
|
|
3284
|
+
repoPlatform = match.platform;
|
|
3285
|
+
repoExternalId = match.externalId;
|
|
3286
|
+
if (!repoName) repoName = match.fullName;
|
|
3287
|
+
spinner.succeed(`Repository: ${chalk6.bold(match.fullName)}`);
|
|
3288
|
+
}
|
|
3289
|
+
} catch {
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
if (!repoId) {
|
|
3293
|
+
spinner.fail(
|
|
3294
|
+
chalk6.red(
|
|
3295
|
+
"This repository is not connected to your team. Ask your team admin to add it on the dashboard."
|
|
3296
|
+
)
|
|
3297
|
+
);
|
|
3298
|
+
process.exitCode = 1;
|
|
3299
|
+
return;
|
|
3300
|
+
}
|
|
3301
|
+
spinner.start("Checking for context files...");
|
|
3302
|
+
let files = [];
|
|
3303
|
+
try {
|
|
3304
|
+
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
3305
|
+
files = listResult.data?.files ?? listResult.files ?? [];
|
|
3306
|
+
} catch {
|
|
3307
|
+
spinner.fail(chalk6.red("Failed to check context files on server."));
|
|
3308
|
+
process.exitCode = 1;
|
|
3309
|
+
return;
|
|
3310
|
+
}
|
|
3311
|
+
if (files.length === 0) {
|
|
3312
|
+
spinner.fail(
|
|
3313
|
+
chalk6.red(
|
|
3314
|
+
"No context files found for this repository. Your team admin needs to run the initial scan first."
|
|
3315
|
+
)
|
|
3316
|
+
);
|
|
3317
|
+
process.exitCode = 1;
|
|
3318
|
+
return;
|
|
3319
|
+
}
|
|
3320
|
+
spinner.succeed(`Found ${chalk6.bold(files.length)} context files on server`);
|
|
3321
|
+
const { tools } = await selectAiTools();
|
|
3322
|
+
spinner.start("Downloading context files...");
|
|
3323
|
+
const contextDir = join19(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3324
|
+
mkdirSync2(contextDir, { recursive: true });
|
|
3325
|
+
let downloadedCount = 0;
|
|
3326
|
+
let failedCount = 0;
|
|
3327
|
+
for (const file of files) {
|
|
3328
|
+
if (file.fileName.includes("..")) {
|
|
3329
|
+
failedCount++;
|
|
3330
|
+
continue;
|
|
3331
|
+
}
|
|
3332
|
+
try {
|
|
3333
|
+
const urlResult = await apiRequest(`/v1/repos/${repoId}/context/files/${file.fileName}`);
|
|
3334
|
+
const presignedUrl = urlResult.data?.url ?? urlResult.url;
|
|
3335
|
+
const response = await fetch(presignedUrl);
|
|
3336
|
+
if (response.ok) {
|
|
3337
|
+
const content = await response.text();
|
|
3338
|
+
const filePath = join19(contextDir, file.fileName);
|
|
3339
|
+
mkdirSync2(dirname7(filePath), { recursive: true });
|
|
3340
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
3341
|
+
downloadedCount++;
|
|
3342
|
+
} else {
|
|
3343
|
+
failedCount++;
|
|
3344
|
+
}
|
|
3345
|
+
} catch {
|
|
3346
|
+
failedCount++;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
if (failedCount > 0) {
|
|
3350
|
+
spinner.warn(
|
|
3351
|
+
`Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER2}/ (${failedCount} failed)`
|
|
3352
|
+
);
|
|
3353
|
+
} else {
|
|
3354
|
+
spinner.succeed(`Downloaded ${downloadedCount} files to ./${DEFAULT_CONTEXT_FOLDER2}/`);
|
|
3355
|
+
}
|
|
3356
|
+
try {
|
|
3357
|
+
ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3358
|
+
} catch {
|
|
3359
|
+
}
|
|
3360
|
+
if (tools.length > 0) {
|
|
3361
|
+
spinner.start("Configuring AI tools...");
|
|
3362
|
+
const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3363
|
+
const configured = [];
|
|
3364
|
+
for (const tool of tools) {
|
|
3365
|
+
const { created } = await updateToolConfig(
|
|
3366
|
+
repoRoot,
|
|
3367
|
+
tool,
|
|
3368
|
+
repoName,
|
|
3369
|
+
DEFAULT_CONTEXT_FOLDER2,
|
|
3370
|
+
contextFiles
|
|
3371
|
+
);
|
|
3372
|
+
const config2 = AI_TOOL_CONFIG[tool];
|
|
3373
|
+
configured.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
|
|
3374
|
+
}
|
|
3375
|
+
spinner.succeed("AI tools configured");
|
|
3376
|
+
for (const msg of configured) {
|
|
3377
|
+
console.log(chalk6.dim(` ${msg}`));
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
spinner.start("Saving configuration...");
|
|
3381
|
+
const repoEntry = {
|
|
3382
|
+
repoId,
|
|
3383
|
+
localPath: repoRoot,
|
|
3384
|
+
apiUrl: getEnvConfig().apiUrl
|
|
3385
|
+
};
|
|
3386
|
+
if (repoPlatform) repoEntry.platform = repoPlatform;
|
|
3387
|
+
if (repoExternalId) repoEntry.externalId = repoExternalId;
|
|
3388
|
+
await mergeAndSaveConfig({
|
|
3389
|
+
aiTools: tools,
|
|
3390
|
+
contextFolder: DEFAULT_CONTEXT_FOLDER2,
|
|
3391
|
+
repos: [repoEntry]
|
|
3392
|
+
});
|
|
3393
|
+
spinner.succeed("Configuration saved");
|
|
3394
|
+
let listenerRunning = false;
|
|
3395
|
+
try {
|
|
3396
|
+
await ensureListenerRunning();
|
|
3397
|
+
listenerRunning = true;
|
|
3398
|
+
} catch {
|
|
3399
|
+
}
|
|
3400
|
+
try {
|
|
3401
|
+
await apiRequest("/v1/onboarding/complete", {
|
|
3402
|
+
method: "POST",
|
|
3403
|
+
body: JSON.stringify({ repoId })
|
|
3404
|
+
});
|
|
3405
|
+
} catch {
|
|
3406
|
+
}
|
|
3407
|
+
const elapsed = formatElapsed2(Date.now() - startTime);
|
|
3408
|
+
console.log("");
|
|
3409
|
+
console.log(chalk6.green.bold("All done! Context downloaded and configured."));
|
|
3410
|
+
console.log("");
|
|
3411
|
+
console.log(`Your AI tools now have access to project context for ${chalk6.bold(repoName)}.`);
|
|
3412
|
+
console.log(
|
|
3413
|
+
`Downloaded ${chalk6.bold(downloadedCount)} context files to ./${DEFAULT_CONTEXT_FOLDER2}/`
|
|
3414
|
+
);
|
|
3415
|
+
console.log("");
|
|
3416
|
+
if (listenerRunning) {
|
|
3417
|
+
console.log(
|
|
3418
|
+
chalk6.dim(
|
|
3419
|
+
"The RepoWise listener is running in the background \u2014\nyour context will stay in sync automatically."
|
|
3420
|
+
)
|
|
3421
|
+
);
|
|
3422
|
+
} else {
|
|
3423
|
+
console.log(
|
|
3424
|
+
chalk6.yellow(
|
|
3425
|
+
"Could not start listener automatically. Run `repowise listen --install` to enable auto-sync."
|
|
3426
|
+
)
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
console.log("");
|
|
3430
|
+
console.log(
|
|
3431
|
+
chalk6.dim(`Head back to the dashboard and click "Complete Onboarding" to finish setup.`)
|
|
3432
|
+
);
|
|
3433
|
+
console.log(chalk6.dim(`Total time: ${elapsed}`));
|
|
3434
|
+
} catch (err) {
|
|
3435
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
3436
|
+
spinner.fail(chalk6.red(`Setup failed: ${message}`));
|
|
3437
|
+
process.exitCode = 1;
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
// src/commands/login.ts
|
|
3442
|
+
import chalk7 from "chalk";
|
|
3443
|
+
import ora3 from "ora";
|
|
2632
3444
|
async function login(options = {}) {
|
|
2633
|
-
const spinner =
|
|
3445
|
+
const spinner = ora3("Preparing login...").start();
|
|
2634
3446
|
try {
|
|
2635
3447
|
const codeVerifier = generateCodeVerifier();
|
|
2636
3448
|
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
@@ -2642,7 +3454,7 @@ async function login(options = {}) {
|
|
|
2642
3454
|
console.log(`
|
|
2643
3455
|
Open this URL in your browser to authenticate:
|
|
2644
3456
|
`);
|
|
2645
|
-
console.log(
|
|
3457
|
+
console.log(chalk7.cyan(authorizeUrl));
|
|
2646
3458
|
console.log(`
|
|
2647
3459
|
Waiting for authentication...`);
|
|
2648
3460
|
} else {
|
|
@@ -2656,7 +3468,7 @@ Waiting for authentication...`);
|
|
|
2656
3468
|
console.log(`
|
|
2657
3469
|
Could not open browser automatically. Open this URL:
|
|
2658
3470
|
`);
|
|
2659
|
-
console.log(
|
|
3471
|
+
console.log(chalk7.cyan(authorizeUrl));
|
|
2660
3472
|
console.log(`
|
|
2661
3473
|
Waiting for authentication...`);
|
|
2662
3474
|
}
|
|
@@ -2670,60 +3482,50 @@ Waiting for authentication...`);
|
|
|
2670
3482
|
credentials.cognito = getCognitoConfigForStorage();
|
|
2671
3483
|
await storeCredentials2(credentials);
|
|
2672
3484
|
const { email } = decodeIdToken(credentials.idToken);
|
|
2673
|
-
spinner.succeed(
|
|
3485
|
+
spinner.succeed(chalk7.green(`Logged in as ${chalk7.bold(email)}`));
|
|
2674
3486
|
} catch (err) {
|
|
2675
3487
|
const message = err instanceof Error ? err.message : "Login failed";
|
|
2676
|
-
spinner.fail(
|
|
3488
|
+
spinner.fail(chalk7.red(message));
|
|
2677
3489
|
process.exitCode = 1;
|
|
2678
3490
|
}
|
|
2679
3491
|
}
|
|
2680
3492
|
|
|
2681
3493
|
// src/commands/logout.ts
|
|
2682
|
-
import
|
|
3494
|
+
import chalk8 from "chalk";
|
|
2683
3495
|
async function logout() {
|
|
2684
3496
|
const creds = await getStoredCredentials2();
|
|
2685
3497
|
if (!creds) {
|
|
2686
|
-
console.log(
|
|
3498
|
+
console.log(chalk8.yellow("Not logged in."));
|
|
2687
3499
|
return;
|
|
2688
3500
|
}
|
|
2689
3501
|
await clearCredentials();
|
|
2690
|
-
console.log(
|
|
3502
|
+
console.log(chalk8.green("Logged out successfully."));
|
|
2691
3503
|
}
|
|
2692
3504
|
|
|
2693
3505
|
// src/commands/status.ts
|
|
2694
3506
|
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");
|
|
3507
|
+
import { basename as basename2, join as join20 } from "path";
|
|
2699
3508
|
async function status() {
|
|
3509
|
+
const configDir = getConfigDir2();
|
|
3510
|
+
const STATE_PATH = join20(configDir, "listener-state.json");
|
|
3511
|
+
const CONFIG_PATH = join20(configDir, "config.json");
|
|
2700
3512
|
let state = null;
|
|
2701
3513
|
try {
|
|
2702
|
-
const data = await readFile9(
|
|
3514
|
+
const data = await readFile9(STATE_PATH, "utf-8");
|
|
2703
3515
|
state = JSON.parse(data);
|
|
2704
3516
|
} catch {
|
|
2705
3517
|
}
|
|
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
|
-
}
|
|
3518
|
+
const listenerStatus = await getListenerStatus();
|
|
2719
3519
|
console.log("RepoWise Status");
|
|
2720
3520
|
console.log("===============");
|
|
2721
|
-
if (
|
|
2722
|
-
console.log(
|
|
3521
|
+
if (listenerStatus.running) {
|
|
3522
|
+
console.log(
|
|
3523
|
+
listenerStatus.method === "service" ? "Listener: running (managed by system service)" : `Listener: running (PID: ${listenerStatus.pid})`
|
|
3524
|
+
);
|
|
2723
3525
|
} else {
|
|
2724
3526
|
console.log("Listener: stopped");
|
|
2725
3527
|
}
|
|
2726
|
-
if (serviceInstalled) {
|
|
3528
|
+
if (listenerStatus.serviceInstalled) {
|
|
2727
3529
|
console.log("Auto-start: enabled");
|
|
2728
3530
|
} else {
|
|
2729
3531
|
console.log("Auto-start: disabled");
|
|
@@ -2735,7 +3537,7 @@ async function status() {
|
|
|
2735
3537
|
}
|
|
2736
3538
|
const repoNames = /* @__PURE__ */ new Map();
|
|
2737
3539
|
try {
|
|
2738
|
-
const configData = await readFile9(
|
|
3540
|
+
const configData = await readFile9(CONFIG_PATH, "utf-8");
|
|
2739
3541
|
const config2 = JSON.parse(configData);
|
|
2740
3542
|
for (const repo of config2.repos ?? []) {
|
|
2741
3543
|
repoNames.set(repo.repoId, basename2(repo.localPath));
|
|
@@ -2753,30 +3555,14 @@ async function status() {
|
|
|
2753
3555
|
}
|
|
2754
3556
|
|
|
2755
3557
|
// src/commands/sync.ts
|
|
2756
|
-
import {
|
|
2757
|
-
import {
|
|
2758
|
-
import
|
|
2759
|
-
import
|
|
2760
|
-
import ora3 from "ora";
|
|
3558
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3559
|
+
import { dirname as dirname8, join as join21 } from "path";
|
|
3560
|
+
import chalk9 from "chalk";
|
|
3561
|
+
import ora4 from "ora";
|
|
2761
3562
|
var POLL_INTERVAL_MS2 = 3e3;
|
|
2762
3563
|
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) {
|
|
3564
|
+
var DEFAULT_CONTEXT_FOLDER3 = "repowise-context";
|
|
3565
|
+
function formatElapsed3(ms) {
|
|
2780
3566
|
const totalSeconds = Math.round(ms / 1e3);
|
|
2781
3567
|
const minutes = Math.floor(totalSeconds / 60);
|
|
2782
3568
|
const seconds = totalSeconds % 60;
|
|
@@ -2785,14 +3571,14 @@ function formatElapsed2(ms) {
|
|
|
2785
3571
|
}
|
|
2786
3572
|
async function sync() {
|
|
2787
3573
|
const startTime = Date.now();
|
|
2788
|
-
const spinner =
|
|
3574
|
+
const spinner = ora4("Checking authentication...").start();
|
|
2789
3575
|
try {
|
|
2790
3576
|
let credentials = await getValidCredentials2();
|
|
2791
3577
|
if (!credentials) {
|
|
2792
|
-
spinner.info(
|
|
3578
|
+
spinner.info(chalk9.yellow("Not logged in. Opening browser to authenticate..."));
|
|
2793
3579
|
credentials = await performLogin();
|
|
2794
3580
|
const { email } = decodeIdToken(credentials.idToken);
|
|
2795
|
-
spinner.succeed(
|
|
3581
|
+
spinner.succeed(chalk9.green(`Authenticated as ${chalk9.bold(email)}`));
|
|
2796
3582
|
} else {
|
|
2797
3583
|
spinner.succeed("Authenticated");
|
|
2798
3584
|
}
|
|
@@ -2800,12 +3586,12 @@ async function sync() {
|
|
|
2800
3586
|
let repoName;
|
|
2801
3587
|
spinner.start("Detecting repository...");
|
|
2802
3588
|
try {
|
|
2803
|
-
repoRoot =
|
|
2804
|
-
repoName =
|
|
2805
|
-
spinner.succeed(`Repository: ${
|
|
3589
|
+
repoRoot = detectRepoRoot();
|
|
3590
|
+
repoName = detectRepoName(repoRoot);
|
|
3591
|
+
spinner.succeed(`Repository: ${chalk9.bold(repoName)}`);
|
|
2806
3592
|
} catch {
|
|
2807
3593
|
spinner.fail(
|
|
2808
|
-
|
|
3594
|
+
chalk9.red("Not in a git repository. Run this command from your repo directory.")
|
|
2809
3595
|
);
|
|
2810
3596
|
process.exitCode = 1;
|
|
2811
3597
|
return;
|
|
@@ -2826,7 +3612,7 @@ async function sync() {
|
|
|
2826
3612
|
}
|
|
2827
3613
|
if (!repoId) {
|
|
2828
3614
|
spinner.fail(
|
|
2829
|
-
|
|
3615
|
+
chalk9.red(
|
|
2830
3616
|
"Could not find this repository in your RepoWise account. Run `repowise create` first."
|
|
2831
3617
|
)
|
|
2832
3618
|
);
|
|
@@ -2863,7 +3649,7 @@ async function sync() {
|
|
|
2863
3649
|
syncId = retryResult.syncId;
|
|
2864
3650
|
} else {
|
|
2865
3651
|
syncId = active.syncId;
|
|
2866
|
-
spinner.info(
|
|
3652
|
+
spinner.info(chalk9.cyan("Resuming existing sync..."));
|
|
2867
3653
|
spinner.start();
|
|
2868
3654
|
}
|
|
2869
3655
|
}
|
|
@@ -2871,7 +3657,7 @@ async function sync() {
|
|
|
2871
3657
|
const progressRenderer = new ProgressRenderer();
|
|
2872
3658
|
while (true) {
|
|
2873
3659
|
if (++pollAttempts > MAX_POLL_ATTEMPTS2) {
|
|
2874
|
-
spinner.fail(
|
|
3660
|
+
spinner.fail(chalk9.red("Sync timed out. Check dashboard for status."));
|
|
2875
3661
|
process.exitCode = 1;
|
|
2876
3662
|
return;
|
|
2877
3663
|
}
|
|
@@ -2895,15 +3681,15 @@ async function sync() {
|
|
|
2895
3681
|
const generatedFiles = syncResult.filesGenerated ?? [];
|
|
2896
3682
|
const fileCount = generatedFiles.length;
|
|
2897
3683
|
if (fileCount > 0) {
|
|
2898
|
-
spinner.succeed(
|
|
3684
|
+
spinner.succeed(chalk9.green(`Incremental update complete \u2014 ${fileCount} files updated`));
|
|
2899
3685
|
} else {
|
|
2900
|
-
spinner.succeed(
|
|
3686
|
+
spinner.succeed(chalk9.green("Incremental sync complete \u2014 no files needed updating"));
|
|
2901
3687
|
}
|
|
2902
3688
|
break;
|
|
2903
3689
|
}
|
|
2904
3690
|
if (syncResult.status === "failed") {
|
|
2905
3691
|
progressRenderer.finalize();
|
|
2906
|
-
spinner.fail(
|
|
3692
|
+
spinner.fail(chalk9.red(`Sync failed: ${syncResult.error ?? "Unknown error"}`));
|
|
2907
3693
|
process.exitCode = 1;
|
|
2908
3694
|
return;
|
|
2909
3695
|
}
|
|
@@ -2913,8 +3699,8 @@ async function sync() {
|
|
|
2913
3699
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
2914
3700
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
2915
3701
|
if (files.length > 0) {
|
|
2916
|
-
const contextDir =
|
|
2917
|
-
|
|
3702
|
+
const contextDir = join21(repoRoot, DEFAULT_CONTEXT_FOLDER3);
|
|
3703
|
+
mkdirSync3(contextDir, { recursive: true });
|
|
2918
3704
|
let downloadedCount = 0;
|
|
2919
3705
|
let failedCount = 0;
|
|
2920
3706
|
for (const file of files) {
|
|
@@ -2927,9 +3713,9 @@ async function sync() {
|
|
|
2927
3713
|
const response = await fetch(presignedUrl);
|
|
2928
3714
|
if (response.ok) {
|
|
2929
3715
|
const content = await response.text();
|
|
2930
|
-
const filePath =
|
|
2931
|
-
|
|
2932
|
-
|
|
3716
|
+
const filePath = join21(contextDir, file.fileName);
|
|
3717
|
+
mkdirSync3(dirname8(filePath), { recursive: true });
|
|
3718
|
+
writeFileSync4(filePath, content, "utf-8");
|
|
2933
3719
|
downloadedCount++;
|
|
2934
3720
|
} else {
|
|
2935
3721
|
failedCount++;
|
|
@@ -2937,13 +3723,13 @@ async function sync() {
|
|
|
2937
3723
|
}
|
|
2938
3724
|
if (failedCount > 0) {
|
|
2939
3725
|
spinner.warn(
|
|
2940
|
-
`Downloaded ${downloadedCount}/${files.length} files to ./${
|
|
3726
|
+
`Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER3}/ (${failedCount} failed)`
|
|
2941
3727
|
);
|
|
2942
3728
|
} else {
|
|
2943
|
-
spinner.succeed(`Context files downloaded to ./${
|
|
3729
|
+
spinner.succeed(`Context files downloaded to ./${DEFAULT_CONTEXT_FOLDER3}/`);
|
|
2944
3730
|
}
|
|
2945
3731
|
try {
|
|
2946
|
-
ensureGitignore(repoRoot,
|
|
3732
|
+
ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER3);
|
|
2947
3733
|
} catch {
|
|
2948
3734
|
}
|
|
2949
3735
|
} else {
|
|
@@ -2951,7 +3737,7 @@ async function sync() {
|
|
|
2951
3737
|
}
|
|
2952
3738
|
} catch (err) {
|
|
2953
3739
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
2954
|
-
spinner.warn(
|
|
3740
|
+
spinner.warn(chalk9.yellow(`Could not download context files: ${msg}
|
|
2955
3741
|
Retry when online.`));
|
|
2956
3742
|
}
|
|
2957
3743
|
if (repoRoot && repoId) {
|
|
@@ -2959,29 +3745,24 @@ Retry when online.`));
|
|
|
2959
3745
|
const existingConfig = await getConfig();
|
|
2960
3746
|
const existingRepos = existingConfig.repos ?? [];
|
|
2961
3747
|
if (!existingRepos.some((r) => r.repoId === repoId)) {
|
|
2962
|
-
const repoEntry = { repoId, localPath: repoRoot };
|
|
2963
|
-
if (isStagingMode()) {
|
|
2964
|
-
repoEntry.apiUrl = getEnvConfig().apiUrl;
|
|
2965
|
-
}
|
|
3748
|
+
const repoEntry = { repoId, localPath: repoRoot, apiUrl: getEnvConfig().apiUrl };
|
|
2966
3749
|
if (repoPlatform) repoEntry.platform = repoPlatform;
|
|
2967
3750
|
if (repoExternalId) repoEntry.externalId = repoExternalId;
|
|
2968
|
-
|
|
2969
|
-
await saveConfig({ ...existingConfig, repos: existingRepos });
|
|
3751
|
+
await mergeAndSaveConfig({ repos: [repoEntry] });
|
|
2970
3752
|
}
|
|
2971
3753
|
} catch {
|
|
2972
3754
|
}
|
|
2973
3755
|
try {
|
|
2974
|
-
await
|
|
2975
|
-
await startBackground();
|
|
3756
|
+
await ensureListenerRunning();
|
|
2976
3757
|
} catch {
|
|
2977
3758
|
}
|
|
2978
3759
|
}
|
|
2979
|
-
const elapsed =
|
|
2980
|
-
console.log(
|
|
3760
|
+
const elapsed = formatElapsed3(Date.now() - startTime);
|
|
3761
|
+
console.log(chalk9.dim(`
|
|
2981
3762
|
Total time: ${elapsed}`));
|
|
2982
3763
|
} catch (err) {
|
|
2983
3764
|
const message = err instanceof Error ? err.message : "Sync failed";
|
|
2984
|
-
spinner.fail(
|
|
3765
|
+
spinner.fail(chalk9.red(message));
|
|
2985
3766
|
process.exitCode = 1;
|
|
2986
3767
|
}
|
|
2987
3768
|
}
|
|
@@ -3029,12 +3810,15 @@ async function listen(options) {
|
|
|
3029
3810
|
// src/commands/start.ts
|
|
3030
3811
|
async function start() {
|
|
3031
3812
|
try {
|
|
3032
|
-
|
|
3813
|
+
const status2 = await getListenerStatus();
|
|
3814
|
+
if (status2.running) {
|
|
3033
3815
|
console.log("Listener is already running.");
|
|
3034
3816
|
return;
|
|
3035
3817
|
}
|
|
3036
|
-
const
|
|
3037
|
-
console.log(
|
|
3818
|
+
const result = await ensureListenerRunning();
|
|
3819
|
+
console.log(
|
|
3820
|
+
result.method === "service" ? "Listener service installed and started." : `Listener started (PID: ${result.pid}).`
|
|
3821
|
+
);
|
|
3038
3822
|
} catch (err) {
|
|
3039
3823
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
3040
3824
|
console.error(`Failed to start listener: ${message}`);
|
|
@@ -3045,11 +3829,12 @@ async function start() {
|
|
|
3045
3829
|
// src/commands/stop.ts
|
|
3046
3830
|
async function stop2() {
|
|
3047
3831
|
try {
|
|
3048
|
-
|
|
3832
|
+
const status2 = await getListenerStatus();
|
|
3833
|
+
if (!status2.running) {
|
|
3049
3834
|
console.log("Listener is not running.");
|
|
3050
3835
|
return;
|
|
3051
3836
|
}
|
|
3052
|
-
await
|
|
3837
|
+
await stopListener();
|
|
3053
3838
|
console.log("Listener stopped.");
|
|
3054
3839
|
} catch (err) {
|
|
3055
3840
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -3059,18 +3844,18 @@ async function stop2() {
|
|
|
3059
3844
|
}
|
|
3060
3845
|
|
|
3061
3846
|
// src/commands/config.ts
|
|
3062
|
-
import
|
|
3063
|
-
import
|
|
3847
|
+
import chalk10 from "chalk";
|
|
3848
|
+
import ora5 from "ora";
|
|
3064
3849
|
import { select } from "@inquirer/prompts";
|
|
3065
3850
|
async function config() {
|
|
3066
|
-
const spinner =
|
|
3851
|
+
const spinner = ora5("Checking authentication...").start();
|
|
3067
3852
|
try {
|
|
3068
3853
|
let credentials = await getValidCredentials2();
|
|
3069
3854
|
if (!credentials) {
|
|
3070
|
-
spinner.info(
|
|
3855
|
+
spinner.info(chalk10.yellow("Not logged in. Opening browser to authenticate..."));
|
|
3071
3856
|
credentials = await performLogin();
|
|
3072
3857
|
const { email } = decodeIdToken(credentials.idToken);
|
|
3073
|
-
spinner.succeed(
|
|
3858
|
+
spinner.succeed(chalk10.green(`Authenticated as ${chalk10.bold(email)}`));
|
|
3074
3859
|
} else {
|
|
3075
3860
|
spinner.succeed("Authenticated");
|
|
3076
3861
|
}
|
|
@@ -3078,7 +3863,7 @@ async function config() {
|
|
|
3078
3863
|
const repos = await apiRequest("/v1/repos");
|
|
3079
3864
|
spinner.stop();
|
|
3080
3865
|
if (repos.length === 0) {
|
|
3081
|
-
console.log(
|
|
3866
|
+
console.log(chalk10.yellow("No repositories connected. Run `repowise create` first."));
|
|
3082
3867
|
return;
|
|
3083
3868
|
}
|
|
3084
3869
|
const repoId = await select({
|
|
@@ -3092,10 +3877,10 @@ async function config() {
|
|
|
3092
3877
|
const currentDelivery = repo.deliveryMode ?? "direct";
|
|
3093
3878
|
const currentBranch = repo.monitoredBranch ?? repo.defaultBranch;
|
|
3094
3879
|
console.log("");
|
|
3095
|
-
console.log(
|
|
3096
|
-
console.log(
|
|
3097
|
-
console.log(
|
|
3098
|
-
console.log(
|
|
3880
|
+
console.log(chalk10.bold(`Settings for ${repo.fullName}`));
|
|
3881
|
+
console.log(chalk10.dim(` Context storage: RepoWise servers`));
|
|
3882
|
+
console.log(chalk10.dim(` Delivery mode: ${currentDelivery}`));
|
|
3883
|
+
console.log(chalk10.dim(` Monitored branch: ${currentBranch}`));
|
|
3099
3884
|
console.log("");
|
|
3100
3885
|
const setting = await select({
|
|
3101
3886
|
message: "What would you like to change?",
|
|
@@ -3115,7 +3900,7 @@ async function config() {
|
|
|
3115
3900
|
default: currentDelivery
|
|
3116
3901
|
});
|
|
3117
3902
|
if (newMode === currentDelivery) {
|
|
3118
|
-
console.log(
|
|
3903
|
+
console.log(chalk10.dim("No change."));
|
|
3119
3904
|
return;
|
|
3120
3905
|
}
|
|
3121
3906
|
patch.deliveryMode = newMode;
|
|
@@ -3126,7 +3911,7 @@ async function config() {
|
|
|
3126
3911
|
default: currentBranch
|
|
3127
3912
|
});
|
|
3128
3913
|
if (newBranch === currentBranch) {
|
|
3129
|
-
console.log(
|
|
3914
|
+
console.log(chalk10.dim("No change."));
|
|
3130
3915
|
return;
|
|
3131
3916
|
}
|
|
3132
3917
|
patch.monitoredBranch = newBranch;
|
|
@@ -3136,29 +3921,29 @@ async function config() {
|
|
|
3136
3921
|
method: "PATCH",
|
|
3137
3922
|
body: JSON.stringify(patch)
|
|
3138
3923
|
});
|
|
3139
|
-
spinner.succeed(
|
|
3924
|
+
spinner.succeed(chalk10.green("Setting updated"));
|
|
3140
3925
|
} catch (err) {
|
|
3141
3926
|
spinner.stop();
|
|
3142
3927
|
const message = err instanceof Error ? err.message : "Config failed";
|
|
3143
|
-
console.error(
|
|
3928
|
+
console.error(chalk10.red(message));
|
|
3144
3929
|
process.exitCode = 1;
|
|
3145
3930
|
}
|
|
3146
3931
|
}
|
|
3147
3932
|
|
|
3148
3933
|
// bin/repowise.ts
|
|
3149
3934
|
var __filename = fileURLToPath3(import.meta.url);
|
|
3150
|
-
var __dirname =
|
|
3151
|
-
var pkg = JSON.parse(readFileSync2(
|
|
3935
|
+
var __dirname = dirname9(__filename);
|
|
3936
|
+
var pkg = JSON.parse(readFileSync2(join22(__dirname, "..", "..", "package.json"), "utf-8"));
|
|
3152
3937
|
var program = new Command();
|
|
3153
|
-
program.name(
|
|
3154
|
-
if (program.opts()["staging"]) {
|
|
3155
|
-
setStagingMode(true);
|
|
3156
|
-
}
|
|
3938
|
+
program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
|
|
3157
3939
|
await showWelcome(pkg.version);
|
|
3158
3940
|
});
|
|
3159
3941
|
program.command("create").description("Create context for a repository").action(async () => {
|
|
3160
3942
|
await create();
|
|
3161
3943
|
});
|
|
3944
|
+
program.command("member").description("Download context and configure AI tools (for team members)").action(async () => {
|
|
3945
|
+
await member();
|
|
3946
|
+
});
|
|
3162
3947
|
program.command("login").description("Authenticate with RepoWise").option("--no-browser", "Print login URL instead of opening browser").action(async (options) => {
|
|
3163
3948
|
await login(options);
|
|
3164
3949
|
});
|
|
@@ -3190,7 +3975,7 @@ if (process.argv[2] === "__listener") {
|
|
|
3190
3975
|
process.on("unhandledRejection", (reason) => {
|
|
3191
3976
|
console.error("Listener unhandled rejection:", reason);
|
|
3192
3977
|
});
|
|
3193
|
-
startListener(
|
|
3978
|
+
startListener().catch((err) => {
|
|
3194
3979
|
console.error("Listener fatal error:", err);
|
|
3195
3980
|
process.exitCode = 1;
|
|
3196
3981
|
});
|