mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-30 04:20:20 +00:00
Split issue triage workflows
This commit is contained in:
parent
f6656950a9
commit
5f49acd75b
6 changed files with 678 additions and 285 deletions
131
.github/workflows/README.md
vendored
131
.github/workflows/README.md
vendored
|
|
@ -1,14 +1,36 @@
|
|||
# GitHub Actions Workflows
|
||||
|
||||
## Issue Triage Automation
|
||||
|
||||
**Files**:
|
||||
- `issue-version-label-sync.yml`
|
||||
- `issue-version-retest-comment.yml`
|
||||
|
||||
Issue intake is split deliberately:
|
||||
|
||||
- `issue-version-label-sync.yml` is the silent metadata path. It runs on `opened`, `edited`, and `reopened` issue events so version labels, `needs-version-info`, and `needs-retest-on-latest` stay correct when maintainers tidy issue metadata.
|
||||
- `issue-version-retest-comment.yml` is the public guidance path. It only runs on `opened` and `reopened`, and only posts reporter-facing retest guidance when the issue is an older-version bug report from a non-maintainer.
|
||||
- Both workflows load the shared helper at `.github/scripts/issue-version-triage.cjs` so parsing and classification logic lives in one place instead of drifting across duplicated inline scripts.
|
||||
|
||||
## Update Demo Server
|
||||
|
||||
**File**: `update-demo-server.yml`
|
||||
|
||||
Updates the public demo server when the release pipeline dispatches a new stable release deployment, or when run manually.
|
||||
Automatically updates the governed demo target after a release is published.
|
||||
Stable releases update the stable public demo. Prerelease tags update the
|
||||
separate v6 preview demo.
|
||||
|
||||
### Configuration Required
|
||||
|
||||
Add these secrets to your GitHub repository settings (`Settings` → `Secrets and variables` → `Actions`):
|
||||
Create two GitHub Environments:
|
||||
|
||||
1. `demo-stable`
|
||||
2. `demo-preview-v6`
|
||||
|
||||
Each environment must define the same secret names so the workflow can select
|
||||
the target by environment instead of hardcoding separate workflows.
|
||||
|
||||
Required environment secrets:
|
||||
|
||||
1. **DEMO_SERVER_SSH_KEY**
|
||||
- The private SSH key for accessing the demo server
|
||||
|
|
@ -17,39 +39,111 @@ Add these secrets to your GitHub repository settings (`Settings` → `Secrets an
|
|||
|
||||
2. **DEMO_SERVER_HOST**
|
||||
- The hostname or IP of the demo server
|
||||
- Value: `174.138.72.137` (or hostname if using DNS)
|
||||
|
||||
3. **DEMO_SERVER_USER**
|
||||
- The SSH username for the demo server
|
||||
- Value: `root` (or the appropriate user with sudo access)
|
||||
- The SSH username for the demo server (e.g. `root` or a deploy user with sudo access)
|
||||
|
||||
Required shared secret:
|
||||
|
||||
1. **TS_AUTHKEY**
|
||||
- Tailscale auth key used by the governed demo deploy/update workflows before SSH
|
||||
- Allows GitHub-hosted runners to reach private demo targets such as the stable `pulse-relay` Tailscale host
|
||||
- May be stored as a repository secret or repeated in the selected environment if desired
|
||||
|
||||
Required environment variables:
|
||||
|
||||
1. **DEMO_EXPECTED_HOSTNAME**
|
||||
- The remote `hostname` value the selected environment is expected to report
|
||||
- Stable example: `pulse-relay`
|
||||
- Preview example: `pulse-v6-preview`
|
||||
- This is a host-identity guard: the workflow fails closed if the SSH secret points at the wrong machine
|
||||
|
||||
2. **DEMO_LOCAL_BASE_URL**
|
||||
- Local URL used on the target host for version and mock-mode verification
|
||||
- Example stable value: `http://localhost:7655`
|
||||
- Example preview value: `http://localhost:8665`
|
||||
|
||||
3. **DEMO_PUBLIC_HEALTH_URL**
|
||||
- Public health endpoint for the selected demo target
|
||||
- Example stable value: `https://demo.pulserelay.pro/api/health`
|
||||
- Example preview value: `https://v6-demo.pulserelay.pro/api/health`
|
||||
|
||||
Optional environment variables:
|
||||
|
||||
1. **DEMO_SERVICE_NAME**
|
||||
- Stable default: `pulse`
|
||||
- Preview example: `pulse-v6-preview`
|
||||
- When set, the server installer derives the instance-specific install dir,
|
||||
config dir, update helper, and update timer from this service identity.
|
||||
|
||||
2. **DEMO_AUTH_USER** / **DEMO_AUTH_PASS**
|
||||
- Demo credentials used for post-update mock verification
|
||||
- Defaults to `demo` / `demo` when omitted
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Trigger**: Dispatched by the release pipeline after a stable release is published, or run manually from the Actions tab
|
||||
2. **Filter**: The release pipeline only dispatches this for stable releases
|
||||
3. **Update**: SSHs to demo server and runs the install script
|
||||
4. **Verify**: Checks that the new version is running and mock mode is active
|
||||
5. **Cleanup**: Removes SSH key from runner
|
||||
1. **Trigger**: Runs automatically when a GitHub release is published
|
||||
2. **Target selection**: Stable tags deploy to `demo-stable`; prerelease tags deploy to `demo-preview-v6`
|
||||
3. **Service identity guard**: Preview runs default to `pulse-v6-preview` and refuse to target the stable `pulse` service identity
|
||||
4. **Governance check**: Validates the selected tag is reachable from the governed release branch for that version
|
||||
5. **Latest check**: Refuses to update a target unless the published tag is the latest release for that target channel
|
||||
6. **Network attach**: Joins Tailscale before any SSH step so governed demo targets can stay on private hostnames or Tailscale IPs
|
||||
7. **Update**: SSHs to the selected demo host and runs the tag-matched root installer from that exact git tag
|
||||
8. **Host identity check**: Verifies the SSH target reports the governed expected hostname before running installer or deploy steps
|
||||
9. **Verify**: Checks that the new version is running, mock mode is active, and the public demo HTML serves the same frontend entry asset as the target service
|
||||
10. **Browser smoke**: Uses the governed Playwright helper to prove the public demo still renders the login shell in a real browser
|
||||
11. **Cleanup**: Removes SSH key from runner
|
||||
|
||||
### Testing
|
||||
|
||||
To test without publishing a release:
|
||||
1. Go to `Actions` tab in GitHub
|
||||
2. Select `Update Demo Server` workflow
|
||||
3. Click `Run workflow` (if manual trigger is enabled)
|
||||
3. Provide a tag and choose `stable`, `preview-v6`, or `auto`
|
||||
|
||||
### Benefits
|
||||
|
||||
- ✅ Demo server always showcases latest stable release
|
||||
- ✅ Validates install script works on real server
|
||||
- ✅ Removes manual step from release process
|
||||
- ✅ Free to run (public repos get unlimited GitHub Actions minutes)
|
||||
- ✅ Stable and preview demos stay on separate governed targets
|
||||
- ✅ Prereleases no longer require a stable demo overwrite or a manual skip
|
||||
- ✅ Validates the real server installer path on the selected target
|
||||
- ✅ Removes release-operator guesswork about which demo should move
|
||||
|
||||
### Preview Bootstrap Note
|
||||
|
||||
The preview environment must be bootstrapped once on the host before the update
|
||||
workflow can keep it current. The supported path is a separate service identity
|
||||
such as `pulse-v6-preview` plus a separate public route such as
|
||||
`v6-demo.pulserelay.pro`; do not reuse the stable `pulse.service` instance.
|
||||
|
||||
## Deploy Demo Server
|
||||
|
||||
**File**: `deploy-demo-server.yml`
|
||||
|
||||
Manually deploys the current branch build to either the stable or preview demo
|
||||
environment without changing the governed release workflow.
|
||||
|
||||
- Uses the same `demo-stable` / `demo-preview-v6` environment contract as the
|
||||
release-driven updater
|
||||
- Joins Tailscale before SSH so governed demo targets can stay on private
|
||||
addresses instead of requiring public runner reachability
|
||||
- Requires `DEMO_EXPECTED_HOSTNAME`, `DEMO_LOCAL_BASE_URL`, and `DEMO_PUBLIC_HEALTH_URL`
|
||||
- Supports optional `DEMO_SERVICE_NAME`, `DEMO_INSTALL_DIR`, `DEMO_TEST_PORT`,
|
||||
`DEMO_AUTH_USER`, and `DEMO_AUTH_PASS`
|
||||
- Assumes the target service and install directory already exist on the host
|
||||
- Defaults preview runs to `pulse-v6-preview` and refuses to target the stable
|
||||
`pulse` service identity
|
||||
- Verifies the SSH target reports the governed expected hostname before deploy
|
||||
- Verifies that the public demo shell serves the same frontend entry asset that
|
||||
was built and deployed
|
||||
- Uses `scripts/run_demo_public_browser_smoke.sh` to prove the public demo
|
||||
still renders the login shell in Chromium after deploy/update verification
|
||||
|
||||
## Helm CI
|
||||
|
||||
**File**: `helm-ci.yml`
|
||||
|
||||
Runs `helm lint --strict` and renders the chart with common configuration combinations on every pull request that touches Helm content (and on pushes to `main` and `release/5.1`). This prevents regressions before they land.
|
||||
Runs `helm lint --strict` and renders the chart with common configuration combinations on every pull request that touches Helm content (and on pushes to `main`). This prevents regressions before they land.
|
||||
|
||||
- Triggered by PRs/pushes touching `deploy/helm/**`, docs, or the workflow itself
|
||||
- Uses Helm v3.15.2
|
||||
- Renders both the default deployment and an agent-enabled configuration to catch template issues
|
||||
|
|
@ -58,8 +152,9 @@ Runs `helm lint --strict` and renders the chart with common configuration combin
|
|||
|
||||
**File**: `publish-helm-chart.yml`
|
||||
|
||||
Packages the Helm chart and pushes it to the GitHub Container Registry (OCI) when dispatched by the release pipeline, or manually via workflow dispatch. Also makes the packaged `.tgz` available as both an Actions artifact and a release asset. The same behaviour can be triggered locally via `./scripts/package-helm-chart.sh <version> [--push]`.
|
||||
- Triggered by the release pipeline via workflow dispatch, or manually from the Actions tab
|
||||
Packages the Helm chart and pushes it to the GitHub Container Registry (OCI) whenever a GitHub Release is published. Also makes the packaged `.tgz` available as both an Actions artifact and a release asset. The same behaviour can be triggered locally via `./scripts/package-helm-chart.sh <version> [--push]`.
|
||||
|
||||
- Triggered automatically on `release: published`, or manually via workflow dispatch (requires `chart_version` input)
|
||||
- Chart and app versions mirror the Pulse release tag (e.g., `v4.24.0` → `4.24.0`)
|
||||
- Publishes to `oci://ghcr.io/<owner>/pulse-chart`
|
||||
- Requires no additional secrets—uses the built-in `GITHUB_TOKEN` with `packages: write` permission
|
||||
|
|
|
|||
31
.github/workflows/issue-version-label-sync.yml
vendored
Normal file
31
.github/workflows/issue-version-label-sync.yml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: Issue Version Label Sync
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: ${{ github.event.issue.pull_request == null }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out triage helper
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/scripts/issue-version-triage.cjs
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Sync issue version metadata
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const triage = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/issue-version-triage.cjs`);
|
||||
await triage.syncLabels({ github, context, core });
|
||||
30
.github/workflows/issue-version-retest-comment.yml
vendored
Normal file
30
.github/workflows/issue-version-retest-comment.yml
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
name: Issue Version Retest Comment
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
if: ${{ github.event.issue.pull_request == null }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out triage helper
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/scripts/issue-version-triage.cjs
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Post retest guidance when needed
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const triage = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/issue-version-triage.cjs`);
|
||||
await triage.postRetestComment({ github, context, core });
|
||||
267
.github/workflows/issue-version-triage.yml
vendored
267
.github/workflows/issue-version-triage.yml
vendored
|
|
@ -1,267 +0,0 @@
|
|||
name: Issue Version Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
if: ${{ github.event.issue.pull_request == null }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Apply version labels and retest guidance
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const labelNames = new Set((issue.labels || []).map((label) => label.name));
|
||||
const VERSION_LABEL_PREFIX = "affects-";
|
||||
const NEEDS_VERSION_LABEL = "needs-version-info";
|
||||
const RETEST_LABEL = "needs-retest-on-latest";
|
||||
const RETEST_COMMENT_MARKER = "<!-- issue-version-triage:v1 -->";
|
||||
const BUG_LABEL = "bug";
|
||||
const DOCS_LABEL = "documentation";
|
||||
const ENHANCEMENT_LABEL = "enhancement";
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function extractSectionValue(body, heading) {
|
||||
if (!body) return null;
|
||||
const pattern = new RegExp(
|
||||
`^#+\\s*${escapeRegExp(heading)}\\s*$\\n+([\\s\\S]*?)(?=^#+\\s+|$)`,
|
||||
"im"
|
||||
);
|
||||
const match = body.match(pattern);
|
||||
if (!match) return null;
|
||||
const value = match[1].trim();
|
||||
return value || null;
|
||||
}
|
||||
|
||||
function normalizeVersion(value) {
|
||||
if (!value) return null;
|
||||
const match = String(value).match(/\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function extractPulseVersion(title, body) {
|
||||
if (body) {
|
||||
const lines = body.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
const line = lines[i] || "";
|
||||
if (/pulse\s*(\||-)?\s*version/i.test(line)) {
|
||||
const inlineVersion = normalizeVersion(line);
|
||||
if (inlineVersion) return inlineVersion;
|
||||
|
||||
// Check nearby lines because issue forms often put values on the next line.
|
||||
for (let j = i + 1; j < Math.min(i + 6, lines.length); j += 1) {
|
||||
const nearby = (lines[j] || "").trim();
|
||||
if (!nearby) continue;
|
||||
const nearbyVersion = normalizeVersion(nearby);
|
||||
if (nearbyVersion) return nearbyVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: issue-form block headed by "Pulse version".
|
||||
const headingMatch = body.match(/#+\s*Pulse version[\s\S]{0,80}?(\bv?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b)/i);
|
||||
if (headingMatch) return normalizeVersion(headingMatch[1]);
|
||||
|
||||
// Final fallback for older templates with "Pulse | Version: [6.0.0]".
|
||||
const legacyMatch = body.match(/pulse\s*\|?\s*version[^\n]*?(\bv?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b)/i);
|
||||
if (legacyMatch) return normalizeVersion(legacyMatch[1]);
|
||||
}
|
||||
|
||||
// Maintainer-created split issues often carry the exact version in the title.
|
||||
return normalizeVersion(title);
|
||||
}
|
||||
|
||||
function classifyV6FeedbackType(body) {
|
||||
const feedbackType = extractSectionValue(body, "Feedback type");
|
||||
if (!feedbackType) return null;
|
||||
|
||||
const normalized = feedbackType.toLowerCase();
|
||||
if (
|
||||
normalized.includes("bug") ||
|
||||
normalized.includes("regression") ||
|
||||
normalized.includes("upgrade / migration issue") ||
|
||||
normalized.includes("performance issue")
|
||||
) {
|
||||
return BUG_LABEL;
|
||||
}
|
||||
if (normalized.includes("documentation issue")) {
|
||||
return DOCS_LABEL;
|
||||
}
|
||||
if (
|
||||
normalized.includes("ux / workflow friction") ||
|
||||
normalized.includes("other actionable feedback")
|
||||
) {
|
||||
return ENHANCEMENT_LABEL;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseCore(version) {
|
||||
const match = String(version || "").match(/^(\d+)\.(\d+)\.(\d+)/);
|
||||
if (!match) return null;
|
||||
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
||||
}
|
||||
|
||||
function compareCore(a, b) {
|
||||
const av = parseCore(a);
|
||||
const bv = parseCore(b);
|
||||
if (!av || !bv) return null;
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
if (av[i] > bv[i]) return 1;
|
||||
if (av[i] < bv[i]) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function ensureLabel(name, color, description) {
|
||||
try {
|
||||
await github.rest.issues.getLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 404) throw error;
|
||||
await github.rest.issues.createLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name,
|
||||
color,
|
||||
description,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function hasRetestComment(issueNumber) {
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
return comments.some((comment) => (comment.body || "").includes(RETEST_COMMENT_MARKER));
|
||||
}
|
||||
|
||||
const action = context.payload.action || "";
|
||||
const authorAssociation = String(issue.author_association || "").toUpperCase();
|
||||
const reporterIsMaintainer = ["OWNER", "MEMBER", "COLLABORATOR"].includes(authorAssociation);
|
||||
const allowRetestComment = (action === "opened" || action === "reopened") && !reporterIsMaintainer;
|
||||
|
||||
const reportedVersion = extractPulseVersion(issue.title, issue.body);
|
||||
core.info(`Reported Pulse version: ${reportedVersion || "not found"}`);
|
||||
|
||||
let latestVersion = null;
|
||||
try {
|
||||
const latest = await github.rest.repos.getLatestRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
latestVersion = normalizeVersion(latest.data.tag_name || latest.data.name || "");
|
||||
} catch (error) {
|
||||
core.warning(`Could not determine latest release: ${error.message}`);
|
||||
}
|
||||
core.info(`Latest stable release: ${latestVersion || "unknown"}`);
|
||||
|
||||
const nextLabels = new Set(labelNames);
|
||||
const v6FeedbackClass = classifyV6FeedbackType(issue.body);
|
||||
if (v6FeedbackClass) {
|
||||
core.info(`Detected v6 feedback issue class: ${v6FeedbackClass}`);
|
||||
nextLabels.add(v6FeedbackClass);
|
||||
}
|
||||
|
||||
const isBugLike = nextLabels.has(BUG_LABEL);
|
||||
if (!isBugLike) {
|
||||
core.info("Issue is not bug-like after classification. Skipping version triage.");
|
||||
if (v6FeedbackClass && !labelNames.has(v6FeedbackClass)) {
|
||||
await github.rest.issues.setLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: [...nextLabels].sort(),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (reportedVersion) {
|
||||
await ensureLabel(
|
||||
`${VERSION_LABEL_PREFIX}${reportedVersion}`,
|
||||
"0e8a16",
|
||||
`Bug reported against Pulse ${reportedVersion}`
|
||||
);
|
||||
await ensureLabel(
|
||||
RETEST_LABEL,
|
||||
"d93f0b",
|
||||
"Reporter should retest on current latest stable release"
|
||||
);
|
||||
|
||||
// Keep only one exact affects-X.Y.Z label for clarity.
|
||||
for (const label of [...nextLabels]) {
|
||||
if (/^affects-\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(label) && label !== `${VERSION_LABEL_PREFIX}${reportedVersion}`) {
|
||||
nextLabels.delete(label);
|
||||
}
|
||||
}
|
||||
|
||||
nextLabels.add(`${VERSION_LABEL_PREFIX}${reportedVersion}`);
|
||||
nextLabels.delete(NEEDS_VERSION_LABEL);
|
||||
|
||||
const comparison = latestVersion ? compareCore(reportedVersion, latestVersion) : null;
|
||||
if (comparison !== null && comparison < 0) {
|
||||
nextLabels.add(RETEST_LABEL);
|
||||
|
||||
if (allowRetestComment && !(await hasRetestComment(issue.number))) {
|
||||
const body = [
|
||||
RETEST_COMMENT_MARKER,
|
||||
"Thanks for the report.",
|
||||
"",
|
||||
`I can see this was reported on **v${reportedVersion}**, while the latest stable release is **v${latestVersion}**.`,
|
||||
`Please retest on **v${latestVersion}** and comment with:`,
|
||||
"",
|
||||
"- whether the issue still reproduces",
|
||||
"- updated logs/diagnostics",
|
||||
"- exact running image tag or digest",
|
||||
"",
|
||||
"If there is no reporter follow-up after 7 days, this issue may be auto-closed until new confirmation is provided.",
|
||||
"",
|
||||
"If it still reproduces on the latest version, I will keep this open as an active regression.",
|
||||
].join("\n");
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
nextLabels.delete(RETEST_LABEL);
|
||||
}
|
||||
} else {
|
||||
await ensureLabel(
|
||||
NEEDS_VERSION_LABEL,
|
||||
"fbca04",
|
||||
"Issue is missing required Pulse version metadata"
|
||||
);
|
||||
nextLabels.add(NEEDS_VERSION_LABEL);
|
||||
nextLabels.delete(RETEST_LABEL);
|
||||
}
|
||||
|
||||
await github.rest.issues.setLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: [...nextLabels].sort(),
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue