@iloom/cli 0.1.14
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/LICENSE +33 -0
- package/README.md +711 -0
- package/dist/ClaudeContextManager-XOSXQ67R.js +13 -0
- package/dist/ClaudeContextManager-XOSXQ67R.js.map +1 -0
- package/dist/ClaudeService-YSZ6EXWP.js +12 -0
- package/dist/ClaudeService-YSZ6EXWP.js.map +1 -0
- package/dist/GitHubService-F7Z3XJOS.js +11 -0
- package/dist/GitHubService-F7Z3XJOS.js.map +1 -0
- package/dist/LoomLauncher-MODG2SEM.js +263 -0
- package/dist/LoomLauncher-MODG2SEM.js.map +1 -0
- package/dist/NeonProvider-PAGPUH7F.js +12 -0
- package/dist/NeonProvider-PAGPUH7F.js.map +1 -0
- package/dist/PromptTemplateManager-7FINLRDE.js +9 -0
- package/dist/PromptTemplateManager-7FINLRDE.js.map +1 -0
- package/dist/SettingsManager-VAZF26S2.js +19 -0
- package/dist/SettingsManager-VAZF26S2.js.map +1 -0
- package/dist/SettingsMigrationManager-MTQIMI54.js +146 -0
- package/dist/SettingsMigrationManager-MTQIMI54.js.map +1 -0
- package/dist/add-issue-22JBNOML.js +54 -0
- package/dist/add-issue-22JBNOML.js.map +1 -0
- package/dist/agents/iloom-issue-analyze-and-plan.md +580 -0
- package/dist/agents/iloom-issue-analyzer.md +290 -0
- package/dist/agents/iloom-issue-complexity-evaluator.md +224 -0
- package/dist/agents/iloom-issue-enhancer.md +266 -0
- package/dist/agents/iloom-issue-implementer.md +262 -0
- package/dist/agents/iloom-issue-planner.md +358 -0
- package/dist/agents/iloom-issue-reviewer.md +63 -0
- package/dist/chunk-2ZPFJQ3B.js +63 -0
- package/dist/chunk-2ZPFJQ3B.js.map +1 -0
- package/dist/chunk-37DYYFVK.js +29 -0
- package/dist/chunk-37DYYFVK.js.map +1 -0
- package/dist/chunk-BLCTGFZN.js +121 -0
- package/dist/chunk-BLCTGFZN.js.map +1 -0
- package/dist/chunk-CP2NU2JC.js +545 -0
- package/dist/chunk-CP2NU2JC.js.map +1 -0
- package/dist/chunk-CWR2SANQ.js +39 -0
- package/dist/chunk-CWR2SANQ.js.map +1 -0
- package/dist/chunk-F3XBU2R7.js +110 -0
- package/dist/chunk-F3XBU2R7.js.map +1 -0
- package/dist/chunk-GEHQXLEI.js +130 -0
- package/dist/chunk-GEHQXLEI.js.map +1 -0
- package/dist/chunk-GYCR2LOU.js +143 -0
- package/dist/chunk-GYCR2LOU.js.map +1 -0
- package/dist/chunk-GZP4UGGM.js +48 -0
- package/dist/chunk-GZP4UGGM.js.map +1 -0
- package/dist/chunk-H4E4THUZ.js +55 -0
- package/dist/chunk-H4E4THUZ.js.map +1 -0
- package/dist/chunk-HPJJSYNS.js +644 -0
- package/dist/chunk-HPJJSYNS.js.map +1 -0
- package/dist/chunk-JBH2ZYYZ.js +220 -0
- package/dist/chunk-JBH2ZYYZ.js.map +1 -0
- package/dist/chunk-JNKJ7NJV.js +78 -0
- package/dist/chunk-JNKJ7NJV.js.map +1 -0
- package/dist/chunk-JQ7VOSTC.js +437 -0
- package/dist/chunk-JQ7VOSTC.js.map +1 -0
- package/dist/chunk-KQDEK2ZW.js +199 -0
- package/dist/chunk-KQDEK2ZW.js.map +1 -0
- package/dist/chunk-O2QWO64Z.js +179 -0
- package/dist/chunk-O2QWO64Z.js.map +1 -0
- package/dist/chunk-OC4H6HJD.js +248 -0
- package/dist/chunk-OC4H6HJD.js.map +1 -0
- package/dist/chunk-PR7FKQBG.js +120 -0
- package/dist/chunk-PR7FKQBG.js.map +1 -0
- package/dist/chunk-PXZBAC2M.js +250 -0
- package/dist/chunk-PXZBAC2M.js.map +1 -0
- package/dist/chunk-QEPVTTHD.js +383 -0
- package/dist/chunk-QEPVTTHD.js.map +1 -0
- package/dist/chunk-RSRO7564.js +203 -0
- package/dist/chunk-RSRO7564.js.map +1 -0
- package/dist/chunk-SJUQ2NDR.js +146 -0
- package/dist/chunk-SJUQ2NDR.js.map +1 -0
- package/dist/chunk-SPYPLHMK.js +177 -0
- package/dist/chunk-SPYPLHMK.js.map +1 -0
- package/dist/chunk-SSCQCCJ7.js +75 -0
- package/dist/chunk-SSCQCCJ7.js.map +1 -0
- package/dist/chunk-SSR5AVRJ.js +41 -0
- package/dist/chunk-SSR5AVRJ.js.map +1 -0
- package/dist/chunk-T7QPXANZ.js +315 -0
- package/dist/chunk-T7QPXANZ.js.map +1 -0
- package/dist/chunk-U3WU5OWO.js +203 -0
- package/dist/chunk-U3WU5OWO.js.map +1 -0
- package/dist/chunk-W3DQTW63.js +124 -0
- package/dist/chunk-W3DQTW63.js.map +1 -0
- package/dist/chunk-WKEWRSDB.js +151 -0
- package/dist/chunk-WKEWRSDB.js.map +1 -0
- package/dist/chunk-Y7SAGNUT.js +66 -0
- package/dist/chunk-Y7SAGNUT.js.map +1 -0
- package/dist/chunk-YETJNRQM.js +39 -0
- package/dist/chunk-YETJNRQM.js.map +1 -0
- package/dist/chunk-YYSKGAZT.js +384 -0
- package/dist/chunk-YYSKGAZT.js.map +1 -0
- package/dist/chunk-ZZZWQGTS.js +169 -0
- package/dist/chunk-ZZZWQGTS.js.map +1 -0
- package/dist/claude-7LUVDZZ4.js +17 -0
- package/dist/claude-7LUVDZZ4.js.map +1 -0
- package/dist/cleanup-3LUWPSM7.js +412 -0
- package/dist/cleanup-3LUWPSM7.js.map +1 -0
- package/dist/cli-overrides-XFZWY7CM.js +16 -0
- package/dist/cli-overrides-XFZWY7CM.js.map +1 -0
- package/dist/cli.js +603 -0
- package/dist/cli.js.map +1 -0
- package/dist/color-ZVALX37U.js +21 -0
- package/dist/color-ZVALX37U.js.map +1 -0
- package/dist/enhance-XJIQHVPD.js +166 -0
- package/dist/enhance-XJIQHVPD.js.map +1 -0
- package/dist/env-MDFL4ZXL.js +23 -0
- package/dist/env-MDFL4ZXL.js.map +1 -0
- package/dist/feedback-23CLXKFT.js +158 -0
- package/dist/feedback-23CLXKFT.js.map +1 -0
- package/dist/finish-CY4CIH6O.js +1608 -0
- package/dist/finish-CY4CIH6O.js.map +1 -0
- package/dist/git-LVRZ57GJ.js +43 -0
- package/dist/git-LVRZ57GJ.js.map +1 -0
- package/dist/ignite-WXEF2ID5.js +359 -0
- package/dist/ignite-WXEF2ID5.js.map +1 -0
- package/dist/index.d.ts +1341 -0
- package/dist/index.js +3058 -0
- package/dist/index.js.map +1 -0
- package/dist/init-RHACUR4E.js +123 -0
- package/dist/init-RHACUR4E.js.map +1 -0
- package/dist/installation-detector-VARGFFRZ.js +11 -0
- package/dist/installation-detector-VARGFFRZ.js.map +1 -0
- package/dist/logger-MKYH4UDV.js +12 -0
- package/dist/logger-MKYH4UDV.js.map +1 -0
- package/dist/mcp/chunk-6SDFJ42P.js +62 -0
- package/dist/mcp/chunk-6SDFJ42P.js.map +1 -0
- package/dist/mcp/claude-YHHHLSXH.js +249 -0
- package/dist/mcp/claude-YHHHLSXH.js.map +1 -0
- package/dist/mcp/color-QS5BFCNN.js +168 -0
- package/dist/mcp/color-QS5BFCNN.js.map +1 -0
- package/dist/mcp/github-comment-server.js +165 -0
- package/dist/mcp/github-comment-server.js.map +1 -0
- package/dist/mcp/terminal-SDCMDVD7.js +202 -0
- package/dist/mcp/terminal-SDCMDVD7.js.map +1 -0
- package/dist/open-X6BTENPV.js +278 -0
- package/dist/open-X6BTENPV.js.map +1 -0
- package/dist/prompt-ANTQWHUF.js +13 -0
- package/dist/prompt-ANTQWHUF.js.map +1 -0
- package/dist/prompts/issue-prompt.txt +230 -0
- package/dist/prompts/pr-prompt.txt +35 -0
- package/dist/prompts/regular-prompt.txt +14 -0
- package/dist/run-2JCPQAX3.js +278 -0
- package/dist/run-2JCPQAX3.js.map +1 -0
- package/dist/schema/settings.schema.json +221 -0
- package/dist/start-LWVRBJ6S.js +982 -0
- package/dist/start-LWVRBJ6S.js.map +1 -0
- package/dist/terminal-3D6TUAKJ.js +16 -0
- package/dist/terminal-3D6TUAKJ.js.map +1 -0
- package/dist/test-git-XPF4SZXJ.js +52 -0
- package/dist/test-git-XPF4SZXJ.js.map +1 -0
- package/dist/test-prefix-XGFXFAYN.js +68 -0
- package/dist/test-prefix-XGFXFAYN.js.map +1 -0
- package/dist/test-tabs-JRKY3QMM.js +69 -0
- package/dist/test-tabs-JRKY3QMM.js.map +1 -0
- package/dist/test-webserver-M2I3EV4J.js +62 -0
- package/dist/test-webserver-M2I3EV4J.js.map +1 -0
- package/dist/update-3ZT2XX2G.js +79 -0
- package/dist/update-3ZT2XX2G.js.map +1 -0
- package/dist/update-notifier-QSSEB5KC.js +11 -0
- package/dist/update-notifier-QSSEB5KC.js.map +1 -0
- package/package.json +113 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ClaudeBranchNameStrategy,
|
|
4
|
+
SimpleBranchNameStrategy,
|
|
5
|
+
createIssue,
|
|
6
|
+
executeGhCommand,
|
|
7
|
+
fetchGhIssue,
|
|
8
|
+
fetchGhPR,
|
|
9
|
+
fetchProjectFields,
|
|
10
|
+
fetchProjectItems,
|
|
11
|
+
fetchProjectList,
|
|
12
|
+
hasProjectScope,
|
|
13
|
+
updateProjectItemField
|
|
14
|
+
} from "./chunk-KQDEK2ZW.js";
|
|
15
|
+
import {
|
|
16
|
+
promptConfirmation
|
|
17
|
+
} from "./chunk-JNKJ7NJV.js";
|
|
18
|
+
import {
|
|
19
|
+
logger
|
|
20
|
+
} from "./chunk-GEHQXLEI.js";
|
|
21
|
+
|
|
22
|
+
// src/types/github.ts
|
|
23
|
+
var GitHubError = class extends Error {
|
|
24
|
+
constructor(code, message, details) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.details = details;
|
|
28
|
+
this.name = "GitHubError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/lib/GitHubService.ts
|
|
33
|
+
var GitHubService = class {
|
|
34
|
+
constructor(options) {
|
|
35
|
+
if (options == null ? void 0 : options.branchNameStrategy) {
|
|
36
|
+
this.defaultBranchNameStrategy = options.branchNameStrategy;
|
|
37
|
+
} else if ((options == null ? void 0 : options.useClaude) !== false) {
|
|
38
|
+
this.defaultBranchNameStrategy = new ClaudeBranchNameStrategy(
|
|
39
|
+
options == null ? void 0 : options.claudeModel
|
|
40
|
+
);
|
|
41
|
+
} else {
|
|
42
|
+
this.defaultBranchNameStrategy = new SimpleBranchNameStrategy();
|
|
43
|
+
}
|
|
44
|
+
this.prompter = (options == null ? void 0 : options.prompter) ?? promptConfirmation;
|
|
45
|
+
}
|
|
46
|
+
// Input detection
|
|
47
|
+
async detectInputType(input) {
|
|
48
|
+
const numberMatch = input.match(/^#?(\d+)$/);
|
|
49
|
+
if (!(numberMatch == null ? void 0 : numberMatch[1])) {
|
|
50
|
+
return { type: "unknown", number: null, rawInput: input };
|
|
51
|
+
}
|
|
52
|
+
const number = parseInt(numberMatch[1], 10);
|
|
53
|
+
logger.debug("Checking if input is a PR", { number });
|
|
54
|
+
const pr = await this.isValidPR(number);
|
|
55
|
+
if (pr) {
|
|
56
|
+
return { type: "pr", number, rawInput: input };
|
|
57
|
+
}
|
|
58
|
+
logger.debug("Checking if input is an issue", { number });
|
|
59
|
+
const issue = await this.isValidIssue(number);
|
|
60
|
+
if (issue) {
|
|
61
|
+
return { type: "issue", number, rawInput: input };
|
|
62
|
+
}
|
|
63
|
+
return { type: "unknown", number: null, rawInput: input };
|
|
64
|
+
}
|
|
65
|
+
// Issue fetching with validation
|
|
66
|
+
async fetchIssue(issueNumber) {
|
|
67
|
+
var _a;
|
|
68
|
+
try {
|
|
69
|
+
return await this.fetchIssueInternal(issueNumber);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error instanceof Error && "stderr" in error && ((_a = error.stderr) == null ? void 0 : _a.includes("Could not resolve"))) {
|
|
72
|
+
throw new GitHubError(
|
|
73
|
+
"NOT_FOUND" /* NOT_FOUND */,
|
|
74
|
+
`Issue #${issueNumber} not found`,
|
|
75
|
+
error
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Silent issue validation (for detection phase)
|
|
82
|
+
async isValidIssue(issueNumber) {
|
|
83
|
+
var _a;
|
|
84
|
+
try {
|
|
85
|
+
return await this.fetchIssueInternal(issueNumber);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof Error && "stderr" in error && ((_a = error.stderr) == null ? void 0 : _a.includes("Could not resolve"))) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Internal issue fetching logic (shared by fetchIssue and isValidIssue)
|
|
94
|
+
async fetchIssueInternal(issueNumber) {
|
|
95
|
+
const ghIssue = await fetchGhIssue(issueNumber);
|
|
96
|
+
return this.mapGitHubIssueToIssue(ghIssue);
|
|
97
|
+
}
|
|
98
|
+
async validateIssueState(issue) {
|
|
99
|
+
if (issue.state === "closed") {
|
|
100
|
+
const response = await this.promptUserConfirmation(
|
|
101
|
+
`Issue #${issue.number} is closed. Continue anyway?`
|
|
102
|
+
);
|
|
103
|
+
if (!response) {
|
|
104
|
+
throw new GitHubError(
|
|
105
|
+
"INVALID_STATE" /* INVALID_STATE */,
|
|
106
|
+
"User cancelled due to closed issue"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// PR fetching with validation
|
|
112
|
+
async fetchPR(prNumber) {
|
|
113
|
+
var _a;
|
|
114
|
+
try {
|
|
115
|
+
return await this.fetchPRInternal(prNumber);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof Error && "stderr" in error && ((_a = error.stderr) == null ? void 0 : _a.includes("Could not resolve"))) {
|
|
118
|
+
throw new GitHubError(
|
|
119
|
+
"NOT_FOUND" /* NOT_FOUND */,
|
|
120
|
+
`PR #${prNumber} not found`,
|
|
121
|
+
error
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Silent PR validation (for detection phase)
|
|
128
|
+
async isValidPR(prNumber) {
|
|
129
|
+
var _a;
|
|
130
|
+
try {
|
|
131
|
+
return await this.fetchPRInternal(prNumber);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error instanceof Error && "stderr" in error && ((_a = error.stderr) == null ? void 0 : _a.includes("Could not resolve"))) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Internal PR fetching logic (shared by fetchPR and isValidPR)
|
|
140
|
+
async fetchPRInternal(prNumber) {
|
|
141
|
+
const ghPR = await fetchGhPR(prNumber);
|
|
142
|
+
return this.mapGitHubPRToPullRequest(ghPR);
|
|
143
|
+
}
|
|
144
|
+
async validatePRState(pr) {
|
|
145
|
+
if (pr.state === "closed" || pr.state === "merged") {
|
|
146
|
+
const response = await this.promptUserConfirmation(
|
|
147
|
+
`PR #${pr.number} is ${pr.state}. Continue anyway?`
|
|
148
|
+
);
|
|
149
|
+
if (!response) {
|
|
150
|
+
throw new GitHubError(
|
|
151
|
+
"INVALID_STATE" /* INVALID_STATE */,
|
|
152
|
+
`User cancelled due to ${pr.state} PR`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Branch name generation using strategy pattern
|
|
158
|
+
async generateBranchName(options) {
|
|
159
|
+
const { issueNumber, title, strategy } = options;
|
|
160
|
+
const nameStrategy = strategy ?? this.defaultBranchNameStrategy;
|
|
161
|
+
logger.debug("Generating branch name", {
|
|
162
|
+
issueNumber,
|
|
163
|
+
title,
|
|
164
|
+
strategy: nameStrategy.constructor.name
|
|
165
|
+
});
|
|
166
|
+
return nameStrategy.generate(issueNumber, title);
|
|
167
|
+
}
|
|
168
|
+
// Issue creation
|
|
169
|
+
async createIssue(title, body, repository, labels) {
|
|
170
|
+
return createIssue(title, body, { repo: repository, labels });
|
|
171
|
+
}
|
|
172
|
+
async getIssueUrl(issueNumber, repo) {
|
|
173
|
+
logger.debug("Fetching issue URL", { issueNumber, repo });
|
|
174
|
+
const issue = await fetchGhIssue(issueNumber, repo);
|
|
175
|
+
return issue.url;
|
|
176
|
+
}
|
|
177
|
+
// GitHub Projects integration
|
|
178
|
+
async moveIssueToInProgress(issueNumber) {
|
|
179
|
+
logger.info("Moving issue to In Progress in GitHub Projects", {
|
|
180
|
+
issueNumber
|
|
181
|
+
});
|
|
182
|
+
if (!await hasProjectScope()) {
|
|
183
|
+
logger.warn("Missing project scope in GitHub CLI auth");
|
|
184
|
+
throw new GitHubError(
|
|
185
|
+
"MISSING_SCOPE" /* MISSING_SCOPE */,
|
|
186
|
+
"GitHub CLI lacks project scope. Run: gh auth refresh -s project"
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
let owner;
|
|
190
|
+
try {
|
|
191
|
+
const repoInfo = await executeGhCommand(["repo", "view", "--json", "owner,name"]);
|
|
192
|
+
owner = repoInfo.owner.login;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
logger.warn("Could not determine repository info", { error });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
let projects;
|
|
198
|
+
try {
|
|
199
|
+
projects = await fetchProjectList(owner);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
logger.warn("Could not fetch projects", { owner, error });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (!projects.length) {
|
|
205
|
+
logger.warn("No projects found", { owner });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
for (const project of projects) {
|
|
209
|
+
await this.updateIssueStatusInProject(project, issueNumber, owner);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async updateIssueStatusInProject(project, issueNumber, owner) {
|
|
213
|
+
var _a;
|
|
214
|
+
let items;
|
|
215
|
+
try {
|
|
216
|
+
items = await fetchProjectItems(project.number, owner);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
logger.debug("Could not fetch project items", { project: project.number, error });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const item = items.find(
|
|
222
|
+
(i) => i.content.type === "Issue" && i.content.number === issueNumber
|
|
223
|
+
);
|
|
224
|
+
if (!item) {
|
|
225
|
+
logger.debug("Issue not found in project", {
|
|
226
|
+
issueNumber,
|
|
227
|
+
projectNumber: project.number
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
let fieldsData;
|
|
232
|
+
try {
|
|
233
|
+
fieldsData = await fetchProjectFields(project.number, owner);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
logger.debug("Could not fetch project fields", { project: project.number, error });
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const statusField = fieldsData.fields.find((f) => f.name === "Status");
|
|
239
|
+
if (!statusField) {
|
|
240
|
+
logger.debug("No Status field found in project", { projectNumber: project.number });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const inProgressOption = (_a = statusField.options) == null ? void 0 : _a.find(
|
|
244
|
+
(o) => o.name === "In Progress" || o.name === "In progress"
|
|
245
|
+
);
|
|
246
|
+
if (!inProgressOption) {
|
|
247
|
+
logger.debug("No In Progress option found in Status field", { projectNumber: project.number });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
await updateProjectItemField(
|
|
252
|
+
item.id,
|
|
253
|
+
project.id,
|
|
254
|
+
statusField.id,
|
|
255
|
+
inProgressOption.id
|
|
256
|
+
);
|
|
257
|
+
logger.info("Updated issue status in project", {
|
|
258
|
+
issueNumber,
|
|
259
|
+
projectNumber: project.number
|
|
260
|
+
});
|
|
261
|
+
} catch (error) {
|
|
262
|
+
logger.debug("Could not update project item", { item: item.id, error });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Utility methods
|
|
266
|
+
extractContext(entity) {
|
|
267
|
+
if ("branch" in entity) {
|
|
268
|
+
return `Pull Request #${entity.number}: ${entity.title}
|
|
269
|
+
Branch: ${entity.branch}
|
|
270
|
+
State: ${entity.state}`;
|
|
271
|
+
} else {
|
|
272
|
+
return `GitHub Issue #${entity.number}: ${entity.title}
|
|
273
|
+
State: ${entity.state}`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
mapGitHubIssueToIssue(ghIssue) {
|
|
277
|
+
return {
|
|
278
|
+
number: ghIssue.number,
|
|
279
|
+
title: ghIssue.title,
|
|
280
|
+
body: ghIssue.body,
|
|
281
|
+
state: ghIssue.state.toLowerCase(),
|
|
282
|
+
labels: ghIssue.labels.map((l) => l.name),
|
|
283
|
+
assignees: ghIssue.assignees.map((a) => a.login),
|
|
284
|
+
url: ghIssue.url
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
mapGitHubPRToPullRequest(ghPR) {
|
|
288
|
+
return {
|
|
289
|
+
number: ghPR.number,
|
|
290
|
+
title: ghPR.title,
|
|
291
|
+
body: ghPR.body,
|
|
292
|
+
state: ghPR.state.toLowerCase(),
|
|
293
|
+
branch: ghPR.headRefName,
|
|
294
|
+
baseBranch: ghPR.baseRefName,
|
|
295
|
+
url: ghPR.url,
|
|
296
|
+
isDraft: ghPR.isDraft
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async promptUserConfirmation(message) {
|
|
300
|
+
return this.prompter(message);
|
|
301
|
+
}
|
|
302
|
+
// Allow setting strategy at runtime for specific operations
|
|
303
|
+
setDefaultBranchNameStrategy(strategy) {
|
|
304
|
+
this.defaultBranchNameStrategy = strategy;
|
|
305
|
+
}
|
|
306
|
+
// Get current strategy for testing
|
|
307
|
+
getBranchNameStrategy() {
|
|
308
|
+
return this.defaultBranchNameStrategy;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export {
|
|
313
|
+
GitHubService
|
|
314
|
+
};
|
|
315
|
+
//# sourceMappingURL=chunk-T7QPXANZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types/github.ts","../src/lib/GitHubService.ts"],"sourcesContent":["// Core GitHub response types\nexport interface GitHubIssue {\n\tnumber: number\n\ttitle: string\n\tbody: string\n\tstate: 'OPEN' | 'CLOSED' // GitHub GraphQL format\n\tlabels: { name: string }[]\n\tassignees: { login: string }[]\n\turl: string\n\tcreatedAt: string\n\tupdatedAt: string\n}\n\n// Pull Request types\nexport interface GitHubPullRequest {\n\tnumber: number\n\ttitle: string\n\tbody: string\n\tstate: 'OPEN' | 'CLOSED' | 'MERGED'\n\theadRefName: string // source branch\n\tbaseRefName: string // target branch\n\turl: string\n\tisDraft: boolean\n\tmergeable: 'CONFLICTING' | 'MERGEABLE' | 'UNKNOWN'\n\tcreatedAt: string\n\tupdatedAt: string\n}\n\n// GitHub Projects types\nexport interface GitHubProject {\n\tnumber: number\n\tid: string\n\tname: string\n\tfields: ProjectField[]\n}\n\nexport interface ProjectField {\n\tid: string\n\tname: string\n\tdataType: 'SINGLE_SELECT' | 'TEXT' | 'NUMBER' | 'DATE'\n\toptions?: ProjectFieldOption[]\n}\n\nexport interface ProjectFieldOption {\n\tid: string\n\tname: string\n}\n\nexport interface ProjectItem {\n\tid: string\n\tcontent: {\n\t\ttype: 'Issue' | 'PullRequest' | 'DraftIssue'\n\t\tnumber: number\n\t}\n\tfieldValues: Record<string, unknown>\n}\n\n// Command result types\nexport interface GitHubCommandResult<T = unknown> {\n\tsuccess: boolean\n\tdata?: T\n\terror?: string\n\trateLimitRemaining?: number\n\trateLimitReset?: Date\n}\n\nexport interface GitHubAuthStatus {\n\thasAuth: boolean\n\tscopes: string[]\n\tusername?: string\n}\n\n// Input detection types\nexport interface GitHubInputDetection {\n\ttype: 'issue' | 'pr' | 'unknown'\n\tnumber: number | null\n\trawInput: string\n}\n\n// Branch name generation strategy interface\nexport interface BranchNameStrategy {\n\tgenerate(issueNumber: number, title: string): Promise<string>\n}\n\nexport interface BranchGenerationOptions {\n\tissueNumber: number\n\ttitle: string\n\tstrategy?: BranchNameStrategy\n}\n\n// Context and error types\nexport interface GitHubContext {\n\tissue?: GitHubIssue\n\tpullRequest?: GitHubPullRequest\n\tformattedContext: string\n}\n\nexport enum GitHubErrorCode {\n\tNOT_FOUND = 'NOT_FOUND',\n\tUNAUTHORIZED = 'UNAUTHORIZED',\n\tRATE_LIMITED = 'RATE_LIMITED',\n\tNETWORK_ERROR = 'NETWORK_ERROR',\n\tINVALID_STATE = 'INVALID_STATE',\n\tMISSING_SCOPE = 'MISSING_SCOPE',\n}\n\nexport class GitHubError extends Error {\n\tconstructor(\n\t\tpublic code: GitHubErrorCode,\n\t\tmessage: string,\n\t\tpublic details?: unknown\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'GitHubError'\n\t}\n}\n","import type { Issue, PullRequest } from '../types/index.js'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubInputDetection,\n\tBranchGenerationOptions,\n\tBranchNameStrategy,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { GitHubError, GitHubErrorCode } from '../types/github.js'\nimport {\n\texecuteGhCommand,\n\thasProjectScope,\n\tfetchGhIssue,\n\tfetchGhPR,\n\tfetchProjectList,\n\tfetchProjectItems,\n\tfetchProjectFields,\n\tupdateProjectItemField,\n\tcreateIssue,\n\tSimpleBranchNameStrategy,\n\tClaudeBranchNameStrategy,\n} from '../utils/github.js'\nimport { logger } from '../utils/logger.js'\nimport { promptConfirmation } from '../utils/prompt.js'\n\nexport class GitHubService {\n\tprivate defaultBranchNameStrategy: BranchNameStrategy\n\tprivate prompter: (message: string) => Promise<boolean>\n\n\tconstructor(options?: {\n\t\tbranchNameStrategy?: BranchNameStrategy\n\t\tuseClaude?: boolean\n\t\tclaudeModel?: string\n\t\tprompter?: (message: string) => Promise<boolean>\n\t}) {\n\t\t// Set up default strategy based on options\n\t\tif (options?.branchNameStrategy) {\n\t\t\tthis.defaultBranchNameStrategy = options.branchNameStrategy\n\t\t} else if (options?.useClaude !== false) {\n\t\t\tthis.defaultBranchNameStrategy = new ClaudeBranchNameStrategy(\n\t\t\t\toptions?.claudeModel\n\t\t\t)\n\t\t} else {\n\t\t\tthis.defaultBranchNameStrategy = new SimpleBranchNameStrategy()\n\t\t}\n\n\t\t// Set up prompter (use provided or default to promptConfirmation)\n\t\tthis.prompter = options?.prompter ?? promptConfirmation\n\t}\n\n\t// Input detection\n\tpublic async detectInputType(input: string): Promise<GitHubInputDetection> {\n\t\t// Pattern: #123 or just 123\n\t\tconst numberMatch = input.match(/^#?(\\d+)$/)\n\n\t\tif (!numberMatch?.[1]) {\n\t\t\treturn { type: 'unknown', number: null, rawInput: input }\n\t\t}\n\n\t\tconst number = parseInt(numberMatch[1], 10)\n\n\t\t// Try PR first (based on bash script logic at lines 500-533)\n\t\tlogger.debug('Checking if input is a PR', { number })\n\t\tconst pr = await this.isValidPR(number)\n\t\tif (pr) {\n\t\t\treturn { type: 'pr', number, rawInput: input }\n\t\t}\n\n\t\t// Try issue next (lines 536-575 in bash)\n\t\tlogger.debug('Checking if input is an issue', { number })\n\t\tconst issue = await this.isValidIssue(number)\n\t\tif (issue) {\n\t\t\treturn { type: 'issue', number, rawInput: input }\n\t\t}\n\n\t\t// Neither PR nor issue found\n\t\treturn { type: 'unknown', number: null, rawInput: input }\n\t}\n\n\t// Issue fetching with validation\n\tpublic async fetchIssue(issueNumber: number): Promise<Issue> {\n\t\ttry {\n\t\t\treturn await this.fetchIssueInternal(issueNumber)\n\t\t} catch (error) {\n\t\t\t// Only throw NOT_FOUND for actual \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.NOT_FOUND,\n\t\t\t\t\t`Issue #${issueNumber} not found`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t\t// Re-throw all other errors unchanged\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Silent issue validation (for detection phase)\n\tpublic async isValidIssue(issueNumber: number): Promise<Issue | false> {\n\t\ttry {\n\t\t\treturn await this.fetchIssueInternal(issueNumber)\n\t\t} catch (error) {\n\t\t\t// Silently return false for \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Re-throw unexpected errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Internal issue fetching logic (shared by fetchIssue and isValidIssue)\n\tprivate async fetchIssueInternal(issueNumber: number): Promise<Issue> {\n\t\tconst ghIssue = await fetchGhIssue(issueNumber)\n\t\treturn this.mapGitHubIssueToIssue(ghIssue)\n\t}\n\n\tpublic async validateIssueState(issue: Issue): Promise<void> {\n\t\tif (issue.state === 'closed') {\n\t\t\tconst response = await this.promptUserConfirmation(\n\t\t\t\t`Issue #${issue.number} is closed. Continue anyway?`\n\t\t\t)\n\t\t\tif (!response) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.INVALID_STATE,\n\t\t\t\t\t'User cancelled due to closed issue'\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// PR fetching with validation\n\tpublic async fetchPR(prNumber: number): Promise<PullRequest> {\n\t\ttry {\n\t\t\treturn await this.fetchPRInternal(prNumber)\n\t\t} catch (error) {\n\t\t\t// Only throw NOT_FOUND for actual \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.NOT_FOUND,\n\t\t\t\t\t`PR #${prNumber} not found`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t\t// Re-throw all other errors unchanged\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Silent PR validation (for detection phase)\n\tpublic async isValidPR(prNumber: number): Promise<PullRequest | false> {\n\t\ttry {\n\t\t\treturn await this.fetchPRInternal(prNumber)\n\t\t} catch (error) {\n\t\t\t// Silently return false for \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Re-throw unexpected errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Internal PR fetching logic (shared by fetchPR and isValidPR)\n\tprivate async fetchPRInternal(prNumber: number): Promise<PullRequest> {\n\t\tconst ghPR = await fetchGhPR(prNumber)\n\t\treturn this.mapGitHubPRToPullRequest(ghPR)\n\t}\n\n\tpublic async validatePRState(pr: PullRequest): Promise<void> {\n\t\tif (pr.state === 'closed' || pr.state === 'merged') {\n\t\t\tconst response = await this.promptUserConfirmation(\n\t\t\t\t`PR #${pr.number} is ${pr.state}. Continue anyway?`\n\t\t\t)\n\t\t\tif (!response) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.INVALID_STATE,\n\t\t\t\t\t`User cancelled due to ${pr.state} PR`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Branch name generation using strategy pattern\n\tpublic async generateBranchName(\n\t\toptions: BranchGenerationOptions\n\t): Promise<string> {\n\t\tconst { issueNumber, title, strategy } = options\n\n\t\t// Use provided strategy or fall back to default\n\t\tconst nameStrategy = strategy ?? this.defaultBranchNameStrategy\n\n\t\tlogger.debug('Generating branch name', {\n\t\t\tissueNumber,\n\t\t\ttitle,\n\t\t\tstrategy: nameStrategy.constructor.name,\n\t\t})\n\n\t\treturn nameStrategy.generate(issueNumber, title)\n\t}\n\n\t// Issue creation\n\tpublic async createIssue(\n\t\ttitle: string,\n\t\tbody: string,\n\t\trepository?: string,\n\t\tlabels?: string[]\n\t): Promise<{ number: number; url: string }> {\n\t\t// logger.info('Creating GitHub issue', { title })\n\t\treturn createIssue(title, body, { repo: repository, labels })\n\t}\n\n\tpublic async getIssueUrl(issueNumber: number, repo?: string): Promise<string> {\n\t\tlogger.debug('Fetching issue URL', { issueNumber, repo })\n\t\tconst issue = await fetchGhIssue(issueNumber, repo)\n\t\treturn issue.url\n\t}\n\n\t// GitHub Projects integration\n\tpublic async moveIssueToInProgress(issueNumber: number): Promise<void> {\n\t\t// Based on bash script lines 374-463\n\t\tlogger.info('Moving issue to In Progress in GitHub Projects', {\n\t\t\tissueNumber,\n\t\t})\n\n\t\t// Check for project scope\n\t\tif (!(await hasProjectScope())) {\n\t\t\tlogger.warn('Missing project scope in GitHub CLI auth')\n\t\t\tthrow new GitHubError(\n\t\t\t\tGitHubErrorCode.MISSING_SCOPE,\n\t\t\t\t'GitHub CLI lacks project scope. Run: gh auth refresh -s project'\n\t\t\t)\n\t\t}\n\n\t\t// Get repository info\n\t\tlet owner: string\n\t\ttry {\n\t\t\tconst repoInfo = await executeGhCommand<{\n\t\t\t\towner: { login: string }\n\t\t\t\tname: string\n\t\t\t}>(['repo', 'view', '--json', 'owner,name'])\n\t\t\towner = repoInfo.owner.login\n\t\t} catch (error) {\n\t\t\tlogger.warn('Could not determine repository info', { error })\n\t\t\treturn\n\t\t}\n\n\t\t// List all projects\n\t\tlet projects: GitHubProject[]\n\t\ttry {\n\t\t\tprojects = await fetchProjectList(owner)\n\t\t} catch (error) {\n\t\t\tlogger.warn('Could not fetch projects', { owner, error })\n\t\t\treturn\n\t\t}\n\n\t\tif (!projects.length) {\n\t\t\tlogger.warn('No projects found', { owner })\n\t\t\treturn\n\t\t}\n\n\t\t// Process each project (lines 404-460 in bash)\n\t\tfor (const project of projects) {\n\t\t\tawait this.updateIssueStatusInProject(project, issueNumber, owner)\n\t\t}\n\t}\n\n\tprivate async updateIssueStatusInProject(\n\t\tproject: GitHubProject,\n\t\tissueNumber: number,\n\t\towner: string\n\t): Promise<void> {\n\t\t// Check if issue is in project\n\t\tlet items: ProjectItem[]\n\t\ttry {\n\t\t\titems = await fetchProjectItems(project.number, owner)\n\t\t} catch (error) {\n\t\t\tlogger.debug('Could not fetch project items', { project: project.number, error })\n\t\t\treturn\n\t\t}\n\n\t\t// Find issue item\n\t\tconst item = items.find(\n\t\t\t(i: ProjectItem) =>\n\t\t\t\ti.content.type === 'Issue' && i.content.number === issueNumber\n\t\t)\n\n\t\tif (!item) {\n\t\t\tlogger.debug('Issue not found in project', {\n\t\t\t\tissueNumber,\n\t\t\t\tprojectNumber: project.number,\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\t// Fetch project fields separately (like bash script does)\n\t\tlet fieldsData: { fields: ProjectField[] }\n\t\ttry {\n\t\t\tfieldsData = await fetchProjectFields(project.number, owner)\n\t\t} catch (error) {\n\t\t\tlogger.debug('Could not fetch project fields', { project: project.number, error })\n\t\t\treturn\n\t\t}\n\n\t\t// Find Status field and In Progress option\n\t\tconst statusField = fieldsData.fields.find((f) => f.name === 'Status')\n\t\tif (!statusField) {\n\t\t\tlogger.debug('No Status field found in project', { projectNumber: project.number })\n\t\t\treturn\n\t\t}\n\n\t\tconst inProgressOption = statusField.options?.find(\n\t\t\t(o: { id: string; name: string }) => o.name === 'In Progress' || o.name === 'In progress'\n\t\t)\n\n\t\tif (!inProgressOption) {\n\t\t\tlogger.debug('No In Progress option found in Status field', { projectNumber: project.number })\n\t\t\treturn\n\t\t}\n\n\t\t// Update status\n\t\ttry {\n\t\t\tawait updateProjectItemField(\n\t\t\t\titem.id,\n\t\t\t\tproject.id,\n\t\t\t\tstatusField.id,\n\t\t\t\tinProgressOption.id\n\t\t\t)\n\n\t\t\tlogger.info('Updated issue status in project', {\n\t\t\t\tissueNumber,\n\t\t\t\tprojectNumber: project.number,\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tlogger.debug('Could not update project item', { item: item.id, error })\n\t\t}\n\t}\n\n\t// Utility methods\n\tpublic extractContext(entity: Issue | PullRequest): string {\n\t\tif ('branch' in entity) {\n\t\t\t// It's a PullRequest\n\t\t\treturn `Pull Request #${entity.number}: ${entity.title}\\nBranch: ${entity.branch}\\nState: ${entity.state}`\n\t\t} else {\n\t\t\t// It's an Issue\n\t\t\treturn `GitHub Issue #${entity.number}: ${entity.title}\\nState: ${entity.state}`\n\t\t}\n\t}\n\n\tprivate mapGitHubIssueToIssue(ghIssue: GitHubIssue): Issue {\n\t\treturn {\n\t\t\tnumber: ghIssue.number,\n\t\t\ttitle: ghIssue.title,\n\t\t\tbody: ghIssue.body,\n\t\t\tstate: ghIssue.state.toLowerCase() as 'open' | 'closed',\n\t\t\tlabels: ghIssue.labels.map((l) => l.name),\n\t\t\tassignees: ghIssue.assignees.map((a) => a.login),\n\t\t\turl: ghIssue.url,\n\t\t}\n\t}\n\n\tprivate mapGitHubPRToPullRequest(ghPR: GitHubPullRequest): PullRequest {\n\t\treturn {\n\t\t\tnumber: ghPR.number,\n\t\t\ttitle: ghPR.title,\n\t\t\tbody: ghPR.body,\n\t\t\tstate: ghPR.state.toLowerCase() as 'open' | 'closed' | 'merged',\n\t\t\tbranch: ghPR.headRefName,\n\t\t\tbaseBranch: ghPR.baseRefName,\n\t\t\turl: ghPR.url,\n\t\t\tisDraft: ghPR.isDraft,\n\t\t}\n\t}\n\n\tprivate async promptUserConfirmation(message: string): Promise<boolean> {\n\t\treturn this.prompter(message)\n\t}\n\n\t// Allow setting strategy at runtime for specific operations\n\tpublic setDefaultBranchNameStrategy(strategy: BranchNameStrategy): void {\n\t\tthis.defaultBranchNameStrategy = strategy\n\t}\n\n\t// Get current strategy for testing\n\tpublic getBranchNameStrategy(): BranchNameStrategy {\n\t\treturn this.defaultBranchNameStrategy\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
logger
|
|
4
|
+
} from "./chunk-GEHQXLEI.js";
|
|
5
|
+
|
|
6
|
+
// src/utils/update-notifier.ts
|
|
7
|
+
import os from "os";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import fs from "fs-extra";
|
|
10
|
+
import { execa } from "execa";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
var UpdateNotifier = class {
|
|
13
|
+
constructor(currentVersion, packageName) {
|
|
14
|
+
this.currentVersion = currentVersion;
|
|
15
|
+
this.packageName = packageName;
|
|
16
|
+
const configDir = path.join(os.homedir(), ".config", "iloom-ai");
|
|
17
|
+
this.cacheFilePath = path.join(configDir, "update-check.json");
|
|
18
|
+
logger.debug(`UpdateNotifier initialized: version=${currentVersion}, package=${packageName}, cachePath=${this.cacheFilePath}`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check for updates, respecting 24hr cache
|
|
22
|
+
* Returns UpdateCheckResult or null if check failed/not needed
|
|
23
|
+
*/
|
|
24
|
+
async checkForUpdates() {
|
|
25
|
+
logger.debug("checkForUpdates: Starting update check");
|
|
26
|
+
try {
|
|
27
|
+
logger.debug("checkForUpdates: Checking cache");
|
|
28
|
+
const cached = await this.getCachedCheck();
|
|
29
|
+
if (cached !== null) {
|
|
30
|
+
logger.debug(`checkForUpdates: Using cached result - latest=${cached.latestVersion}, lastCheck=${new Date(cached.lastCheck).toISOString()}`);
|
|
31
|
+
const updateAvailable2 = this.isNewerVersion(this.currentVersion, cached.latestVersion);
|
|
32
|
+
logger.debug(`checkForUpdates: Update available from cache: ${updateAvailable2}`);
|
|
33
|
+
return {
|
|
34
|
+
currentVersion: this.currentVersion,
|
|
35
|
+
latestVersion: cached.latestVersion,
|
|
36
|
+
updateAvailable: updateAvailable2
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
logger.debug("checkForUpdates: No valid cache, querying npm registry");
|
|
40
|
+
const latestVersion = await this.fetchLatestVersion();
|
|
41
|
+
if (latestVersion === null) {
|
|
42
|
+
logger.debug("checkForUpdates: Failed to fetch latest version from npm");
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
logger.debug(`checkForUpdates: Fetched latest version: ${latestVersion}`);
|
|
46
|
+
logger.debug("checkForUpdates: Saving to cache");
|
|
47
|
+
await this.saveCacheFile(latestVersion);
|
|
48
|
+
const updateAvailable = this.isNewerVersion(this.currentVersion, latestVersion);
|
|
49
|
+
logger.debug(`checkForUpdates: Update available: ${updateAvailable} (current=${this.currentVersion}, latest=${latestVersion})`);
|
|
50
|
+
return {
|
|
51
|
+
currentVersion: this.currentVersion,
|
|
52
|
+
latestVersion,
|
|
53
|
+
updateAvailable
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.debug(`checkForUpdates: Error during update check: ${error}`);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Read cache file, return null if stale or missing
|
|
62
|
+
*/
|
|
63
|
+
async getCachedCheck() {
|
|
64
|
+
logger.debug(`getCachedCheck: Checking cache file at ${this.cacheFilePath}`);
|
|
65
|
+
try {
|
|
66
|
+
if (!fs.existsSync(this.cacheFilePath)) {
|
|
67
|
+
logger.debug("getCachedCheck: Cache file does not exist");
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
logger.debug("getCachedCheck: Cache file exists, reading contents");
|
|
71
|
+
const content = await fs.readFile(this.cacheFilePath, "utf8");
|
|
72
|
+
logger.debug(`getCachedCheck: Cache file content: ${content}`);
|
|
73
|
+
const cache = JSON.parse(content);
|
|
74
|
+
const cacheTimeoutMins = parseInt(process.env.ILOOM_UPDATE_CACHE_TIMEOUT_MINS ?? "360", 10);
|
|
75
|
+
const cacheTimeoutMs = cacheTimeoutMins * 60 * 1e3;
|
|
76
|
+
logger.debug(`getCachedCheck: Using cache timeout of ${cacheTimeoutMins} minutes`);
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
const age = now - cache.lastCheck;
|
|
79
|
+
const ageHours = age / (60 * 60 * 1e3);
|
|
80
|
+
logger.debug(`getCachedCheck: Cache age: ${ageHours.toFixed(2)} hours (threshold: ${cacheTimeoutMins / 60} hours)`);
|
|
81
|
+
if (now - cache.lastCheck < cacheTimeoutMs) {
|
|
82
|
+
logger.debug("getCachedCheck: Cache is fresh, returning cached data");
|
|
83
|
+
return cache;
|
|
84
|
+
}
|
|
85
|
+
logger.debug("getCachedCheck: Cache is stale, will query npm registry");
|
|
86
|
+
return null;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.debug(`getCachedCheck: Error reading cache: ${error}`);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Save successful check to cache
|
|
94
|
+
*/
|
|
95
|
+
async saveCacheFile(latestVersion) {
|
|
96
|
+
logger.debug(`saveCacheFile: Attempting to save cache for version ${latestVersion}`);
|
|
97
|
+
try {
|
|
98
|
+
const configDir = path.dirname(this.cacheFilePath);
|
|
99
|
+
logger.debug(`saveCacheFile: Ensuring cache directory exists: ${configDir}`);
|
|
100
|
+
await fs.ensureDir(configDir);
|
|
101
|
+
const cache = {
|
|
102
|
+
lastCheck: Date.now(),
|
|
103
|
+
latestVersion
|
|
104
|
+
};
|
|
105
|
+
const cacheJson = JSON.stringify(cache, null, 2);
|
|
106
|
+
logger.debug(`saveCacheFile: Writing cache file: ${cacheJson}`);
|
|
107
|
+
await fs.writeFile(this.cacheFilePath, cacheJson, "utf8");
|
|
108
|
+
logger.debug(`saveCacheFile: Cache file saved successfully to ${this.cacheFilePath}`);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
logger.debug(`saveCacheFile: Failed to save update check cache to ${this.cacheFilePath}: ${error}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Display update notification to user
|
|
115
|
+
*/
|
|
116
|
+
displayUpdateNotification(result) {
|
|
117
|
+
logger.debug(`displayUpdateNotification: updateAvailable=${result.updateAvailable}, current=${result.currentVersion}, latest=${result.latestVersion}`);
|
|
118
|
+
if (result.updateAvailable) {
|
|
119
|
+
logger.debug("displayUpdateNotification: Displaying update notification to user");
|
|
120
|
+
console.log("");
|
|
121
|
+
console.log(" " + chalk.bold(`Update available: ${result.currentVersion} \u2192 ${result.latestVersion}`));
|
|
122
|
+
console.log(" " + chalk.bold("Run: il update"));
|
|
123
|
+
console.log("");
|
|
124
|
+
} else {
|
|
125
|
+
logger.debug("displayUpdateNotification: No update available, skipping notification");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Query npm registry for latest version
|
|
130
|
+
*/
|
|
131
|
+
async fetchLatestVersion() {
|
|
132
|
+
logger.debug(`fetchLatestVersion: Querying npm for package ${this.packageName}`);
|
|
133
|
+
try {
|
|
134
|
+
const { stdout } = await execa("npm", ["view", this.packageName, "version"], {
|
|
135
|
+
timeout: 5e3
|
|
136
|
+
});
|
|
137
|
+
const version = stdout.trim();
|
|
138
|
+
logger.debug(`fetchLatestVersion: npm returned version: ${version}`);
|
|
139
|
+
return version;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.debug(`fetchLatestVersion: Failed to query npm: ${error}`);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Compare semver versions
|
|
147
|
+
* Returns true if latest > current
|
|
148
|
+
*/
|
|
149
|
+
isNewerVersion(current, latest) {
|
|
150
|
+
logger.debug(`isNewerVersion: Comparing versions - current=${current}, latest=${latest}`);
|
|
151
|
+
try {
|
|
152
|
+
const currentParts = current.split(".").map((p) => parseInt(p, 10));
|
|
153
|
+
const latestParts = latest.split(".").map((p) => parseInt(p, 10));
|
|
154
|
+
logger.debug(`isNewerVersion: Parsed parts - current=[${currentParts.join(", ")}], latest=[${latestParts.join(", ")}]`);
|
|
155
|
+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
156
|
+
const curr = currentParts[i] ?? 0;
|
|
157
|
+
const next = latestParts[i] ?? 0;
|
|
158
|
+
logger.debug(`isNewerVersion: Comparing part ${i}: current=${curr}, latest=${next}`);
|
|
159
|
+
if (next > curr) {
|
|
160
|
+
logger.debug(`isNewerVersion: Latest is newer (${next} > ${curr})`);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
if (next < curr) {
|
|
164
|
+
logger.debug(`isNewerVersion: Current is newer (${curr} > ${next})`);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
logger.debug("isNewerVersion: Versions are equal");
|
|
169
|
+
return false;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
logger.debug(`isNewerVersion: Error comparing versions: ${error}`);
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
async function checkAndNotifyUpdate(currentVersion, packageName, installMethod) {
|
|
177
|
+
logger.debug(`checkAndNotifyUpdate: Called with version=${currentVersion}, package=${packageName}, installMethod=${installMethod}`);
|
|
178
|
+
try {
|
|
179
|
+
if (installMethod !== "global") {
|
|
180
|
+
logger.debug(`checkAndNotifyUpdate: Skipping update check - not a global installation (method=${installMethod})`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
logger.debug("checkAndNotifyUpdate: Creating UpdateNotifier instance");
|
|
184
|
+
const notifier = new UpdateNotifier(currentVersion, packageName);
|
|
185
|
+
logger.debug("checkAndNotifyUpdate: Calling checkForUpdates()");
|
|
186
|
+
const result = await notifier.checkForUpdates();
|
|
187
|
+
if (result !== null) {
|
|
188
|
+
logger.debug(`checkAndNotifyUpdate: Got result, calling displayUpdateNotification`);
|
|
189
|
+
notifier.displayUpdateNotification(result);
|
|
190
|
+
} else {
|
|
191
|
+
logger.debug("checkAndNotifyUpdate: Result was null, not displaying notification");
|
|
192
|
+
}
|
|
193
|
+
logger.debug("checkAndNotifyUpdate: Completed");
|
|
194
|
+
} catch (error) {
|
|
195
|
+
logger.debug(`checkAndNotifyUpdate: Unexpected error: ${error}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
UpdateNotifier,
|
|
201
|
+
checkAndNotifyUpdate
|
|
202
|
+
};
|
|
203
|
+
//# sourceMappingURL=chunk-U3WU5OWO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/update-notifier.ts"],"sourcesContent":["import os from 'os'\nimport path from 'path'\nimport fs from 'fs-extra'\nimport { execa } from 'execa'\nimport chalk from 'chalk'\nimport { logger } from './logger.js'\n\nexport interface UpdateCheckCache {\n lastCheck: number // timestamp\n latestVersion: string\n}\n\nexport interface UpdateCheckResult {\n currentVersion: string\n latestVersion: string\n updateAvailable: boolean\n}\n\nexport class UpdateNotifier {\n private cacheFilePath: string\n private currentVersion: string\n private packageName: string\n\n constructor(currentVersion: string, packageName: string) {\n this.currentVersion = currentVersion\n this.packageName = packageName\n // Cross-platform cache directory\n const configDir = path.join(os.homedir(), '.config', 'iloom-ai')\n this.cacheFilePath = path.join(configDir, 'update-check.json')\n logger.debug(`UpdateNotifier initialized: version=${currentVersion}, package=${packageName}, cachePath=${this.cacheFilePath}`)\n }\n\n /**\n * Check for updates, respecting 24hr cache\n * Returns UpdateCheckResult or null if check failed/not needed\n */\n async checkForUpdates(): Promise<UpdateCheckResult | null> {\n logger.debug('checkForUpdates: Starting update check')\n try {\n // Check cache first\n logger.debug('checkForUpdates: Checking cache')\n const cached = await this.getCachedCheck()\n if (cached !== null) {\n logger.debug(`checkForUpdates: Using cached result - latest=${cached.latestVersion}, lastCheck=${new Date(cached.lastCheck).toISOString()}`)\n const updateAvailable = this.isNewerVersion(this.currentVersion, cached.latestVersion)\n logger.debug(`checkForUpdates: Update available from cache: ${updateAvailable}`)\n return {\n currentVersion: this.currentVersion,\n latestVersion: cached.latestVersion,\n updateAvailable,\n }\n }\n\n logger.debug('checkForUpdates: No valid cache, querying npm registry')\n // Query npm registry\n const latestVersion = await this.fetchLatestVersion()\n if (latestVersion === null) {\n logger.debug('checkForUpdates: Failed to fetch latest version from npm')\n return null\n }\n\n logger.debug(`checkForUpdates: Fetched latest version: ${latestVersion}`)\n\n // Save to cache\n logger.debug('checkForUpdates: Saving to cache')\n await this.saveCacheFile(latestVersion)\n\n const updateAvailable = this.isNewerVersion(this.currentVersion, latestVersion)\n logger.debug(`checkForUpdates: Update available: ${updateAvailable} (current=${this.currentVersion}, latest=${latestVersion})`)\n\n return {\n currentVersion: this.currentVersion,\n latestVersion,\n updateAvailable,\n }\n } catch (error) {\n // Handle all errors gracefully - update check should never break user experience\n logger.debug(`checkForUpdates: Error during update check: ${error}`)\n return null\n }\n }\n\n /**\n * Read cache file, return null if stale or missing\n */\n private async getCachedCheck(): Promise<UpdateCheckCache | null> {\n logger.debug(`getCachedCheck: Checking cache file at ${this.cacheFilePath}`)\n try {\n if (!fs.existsSync(this.cacheFilePath)) {\n logger.debug('getCachedCheck: Cache file does not exist')\n return null\n }\n\n logger.debug('getCachedCheck: Cache file exists, reading contents')\n const content = await fs.readFile(this.cacheFilePath, 'utf8')\n logger.debug(`getCachedCheck: Cache file content: ${content}`)\n const cache = JSON.parse(content) as UpdateCheckCache\n\n // Check if cache is still fresh (< configurable hours)\n const cacheTimeoutMins = parseInt(process.env.ILOOM_UPDATE_CACHE_TIMEOUT_MINS ?? '360', 10) // Default 6 hours\n const cacheTimeoutMs = cacheTimeoutMins * 60 * 1000\n logger.debug(`getCachedCheck: Using cache timeout of ${cacheTimeoutMins} minutes`)\n const now = Date.now()\n const age = now - cache.lastCheck\n const ageHours = age / (60 * 60 * 1000)\n logger.debug(`getCachedCheck: Cache age: ${ageHours.toFixed(2)} hours (threshold: ${cacheTimeoutMins / 60} hours)`)\n\n if (now - cache.lastCheck < cacheTimeoutMs) {\n logger.debug('getCachedCheck: Cache is fresh, returning cached data')\n return cache\n }\n\n logger.debug('getCachedCheck: Cache is stale, will query npm registry')\n return null\n } catch (error) {\n // If cache is corrupted or unreadable, treat as missing\n logger.debug(`getCachedCheck: Error reading cache: ${error}`)\n return null\n }\n }\n\n /**\n * Save successful check to cache\n */\n private async saveCacheFile(latestVersion: string): Promise<void> {\n logger.debug(`saveCacheFile: Attempting to save cache for version ${latestVersion}`)\n try {\n // Ensure cache directory exists\n const configDir = path.dirname(this.cacheFilePath)\n logger.debug(`saveCacheFile: Ensuring cache directory exists: ${configDir}`)\n await fs.ensureDir(configDir)\n\n // Write cache file\n const cache: UpdateCheckCache = {\n lastCheck: Date.now(),\n latestVersion,\n }\n const cacheJson = JSON.stringify(cache, null, 2)\n logger.debug(`saveCacheFile: Writing cache file: ${cacheJson}`)\n await fs.writeFile(this.cacheFilePath, cacheJson, 'utf8')\n logger.debug(`saveCacheFile: Cache file saved successfully to ${this.cacheFilePath}`)\n } catch (error) {\n // Log debug message but don't throw - cache write failure shouldn't break anything\n logger.debug(`saveCacheFile: Failed to save update check cache to ${this.cacheFilePath}: ${error}`)\n }\n }\n\n /**\n * Display update notification to user\n */\n displayUpdateNotification(result: UpdateCheckResult): void {\n logger.debug(`displayUpdateNotification: updateAvailable=${result.updateAvailable}, current=${result.currentVersion}, latest=${result.latestVersion}`)\n if (result.updateAvailable) {\n logger.debug('displayUpdateNotification: Displaying update notification to user')\n // Simple, clear update notification\n /* eslint-disable no-console */\n console.log('')\n console.log(' ' + chalk.bold(`Update available: ${result.currentVersion} → ${result.latestVersion}`))\n console.log(' ' + chalk.bold('Run: il update'))\n console.log('')\n /* eslint-enable no-console */\n } else {\n logger.debug('displayUpdateNotification: No update available, skipping notification')\n }\n }\n\n /**\n * Query npm registry for latest version\n */\n private async fetchLatestVersion(): Promise<string | null> {\n logger.debug(`fetchLatestVersion: Querying npm for package ${this.packageName}`)\n try {\n const { stdout } = await execa('npm', ['view', this.packageName, 'version'], {\n timeout: 5000,\n })\n const version = stdout.trim()\n logger.debug(`fetchLatestVersion: npm returned version: ${version}`)\n return version\n } catch (error) {\n // Network errors, timeouts, npm not available, or package not found\n logger.debug(`fetchLatestVersion: Failed to query npm: ${error}`)\n return null\n }\n }\n\n /**\n * Compare semver versions\n * Returns true if latest > current\n */\n private isNewerVersion(current: string, latest: string): boolean {\n logger.debug(`isNewerVersion: Comparing versions - current=${current}, latest=${latest}`)\n // Simple version comparison: split by dots and compare numerically\n try {\n const currentParts = current.split('.').map(p => parseInt(p, 10))\n const latestParts = latest.split('.').map(p => parseInt(p, 10))\n logger.debug(`isNewerVersion: Parsed parts - current=[${currentParts.join(', ')}], latest=[${latestParts.join(', ')}]`)\n\n for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {\n const curr = currentParts[i] ?? 0\n const next = latestParts[i] ?? 0\n\n logger.debug(`isNewerVersion: Comparing part ${i}: current=${curr}, latest=${next}`)\n\n if (next > curr) {\n logger.debug(`isNewerVersion: Latest is newer (${next} > ${curr})`)\n return true\n }\n if (next < curr) {\n logger.debug(`isNewerVersion: Current is newer (${curr} > ${next})`)\n return false\n }\n }\n\n logger.debug('isNewerVersion: Versions are equal')\n return false\n } catch (error) {\n // If parsing fails, assume no update\n logger.debug(`isNewerVersion: Error comparing versions: ${error}`)\n return false\n }\n }\n}\n\n/**\n * Main entry point for update check\n * Call from CLI postAction hook\n */\nexport async function checkAndNotifyUpdate(\n currentVersion: string,\n packageName: string,\n installMethod: string\n): Promise<void> {\n logger.debug(`checkAndNotifyUpdate: Called with version=${currentVersion}, package=${packageName}, installMethod=${installMethod}`)\n try {\n // Only check for global installations\n if (installMethod !== 'global') {\n logger.debug(`checkAndNotifyUpdate: Skipping update check - not a global installation (method=${installMethod})`)\n return\n }\n\n logger.debug('checkAndNotifyUpdate: Creating UpdateNotifier instance')\n const notifier = new UpdateNotifier(currentVersion, packageName)\n\n logger.debug('checkAndNotifyUpdate: Calling checkForUpdates()')\n const result = await notifier.checkForUpdates()\n\n if (result !== null) {\n logger.debug(`checkAndNotifyUpdate: Got result, calling displayUpdateNotification`)\n notifier.displayUpdateNotification(result)\n } else {\n logger.debug('checkAndNotifyUpdate: Result was null, not displaying notification')\n }\n\n logger.debug('checkAndNotifyUpdate: Completed')\n } catch (error) {\n // All errors handled internally - this should never throw\n logger.debug(`checkAndNotifyUpdate: Unexpected error: ${error}`)\n }\n}\n"],"mappings":"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","names":["updateAvailable"]}
|