hub-launch 1.0.3
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/CHANGELOG.md +144 -0
- package/README.md +195 -0
- package/bin/hublaunch +20 -0
- package/dist/commands/checkout.d.ts +7 -0
- package/dist/commands/checkout.d.ts.map +1 -0
- package/dist/commands/checkout.js +164 -0
- package/dist/commands/checkout.js.map +1 -0
- package/dist/commands/clean.d.ts +7 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +206 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/close.d.ts +7 -0
- package/dist/commands/close.d.ts.map +1 -0
- package/dist/commands/close.js +109 -0
- package/dist/commands/close.js.map +1 -0
- package/dist/commands/create.d.ts +7 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +367 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/diff.d.ts +7 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +98 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/execute.d.ts +7 -0
- package/dist/commands/execute.d.ts.map +1 -0
- package/dist/commands/execute.js +376 -0
- package/dist/commands/execute.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +1243 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/interactive.d.ts +12 -0
- package/dist/commands/interactive.d.ts.map +1 -0
- package/dist/commands/interactive.js +1071 -0
- package/dist/commands/interactive.js.map +1 -0
- package/dist/commands/launch.d.ts +16 -0
- package/dist/commands/launch.d.ts.map +1 -0
- package/dist/commands/launch.js +684 -0
- package/dist/commands/launch.js.map +1 -0
- package/dist/commands/login.d.ts +40 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +469 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logs.d.ts +22 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +105 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/merge.d.ts +7 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/merge.js +240 -0
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/preview.d.ts +7 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +160 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/project.d.ts +18 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +352 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/respond.d.ts +7 -0
- package/dist/commands/respond.d.ts.map +1 -0
- package/dist/commands/respond.js +112 -0
- package/dist/commands/respond.js.map +1 -0
- package/dist/commands/setPriority.d.ts +7 -0
- package/dist/commands/setPriority.d.ts.map +1 -0
- package/dist/commands/setPriority.js +112 -0
- package/dist/commands/setPriority.js.map +1 -0
- package/dist/commands/track.d.ts +7 -0
- package/dist/commands/track.d.ts.map +1 -0
- package/dist/commands/track.js +140 -0
- package/dist/commands/track.js.map +1 -0
- package/dist/commands/upload.d.ts +21 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +145 -0
- package/dist/commands/upload.js.map +1 -0
- package/dist/commands/view.d.ts +7 -0
- package/dist/commands/view.d.ts.map +1 -0
- package/dist/commands/view.js +168 -0
- package/dist/commands/view.js.map +1 -0
- package/dist/commands/watch.d.ts +7 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +438 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/commands/worktree.d.ts +7 -0
- package/dist/commands/worktree.d.ts.map +1 -0
- package/dist/commands/worktree.js +122 -0
- package/dist/commands/worktree.js.map +1 -0
- package/dist/config/command-names.d.ts +16 -0
- package/dist/config/command-names.d.ts.map +1 -0
- package/dist/config/command-names.js +17 -0
- package/dist/config/command-names.js.map +1 -0
- package/dist/config/constants.d.ts +12 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +12 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/index.d.ts +47 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +175 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/services/api/HulaApiClient.d.ts +170 -0
- package/dist/services/api/HulaApiClient.d.ts.map +1 -0
- package/dist/services/api/HulaApiClient.js +233 -0
- package/dist/services/api/HulaApiClient.js.map +1 -0
- package/dist/services/diff/DiffService.d.ts +44 -0
- package/dist/services/diff/DiffService.d.ts.map +1 -0
- package/dist/services/diff/DiffService.js +126 -0
- package/dist/services/diff/DiffService.js.map +1 -0
- package/dist/services/editor/EditorService.d.ts +53 -0
- package/dist/services/editor/EditorService.d.ts.map +1 -0
- package/dist/services/editor/EditorService.js +161 -0
- package/dist/services/editor/EditorService.js.map +1 -0
- package/dist/services/git/GitService.d.ts +124 -0
- package/dist/services/git/GitService.d.ts.map +1 -0
- package/dist/services/git/GitService.js +423 -0
- package/dist/services/git/GitService.js.map +1 -0
- package/dist/services/git/WorktreeService.d.ts +144 -0
- package/dist/services/git/WorktreeService.d.ts.map +1 -0
- package/dist/services/git/WorktreeService.js +505 -0
- package/dist/services/git/WorktreeService.js.map +1 -0
- package/dist/services/github/ActionsService.d.ts +66 -0
- package/dist/services/github/ActionsService.d.ts.map +1 -0
- package/dist/services/github/ActionsService.js +114 -0
- package/dist/services/github/ActionsService.js.map +1 -0
- package/dist/services/github/AssetUploadService.d.ts +103 -0
- package/dist/services/github/AssetUploadService.d.ts.map +1 -0
- package/dist/services/github/AssetUploadService.js +299 -0
- package/dist/services/github/AssetUploadService.js.map +1 -0
- package/dist/services/github/BatchCopilotDetectionService.d.ts +58 -0
- package/dist/services/github/BatchCopilotDetectionService.d.ts.map +1 -0
- package/dist/services/github/BatchCopilotDetectionService.js +321 -0
- package/dist/services/github/BatchCopilotDetectionService.js.map +1 -0
- package/dist/services/github/CopilotDetectionService.d.ts +40 -0
- package/dist/services/github/CopilotDetectionService.d.ts.map +1 -0
- package/dist/services/github/CopilotDetectionService.js +280 -0
- package/dist/services/github/CopilotDetectionService.js.map +1 -0
- package/dist/services/github/CopilotService.d.ts +27 -0
- package/dist/services/github/CopilotService.d.ts.map +1 -0
- package/dist/services/github/CopilotService.js +249 -0
- package/dist/services/github/CopilotService.js.map +1 -0
- package/dist/services/github/DeploymentDetectionService.d.ts +38 -0
- package/dist/services/github/DeploymentDetectionService.d.ts.map +1 -0
- package/dist/services/github/DeploymentDetectionService.js +104 -0
- package/dist/services/github/DeploymentDetectionService.js.map +1 -0
- package/dist/services/github/GithubAppService.d.ts +40 -0
- package/dist/services/github/GithubAppService.d.ts.map +1 -0
- package/dist/services/github/GithubAppService.js +187 -0
- package/dist/services/github/GithubAppService.js.map +1 -0
- package/dist/services/github/IssueService.d.ts +65 -0
- package/dist/services/github/IssueService.d.ts.map +1 -0
- package/dist/services/github/IssueService.js +231 -0
- package/dist/services/github/IssueService.js.map +1 -0
- package/dist/services/github/LabelService.d.ts +36 -0
- package/dist/services/github/LabelService.d.ts.map +1 -0
- package/dist/services/github/LabelService.js +122 -0
- package/dist/services/github/LabelService.js.map +1 -0
- package/dist/services/github/PRService.d.ts +77 -0
- package/dist/services/github/PRService.d.ts.map +1 -0
- package/dist/services/github/PRService.js +287 -0
- package/dist/services/github/PRService.js.map +1 -0
- package/dist/services/github/ProjectService.d.ts +144 -0
- package/dist/services/github/ProjectService.d.ts.map +1 -0
- package/dist/services/github/ProjectService.js +804 -0
- package/dist/services/github/ProjectService.js.map +1 -0
- package/dist/services/hooks/HookExecutor.d.ts +66 -0
- package/dist/services/hooks/HookExecutor.d.ts.map +1 -0
- package/dist/services/hooks/HookExecutor.js +227 -0
- package/dist/services/hooks/HookExecutor.js.map +1 -0
- package/dist/services/hooks/index.d.ts +2 -0
- package/dist/services/hooks/index.d.ts.map +1 -0
- package/dist/services/hooks/index.js +2 -0
- package/dist/services/hooks/index.js.map +1 -0
- package/dist/services/logs/LogFormatterService.d.ts +17 -0
- package/dist/services/logs/LogFormatterService.d.ts.map +1 -0
- package/dist/services/logs/LogFormatterService.js +35 -0
- package/dist/services/logs/LogFormatterService.js.map +1 -0
- package/dist/services/logs/TempFileService.d.ts +27 -0
- package/dist/services/logs/TempFileService.d.ts.map +1 -0
- package/dist/services/logs/TempFileService.js +90 -0
- package/dist/services/logs/TempFileService.js.map +1 -0
- package/dist/services/logs/index.d.ts +3 -0
- package/dist/services/logs/index.d.ts.map +1 -0
- package/dist/services/logs/index.js +4 -0
- package/dist/services/logs/index.js.map +1 -0
- package/dist/services/plan/PlanService.d.ts +70 -0
- package/dist/services/plan/PlanService.d.ts.map +1 -0
- package/dist/services/plan/PlanService.js +211 -0
- package/dist/services/plan/PlanService.js.map +1 -0
- package/dist/services/polling/GitHubDirectPollingAdapter.d.ts +16 -0
- package/dist/services/polling/GitHubDirectPollingAdapter.d.ts.map +1 -0
- package/dist/services/polling/GitHubDirectPollingAdapter.js +87 -0
- package/dist/services/polling/GitHubDirectPollingAdapter.js.map +1 -0
- package/dist/services/polling/HulaServerPollingAdapter.d.ts +23 -0
- package/dist/services/polling/HulaServerPollingAdapter.d.ts.map +1 -0
- package/dist/services/polling/HulaServerPollingAdapter.js +78 -0
- package/dist/services/polling/HulaServerPollingAdapter.js.map +1 -0
- package/dist/services/polling/IStatusPollingAdapter.d.ts +29 -0
- package/dist/services/polling/IStatusPollingAdapter.d.ts.map +1 -0
- package/dist/services/polling/IStatusPollingAdapter.js +2 -0
- package/dist/services/polling/IStatusPollingAdapter.js.map +1 -0
- package/dist/services/polling/StatusPollingAdapterFactory.d.ts +14 -0
- package/dist/services/polling/StatusPollingAdapterFactory.d.ts.map +1 -0
- package/dist/services/polling/StatusPollingAdapterFactory.js +41 -0
- package/dist/services/polling/StatusPollingAdapterFactory.js.map +1 -0
- package/dist/services/polling/index.d.ts +5 -0
- package/dist/services/polling/index.d.ts.map +1 -0
- package/dist/services/polling/index.js +5 -0
- package/dist/services/polling/index.js.map +1 -0
- package/dist/services/preview/PreviewService.d.ts +86 -0
- package/dist/services/preview/PreviewService.d.ts.map +1 -0
- package/dist/services/preview/PreviewService.js +439 -0
- package/dist/services/preview/PreviewService.js.map +1 -0
- package/dist/services/tracking/ProjectTrackingAdapter.d.ts +41 -0
- package/dist/services/tracking/ProjectTrackingAdapter.d.ts.map +1 -0
- package/dist/services/tracking/ProjectTrackingAdapter.js +230 -0
- package/dist/services/tracking/ProjectTrackingAdapter.js.map +1 -0
- package/dist/services/tracking/RemoteTrackingAdapter.d.ts +62 -0
- package/dist/services/tracking/RemoteTrackingAdapter.d.ts.map +1 -0
- package/dist/services/tracking/RemoteTrackingAdapter.js +242 -0
- package/dist/services/tracking/RemoteTrackingAdapter.js.map +1 -0
- package/dist/services/tracking/TrackingAdapterFactory.d.ts +25 -0
- package/dist/services/tracking/TrackingAdapterFactory.d.ts.map +1 -0
- package/dist/services/tracking/TrackingAdapterFactory.js +55 -0
- package/dist/services/tracking/TrackingAdapterFactory.js.map +1 -0
- package/dist/services/tracking/TrackingService.d.ts +104 -0
- package/dist/services/tracking/TrackingService.d.ts.map +1 -0
- package/dist/services/tracking/TrackingService.js +256 -0
- package/dist/services/tracking/TrackingService.js.map +1 -0
- package/dist/services/tracking/UntrackedIssuesService.d.ts +20 -0
- package/dist/services/tracking/UntrackedIssuesService.d.ts.map +1 -0
- package/dist/services/tracking/UntrackedIssuesService.js +148 -0
- package/dist/services/tracking/UntrackedIssuesService.js.map +1 -0
- package/dist/services/tracking/local-file-migration.d.ts +16 -0
- package/dist/services/tracking/local-file-migration.d.ts.map +1 -0
- package/dist/services/tracking/local-file-migration.js +86 -0
- package/dist/services/tracking/local-file-migration.js.map +1 -0
- package/dist/templates/planning-instructions.md +752 -0
- package/dist/templates/proceed-instructions.md +453 -0
- package/dist/templates/scripts/hula-fix-commit.sh +110 -0
- package/dist/templates/scripts/hula-fix-setup.sh +145 -0
- package/dist/templates/scripts/hula-launch-run.sh +89 -0
- package/dist/templates/scripts/hula-merge-local.sh +138 -0
- package/dist/templates/scripts/hula-merge-remote.sh +74 -0
- package/dist/templates/scripts/hula-read-config.sh +38 -0
- package/dist/templates/scripts/hula-verify-gather.sh +135 -0
- package/dist/templates/scripts/hula-verify-post.sh +84 -0
- package/dist/templates/skills/hula-confirm/SKILL.md +361 -0
- package/dist/templates/skills/hula-create/SKILL.md +211 -0
- package/dist/templates/skills/hula-fix/SKILL.md +224 -0
- package/dist/templates/skills/hula-launch/SKILL.md +159 -0
- package/dist/templates/skills/hula-log/SKILL.md +105 -0
- package/dist/templates/skills/hula-merge/SKILL.md +387 -0
- package/dist/templates/skills/hula-plan/SKILL.md +148 -0
- package/dist/templates/skills/hula-upload/SKILL.md +115 -0
- package/dist/templates/skills/hula-verify/SKILL.md +754 -0
- package/dist/types/common.schema.d.ts +46 -0
- package/dist/types/common.schema.d.ts.map +1 -0
- package/dist/types/common.schema.js +60 -0
- package/dist/types/common.schema.js.map +1 -0
- package/dist/types/config.schema.d.ts +443 -0
- package/dist/types/config.schema.d.ts.map +1 -0
- package/dist/types/config.schema.js +126 -0
- package/dist/types/config.schema.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/issue.schema.d.ts +136 -0
- package/dist/types/issue.schema.d.ts.map +1 -0
- package/dist/types/issue.schema.js +49 -0
- package/dist/types/issue.schema.js.map +1 -0
- package/dist/types/pr.schema.d.ts +118 -0
- package/dist/types/pr.schema.d.ts.map +1 -0
- package/dist/types/pr.schema.js +40 -0
- package/dist/types/pr.schema.js.map +1 -0
- package/dist/types/tracking.schema.d.ts +340 -0
- package/dist/types/tracking.schema.d.ts.map +1 -0
- package/dist/types/tracking.schema.js +56 -0
- package/dist/types/tracking.schema.js.map +1 -0
- package/dist/utils/browser-launcher.d.ts +60 -0
- package/dist/utils/browser-launcher.d.ts.map +1 -0
- package/dist/utils/browser-launcher.js +263 -0
- package/dist/utils/browser-launcher.js.map +1 -0
- package/dist/utils/cache.d.ts +44 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +94 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/config-parser.d.ts +16 -0
- package/dist/utils/config-parser.d.ts.map +1 -0
- package/dist/utils/config-parser.js +210 -0
- package/dist/utils/config-parser.js.map +1 -0
- package/dist/utils/editor.d.ts +21 -0
- package/dist/utils/editor.d.ts.map +1 -0
- package/dist/utils/editor.js +213 -0
- package/dist/utils/editor.js.map +1 -0
- package/dist/utils/git-utils.d.ts +5 -0
- package/dist/utils/git-utils.d.ts.map +1 -0
- package/dist/utils/git-utils.js +18 -0
- package/dist/utils/git-utils.js.map +1 -0
- package/dist/utils/github-cli.d.ts +62 -0
- package/dist/utils/github-cli.d.ts.map +1 -0
- package/dist/utils/github-cli.js +492 -0
- package/dist/utils/github-cli.js.map +1 -0
- package/dist/utils/logger.d.ts +45 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +118 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/open-url.d.ts +19 -0
- package/dist/utils/open-url.d.ts.map +1 -0
- package/dist/utils/open-url.js +37 -0
- package/dist/utils/open-url.js.map +1 -0
- package/dist/utils/project-resolver.d.ts +13 -0
- package/dist/utils/project-resolver.d.ts.map +1 -0
- package/dist/utils/project-resolver.js +35 -0
- package/dist/utils/project-resolver.js.map +1 -0
- package/dist/utils/prompts.d.ts +80 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +326 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/shell.d.ts +54 -0
- package/dist/utils/shell.d.ts.map +1 -0
- package/dist/utils/shell.js +152 -0
- package/dist/utils/shell.js.map +1 -0
- package/dist/utils/validators.d.ts +32 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +64 -0
- package/dist/utils/validators.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { logger } from "../../utils/logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Service for detecting deployment status from PR comments
|
|
4
|
+
* Parses Vercel bot comments to determine deployment status
|
|
5
|
+
*/
|
|
6
|
+
export class DeploymentDetectionService {
|
|
7
|
+
/**
|
|
8
|
+
* Detect deployment status from PR comments
|
|
9
|
+
* Looks for Vercel bot comments indicating deployment success or failure
|
|
10
|
+
*/
|
|
11
|
+
detectDeploymentStatus(comments) {
|
|
12
|
+
// Filter for bot comments (Vercel, GitHub Actions, etc.)
|
|
13
|
+
const deploymentComments = comments.filter((comment) => comment.author.toLowerCase().includes("vercel") ||
|
|
14
|
+
comment.author.toLowerCase().includes("bot") ||
|
|
15
|
+
comment.author.toLowerCase().includes("github-actions"));
|
|
16
|
+
if (deploymentComments.length === 0) {
|
|
17
|
+
return { status: "pending" };
|
|
18
|
+
}
|
|
19
|
+
// Sort by timestamp descending to get most recent first
|
|
20
|
+
const sortedComments = [...deploymentComments].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
21
|
+
// Check most recent deployment comment
|
|
22
|
+
for (const comment of sortedComments) {
|
|
23
|
+
const bodyLower = comment.body.toLowerCase();
|
|
24
|
+
// Check for failure patterns FIRST (highest priority)
|
|
25
|
+
// Vercel pattern:  or explicit failure messages
|
|
26
|
+
if (bodyLower.includes("![failed]") ||
|
|
27
|
+
bodyLower.includes("[failed]") ||
|
|
28
|
+
bodyLower.includes("❌") ||
|
|
29
|
+
bodyLower.includes("deployment failed") ||
|
|
30
|
+
bodyLower.includes("build failed") ||
|
|
31
|
+
(bodyLower.includes("deployment") && bodyLower.includes("error"))) {
|
|
32
|
+
return {
|
|
33
|
+
status: "failed",
|
|
34
|
+
timestamp: comment.createdAt,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Check for success patterns (second priority)
|
|
38
|
+
// Vercel pattern:  [Ready](...) in Deployment column
|
|
39
|
+
if (bodyLower.includes("![ready]") ||
|
|
40
|
+
(bodyLower.includes("[ready]") && bodyLower.includes("deployment")) ||
|
|
41
|
+
bodyLower.includes("✅") ||
|
|
42
|
+
bodyLower.includes("successfully deployed") ||
|
|
43
|
+
bodyLower.includes("preview deployed")) {
|
|
44
|
+
return {
|
|
45
|
+
status: "success",
|
|
46
|
+
timestamp: comment.createdAt,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Check for pending/in-progress patterns (lowest priority)
|
|
50
|
+
// Vercel pattern:  or 
|
|
51
|
+
if (bodyLower.includes("![building]") ||
|
|
52
|
+
bodyLower.includes("[building]") ||
|
|
53
|
+
bodyLower.includes("![queued]") ||
|
|
54
|
+
bodyLower.includes("[queued]") ||
|
|
55
|
+
bodyLower.includes("deploying") ||
|
|
56
|
+
(bodyLower.includes("deployment") &&
|
|
57
|
+
bodyLower.includes("in progress"))) {
|
|
58
|
+
return {
|
|
59
|
+
status: "pending",
|
|
60
|
+
timestamp: comment.createdAt,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// If we found deployment comments but couldn't determine status, assume pending
|
|
65
|
+
return { status: "pending" };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if we should notify Copilot about a deployment failure
|
|
69
|
+
*
|
|
70
|
+
* Returns true if:
|
|
71
|
+
* - Deployment status is "failed" AND
|
|
72
|
+
* - Either no previous notification exists OR
|
|
73
|
+
* - The deployment failure timestamp is newer than the last notification timestamp
|
|
74
|
+
*
|
|
75
|
+
* This allows multiple notifications if deployment fails again after a fix attempt,
|
|
76
|
+
* while preventing duplicate notifications for the same failure.
|
|
77
|
+
*
|
|
78
|
+
* @param deploymentStatus The current deployment status with optional timestamp
|
|
79
|
+
* @param deploymentNotifiedAt ISO timestamp of when Copilot was last notified
|
|
80
|
+
* @returns true if Copilot should be notified about this failure
|
|
81
|
+
*/
|
|
82
|
+
shouldNotifyCopilot(deploymentStatus, deploymentNotifiedAt) {
|
|
83
|
+
if (deploymentStatus.status !== "failed") {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
// If never notified, should notify
|
|
87
|
+
if (!deploymentNotifiedAt) {
|
|
88
|
+
logger.debug("No previous notification found, should notify");
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
// If deployment failure has a timestamp, compare with last notification
|
|
92
|
+
if (deploymentStatus.timestamp) {
|
|
93
|
+
const failureTime = new Date(deploymentStatus.timestamp).getTime();
|
|
94
|
+
const notifiedTime = new Date(deploymentNotifiedAt).getTime();
|
|
95
|
+
const shouldNotify = failureTime > notifiedTime;
|
|
96
|
+
logger.debug(`Failure time: ${deploymentStatus.timestamp}, Last notified: ${deploymentNotifiedAt}, Should notify: ${shouldNotify}`);
|
|
97
|
+
return shouldNotify;
|
|
98
|
+
}
|
|
99
|
+
// If no timestamp on failure, be conservative and don't notify again
|
|
100
|
+
logger.debug("No timestamp on deployment failure, not notifying");
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=DeploymentDetectionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeploymentDetectionService.js","sourceRoot":"","sources":["../../../src/services/github/DeploymentDetectionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub App Installation Service
|
|
3
|
+
* Checks if user has GitHub App installed and prompts for installation
|
|
4
|
+
*/
|
|
5
|
+
export declare class GithubAppService {
|
|
6
|
+
private static readonly APP_CHECK_FILE;
|
|
7
|
+
private static readonly CHECK_INTERVAL_DAYS;
|
|
8
|
+
/**
|
|
9
|
+
* Check if GitHub App is installed on user's repositories
|
|
10
|
+
* Returns install URL if not installed or if check is stale
|
|
11
|
+
*/
|
|
12
|
+
checkAndPromptInstallation(config: any): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Force check and prompt for installation (used after login)
|
|
15
|
+
*/
|
|
16
|
+
checkAndPromptAfterLogin(installUrl: string | null | undefined): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Check if we should skip the installation check
|
|
19
|
+
* Skip if recently checked (within CHECK_INTERVAL_DAYS)
|
|
20
|
+
*/
|
|
21
|
+
private shouldSkipCheck;
|
|
22
|
+
/**
|
|
23
|
+
* Get installation status from API
|
|
24
|
+
*/
|
|
25
|
+
private getInstallationStatus;
|
|
26
|
+
/**
|
|
27
|
+
* Prompt user to install GitHub App
|
|
28
|
+
*/
|
|
29
|
+
private promptForInstallation;
|
|
30
|
+
/**
|
|
31
|
+
* Update check timestamp
|
|
32
|
+
*/
|
|
33
|
+
private updateCheckTimestamp;
|
|
34
|
+
/**
|
|
35
|
+
* Clear check cache (force next check)
|
|
36
|
+
*/
|
|
37
|
+
clearCheckCache(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export declare const githubAppService: GithubAppService;
|
|
40
|
+
//# sourceMappingURL=GithubAppService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GithubAppService.d.ts","sourceRoot":"","sources":["../../../src/services/github/GithubAppService.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAIpC;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAK;IAEhD;;;OAGG;IACG,0BAA0B,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B5D;;OAEG;IACG,wBAAwB,CAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACpC,OAAO,CAAC,IAAI,CAAC;IAWhB;;;OAGG;YACW,eAAe;IA6B7B;;OAEG;YACW,qBAAqB;IAqDnC;;OAEG;YACW,qBAAqB;IA4CnC;;OAEG;YACW,oBAAoB;IAoBlC;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;CAOvC;AAED,eAAO,MAAM,gBAAgB,kBAAyB,CAAC"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as os from 'os';
|
|
6
|
+
import { logger } from '../../utils/logger.js';
|
|
7
|
+
import { HULA_PROJECT_URL } from '../../config/constants.js';
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
/**
|
|
10
|
+
* GitHub App Installation Service
|
|
11
|
+
* Checks if user has GitHub App installed and prompts for installation
|
|
12
|
+
*/
|
|
13
|
+
export class GithubAppService {
|
|
14
|
+
static APP_CHECK_FILE = path.join(os.homedir(), '.hublaunch', 'app-check.json');
|
|
15
|
+
static CHECK_INTERVAL_DAYS = 7; // Check every 7 days
|
|
16
|
+
/**
|
|
17
|
+
* Check if GitHub App is installed on user's repositories
|
|
18
|
+
* Returns install URL if not installed or if check is stale
|
|
19
|
+
*/
|
|
20
|
+
async checkAndPromptInstallation(config) {
|
|
21
|
+
try {
|
|
22
|
+
// Check if we should skip this check (recently checked)
|
|
23
|
+
if (await this.shouldSkipCheck()) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Get installation status from API
|
|
27
|
+
const installStatus = await this.getInstallationStatus(config);
|
|
28
|
+
if (!installStatus.isInstalled && installStatus.installUrl) {
|
|
29
|
+
await this.promptForInstallation(installStatus.installUrl);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
logger.debug('✓ GitHub App is installed');
|
|
33
|
+
}
|
|
34
|
+
// Update check timestamp
|
|
35
|
+
await this.updateCheckTimestamp(!installStatus.isInstalled);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
// Don't block execution if check fails
|
|
39
|
+
logger.debug(`Failed to check GitHub App installation: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Force check and prompt for installation (used after login)
|
|
44
|
+
*/
|
|
45
|
+
async checkAndPromptAfterLogin(installUrl) {
|
|
46
|
+
if (!installUrl) {
|
|
47
|
+
logger.debug('No GitHub App install URL provided');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
logger.blank();
|
|
51
|
+
await this.promptForInstallation(installUrl, true);
|
|
52
|
+
await this.updateCheckTimestamp(false); // Reset check timer
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if we should skip the installation check
|
|
56
|
+
* Skip if recently checked (within CHECK_INTERVAL_DAYS)
|
|
57
|
+
*/
|
|
58
|
+
async shouldSkipCheck() {
|
|
59
|
+
try {
|
|
60
|
+
const data = await fs.readFile(GithubAppService.APP_CHECK_FILE, 'utf-8');
|
|
61
|
+
const checkData = JSON.parse(data);
|
|
62
|
+
const lastCheck = new Date(checkData.lastCheck);
|
|
63
|
+
const daysSinceCheck = (Date.now() - lastCheck.getTime()) / (1000 * 60 * 60 * 24);
|
|
64
|
+
// Skip if checked recently and user chose to skip
|
|
65
|
+
if (checkData.userSkipped &&
|
|
66
|
+
daysSinceCheck < GithubAppService.CHECK_INTERVAL_DAYS) {
|
|
67
|
+
logger.debug(`Skipping GitHub App check (last check: ${daysSinceCheck.toFixed(1)} days ago)`);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// File doesn't exist or is invalid, don't skip
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get installation status from API
|
|
79
|
+
*/
|
|
80
|
+
async getInstallationStatus(config) {
|
|
81
|
+
const hulaProjectUrl = config.hulaProjectUrl || HULA_PROJECT_URL;
|
|
82
|
+
// If no API key configured, skip check
|
|
83
|
+
if (!config.hulaApiKey) {
|
|
84
|
+
logger.debug('No Hula API key configured, skipping GitHub App check');
|
|
85
|
+
return { isInstalled: true, installUrl: null }; // Don't prompt if not authenticated
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
// Check if user has any repositories with GitHub App installed
|
|
89
|
+
const response = await fetch(`${hulaProjectUrl}/api/v1/user/github-app-status`, {
|
|
90
|
+
headers: {
|
|
91
|
+
Authorization: `Bearer ${config.hulaApiKey}`,
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
logger.debug(`GitHub App status check failed: ${response.status} ${response.statusText}`);
|
|
97
|
+
// If endpoint doesn't exist or returns error, don't prompt
|
|
98
|
+
// This prevents false negatives
|
|
99
|
+
return {
|
|
100
|
+
isInstalled: true, // Assume installed to avoid false warnings
|
|
101
|
+
installUrl: null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const data = await response.json();
|
|
105
|
+
logger.debug(`GitHub App status response: ${JSON.stringify(data)}`);
|
|
106
|
+
return {
|
|
107
|
+
isInstalled: data.hasAppInstalled || false,
|
|
108
|
+
installUrl: data.installUrl || null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
logger.debug(`Failed to check installation status: ${error instanceof Error ? error.message : 'Unknown'}`);
|
|
113
|
+
// On error, assume installed to avoid false warnings
|
|
114
|
+
return { isInstalled: true, installUrl: null };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Prompt user to install GitHub App
|
|
119
|
+
*/
|
|
120
|
+
async promptForInstallation(installUrl, forcePrompt = false) {
|
|
121
|
+
if (!forcePrompt) {
|
|
122
|
+
logger.blank();
|
|
123
|
+
logger.section('GitHub App Installation');
|
|
124
|
+
}
|
|
125
|
+
logger.warning('⚠️ GitHub App not installed on your repositories');
|
|
126
|
+
logger.blank();
|
|
127
|
+
logger.info('The Hula GitHub App enables:');
|
|
128
|
+
logger.info(' • Automatic issue status updates');
|
|
129
|
+
logger.info(' • Background polling every 5 minutes');
|
|
130
|
+
logger.info(' • Real-time sync when issues change');
|
|
131
|
+
logger.blank();
|
|
132
|
+
logger.info('Without the app:');
|
|
133
|
+
logger.info(' • You can still track issues manually');
|
|
134
|
+
logger.info(' • Use hub-launch commands to update status');
|
|
135
|
+
logger.blank();
|
|
136
|
+
logger.info(`Install URL: ${installUrl}`);
|
|
137
|
+
logger.blank();
|
|
138
|
+
// Try to open browser
|
|
139
|
+
try {
|
|
140
|
+
const openCommand = process.platform === 'darwin'
|
|
141
|
+
? 'open'
|
|
142
|
+
: process.platform === 'win32'
|
|
143
|
+
? 'start'
|
|
144
|
+
: 'xdg-open';
|
|
145
|
+
await execAsync(`${openCommand} "${installUrl}"`);
|
|
146
|
+
logger.success('✓ Opened installation page in browser');
|
|
147
|
+
logger.blank();
|
|
148
|
+
logger.info('After installing the app, run any hula command to verify.');
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
logger.info('Copy the URL above and paste it in your browser to install.');
|
|
152
|
+
}
|
|
153
|
+
logger.blank();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Update check timestamp
|
|
157
|
+
*/
|
|
158
|
+
async updateCheckTimestamp(userSkipped) {
|
|
159
|
+
try {
|
|
160
|
+
const checkData = {
|
|
161
|
+
lastCheck: new Date().toISOString(),
|
|
162
|
+
userSkipped,
|
|
163
|
+
};
|
|
164
|
+
await fs.mkdir(path.dirname(GithubAppService.APP_CHECK_FILE), {
|
|
165
|
+
recursive: true,
|
|
166
|
+
});
|
|
167
|
+
await fs.writeFile(GithubAppService.APP_CHECK_FILE, JSON.stringify(checkData, null, 2));
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
// Non-critical, ignore errors
|
|
171
|
+
logger.debug(`Failed to update check timestamp: ${error}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Clear check cache (force next check)
|
|
176
|
+
*/
|
|
177
|
+
async clearCheckCache() {
|
|
178
|
+
try {
|
|
179
|
+
await fs.unlink(GithubAppService.APP_CHECK_FILE);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Ignore if file doesn't exist
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export const githubAppService = new GithubAppService();
|
|
187
|
+
//# sourceMappingURL=GithubAppService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GithubAppService.js","sourceRoot":"","sources":["../../../src/services/github/GithubAppService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Issue, type CreateIssueParams, type IssueFilters } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Service for GitHub issue operations
|
|
4
|
+
* Single Responsibility: Issue CRUD and listing
|
|
5
|
+
*/
|
|
6
|
+
export declare class IssueService {
|
|
7
|
+
/**
|
|
8
|
+
* Build a consistent cache key for an issue.
|
|
9
|
+
* Uses `issue:owner/repo#number` when owner/repo are known, otherwise `issue:number`.
|
|
10
|
+
*/
|
|
11
|
+
static getCacheKey(issueNumber: number, owner?: string, repo?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Create a new GitHub issue
|
|
14
|
+
*/
|
|
15
|
+
create(params: CreateIssueParams): Promise<Issue>;
|
|
16
|
+
/**
|
|
17
|
+
* Get a specific issue by number.
|
|
18
|
+
* When owner/repo are provided, queries that repo directly instead of the CWD repo.
|
|
19
|
+
*/
|
|
20
|
+
get(issueNumber: number, owner?: string, repo?: string): Promise<Issue>;
|
|
21
|
+
/**
|
|
22
|
+
* Update issue body
|
|
23
|
+
*/
|
|
24
|
+
updateBody(issueNumber: number, body: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Close an issue
|
|
27
|
+
*/
|
|
28
|
+
close(issueNumber: number, owner?: string, repo?: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* List issues with filters
|
|
31
|
+
*/
|
|
32
|
+
list(filters?: IssueFilters): Promise<Issue[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Add a comment to an issue
|
|
35
|
+
*/
|
|
36
|
+
addComment(issueNumber: number, body: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Get comments for an issue/PR
|
|
39
|
+
*/
|
|
40
|
+
getComments(issueNumber: number): Promise<Array<{
|
|
41
|
+
id: number;
|
|
42
|
+
body: string;
|
|
43
|
+
user: {
|
|
44
|
+
login: string;
|
|
45
|
+
};
|
|
46
|
+
created_at: string;
|
|
47
|
+
}>>;
|
|
48
|
+
/**
|
|
49
|
+
* Add labels to an issue
|
|
50
|
+
*/
|
|
51
|
+
addLabels(issueNumber: number, labels: string[]): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Remove a label from an issue
|
|
54
|
+
*/
|
|
55
|
+
removeLabel(issueNumber: number, label: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* List user's open issues (authored or assigned)
|
|
58
|
+
*/
|
|
59
|
+
listUserIssues(username: string): Promise<Issue[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Transform GitHub API issue to internal format
|
|
62
|
+
*/
|
|
63
|
+
private transformToInternalIssue;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=IssueService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IssueService.d.ts","sourceRoot":"","sources":["../../../src/services/github/IssueService.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,KAAK,EACV,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAGlB,MAAM,sBAAsB,CAAC;AAE9B;;;GAGG;AACH,qBAAa,YAAY;IACvB;;;OAGG;IACH,MAAM,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM;IAK9E;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,KAAK,CAAC;IA0BvD;;;OAGG;IACG,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAqB7E;;OAEG;IACG,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlE;;OAEG;IACG,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB9E;;OAEG;IACG,IAAI,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAwBxD;;OAEG;IACG,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAalE;;OAEG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAY5F;;OAEG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBrE;;OAEG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBpE;;OAEG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAuFxD;;OAEG;IACH,OAAO,CAAC,wBAAwB;CAYjC"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { ghApi, ghGraphQL, getRepoInfo } from "../../utils/github-cli.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { globalCache } from "../../utils/cache.js";
|
|
4
|
+
import { runCommandWithStdin } from "../../utils/shell.js";
|
|
5
|
+
import { GitHubIssueSchema, } from "../../types/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Service for GitHub issue operations
|
|
8
|
+
* Single Responsibility: Issue CRUD and listing
|
|
9
|
+
*/
|
|
10
|
+
export class IssueService {
|
|
11
|
+
/**
|
|
12
|
+
* Build a consistent cache key for an issue.
|
|
13
|
+
* Uses `issue:owner/repo#number` when owner/repo are known, otherwise `issue:number`.
|
|
14
|
+
*/
|
|
15
|
+
static getCacheKey(issueNumber, owner, repo) {
|
|
16
|
+
return (owner && repo)
|
|
17
|
+
? `issue:${owner}/${repo}#${issueNumber}`
|
|
18
|
+
: `issue:${issueNumber}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a new GitHub issue
|
|
22
|
+
*/
|
|
23
|
+
async create(params) {
|
|
24
|
+
const repo = await getRepoInfo();
|
|
25
|
+
logger.debug(`Creating issue in ${repo.owner}/${repo.name}`);
|
|
26
|
+
const response = await ghApi(`/repos/${repo.owner}/${repo.name}/issues`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
fields: {
|
|
29
|
+
title: params.title,
|
|
30
|
+
body: params.body ?? "",
|
|
31
|
+
...(params.assignees && params.assignees.length > 0
|
|
32
|
+
? { assignees: params.assignees }
|
|
33
|
+
: {}),
|
|
34
|
+
...(params.labels && params.labels.length > 0
|
|
35
|
+
? { labels: params.labels }
|
|
36
|
+
: {}),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const validated = GitHubIssueSchema.parse(response);
|
|
40
|
+
return this.transformToInternalIssue(validated);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get a specific issue by number.
|
|
44
|
+
* When owner/repo are provided, queries that repo directly instead of the CWD repo.
|
|
45
|
+
*/
|
|
46
|
+
async get(issueNumber, owner, repo) {
|
|
47
|
+
const cacheKey = IssueService.getCacheKey(issueNumber, owner, repo);
|
|
48
|
+
const cached = globalCache.get(cacheKey);
|
|
49
|
+
if (cached) {
|
|
50
|
+
return cached;
|
|
51
|
+
}
|
|
52
|
+
const repoInfo = (owner && repo) ? { owner, name: repo } : await getRepoInfo();
|
|
53
|
+
const response = await ghApi(`/repos/${repoInfo.owner}/${repoInfo.name}/issues/${issueNumber}`);
|
|
54
|
+
const validated = GitHubIssueSchema.parse(response);
|
|
55
|
+
const issue = this.transformToInternalIssue(validated);
|
|
56
|
+
// Cache for 5 minutes
|
|
57
|
+
globalCache.set(cacheKey, issue, 300);
|
|
58
|
+
return issue;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Update issue body
|
|
62
|
+
*/
|
|
63
|
+
async updateBody(issueNumber, body) {
|
|
64
|
+
logger.debug(`Updating body for issue #${issueNumber}`);
|
|
65
|
+
const repo = await getRepoInfo();
|
|
66
|
+
// Use gh issue edit with --body-file - to safely handle special characters
|
|
67
|
+
// This pipes the body via stdin to avoid shell interpretation issues
|
|
68
|
+
await runCommandWithStdin(`gh issue edit ${issueNumber} --body-file -`, body, { silent: true });
|
|
69
|
+
// Invalidate both bare and repo-aware cache keys
|
|
70
|
+
globalCache.delete(IssueService.getCacheKey(issueNumber));
|
|
71
|
+
globalCache.delete(IssueService.getCacheKey(issueNumber, repo.owner, repo.name));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Close an issue
|
|
75
|
+
*/
|
|
76
|
+
async close(issueNumber, owner, repo) {
|
|
77
|
+
const repoInfo = (owner && repo) ? { owner, name: repo } : await getRepoInfo();
|
|
78
|
+
logger.debug(`Closing issue #${issueNumber}`);
|
|
79
|
+
await ghApi(`/repos/${repoInfo.owner}/${repoInfo.name}/issues/${issueNumber}`, {
|
|
80
|
+
method: "PATCH",
|
|
81
|
+
fields: {
|
|
82
|
+
state: "closed",
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
// Invalidate both bare and repo-aware cache keys
|
|
86
|
+
globalCache.delete(IssueService.getCacheKey(issueNumber));
|
|
87
|
+
globalCache.delete(IssueService.getCacheKey(issueNumber, repoInfo.owner, repoInfo.name));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* List issues with filters
|
|
91
|
+
*/
|
|
92
|
+
async list(filters = {}) {
|
|
93
|
+
const repo = await getRepoInfo();
|
|
94
|
+
// Build query parameters
|
|
95
|
+
const params = [];
|
|
96
|
+
if (filters.state)
|
|
97
|
+
params.push(`state=${filters.state}`);
|
|
98
|
+
if (filters.assignee)
|
|
99
|
+
params.push(`assignee=${filters.assignee}`);
|
|
100
|
+
if (filters.labels && filters.labels.length > 0) {
|
|
101
|
+
params.push(`labels=${filters.labels.join(",")}`);
|
|
102
|
+
}
|
|
103
|
+
if (filters.since)
|
|
104
|
+
params.push(`since=${filters.since}`);
|
|
105
|
+
const queryString = params.length > 0 ? `?${params.join("&")}` : "";
|
|
106
|
+
const response = await ghApi(`/repos/${repo.owner}/${repo.name}/issues${queryString}`);
|
|
107
|
+
return response.map((issue) => {
|
|
108
|
+
const validated = GitHubIssueSchema.parse(issue);
|
|
109
|
+
return this.transformToInternalIssue(validated);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Add a comment to an issue
|
|
114
|
+
*/
|
|
115
|
+
async addComment(issueNumber, body) {
|
|
116
|
+
const repo = await getRepoInfo();
|
|
117
|
+
logger.debug(`Adding comment to issue #${issueNumber}`);
|
|
118
|
+
// Use stdin to safely pass comment body with special characters
|
|
119
|
+
await runCommandWithStdin(`gh api /repos/${repo.owner}/${repo.name}/issues/${issueNumber}/comments -X POST -F body=@-`, body, { silent: true });
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get comments for an issue/PR
|
|
123
|
+
*/
|
|
124
|
+
async getComments(issueNumber) {
|
|
125
|
+
const repo = await getRepoInfo();
|
|
126
|
+
logger.debug(`Getting comments for issue #${issueNumber}`);
|
|
127
|
+
const comments = await ghApi(`/repos/${repo.owner}/${repo.name}/issues/${issueNumber}/comments`);
|
|
128
|
+
return comments;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Add labels to an issue
|
|
132
|
+
*/
|
|
133
|
+
async addLabels(issueNumber, labels) {
|
|
134
|
+
const repo = await getRepoInfo();
|
|
135
|
+
logger.debug(`Adding labels to issue #${issueNumber}: ${labels.join(", ")}`);
|
|
136
|
+
await ghApi(`/repos/${repo.owner}/${repo.name}/issues/${issueNumber}/labels`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
fields: {
|
|
139
|
+
labels: labels.join(","),
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
// Invalidate both bare and repo-aware cache keys
|
|
143
|
+
globalCache.delete(IssueService.getCacheKey(issueNumber));
|
|
144
|
+
globalCache.delete(IssueService.getCacheKey(issueNumber, repo.owner, repo.name));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Remove a label from an issue
|
|
148
|
+
*/
|
|
149
|
+
async removeLabel(issueNumber, label) {
|
|
150
|
+
const repo = await getRepoInfo();
|
|
151
|
+
logger.debug(`Removing label from issue #${issueNumber}: ${label}`);
|
|
152
|
+
await ghApi(`/repos/${repo.owner}/${repo.name}/issues/${issueNumber}/labels/${label}`, {
|
|
153
|
+
method: "DELETE",
|
|
154
|
+
});
|
|
155
|
+
// Invalidate both bare and repo-aware cache keys
|
|
156
|
+
globalCache.delete(IssueService.getCacheKey(issueNumber));
|
|
157
|
+
globalCache.delete(IssueService.getCacheKey(issueNumber, repo.owner, repo.name));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* List user's open issues (authored or assigned)
|
|
161
|
+
*/
|
|
162
|
+
async listUserIssues(username) {
|
|
163
|
+
const repo = await getRepoInfo();
|
|
164
|
+
const query = `
|
|
165
|
+
query($owner: String!, $name: String!) {
|
|
166
|
+
repository(owner: $owner, name: $name) {
|
|
167
|
+
issues(first: 100, states: OPEN, orderBy: {field: CREATED_AT, direction: DESC}) {
|
|
168
|
+
nodes {
|
|
169
|
+
number
|
|
170
|
+
title
|
|
171
|
+
body
|
|
172
|
+
state
|
|
173
|
+
url
|
|
174
|
+
createdAt
|
|
175
|
+
updatedAt
|
|
176
|
+
author {
|
|
177
|
+
login
|
|
178
|
+
}
|
|
179
|
+
assignees(first: 10) {
|
|
180
|
+
nodes {
|
|
181
|
+
login
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
labels(first: 10) {
|
|
185
|
+
nodes {
|
|
186
|
+
name
|
|
187
|
+
color
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
const response = await ghGraphQL(query, { owner: repo.owner, name: repo.name });
|
|
196
|
+
const issues = response.data.repository.issues.nodes;
|
|
197
|
+
logger.debug(`GraphQL returned ${issues.length} issues with states: OPEN filter`);
|
|
198
|
+
// Filter to only OPEN issues where user is author or assignee
|
|
199
|
+
const userIssues = issues.filter((issue) => issue.state === "OPEN" && // Ensure it's actually open
|
|
200
|
+
(issue.author.login === username ||
|
|
201
|
+
issue.assignees.nodes.some((a) => a.login === username)));
|
|
202
|
+
logger.debug(`Filtered to ${userIssues.length} open issues for user ${username}`);
|
|
203
|
+
// Transform to internal format
|
|
204
|
+
return userIssues.map((issue) => ({
|
|
205
|
+
number: issue.number,
|
|
206
|
+
title: issue.title,
|
|
207
|
+
body: issue.body ?? undefined,
|
|
208
|
+
state: issue.state.toLowerCase(),
|
|
209
|
+
url: issue.url,
|
|
210
|
+
createdAt: issue.createdAt,
|
|
211
|
+
updatedAt: issue.updatedAt,
|
|
212
|
+
assignees: issue.assignees.nodes.map((a) => a.login),
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Transform GitHub API issue to internal format
|
|
217
|
+
*/
|
|
218
|
+
transformToInternalIssue(githubIssue) {
|
|
219
|
+
return {
|
|
220
|
+
number: githubIssue.number,
|
|
221
|
+
title: githubIssue.title,
|
|
222
|
+
body: githubIssue.body ?? undefined,
|
|
223
|
+
state: githubIssue.state,
|
|
224
|
+
url: githubIssue.html_url,
|
|
225
|
+
createdAt: githubIssue.created_at,
|
|
226
|
+
updatedAt: githubIssue.updated_at,
|
|
227
|
+
assignees: githubIssue.assignees.map((a) => a.login),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=IssueService.js.map
|