github-delivery-os 1.0.0
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/.github/ISSUE_TEMPLATE/bug_report.yml +84 -0
- package/.github/ISSUE_TEMPLATE/config.yml +2 -0
- package/.github/ISSUE_TEMPLATE/production_release_qa_signoff.yml +51 -0
- package/.github/ISSUE_TEMPLATE/qa_request.yml +55 -0
- package/.github/ISSUE_TEMPLATE/sprint_planning.yml +68 -0
- package/.github/ISSUE_TEMPLATE/task.yml +91 -0
- package/.github/workflows/authorize-deployment.yml +106 -0
- package/.github/workflows/auto-assign-qa.yml +25 -0
- package/.github/workflows/auto-close-sprint.yml +135 -0
- package/.github/workflows/notify-release-approver.yml +54 -0
- package/.github/workflows/setup-labels.yml +61 -0
- package/.github/workflows/sprint-child-creator.yml +44 -0
- package/.github/workflows/telegram-issues.yml +135 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/bin/delivery-os.js +3 -0
- package/package.json +44 -0
- package/src/cli.js +42 -0
- package/src/install.js +232 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: 🐞 Bug Report
|
|
2
|
+
description: Report a defect or unexpected behavior
|
|
3
|
+
title: "[BUG] "
|
|
4
|
+
labels:
|
|
5
|
+
- bug
|
|
6
|
+
- qa
|
|
7
|
+
|
|
8
|
+
body:
|
|
9
|
+
- type: checkboxes
|
|
10
|
+
id: platform
|
|
11
|
+
attributes:
|
|
12
|
+
label: Platform(s) Affected
|
|
13
|
+
description: Select all platforms where this issue occurs
|
|
14
|
+
options:
|
|
15
|
+
- label: Android
|
|
16
|
+
- label: iOS
|
|
17
|
+
- label: Web
|
|
18
|
+
- label: Backend / API
|
|
19
|
+
validations:
|
|
20
|
+
required: true
|
|
21
|
+
|
|
22
|
+
- type: dropdown
|
|
23
|
+
id: severity
|
|
24
|
+
attributes:
|
|
25
|
+
label: Severity
|
|
26
|
+
options:
|
|
27
|
+
- P0 – Blocker (Crash / Data Loss)
|
|
28
|
+
- P1 – Critical (Core flow broken)
|
|
29
|
+
- P2 – Major (Workaround exists)
|
|
30
|
+
- P3 – Minor (UI / Cosmetic)
|
|
31
|
+
validations:
|
|
32
|
+
required: true
|
|
33
|
+
|
|
34
|
+
- type: input
|
|
35
|
+
id: build
|
|
36
|
+
attributes:
|
|
37
|
+
label: "Build / Version"
|
|
38
|
+
placeholder: "e.g. 4227, v1.3.0"
|
|
39
|
+
validations:
|
|
40
|
+
required: true
|
|
41
|
+
|
|
42
|
+
- type: textarea
|
|
43
|
+
id: summary
|
|
44
|
+
attributes:
|
|
45
|
+
label: Bug Summary
|
|
46
|
+
description: One clear sentence describing the issue
|
|
47
|
+
validations:
|
|
48
|
+
required: true
|
|
49
|
+
|
|
50
|
+
- type: textarea
|
|
51
|
+
id: steps
|
|
52
|
+
attributes:
|
|
53
|
+
label: Steps to Reproduce
|
|
54
|
+
description: Provide clear, numbered steps
|
|
55
|
+
validations:
|
|
56
|
+
required: true
|
|
57
|
+
|
|
58
|
+
- type: textarea
|
|
59
|
+
id: expected
|
|
60
|
+
attributes:
|
|
61
|
+
label: Expected Result
|
|
62
|
+
validations:
|
|
63
|
+
required: true
|
|
64
|
+
|
|
65
|
+
- type: textarea
|
|
66
|
+
id: actual
|
|
67
|
+
attributes:
|
|
68
|
+
label: Actual Result
|
|
69
|
+
validations:
|
|
70
|
+
required: true
|
|
71
|
+
|
|
72
|
+
- type: textarea
|
|
73
|
+
id: environment
|
|
74
|
+
attributes:
|
|
75
|
+
label: Test Environment
|
|
76
|
+
description: Device, OS, browser, network, etc.
|
|
77
|
+
validations:
|
|
78
|
+
required: true
|
|
79
|
+
|
|
80
|
+
- type: textarea
|
|
81
|
+
id: evidence
|
|
82
|
+
attributes:
|
|
83
|
+
label: Logs / Screenshots / Videos
|
|
84
|
+
description: Attach any supporting evidence
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: "PRODUCTION RELEASE & QA SIGN-OFF FORM"
|
|
2
|
+
description: "Formal Production Release Governance & QA Sign-Off"
|
|
3
|
+
title: "PRODUCTION RELEASE - [Project Name] - vX.X.X"
|
|
4
|
+
labels: ["release", "production", "approval"]
|
|
5
|
+
|
|
6
|
+
body:
|
|
7
|
+
- type: input
|
|
8
|
+
id: sprint_reference
|
|
9
|
+
attributes:
|
|
10
|
+
label: "Sprint Reference (Sprint Planning Issue #)"
|
|
11
|
+
placeholder: "#45"
|
|
12
|
+
validations:
|
|
13
|
+
required: true
|
|
14
|
+
|
|
15
|
+
- type: input
|
|
16
|
+
id: version
|
|
17
|
+
attributes:
|
|
18
|
+
label: "Version / Build Number"
|
|
19
|
+
placeholder: "v2.4.1"
|
|
20
|
+
validations:
|
|
21
|
+
required: true
|
|
22
|
+
|
|
23
|
+
- type: textarea
|
|
24
|
+
id: release_summary
|
|
25
|
+
attributes:
|
|
26
|
+
label: "Release Summary"
|
|
27
|
+
validations:
|
|
28
|
+
required: true
|
|
29
|
+
|
|
30
|
+
- type: textarea
|
|
31
|
+
id: qa_summary
|
|
32
|
+
attributes:
|
|
33
|
+
label: "QA Summary + Evidence Links"
|
|
34
|
+
placeholder: "Link QA Request issues, screenshots folder, logs, etc."
|
|
35
|
+
|
|
36
|
+
- type: dropdown
|
|
37
|
+
id: qa_recommendation
|
|
38
|
+
attributes:
|
|
39
|
+
label: "Overall QA Recommendation"
|
|
40
|
+
options:
|
|
41
|
+
- "Approve for Production"
|
|
42
|
+
- "Reject Release"
|
|
43
|
+
- "Conditional Approval"
|
|
44
|
+
|
|
45
|
+
- type: dropdown
|
|
46
|
+
id: deployment_authorized
|
|
47
|
+
attributes:
|
|
48
|
+
label: "Deployment Authorized"
|
|
49
|
+
options:
|
|
50
|
+
- "Yes"
|
|
51
|
+
- "No"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: "QA REQUEST"
|
|
2
|
+
description: "Request QA testing for a sprint task or bug fix"
|
|
3
|
+
title: "QA REQUEST - [Feature/Issue]"
|
|
4
|
+
labels: ["qa-request"]
|
|
5
|
+
|
|
6
|
+
body:
|
|
7
|
+
- type: input
|
|
8
|
+
id: related_issue
|
|
9
|
+
attributes:
|
|
10
|
+
label: "Related Sprint Task Issue (#)"
|
|
11
|
+
placeholder: "#101"
|
|
12
|
+
validations:
|
|
13
|
+
required: true
|
|
14
|
+
|
|
15
|
+
- type: textarea
|
|
16
|
+
id: what_to_test
|
|
17
|
+
attributes:
|
|
18
|
+
label: "What to Test"
|
|
19
|
+
validations:
|
|
20
|
+
required: true
|
|
21
|
+
|
|
22
|
+
- type: textarea
|
|
23
|
+
id: env_build
|
|
24
|
+
attributes:
|
|
25
|
+
label: "Environment + Build Link"
|
|
26
|
+
placeholder: |
|
|
27
|
+
Staging build link:
|
|
28
|
+
TestFlight / APK / Web URL:
|
|
29
|
+
|
|
30
|
+
- type: textarea
|
|
31
|
+
id: acceptance_criteria
|
|
32
|
+
attributes:
|
|
33
|
+
label: "Acceptance Criteria"
|
|
34
|
+
placeholder: |
|
|
35
|
+
- Expected behavior
|
|
36
|
+
- Edge cases
|
|
37
|
+
|
|
38
|
+
- type: checkboxes
|
|
39
|
+
id: artifacts
|
|
40
|
+
attributes:
|
|
41
|
+
label: "Artifacts"
|
|
42
|
+
options:
|
|
43
|
+
- label: "Screenshots"
|
|
44
|
+
- label: "Logs"
|
|
45
|
+
- label: "Video Recording"
|
|
46
|
+
- label: "Crash Report"
|
|
47
|
+
|
|
48
|
+
- type: dropdown
|
|
49
|
+
id: qa_outcome
|
|
50
|
+
attributes:
|
|
51
|
+
label: "QA Outcome"
|
|
52
|
+
options:
|
|
53
|
+
- "Pending"
|
|
54
|
+
- "Pass"
|
|
55
|
+
- "Fail"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: "SPRINT PLANNING"
|
|
2
|
+
description: "Plan sprint scope; each feature becomes a task issue"
|
|
3
|
+
title: "SPRINT - [Sprint Name]"
|
|
4
|
+
labels: ["sprint", "planning"]
|
|
5
|
+
|
|
6
|
+
body:
|
|
7
|
+
- type: markdown
|
|
8
|
+
attributes:
|
|
9
|
+
value: |
|
|
10
|
+
# SPRINT PLANNING
|
|
11
|
+
|
|
12
|
+
Enter ONE feature per line.
|
|
13
|
+
Do not use bullets.
|
|
14
|
+
Do not number items.
|
|
15
|
+
Do not combine multiple features on one line.
|
|
16
|
+
|
|
17
|
+
- type: input
|
|
18
|
+
id: sprint_name
|
|
19
|
+
attributes:
|
|
20
|
+
label: "Sprint Name"
|
|
21
|
+
placeholder: "Sprint 12 - March 1–14"
|
|
22
|
+
validations:
|
|
23
|
+
required: true
|
|
24
|
+
|
|
25
|
+
- type: input
|
|
26
|
+
id: sprint_start
|
|
27
|
+
attributes:
|
|
28
|
+
label: "Sprint Start"
|
|
29
|
+
description: "Format: YYYY-MM-DD"
|
|
30
|
+
placeholder: "2026-03-07"
|
|
31
|
+
validations:
|
|
32
|
+
required: true
|
|
33
|
+
|
|
34
|
+
- type: input
|
|
35
|
+
id: sprint_end
|
|
36
|
+
attributes:
|
|
37
|
+
label: "Sprint End"
|
|
38
|
+
description: "Format: YYYY-MM-DD"
|
|
39
|
+
placeholder: "2026-03-21"
|
|
40
|
+
validations:
|
|
41
|
+
required: true
|
|
42
|
+
|
|
43
|
+
- type: textarea
|
|
44
|
+
id: sprint_goal
|
|
45
|
+
attributes:
|
|
46
|
+
label: "Sprint Goal"
|
|
47
|
+
validations:
|
|
48
|
+
required: true
|
|
49
|
+
|
|
50
|
+
- type: textarea
|
|
51
|
+
id: sprint_features
|
|
52
|
+
attributes:
|
|
53
|
+
label: "Sprint Features (One Per Line)"
|
|
54
|
+
placeholder: |
|
|
55
|
+
Implement burn-down chart
|
|
56
|
+
Add sprint health indicator
|
|
57
|
+
Prevent duplicate child creation
|
|
58
|
+
validations:
|
|
59
|
+
required: true
|
|
60
|
+
|
|
61
|
+
- type: dropdown
|
|
62
|
+
id: sprint_approval
|
|
63
|
+
attributes:
|
|
64
|
+
label: "Sprint Approved"
|
|
65
|
+
options:
|
|
66
|
+
- "Pending"
|
|
67
|
+
- "Approved"
|
|
68
|
+
- "Rejected"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
name: "Task"
|
|
2
|
+
description: "Create a structured task with owner, priority, and acceptance criteria"
|
|
3
|
+
title: "TASK - "
|
|
4
|
+
labels: ["task"]
|
|
5
|
+
|
|
6
|
+
body:
|
|
7
|
+
- type: markdown
|
|
8
|
+
attributes:
|
|
9
|
+
value: |
|
|
10
|
+
## 🧩 Task Overview
|
|
11
|
+
Fill out the details below to create a structured, trackable task.
|
|
12
|
+
|
|
13
|
+
- type: input
|
|
14
|
+
id: title_summary
|
|
15
|
+
attributes:
|
|
16
|
+
label: "Task Summary"
|
|
17
|
+
description: "One-line description of the task"
|
|
18
|
+
placeholder: "Implement login validation error handling"
|
|
19
|
+
validations:
|
|
20
|
+
required: true
|
|
21
|
+
|
|
22
|
+
- type: textarea
|
|
23
|
+
id: description
|
|
24
|
+
attributes:
|
|
25
|
+
label: "Description"
|
|
26
|
+
description: "Provide context and details"
|
|
27
|
+
placeholder: "Explain what needs to be done and why..."
|
|
28
|
+
validations:
|
|
29
|
+
required: true
|
|
30
|
+
|
|
31
|
+
- type: dropdown
|
|
32
|
+
id: owner
|
|
33
|
+
attributes:
|
|
34
|
+
label: "Owner (Select if applicable)"
|
|
35
|
+
description: "Select the responsible person"
|
|
36
|
+
options:
|
|
37
|
+
- Unassigned
|
|
38
|
+
- Other (Specify Below)
|
|
39
|
+
validations:
|
|
40
|
+
required: true
|
|
41
|
+
|
|
42
|
+
- type: input
|
|
43
|
+
id: custom_owner
|
|
44
|
+
attributes:
|
|
45
|
+
label: "Custom Owner (if 'Other')"
|
|
46
|
+
description: "Type name if not listed above"
|
|
47
|
+
placeholder: "Enter full name"
|
|
48
|
+
|
|
49
|
+
- type: dropdown
|
|
50
|
+
id: priority
|
|
51
|
+
attributes:
|
|
52
|
+
label: "Priority"
|
|
53
|
+
options:
|
|
54
|
+
- P0 - Critical
|
|
55
|
+
- P1 - High
|
|
56
|
+
- P2 - Medium
|
|
57
|
+
- P3 - Low
|
|
58
|
+
validations:
|
|
59
|
+
required: true
|
|
60
|
+
|
|
61
|
+
- type: dropdown
|
|
62
|
+
id: status
|
|
63
|
+
attributes:
|
|
64
|
+
label: "Status"
|
|
65
|
+
options:
|
|
66
|
+
- Backlog
|
|
67
|
+
- In Progress
|
|
68
|
+
- Blocked
|
|
69
|
+
- Ready for Review
|
|
70
|
+
- Done
|
|
71
|
+
validations:
|
|
72
|
+
required: true
|
|
73
|
+
|
|
74
|
+
- type: textarea
|
|
75
|
+
id: acceptance_criteria
|
|
76
|
+
attributes:
|
|
77
|
+
label: "Acceptance Criteria"
|
|
78
|
+
description: "Define what 'done' looks like"
|
|
79
|
+
placeholder: |
|
|
80
|
+
- [ ] Requirement 1
|
|
81
|
+
- [ ] Requirement 2
|
|
82
|
+
- [ ] Requirement 3
|
|
83
|
+
validations:
|
|
84
|
+
required: true
|
|
85
|
+
|
|
86
|
+
- type: textarea
|
|
87
|
+
id: artifacts
|
|
88
|
+
attributes:
|
|
89
|
+
label: "Artifacts / Links (Optional)"
|
|
90
|
+
description: "Add PR links, designs, screenshots, docs"
|
|
91
|
+
placeholder: "https://figma.com/..., https://github.com/..., etc."
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
name: Authorize Deployment on Dual Approval
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
issues: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
authorize:
|
|
12
|
+
if: github.event.issue && contains(github.event.issue.labels.*.name || fromJSON('[]'), 'production')
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
issues: write
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Check Dual Approval (Flexible + Decline)
|
|
19
|
+
uses: actions/github-script@v7
|
|
20
|
+
env:
|
|
21
|
+
RELEASE_APPROVER: ${{ vars.RELEASE_APPROVER || 'release-approver' }}
|
|
22
|
+
QA_APPROVER: ${{ vars.QA_APPROVER || 'qa-approver' }}
|
|
23
|
+
with:
|
|
24
|
+
script: |
|
|
25
|
+
const issueNumber = context.issue.number;
|
|
26
|
+
const owner = context.repo.owner;
|
|
27
|
+
const repo = context.repo.repo;
|
|
28
|
+
const releaseApprover = process.env.RELEASE_APPROVER;
|
|
29
|
+
const qaApprover = process.env.QA_APPROVER;
|
|
30
|
+
|
|
31
|
+
const comments = await github.rest.issues.listComments({
|
|
32
|
+
owner,
|
|
33
|
+
repo,
|
|
34
|
+
issue_number: issueNumber
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let releaseApproved = false;
|
|
38
|
+
let qaApproved = false;
|
|
39
|
+
|
|
40
|
+
for (const comment of comments.data) {
|
|
41
|
+
const body = (comment.body || "").trim().toLowerCase();
|
|
42
|
+
|
|
43
|
+
if (
|
|
44
|
+
comment.user.login === releaseApprover &&
|
|
45
|
+
/(declined|reject|not approved)/i.test(body)
|
|
46
|
+
) {
|
|
47
|
+
|
|
48
|
+
await github.rest.issues.addLabels({
|
|
49
|
+
owner,
|
|
50
|
+
repo,
|
|
51
|
+
issue_number: issueNumber,
|
|
52
|
+
labels: ["declined"]
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await github.rest.issues.removeLabel({
|
|
57
|
+
owner,
|
|
58
|
+
repo,
|
|
59
|
+
issue_number: issueNumber,
|
|
60
|
+
name: "ready-for-deploy"
|
|
61
|
+
});
|
|
62
|
+
} catch (e) {}
|
|
63
|
+
|
|
64
|
+
await github.rest.issues.createComment({
|
|
65
|
+
owner,
|
|
66
|
+
repo,
|
|
67
|
+
issue_number: issueNumber,
|
|
68
|
+
body: "🔴 **Release Declined**\n\nThis release requires additional fixes before production deployment."
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
comment.user.login === releaseApprover &&
|
|
76
|
+
/^(approved|approve|ok|go ahead)/i.test(body)
|
|
77
|
+
) {
|
|
78
|
+
releaseApproved = true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
comment.user.login === qaApprover &&
|
|
83
|
+
/(qa approved|approved|qa ok|looks good)/i.test(body)
|
|
84
|
+
) {
|
|
85
|
+
qaApproved = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (releaseApproved && qaApproved) {
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await github.rest.issues.addLabels({
|
|
93
|
+
owner,
|
|
94
|
+
repo,
|
|
95
|
+
issue_number: issueNumber,
|
|
96
|
+
labels: ["ready-for-deploy"]
|
|
97
|
+
});
|
|
98
|
+
} catch (e) {}
|
|
99
|
+
|
|
100
|
+
await github.rest.issues.createComment({
|
|
101
|
+
owner,
|
|
102
|
+
repo,
|
|
103
|
+
issue_number: issueNumber,
|
|
104
|
+
body: "🟢🚀 **Deployment Authorized**\n\nDual approval received. This release is cleared for production deployment."
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Auto-assign QA submissions
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types: [opened, labeled]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
issues: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
assign:
|
|
12
|
+
if: |
|
|
13
|
+
(contains(github.event.issue.labels.*.name || fromJSON('[]'), 'qa') ||
|
|
14
|
+
contains(github.event.issue.labels.*.name || fromJSON('[]'), 'qa-request')) &&
|
|
15
|
+
vars.QA_ASSIGNEES != ''
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
issues: write
|
|
19
|
+
steps:
|
|
20
|
+
# pozil/auto-assign-issue does not support 'labels' input; labels come from issue template (qa_request.yml)
|
|
21
|
+
- name: Assign QA team to QA submission issues
|
|
22
|
+
uses: pozil/auto-assign-issue@v1
|
|
23
|
+
with:
|
|
24
|
+
assignees: ${{ vars.QA_ASSIGNEES }}
|
|
25
|
+
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
name: Update Sprint Progress + Intelligence + Auto Close
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types: [closed]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
issues: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
update-sprint:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
issues: write
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Update sprint health, burn-down, and auto-close
|
|
18
|
+
uses: actions/github-script@v7
|
|
19
|
+
env:
|
|
20
|
+
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
|
21
|
+
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
|
22
|
+
with:
|
|
23
|
+
script: |
|
|
24
|
+
const issue = context.payload.issue;
|
|
25
|
+
const body = issue.body || "";
|
|
26
|
+
|
|
27
|
+
const parentMatch = body.match(/Parent Sprint:\s*#(\d+)/);
|
|
28
|
+
if (!parentMatch) return;
|
|
29
|
+
|
|
30
|
+
const sprintNumber = parseInt(parentMatch[1]);
|
|
31
|
+
|
|
32
|
+
const sprintIssue = await github.rest.issues.get({
|
|
33
|
+
owner: context.repo.owner,
|
|
34
|
+
repo: context.repo.repo,
|
|
35
|
+
issue_number: sprintNumber
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const sprintBody = sprintIssue.data.body || "";
|
|
39
|
+
|
|
40
|
+
let startDate, endDate;
|
|
41
|
+
const dateMatch1 = sprintBody.match(/Sprint Dates[\s\S]*?(\d{4}-\d{2}-\d{2})\s*to\s*(\d{4}-\d{2}-\d{2})/i);
|
|
42
|
+
const dateMatch2a = sprintBody.match(/Sprint Start[\s\S]*?(\d{4}-\d{2}-\d{2})/i);
|
|
43
|
+
const dateMatch2b = sprintBody.match(/Sprint End[\s\S]*?(\d{4}-\d{2}-\d{2})/i);
|
|
44
|
+
if (dateMatch1) {
|
|
45
|
+
startDate = new Date(dateMatch1[1]);
|
|
46
|
+
endDate = new Date(dateMatch1[2]);
|
|
47
|
+
} else if (dateMatch2a && dateMatch2b) {
|
|
48
|
+
startDate = new Date(dateMatch2a[1]);
|
|
49
|
+
endDate = new Date(dateMatch2b[1]);
|
|
50
|
+
}
|
|
51
|
+
if (!startDate || !endDate) {
|
|
52
|
+
console.log("Sprint dates not found.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const now = new Date();
|
|
56
|
+
|
|
57
|
+
const totalDuration = endDate - startDate;
|
|
58
|
+
const elapsed = now - startDate;
|
|
59
|
+
const timePercent = Math.max(0, Math.min(100, Math.round((elapsed / totalDuration) * 100)));
|
|
60
|
+
|
|
61
|
+
const { data: allIssues } = await github.rest.issues.listForRepo({
|
|
62
|
+
owner: context.repo.owner,
|
|
63
|
+
repo: context.repo.repo,
|
|
64
|
+
state: "all",
|
|
65
|
+
per_page: 100
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const children = allIssues.filter(i =>
|
|
69
|
+
i.body && i.body.includes(`Parent Sprint: #${sprintNumber}`)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const closedChildren = children.filter(i => i.state === "closed");
|
|
73
|
+
|
|
74
|
+
const progressPercent = children.length === 0
|
|
75
|
+
? 0
|
|
76
|
+
: Math.round((closedChildren.length / children.length) * 100);
|
|
77
|
+
|
|
78
|
+
let healthEmoji = "🟢";
|
|
79
|
+
if (progressPercent < timePercent - 10) healthEmoji = "🔴";
|
|
80
|
+
else if (progressPercent < timePercent) healthEmoji = "🟡";
|
|
81
|
+
|
|
82
|
+
const totalBars = 20;
|
|
83
|
+
const filledBars = Math.round((progressPercent / 100) * totalBars);
|
|
84
|
+
const burnDown =
|
|
85
|
+
"█".repeat(filledBars) + "░".repeat(totalBars - filledBars);
|
|
86
|
+
|
|
87
|
+
let updatedBody = sprintBody;
|
|
88
|
+
updatedBody = updatedBody.replace(/## 🚦 Sprint Status[\s\S]*?---/g, "");
|
|
89
|
+
|
|
90
|
+
updatedBody +=
|
|
91
|
+
`\n\n---\n\n## 🚦 Sprint Status\n\n` +
|
|
92
|
+
`Progress: **${progressPercent}%**\n` +
|
|
93
|
+
`Time Elapsed: **${timePercent}%**\n\n` +
|
|
94
|
+
`Health: ${healthEmoji}\n\n` +
|
|
95
|
+
`### 📉 Burn-down\n` +
|
|
96
|
+
`\`${burnDown}\` ${progressPercent}%\n\n---`;
|
|
97
|
+
|
|
98
|
+
await github.rest.issues.update({
|
|
99
|
+
owner: context.repo.owner,
|
|
100
|
+
repo: context.repo.repo,
|
|
101
|
+
issue_number: sprintNumber,
|
|
102
|
+
body: updatedBody
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (progressPercent === 100) {
|
|
106
|
+
|
|
107
|
+
await github.rest.issues.update({
|
|
108
|
+
owner: context.repo.owner,
|
|
109
|
+
repo: context.repo.repo,
|
|
110
|
+
issue_number: sprintNumber,
|
|
111
|
+
state: "closed"
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await github.rest.issues.createComment({
|
|
115
|
+
owner: context.repo.owner,
|
|
116
|
+
repo: context.repo.repo,
|
|
117
|
+
issue_number: sprintNumber,
|
|
118
|
+
body: "🎉 All sprint tasks complete. Sprint automatically closed."
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_CHAT_ID) {
|
|
122
|
+
await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: { "Content-Type": "application/json" },
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
chat_id: process.env.TELEGRAM_CHAT_ID,
|
|
127
|
+
text:
|
|
128
|
+
`🚀 Sprint Completed!\n\n` +
|
|
129
|
+
`${sprintIssue.data.title}\n\n` +
|
|
130
|
+
`Progress: 100%\n` +
|
|
131
|
+
`Repository: ${context.repo.repo}`
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Notify Release Approver
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types: [opened]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
issues: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
notify:
|
|
12
|
+
if: contains(github.event.issue.labels.*.name || fromJSON('[]'), 'production')
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
issues: write
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Post Approval Comment
|
|
19
|
+
uses: actions/github-script@v7
|
|
20
|
+
env:
|
|
21
|
+
RELEASE_APPROVER: ${{ vars.RELEASE_APPROVER || 'release-approver' }}
|
|
22
|
+
PROJECT_NAME: ${{ vars.PROJECT_NAME || '' }}
|
|
23
|
+
with:
|
|
24
|
+
script: |
|
|
25
|
+
const issueTitle = context.payload.issue.title;
|
|
26
|
+
const issueBody = context.payload.issue.body || "";
|
|
27
|
+
const approver = process.env.RELEASE_APPROVER;
|
|
28
|
+
const projectName = process.env.PROJECT_NAME;
|
|
29
|
+
|
|
30
|
+
let sprintRef = "Not specified";
|
|
31
|
+
const sprintMatch = issueBody.match(/#\d+/);
|
|
32
|
+
if (sprintMatch) sprintRef = sprintMatch[0];
|
|
33
|
+
|
|
34
|
+
let qaRecommendation = "Not specified";
|
|
35
|
+
if (issueBody.includes("Approve for Production")) qaRecommendation = "Approve for Production";
|
|
36
|
+
else if (issueBody.includes("Reject Release")) qaRecommendation = "Reject Release";
|
|
37
|
+
else if (issueBody.includes("Conditional Approval")) qaRecommendation = "Conditional Approval";
|
|
38
|
+
|
|
39
|
+
const projectLine = projectName ? "**Project:** " + projectName + "\n\n" : "";
|
|
40
|
+
const commentBody =
|
|
41
|
+
"🚨 @" + approver + " Production Release requires approval.\n\n" +
|
|
42
|
+
projectLine +
|
|
43
|
+
"**Issue:** " + issueTitle + "\n\n" +
|
|
44
|
+
"**Linked Sprint:** " + sprintRef + "\n\n" +
|
|
45
|
+
"**QA Recommendation:** " + qaRecommendation + "\n\n" +
|
|
46
|
+
"Please review the sprint delivery and QA sign-off details above before approving deployment.\n\n" +
|
|
47
|
+
"🔒 Deployment must not proceed without your approval.";
|
|
48
|
+
|
|
49
|
+
await github.rest.issues.createComment({
|
|
50
|
+
owner: context.repo.owner,
|
|
51
|
+
repo: context.repo.repo,
|
|
52
|
+
issue_number: context.issue.number,
|
|
53
|
+
body: commentBody
|
|
54
|
+
});
|