Dashboard › publish › Session 0ScoBEWOQxXp
0ScoBEWOQxXpF1oXsREAD ONLY. In /home/byk/Code/getsentry/publish, compare HEAD/current worktree/origin/main for JavaScript, parser, docs/package changes from origin/main commit 7c60ddb. Focus details-from-context, update-issue, inputs, all newly added parser/location/revision/ci-input files and tests, package/yarn lock. Explain exact origin/main behavior to integrate, conflicts with current dirty security worktree, file dependencies, and safest integration sequence. Do not edit files. Return concise file-specific guidance and risks.
No files found
## main...origin/main [behind 1] M .github/workflows/auto-approve.yml M .github/workflows/ci-poller.yml M .github/workflows/cocoapods-keepalive.yml M .github/workflows/publish.yml M .github/workflows/test.yml M AGENTS.md M README.md M docs/key_rotation_vault_changes.md M docs/rfc.md M src/libs/__tests__/github.js M src/libs/github.js M src/modules/__tests__/details-from-context.js M src/modules/__tests__/update-issue.js M src/modules/details-from-context.js M src/modules/post-workflow-details.js M src/modules/process-end-state.js M src/modules/update-issue.js M src/publish/inputs.js M src/publish/post-result.js M src/publish/post-workflow-details.js M src/publish/update-issue.js ?? .github/workflows/ci-poller-dispatch.yml ?? .lore.md ?? src/modules/__tests__/approval-attestation.js ?? src/modules/__tests__/approval-authorizer.js ?? src/modules/approval-attestation.js ?? src/modules/approval-authorizer.js ?? src/publish/__tests__/ ?? ...
* 4f4b00f (origin/fix/root-location-bootstrap, fix/root-location-bootstrap) fix: bootstrap root publish locations * 7c60ddb (origin/main, origin/HEAD) feat: accept workspace publish requests | * fd1e115 (origin/feat/workspace-acceptance, feat/workspace-acceptance) fix: address publish workspace review feedback | * a81ab03 feat: resolve workspace publish paths | * 52ef600 feat: resolve compact workspace publish requests | * b658ffa fix: validate publish workspace JSON | * 33de3f1 feat: accept workspace publish requests |/ * 83d210b (HEAD -> main, opencode/tidy-orchid) build(deps): bump js-yaml from 4.3.0 to 4.3.1 (#9174) * 5268a23 feat: Add Vercel creds * 92eba3f feat: Add CLOUDFLARE_API_TOKEN (#9122) * ada6147 build(deps): bump js-yaml from 4.1.1 to 4.3.0 (#8731) * a4f961a fix: Add GITHUB_API_TOKEN for commit-on-git target (#9069) * bf10f82 build(deps): bump postcss from 8.5.15 to 8.5.23 (#9017) * 837edc0 docs: fix possessive apostrophe placement (#9015) * 4da4a25 chore: replace ...
M .github/workflows/auto-approve.yml M .github/workflows/ci-poller.yml M .github/workflows/cocoapods-keepalive.yml M .github/workflows/publish.yml M .github/workflows/test.yml M AGENTS.md M README.md M docs/key_rotation_vault_changes.md M docs/rfc.md M src/libs/__tests__/github.js M src/libs/github.js M src/modules/__tests__/details-from-context.js M src/modules/__tests__/update-issue.js M src/modules/details-from-context.js M src/modules/post-workflow-details.js M src/modules/process-end-state.js M src/modules/update-issue.js M src/publish/inputs.js M src/publish/post-result.js M src/publish/post-workflow-details.js M src/publish/update-issue.js .github/workflows/auto-approve.yml | 26 +++- .github/workflows/ci-poller.yml | 195 +++++++++++++++++++++++--- .github/workflows/cocoapods-keepalive.yml | 3 +- .github/workflows/publish.yml | 139 ++++++++++++++++-- .github/workflows/test.yml | 6 +- AGENTS.md ...
M .github/workflows/ci-poller.yml M .github/workflows/cocoapods-keepalive.yml M .github/workflows/publish.yml M README.md A docs/publish-issue-format.md M package.json A scripts/generate-publish-issue-title-parser.js A src/modules/__tests__/ci-poller-input.js A src/modules/__tests__/ci-poller-workflow.js M src/modules/__tests__/details-from-context.js A src/modules/__tests__/generate-publish-issue-title-parser.js A src/modules/__tests__/publish-location.js A src/modules/__tests__/publish-workflow.js A src/modules/__tests__/release-revision.js M src/modules/__tests__/update-issue.js A src/modules/ci-poller-input.js M src/modules/details-from-context.js A src/modules/publish-issue-title.js A src/modules/publish-issue-title.peggy A src/modules/publish-issue-validation.js A src/modules/publish-location.js A src/modules/release-revision.js M src/modules/update-issue.js A src/publish/__tests__/discover-location.js A src/publish/__tests__/resolve-location.js A src/publish/__tests__/resolve-re...
/home/byk/Code/getsentry/publish/src/publish/record-auto-approval-attestation.js /home/byk/Code/getsentry/publish/src/publish/current-accepted-event.js /home/byk/Code/getsentry/publish/src/publish/record-ci-ready-attestation.js /home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js /home/byk/Code/getsentry/publish/src/publish/__tests__/workflow-action-pinning.js /home/byk/Code/getsentry/publish/src/publish/__tests__/record-auto-approval-attestation.js /home/byk/Code/getsentry/publish/src/publish/__tests__/publish-workflow.js /home/byk/Code/getsentry/publish/src/publish/__tests__/auto-approval-workflow.js /home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js /home/byk/Code/getsentry/publish/src/publish/__tests__/current-accepted-event.js /home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js /home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js /home/byk/Code/getsentry/publish/...
diff --git a/src/modules/details-from-context.js b/src/modules/details-from-context.js
index e58b0be..9288b5f 100644
--- a/src/modules/details-from-context.js
+++ b/src/modules/details-from-context.js
@@ -1,64 +1,83 @@
+const { parse: parsePublishIssueTitle } = require("./publish-issue-title");
+const { getReleaseRevisionDetails } = require("./release-revision");
+const { isPublishPath } = require("./publish-location");
+const {
+ isPublishRepository,
+ isReleaseVersion,
+} = require("./publish-issue-validation");
+
/**
* Matches the entire "Targets" section of a github publish issue body.
*/
const TARGETS_SECTION_PARSER_REGEX =
/^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m;
/**
* Matches all targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
* The "id" of the targets is captured within a capture group. ...
diff --git a/src/modules/details-from-context.js b/src/modules/details-from-context.js index e58b0be..bd2c123 100644 --- a/src/modules/details-from-context.js +++ b/src/modules/details-from-context.js @@ -1,64 +1,75 @@ /** * Matches the entire "Targets" section of a github publish issue body. */ const TARGETS_SECTION_PARSER_REGEX = /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m; /** * Matches all targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`. * The "id" of the targets is captured within a capture group. */ const TARGETS_PARSER_REGEX = /^\s*- \[[ x]\] (\S+)/gim; /** * Matches checked targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`. * The "id" of the targets is captured within a capture group. */ const CHECKED_TARGETS_PARSER_REGEX = /^\s*- \[x\] (\S+)/gim; +const PUBLISH_TITLE_REGEX = + /^publish: ...
Found 36 matches
/home/byk/Code/getsentry/publish/src/publish/inputs.js:
Line 3: const { detailsFromContext } = require("../modules/details-from-context");
Line 6: const result = await detailsFromContext({
/home/byk/Code/getsentry/publish/src/modules/approval-authorizer.js:
Line 2: const { parsePublishTitle } = require("./details-from-context.js");
Line 16: const title = parsePublishTitle(issueTitle);
Line 28: const title = parsePublishTitle(issueTitle);
/home/byk/Code/getsentry/publish/src/modules/details-from-context.js:
Line 18: const PUBLISH_TITLE_REGEX =
Line 21: function parsePublishTitle(title) {
Line 22: return title.match(PUBLISH_TITLE_REGEX)?.groups || null;
Line 25: async function detailsFromContext({ context }) {
Line 30: const titleMatch = parsePublishTitle(context.payload.issue.title);
Line 69: detailsFromContext,
Line 73: parsePublishTitle,
Line 74: ...
<path>/home/byk/Code/getsentry/publish/src/modules/approval-authorizer.js</path>
<type>file</type>
<content>
1: const ALLOWED_ROLE_NAMES = new Set(["write", "maintain", "admin"]);
2: const { parsePublishTitle } = require("./details-from-context.js");
3:
4: const AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]";
5: const AUTO_APPROVERS = new Set(["getsantry[bot]", "sentry-release-bot[bot]"]);
6:
7: function isAutoApprovedRepository({
8: actor,
9: autoApprovedRepositories,
10: issueTitle,
11: }) {
12: if (!AUTO_APPROVERS.has(actor)) {
13: return false;
14: }
15:
16: const title = parsePublishTitle(issueTitle);
17:
18: if (!title) {
19: return false;
20: }
21:
22: return autoApprovedRepositories.has(
23: `getsentry/${title.repo}${title.path || ""}`
24: );
25: }
26:
27: async function authorizeApproval({ actor, issueTitle, getPermission }) {
28: const title = parsePublishTitle(issueTitle);
29:
30: if (!title) {
31: return { authorized: ...
diff --git a/src/modules/ci-poller-input.js b/src/modules/ci-poller-input.js
new file mode 100644
index 0000000..ca08a72
--- /dev/null
+++ b/src/modules/ci-poller-input.js
@@ -0,0 +1,42 @@
+const { parse: parsePublishIssueTitle } = require("./publish-issue-title");
+const {
+ getReleaseRevision,
+ updateReleaseRevision,
+} = require("./release-revision");
+const {
+ isPublishRepository,
+ isReleaseVersion,
+} = require("./publish-issue-validation");
+const { isPublishPath } = require("./publish-location");
+
+function getCiPollerInput({ title, issueBody, revision }) {
+ let parsedTitle;
+ try {
+ parsedTitle = parsePublishIssueTitle(title);
+ } catch {
+ throw new Error(`Invalid publish issue title: '${title}'`);
+ }
+ const { repo, version } = parsedTitle;
+ if (!isPublishRepository(repo)) {
+ throw new Error(`Invalid publish issue repository: '${repo}'`);
+ }
+ if (!isReleaseVersion(version)) {
+ throw new Error(`Invalid publish issue version: ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0890607de001jDhs4SEuD6NBvd
"@opentelemetry/instrumentation" "^0.208.0"
"@opentelemetry/semantic-conventions" "^1.36.0"
"@opentelemetry/instrumentation-lru-memoizer@0.53.0":
version "0.53.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.53.0.tgz#936c05263b719ee66999a9240b82fded044ebd2c"
integrity sha512-LDwWz5cPkWWr0HBIuZUjslyvijljTwmwiItpMTHujaULZCxcYE9eU44Qf/pbVC8TulT0IhZi+RoGvHKXvNhysw==
dependencies:
"@opentelemetry/instrumentation" "^0.208.0"
"@opentelemetry/instrumentation-mongodb@0.61.0":
version "0.61.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.61.0.tgz#4db130d537d630c3089115d2d214d29bcfb49f41"
integrity sha512-OV3i2DSoY5M/pmLk+68xr5RvkHU8DRB3DKMzYJdwDdcxeLs62tLbkmRyqJZsYf3Ht7j11rq35pHOWLuLzXL7pQ==
...
diff --git a/package.json b/package.json
index d59a8b6..0790379 100644
--- a/package.json
+++ b/package.json
@@ -1,35 +1,38 @@
{
"name": "publish",
"private": true,
"version": "0.0.1",
"description": "Approval-based publishing system for Sentry",
"main": "index.js",
"repository": "git@github.com:getsentry/publish.git",
"author": "Sentry Open Source <oss@sentry.io>",
"license": "Apache-2.0",
"volta": {
"node": "24.0.0",
"yarn": "1.22.22"
},
"resolutions": {
"undici": "^6.23.0"
},
"scripts": {
- "test": "vitest run",
+ "generate": "node scripts/generate-publish-issue-title-parser.js",
+ "check:generated": "node scripts/generate-publish-issue-title-parser.js --check",
+ "test": "yarn check:generated && vitest run",
"test:watch": "vitest",
"lint": "eslint src .github --ignore-pattern '!.github'",
"prettier": "prettier --write src"
},
"devDependencies": {
"eslint": "^8.9.0",
...
diff --git a/yarn.lock b/yarn.lock index 571d807..1fcddf4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -535,6 +535,13 @@ resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.133.0.tgz#2e282ef9e1d26e06b68ccd14b73f310a3b2cf7f8" integrity sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA== +"@peggyjs/from-mem@3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@peggyjs/from-mem/-/from-mem-3.1.3.tgz#2956c3c68ec2e08e55178049a327009ee61541b8" + integrity sha512-LLlgtfXIaeYXoOYovOI0spLM8ZXaqkAlmcRRrLzHJzLMqkU6Sw0R4KMoCoHx1PjaP815pSCBlS+BN6aD8t1Jgg== + dependencies: + semver "7.7.4" + "@prisma/instrumentation@6.19.0": version "6.19.0" resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.19.0.tgz#46d15adc8bc4a5a3167032eea6d0a7aa64fb7d93" @@ -944,6 +951,11 @@ color-name@~1.1.4: resolved ...
diff --git a/src/publish/discover-location.js b/src/publish/discover-location.js
new file mode 100644
index 0000000..5885443
--- /dev/null
+++ b/src/publish/discover-location.js
@@ -0,0 +1,78 @@
+const { execFileSync } = require("child_process");
+const { existsSync } = require("fs");
+const path = require("path");
+
+const core = require("@actions/core");
+
+const { resolvePublishLocation } = require("../modules/publish-location");
+
+function getWorkspaceNames({
+ repositoryDirectory,
+ exists = existsSync,
+ execFile = execFileSync,
+}) {
+ if (!exists(path.join(repositoryDirectory, ".craft.yml"))) {
+ return [];
+ }
+
+ const output = execFile(
+ "docker",
+ [
+ "run",
+ "--rm",
+ "--volume",
+ `${path.resolve(repositoryDirectory)}:/github/workspace/__repo__`,
+ "--workdir",
+ "/github/workspace/__repo__",
+ "getsentry/craft:latest",
+ "workspace",
+ "list",
+ ],
+ { encoding: "utf8" }
+ );
+ let workspaceNames;
+ ...
diff --git a/src/modules/__tests__/ci-poller-input.js b/src/modules/__tests__/ci-poller-input.js
new file mode 100644
index 0000000..140ccef
--- /dev/null
+++ b/src/modules/__tests__/ci-poller-input.js
@@ -0,0 +1,97 @@
+import { readFileSync } from "fs";
+import { join } from "path";
+
+import { expect, test } from "vitest";
+
+const { getCiPollerInput } = require("../ci-poller-input.js");
+
+const REVISION = "7e5ca7ed5581552de066e2a8bc295b8306be38ac";
+const issueBody = `Requested by: @byk
+
+Merge target: (default)
+
+Quick links:
+- [View changes](https://github.com/getsentry/toolkit/compare/1.2.2...release/1.2.3)
+- [View check runs](https://github.com/getsentry/toolkit/commit/${REVISION}/checks/)`;
+
+test("parses root and full-path publish titles", () => {
+ expect(
+ getCiPollerInput({
+ issueBody,
+ title: "publish: getsentry/toolkit/cli@1.2.3",
+ })
+ ).toEqual({
+ repo: "getsentry/toolkit",
+ revision: REVISION,
+ version: "1.2.3",
+ });
+
+ ...
diff --git a/src/publish/__tests__/discover-location.js b/src/publish/__tests__/discover-location.js
new file mode 100644
index 0000000..7b522a6
--- /dev/null
+++ b/src/publish/__tests__/discover-location.js
@@ -0,0 +1,59 @@
+import { afterEach, expect, test, vi } from "vitest";
+
+const { discoverLocation, getWorkspaceNames } = require("../discover-location.js");
+
+afterEach(() => {
+ vi.restoreAllMocks();
+});
+
+test("retains checkout-path behavior when the root config is absent", () => {
+ expect(
+ discoverLocation({
+ input: { path: "./packages/cli" },
+ repositoryDirectory: "__repo__",
+ exists: () => false,
+ })
+ ).toEqual({ path: "./packages/cli" });
+});
+
+test("discovers exact workspace paths using the released Craft image", () => {
+ const execFile = vi.fn(() => '["packages/CLI"]');
+
+ expect(
+ discoverLocation({
+ input: { path: "./packages/CLI" },
+ repositoryDirectory: "__repo__",
+ exists: () => true,
+ execFile,
+ ...
diff --git a/src/modules/__tests__/details-from-context.js b/src/modules/__tests__/details-from-context.js
index e6411c5..aa1d199 100644
--- a/src/modules/__tests__/details-from-context.js
+++ b/src/modules/__tests__/details-from-context.js
@@ -1,107 +1,259 @@
import { test, expect } from "vitest";
const { detailsFromContext } = require("../details-from-context.js");
const inputsArgs = {
context: {
repo: { owner: "getsentry", repo: "publish" },
payload: {
issue: {
number: "223",
title: "publish: getsentry/sentry@21.3.1",
- body: `
-Requested by: @BYK
+ body: `Requested by: @BYK
Merge target: custom-branch
Quick links:
- [View changes](https://github.com/getsentry/sentry/compare/21.3.0...refs/heads/releases/21.3.1)
- [View check runs](https://github.com/getsentry/sentry/commit/7e5ca7ed5581552de066e2a8bc295b8306be38ac/checks/)
Assign the **accepted** label to this issue to approve the release. ...
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/details-from-context.js</path>
<type>file</type>
<content>
1: import { test, expect } from "vitest";
2:
3: const {
4: detailsFromContext,
5: parsePublishTitle,
6: } = require("../details-from-context.js");
7:
8: const inputsArgs = {
9: context: {
10: repo: { owner: "getsentry", repo: "publish" },
11: payload: {
12: issue: {
13: number: "223",
14: title: "publish: getsentry/sentry@21.3.1",
15: body: `
16: Requested by: @BYK
17:
18: Merge target: custom-branch
19:
20: Quick links:
21: - [View changes](https://github.com/getsentry/sentry/compare/21.3.0...refs/heads/releases/21.3.1)
22: - [View check runs](https://github.com/getsentry/sentry/commit/7e5ca7ed5581552de066e2a8bc295b8306be38ac/checks/)
23:
24: Assign the **accepted** label to this issue to approve the release. ...
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/update-issue.js</path>
<type>file</type>
<content>
1: import { vi, describe, test, expect, beforeAll, beforeEach, it } from "vitest";
2: import fs from "fs";
3:
4: const { updateIssue, transformIssueBody } = require("../update-issue.js");
5:
6: let mockExistsSync;
7:
8: const updateTargetsArgs = {
9: inputs: { repo: "sentry", version: "21.3.1" },
10: context: {
11: runId: "1234",
12: repo: { owner: "getsentry", repo: "publish" },
13: payload: { issue: { number: "211" } },
14: },
15: octokit: {
16: rest: {
17: actions: {
18: getWorkflowRun: async () => ({
19: data: {
20: html_url: "https://github.com/getsentry/sentry/actions/runs/1234",
21: },
22: }),
23: },
24: issues: {
25: get: vi.fn(),
26: update: vi.fn(),
27: removeLabel: vi.fn(),
28: },
29: },
30: },
31: Sentry: {
32: Scope: class Scope ...
diff --git a/.github/workflows/ci-poller.yml b/.github/workflows/ci-poller.yml
index a3db430..487e9da 100644
--- a/.github/workflows/ci-poller.yml
+++ b/.github/workflows/ci-poller.yml
@@ -11,275 +11,285 @@ on:
default: "0"
permissions:
contents: read
issues: read
jobs:
check-ci:
runs-on: ubuntu-latest
environment: production
# Skip entirely (no runner provisioned) when there's nothing to check.
# Set to "true" by ci-pending.yml, reset to "false" here when done.
# Always allow workflow_dispatch for manual recovery.
if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch'
concurrency:
group: ci-status-poller
cancel-in-progress: false
steps:
# sentry-internal-app token for label changes on this repo.
# This token is what triggers publish.yml downstream — see
# https://github.com/getsentry/publish for why GITHUB_TOKEN can't. ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path> <type>file</type> <content> 1: name: CI Status Poller 2: 3: on: 4: schedule: 5: - cron: "*/5 * * * *" 6: repository_dispatch: 7: types: [ci-poller] 8: workflow_run: 9: workflows: [Run CI Status Poller] 10: types: [completed] 11: 12: permissions: 13: contents: read 14: issues: write 15: 16: jobs: 17: check-ci: 18: runs-on: ubuntu-latest 19: environment: production 20: # Skip entirely (no runner provisioned) when there's nothing to check. 21: # Set to "true" by ci-pending.yml, reset to "false" here when done. 22: # repository_dispatch and a successful main-branch run of the protected 23: # workflow_dispatch relay are the trusted manual-recovery paths. 24: if: >- 25: vars.CI_POLLER_HAS_PENDING == 'true' 26: || github.event_name == 'repository_dispatch' 27: || ( 28: github.event_name == 'workflow_run' 29: && ...
<path>/home/byk/Code/getsentry/publish/src/modules/approval-attestation.js</path>
<type>file</type>
<content>
1: const crypto = require("crypto");
2:
3: const APPROVAL_ATTESTATION_PREFIX = "<!-- publish-approval ";
4: const AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7:
8: function createAttestation(prefix, value) {
9: return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10: "base64url"
11: )}${ATTESTATION_SUFFIX}`;
12: }
13:
14: function requestDigest({ body, labels, title }) {
15: if (typeof body !== "string" || !Array.isArray(labels)) {
16: throw new Error("Invalid publish request");
17: }
18:
19: return crypto
20: .createHash("sha256")
21: .update(
22: JSON.stringify({
23: body,
24: dryRun: labels.some((label) => label.name === "dry-run"),
25: title,
26: })
27: )
28: ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
1: name: Publish
2: on:
3: issues:
4: types: [labeled]
5:
6: concurrency:
7: # Use the issue title (e.g. "publish: getsentry/foo@1.2.3") so duplicate
8: # issues for the same repo@version share a concurrency group.
9: group: ${{ github.event.issue.title }}
10: cancel-in-progress: false
11:
12: permissions:
13: contents: read
14: issues: write
15: packages: write
16:
17: jobs:
18: # When accepted is added to a publish issue:
19: # - Add ci-pending (and remove ci-failed if retrying)
20: # - Enable the poller via CI_POLLER_HAS_PENDING=true
21: # - Comment on the issue
22: # - Trigger the poller immediately so we don't wait for the next cron tick
23: # The publish job below requires ci-ready, so it will not fire until the
24: # poller flips ci-pending → ci-ready (which also prevents publishing without
25: # CI verification in the ...
diff --git a/README.md b/README.md
index b8e07f0..4cbc9b9 100644
--- a/README.md
+++ b/README.md
@@ -1,99 +1,104 @@
# Sentry Publish 🏠
This is a meta/control repository that implements the [Central Publish Repository](docs/rfc.md) RFC
## Quick Start
[craft quick start](https://craft.sentry.dev/github-actions/)
## Release Flow
```mermaid
flowchart TD
A[Developer triggers release workflow] --> B["SDK Repo: craft prepare"]
B --> C[Build artifacts & create release branch]
C --> D[Upload artifacts to GitHub]
D --> E["Create issue in getsentry/publish"]
E --> F{Release Manager Review}
F -->|"Add 'accepted' label"| G[Publish workflow triggers]
G --> I[Download artifacts from GitHub]
I --> J["craft publish to registries"]
J --> K{Publish successful?}
K -->|Yes| L[Issue closed - success]
K -->|No| M[Issue updated with failure]
```
## Goals
1. We do not want employees to publish through their own accounts
1. ...
diff --git a/AGENTS.md b/AGENTS.md
index cf473df..51bf3a8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -236,40 +236,46 @@ expect(result).toMatchInlineSnapshot(`"expected output"`);
await Promise.all([operation1(), operation2()]);
```
### Destructuring
```javascript
const { repo, owner, issueNumber } = context;
```
### Spread Operator for Object Merging
```javascript
const fullContext = { ...publishRepo, additionalProp: value };
```
### Template Literals
```javascript
const message = `Release ${version} published successfully`;
```
### Regular Expressions with Named Groups
```javascript
const PARSER_REGEX = /(?<name>\w+): (?<value>.+)/;
const match = text.match(PARSER_REGEX);
const { name, value } = match.groups;
```
## GitHub Actions Context
This codebase interacts heavily with GitHub Actions. ...
diff --git a/src/modules/__tests__/ci-poller-workflow.js b/src/modules/__tests__/ci-poller-workflow.js
new file mode 100644
index 0000000..c859f5a
--- /dev/null
+++ b/src/modules/__tests__/ci-poller-workflow.js
@@ -0,0 +1,197 @@
+import {
+ mkdtempSync,
+ mkdirSync,
+ readFileSync,
+ readdirSync,
+ rmSync,
+ writeFileSync,
+} from "fs";
+import { tmpdir } from "os";
+import { join } from "path";
+import { spawnSync } from "child_process";
+
+import { afterEach, expect, test } from "vitest";
+
+const temporaryDirectories = [];
+const INITIAL_REVISION = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const UPDATED_REVISION = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+
+function getPollerScript() {
+ const workflow = readFileSync(
+ join(__dirname, "../../../.github/workflows/ci-poller.yml"),
+ "utf8"
+ );
+ const section = workflow.match(
+ / {6}- name: Check CI status for ci-pending issues[\s\S]*? {8}run: \|\n(?<script>[\s\S]*?)\n {6}- name: Check for remaining pending ...
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/workflow-action-pinning.js</path>
<type>file</type>
<content>
1: import { readFileSync, readdirSync } from "fs";
2: import { describe, expect, test } from "vitest";
3:
4: const workflowPaths = readdirSync(".github/workflows")
5: .filter((path) => path.endsWith(".yml"))
6: .map((path) => `.github/workflows/${path}`);
7: const workflows = workflowPaths.map((path) => ({
8: path,
9: contents: readFileSync(path, "utf8"),
10: }));
11: const SECRET_BEARING_JOBS = {
12: ".github/workflows/auto-approve.yml": ["auto-approve"],
13: ".github/workflows/ci-poller.yml": ["check-ci"],
14: ".github/workflows/cocoapods-keepalive.yml": ["keepalive"],
15: ".github/workflows/publish.yml": ["waiting-for-ci", "publish"],
16: };
17:
18: function jobContents(workflow, jobName) {
19: const header = ` ${jobName}:`;
20: const jobStart = workflow.indexOf(header);
21: const nextJobOffset = workflow
22: .slice(jobStart + ...
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/publish-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3:
4: const workflow = readFileSync(".github/workflows/publish.yml", "utf8");
5:
6: describe("publish workflow", () => {
7: test("revalidates approval immediately before Craft", () => {
8: const fence = workflow.indexOf("Revalidate approval attestation");
9:
10: expect(fence).toBeGreaterThan(workflow.indexOf("Set targets"));
11: expect(fence).toBeLessThan(workflow.indexOf("Publish using Craft"));
12: expect(workflow.slice(fence)).toContain(
13: 'REQUIRE_CI_READY_ATTESTATION: "true"'
14: );
15: });
16:
17: test("pins Craft to an immutable digest", () => {
18: expect(workflow).toMatch(
19: /uses: docker:\/\/getsentry\/craft@sha256:[a-f0-9]{64}/
20: );
21: });
22: });
(End of file - total 22 lines)
</content>
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3:
4: const workflow = readFileSync(".github/workflows/ci-poller.yml", "utf8");
5: const manualDispatchWorkflow = readFileSync(
6: ".github/workflows/ci-poller-dispatch.yml",
7: "utf8"
8: );
9:
10: describe("CI poller workflow", () => {
11: test("checks out trusted code before requesting app tokens", () => {
12: expect(workflow).toMatch(
13: /- name: Get publish code\n\s+uses: actions\/checkout@[a-f0-9]{40}\n\s+with:\n(?:\s+#.*\n)*\s+ref: \$\{\{ github\.event\.repository\.default_branch \}\}/
14: );
15:
16: expect(workflow.indexOf("Get publish code")).toBeLessThan(
17: workflow.indexOf("Get auth token")
18: );
19: });
20:
21: test("uses a protected workflow dispatch relay for manual recovery", () => {
22: ...
Found 16 matches
/home/byk/Code/getsentry/publish/src/publish/__tests__/publish-workflow.js:
Line 10: expect(fence).toBeGreaterThan(workflow.indexOf("Set targets"));
/home/byk/Code/getsentry/publish/src/publish/inputs.js:
Line 3: const { detailsFromContext } = require("../modules/details-from-context");
/home/byk/Code/getsentry/publish/src/modules/approval-authorizer.js:
Line 2: const { parsePublishTitle } = require("./details-from-context.js");
Line 16: const title = parsePublishTitle(issueTitle);
Line 28: const title = parsePublishTitle(issueTitle);
/home/byk/Code/getsentry/publish/src/modules/update-issue.js:
Line 5: } = require("./details-from-context");
Line 8: const CRAFT_STATE_FILE_PATH = `${process.env.GITHUB_WORKSPACE}/__repo__/.craft-publish-${version}.json`;
Line 10: if (!fs.existsSync(CRAFT_STATE_FILE_PATH)) {
Line 20: .readFile(CRAFT_STATE_FILE_PATH, { encoding: "utf-8" ...
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-authorizer.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2:
3: const { authorizeApproval } = require("../approval-authorizer.js");
4:
5: const issueTitle = "publish: getsentry/sentry-javascript@10.0.0";
6:
7: describe("authorizeApproval", () => {
8: test.each(["getsantry[bot]", "sentry-release-bot[bot]"])(
9: "rejects direct approval by %s without a target repository lookup",
10: async (actor) => {
11: const getPermission = vi.fn();
12:
13: await expect(
14: authorizeApproval({
15: actor,
16: issueTitle,
17: getPermission,
18: autoApprovedRepositories: new Set(["getsentry/sentry-javascript"]),
19: })
20: ).resolves.toEqual({
21: authorized: false,
22: repository: "getsentry/sentry-javascript",
23: });
24:
25: ...
origin/main:.github/workflows/ci-poller.yml:106: node .__publish__/src/publish/resolve-ci-poller-input.js); then
origin/main:.github/workflows/ci-poller.yml:153: node .__publish__/src/publish/resolve-ci-poller-input.js > "$resolver_output_file"; then
origin/main:.github/workflows/publish.yml:139: id: release-revision
origin/main:.github/workflows/publish.yml:143: run: node .__publish__/src/publish/resolve-release-revision.js
origin/main:.github/workflows/publish.yml:146: if: steps.inputs.outcome == 'success' && steps.release-revision.outcome == 'success'
origin/main:.github/workflows/publish.yml:164: ref: ${{ steps.release-revision.outputs.revision }}
origin/main:.github/workflows/publish.yml:174: run: node .__publish__/src/publish/discover-location.js
origin/main:.github/workflows/publish.yml:240: exec craft publish ${{ fromJSON(steps.inputs.outputs.result).version }} --rev ${{ ...
<path>/home/byk/Code/getsentry/publish/src/publish/update-issue.js</path>
<type>file</type>
<content>
1: const { updateIssue } = require("../modules/update-issue.js");
2: const { getGitHubToken } = require("../libs/github");
3: const github = require("@actions/github");
4:
5: async function main() {
6: const context = github.context;
7: const octokit = github.getOctokit(getGitHubToken());
8: let inputs;
9:
10: if (process.env.PUBLISH_ARGS) {
11: try {
12: inputs = JSON.parse(process.env.PUBLISH_ARGS);
13: } catch {
14: console.warn("Could not parse publish inputs; skipping target update");
15: }
16: }
17:
18: await updateIssue({ context, octokit, inputs });
19: }
20:
21: if (require.main === module) {
22: main().catch((error) => {
23: console.error(error);
24: process.exitCode = 1;
25: });
26: }
27:
28: module.exports = { main };
(End of file - total 28 lines)
</content>
const { updateIssue } = require('../modules/update-issue.js');
const {getGitHubToken} = require('../libs/github');
const github = require('@actions/github');
const context = github.context;
const octokit = github.getOctokit(getGitHubToken());
const inputs = JSON.parse(process.env.PUBLISH_ARGS);
updateIssue({ context, octokit, inputs });
const { updateIssue } = require('../modules/update-issue.js');
const {getGitHubToken} = require('../libs/github');
const github = require('@actions/github');
const context = github.context;
const octokit = github.getOctokit(getGitHubToken());
const inputs = JSON.parse(process.env.PUBLISH_ARGS);
updateIssue({ context, octokit, inputs });
origin/main:src/modules/publish-issue-title.js:198: PublishIssueTitle: peg$parsePublishIssueTitle,
origin/main:src/modules/publish-issue-title.js:199: ReleaseRevision: peg$parseReleaseRevision,
origin/main:src/modules/publish-issue-title.js:200: CheckRunsLinkCount: peg$parseCheckRunsLinkCount,
origin/main:src/modules/publish-issue-title.js:202: let peg$startRuleFunction = peg$parsePublishIssueTitle;
origin/main:src/modules/publish-issue-title.js:503: function peg$parsePublishIssueTitle() {
origin/main:src/modules/publish-issue-title.js:760: function peg$parseReleaseRevision() {
origin/main:src/modules/publish-issue-title.js:1105: function peg$parseCheckRunsLinkCount() {
origin/main:src/modules/publish-issue-title.js:1469:module.exports = {
origin/main:src/modules/publish-issue-title.js:1470: StartRules: ["PublishIssueTitle", "ReleaseRevision", "CheckRunsLinkCount"],
diff --git a/- b/-
new file mode 100644
--- /dev/null
+++ b/-
@@ -0,0 +1,1473 @@
+/* eslint-disable */
+// @generated by Peggy 5.1.0.
+//
+// https://peggyjs.org/
+
+"use strict";
+
+class peg$SyntaxError extends SyntaxError {
+ constructor(message, expected, found, location) {
+ super(message);
+ this.expected = expected;
+ this.found = found;
+ this.location = location;
+ this.name = "SyntaxError";
+ }
+
+ format(sources) {
+ let str = "Error: " + this.message;
+ if (this.location) {
+ let src = null;
+ const st = sources.find((s) => s.source === this.location.source);
+ if (st) {
+ src = st.text.split(/\r\n|\n|\r/g);
+ }
+ const s = this.location.start;
+ const offset_s =
+ this.location.source &&
+ typeof this.location.source.offset === "function"
+ ? this.location.source.offset(s)
+ : s;
+ const loc =
+ this.location.source + ":" + offset_s.line + ":" + offset_s.column;
+ ...
Checking patch .github/workflows/ci-poller.yml...
Hunk #1 succeeded at 74 (offset 26 lines).
error: while searching for:
# Check each issue's CI status
echo "$issues" | jq -c '.[]' | while read -r issue; do
number=$(echo "$issue" | jq -r '.number')
title=$(echo "$issue" | jq -r '.title')
body=$(echo "$issue" | jq -r '.body')
# Parse repo and version from title: "publish: owner/repo[/path]@version"
# Only take owner/repo (first two segments) — monorepos like
# "getsentry/relay/py@0.9.26" have a path suffix that isn't part
# of the GitHub repo name.
repo=$(echo "$title" | sed -n 's|^publish: \([^/]*/[^/@]*\).*@.*|\1|p')
version=$(echo "$title" | sed -n 's/^publish: .*@\(.*\)/\1/p')
if [[ -z "$repo" || -z "$version" ]]; then
echo "::warning::Could not parse repo/version from issue #${number}: ${title}"
continue
...
<path>/home/byk/Code/getsentry/publish/.github/workflows/cocoapods-keepalive.yml</path>
<type>file</type>
<content>
1: name: CocoaPods Token Keep-Alive
2:
3: on:
4: schedule:
5: # CocoaPods sessions currently expire after 3 days of inactivity (VALIDITY_LENGTH)
6: # This runs every day at midnight UTC to keep the current token alive
7: # See: https://github.com/CocoaPods/trunk.cocoapods.org/blob/a1869790e9ae2229b6985b6af4532da814202558/app/models/session.rb#L9
8: - cron: "0 0 * * *" # Runs every day at midnight UTC
9:
10: permissions:
11: contents: read
12:
13: jobs:
14: keepalive:
15: name: Refresh CocoaPods Session
16: runs-on: macos-15
17: environment: production
18: env:
19: COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
20:
21: steps:
22: - name: Install CocoaPods
23: run: gem install cocoapods
24: - name: Print CocoaPods Version
25: run: pod --version
26: - name: Refresh CocoaPods ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path> <type>file</type> <content> 431: echo "All ci-pending issues resolved. Disabling poller." 432: gh variable set CI_POLLER_HAS_PENDING -R "$GITHUB_REPOSITORY" -b "false" 433: else 434: echo "Still pending issues. Ensuring poller stays enabled." 435: gh variable set CI_POLLER_HAS_PENDING -R "$GITHUB_REPOSITORY" -b "true" 436: fi 437: 438: # Self-dispatch for fast re-checking when issues are still pending. 439: # GitHub's cron is unreliable (*/5 can drift to 30-40 min under load). 440: # Self-dispatch gives ~30-60s between checks via GHA startup latency. 441: # The concurrency group prevents accumulation (1 running + 1 queued). 442: # Cap at 60 attempts (~30 min). ...
Found 4 matches
/home/byk/Code/getsentry/publish/src/publish/post-result.js:
Line 7: const inputs = JSON.parse(process.env.PUBLISH_ARGS);
/home/byk/Code/getsentry/publish/src/publish/inputs.js:
Line 9: core.setOutput("result", result);
/home/byk/Code/getsentry/publish/src/publish/update-issue.js:
Line 10: if (process.env.PUBLISH_ARGS) {
Line 12: inputs = JSON.parse(process.env.PUBLISH_ARGS);
Your module analysis returned empty. READ ONLY and return NON-EMPTY integration guidance now. Compare current worktree, HEAD, and origin/main commit 7c60ddb for package.json, yarn.lock, src/modules/details-from-context.js, src/modules/update-issue.js, src/publish/inputs.js, every newly added parser/location/revision/ci-input file and its tests, scripts/generate-publish-issue-title-parser.js, and docs/publish-issue-format.md. Provide: (1) dependency/order map, (2) exact upstream behavior per file with line evidence, (3) conflicts with current security changes, (4) safest integration sequence, and (5) tests that must remain/add. If blocked, state exact tool/error. Never edit files and never return empty.
<path>/home/byk/Code/getsentry/publish/package.json</path>
<type>file</type>
<content>
1: {
2: "name": "publish",
3: "private": true,
4: "version": "0.0.1",
5: "description": "Approval-based publishing system for Sentry",
6: "main": "index.js",
7: "repository": "git@github.com:getsentry/publish.git",
8: "author": "Sentry Open Source <oss@sentry.io>",
9: "license": "Apache-2.0",
10: "volta": {
11: "node": "24.0.0",
12: "yarn": "1.22.22"
13: },
14: "resolutions": {
15: "undici": "^6.23.0"
16: },
17: "scripts": {
18: "test": "vitest run",
19: "test:watch": "vitest",
20: "lint": "eslint src .github --ignore-pattern '!.github'",
21: "prettier": "prettier --write src"
22: },
23: "devDependencies": {
24: "eslint": "^8.9.0",
25: "eslint-config-prettier": "^8.3.0",
26: "eslint-plugin-yml": "^0.13.0",
27: "prettier": "^2.2.1",
28: "vitest": "^4.1.0"
29: },
30: "dependencies": {
31: "@actions/core": ...
origin/main:src/modules/ci-poller-input.js:1:const { parse: parsePublishIssueTitle } = require("./publish-issue-title");
origin/main:src/modules/ci-poller-input.js:2:const {
origin/main:src/modules/ci-poller-input.js:3: getReleaseRevision,
origin/main:src/modules/ci-poller-input.js:4: updateReleaseRevision,
origin/main:src/modules/ci-poller-input.js:5:} = require("./release-revision");
origin/main:src/modules/ci-poller-input.js:6:const {
origin/main:src/modules/ci-poller-input.js:7: isPublishRepository,
origin/main:src/modules/ci-poller-input.js:8: isReleaseVersion,
origin/main:src/modules/ci-poller-input.js:9:} = require("./publish-issue-validation");
origin/main:src/modules/ci-poller-input.js:10:const { isPublishPath } = require("./publish-location");
origin/main:src/modules/ci-poller-input.js:11:
origin/main:src/modules/ci-poller-input.js:12:function getCiPollerInput({ title, issueBody, revision }) {
origin/main:src/modules/ci-poller-input.js:13: let ...
origin/main:docs/publish-issue-format.md:1:# Publish Issue Format origin/main:docs/publish-issue-format.md:2: origin/main:docs/publish-issue-format.md:3:The Publish workflow treats a publish issue as a release request. The title identifies origin/main:docs/publish-issue-format.md:4:the release; the body supplies the merge target and selected targets. Do not edit the origin/main:docs/publish-issue-format.md:5:title by hand unless it remains valid under this format. origin/main:docs/publish-issue-format.md:6: origin/main:docs/publish-issue-format.md:7:## Title origin/main:docs/publish-issue-format.md:8: origin/main:docs/publish-issue-format.md:9:Every title starts with `publish: `. The following canonical Peggy grammar is origin/main:docs/publish-issue-format.md:10:generated from `src/modules/publish-issue-title.peggy`: origin/main:docs/publish-issue-format.md:11: origin/main:docs/publish-issue-format.md:12:<!-- BEGIN GENERATED TITLE GRAMMAR ...
origin/main:src/publish/discover-location.js:1:const { execFileSync } = require("child_process");
origin/main:src/publish/discover-location.js:2:const { existsSync } = require("fs");
origin/main:src/publish/discover-location.js:3:const path = require("path");
origin/main:src/publish/discover-location.js:4:
origin/main:src/publish/discover-location.js:5:const core = require("@actions/core");
origin/main:src/publish/discover-location.js:6:
origin/main:src/publish/discover-location.js:7:const { resolvePublishLocation } = require("../modules/publish-location");
origin/main:src/publish/discover-location.js:8:
origin/main:src/publish/discover-location.js:9:function getWorkspaceNames({
origin/main:src/publish/discover-location.js:10: repositoryDirectory,
origin/main:src/publish/discover-location.js:11: exists = existsSync,
origin/main:src/publish/discover-location.js:12: execFile = execFileSync,
origin/main:src/publish/discover-location.js:13:}) ...
origin/main:src/modules/__tests__/ci-poller-input.js:17:test("parses root and full-path publish titles", () => {
origin/main:src/modules/__tests__/ci-poller-input.js:41:test("uses the shared resolver in the CI poller", () => {
origin/main:src/modules/__tests__/ci-poller-input.js:55:test.each(["-toolkit", ".", "..", "__proto__"])(
origin/main:src/modules/__tests__/ci-poller-input.js:67:test.each(["--config", "1.2"])(
origin/main:src/modules/__tests__/ci-poller-input.js:79:test.each(["publish: getsentry/@1.2.3", "publish: getsentry/toolkit$@1.2.3"])(
origin/main:src/modules/__tests__/ci-poller-input.js:88:test.each([
origin/main:src/modules/__tests__/ci-poller-workflow.js:152:test("skips an issue without editing it when the poller resolver fails", () => {
origin/main:src/modules/__tests__/ci-poller-workflow.js:160:test("skips an issue without editing it when the rewrite response has no body", () => {
origin/main:src/modules/__tests__/ci-poller-workflow.js:168:test("skips an issue ...
origin/main:src/modules/publish-issue-title.js:2:// @generated by Peggy 5.1.0.
origin/main:src/modules/publish-issue-title.js:197: const peg$startRuleFunctions = {
origin/main:src/modules/publish-issue-title.js:337: if (!(options.startRule in peg$startRuleFunctions)) {
origin/main:src/modules/publish-issue-title.js:343: peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
origin/main:src/modules/publish-issue-title.js:1469:module.exports = {
origin/main:src/modules/publish-issue-title.js:1470: StartRules: ["PublishIssueTitle", "ReleaseRevision", "CheckRunsLinkCount"],
origin/main:src/modules/publish-issue-title.js:1471: SyntaxError: peg$SyntaxError,
origin/main:src/modules/publish-issue-title.js:1472: parse: peg$parse,
origin/main:yarn.lock:538:"@peggyjs/from-mem@3.1.3":
origin/main:yarn.lock:954:commander@^14.0.3:
origin/main:yarn.lock:1538:peggy@5.1.0:
origin/main:yarn.lock:1685:semver@7.7.4:
origin/main:yarn.lock:1707:source-map-generator@2.0.6:
No files found
Found 3 matches
/home/byk/Code/getsentry/publish/src/publish/update-issue.js:
Line 14: console.warn("Could not parse publish inputs; skipping target update");
/home/byk/Code/getsentry/publish/src/modules/__tests__/update-issue.js:
Line 120: test("remove label", async () => {
Line 132: test("removes accepted without parsed publish inputs", async () => {
origin/main:.github/workflows/ci-poller.yml:51: - name: Check out publish controller
origin/main:.github/workflows/ci-poller.yml:52: uses: actions/checkout@v7
origin/main:.github/workflows/ci-poller.yml:99: body_file=$(mktemp)
origin/main:.github/workflows/ci-poller.yml:101: updated_body_file=""
origin/main:.github/workflows/ci-poller.yml:102: trap 'for file in "${body_file:-}" "${resolver_output_file:-}" "${updated_body_file:-}"; do [[ -z "$file" ]] || rm -f "$file"; done' EXIT
origin/main:.github/workflows/ci-poller.yml:103: printf '%s' "$issue" | jq -jr '.body' > "$body_file"
origin/main:.github/workflows/ci-poller.yml:105: if ! publish_input=$(PUBLISH_ISSUE_BODY_FILE="$body_file" PUBLISH_TITLE="$title" \
origin/main:.github/workflows/ci-poller.yml:106: node .__publish__/src/publish/resolve-ci-poller-input.js); then
origin/main:.github/workflows/ci-poller.yml:151: ...
## main...origin/main [behind 1] M .github/workflows/auto-approve.yml M .github/workflows/ci-poller.yml M .github/workflows/cocoapods-keepalive.yml M .github/workflows/publish.yml M .github/workflows/test.yml M AGENTS.md M README.md M docs/key_rotation_vault_changes.md M docs/rfc.md M src/libs/__tests__/github.js M src/libs/github.js M src/modules/__tests__/details-from-context.js M src/modules/__tests__/update-issue.js M src/modules/details-from-context.js M src/modules/post-workflow-details.js M src/modules/process-end-state.js M src/modules/update-issue.js M src/publish/inputs.js M src/publish/post-result.js M src/publish/post-workflow-details.js M src/publish/update-issue.js ?? .github/workflows/ci-poller-dispatch.yml ?? .lore.md ?? src/modules/__tests__/approval-attestation.js ?? src/modules/__tests__/approval-authorizer.js ?? src/modules/approval-attestation.js ?? src/modules/approval-authorizer.js ?? src/publish/__tests__/ ?? ...
<path>/home/byk/Code/getsentry/publish/auto-approve-repos.txt</path> <type>file</type> <content> 1: getsentry/arroyo 2: getsentry/auto-type-annotate 3: getsentry/devenv 4: getsentry/infra-event-notifier 5: getsentry/jest-sentry-environment 6: getsentry/json-schema-diff 7: getsentry/js-source-scopes 8: getsentry/objectstore/clients 9: getsentry/ophio 10: getsentry/pdb 11: getsentry/pyo3-python-tracing-subscriber 12: getsentry/pytest-sentry 13: getsentry/relay/py 14: getsentry/responses 15: getsentry/rust-proguard 16: getsentry/rust-sourcemap 17: getsentry/rust-usage-accountant 18: getsentry/script-runner 19: getsentry/sentry-api-schema 20: getsentry/sentry-forked-djangorestframework-stubs 21: getsentry/sentry-forked-django-stubs 22: getsentry/sentry-forked-jsonnet 23: getsentry/sentry-infra-tools 24: getsentry/sentry-kafka-management 25: getsentry/sentry-kafka-schemas 26: getsentry/sentry-protos 27: getsentry/sentry-redis-tools 28: getsentry/service-registry 29: getsentry/skrooge 30: ...
origin/main:README.md:42:## Publish Issue Format origin/main:README.md:45:[Publish Issue Format](docs/publish-issue-format.md) for the accepted syntax and fields. README.md:97:This repo is read-only for everyone except release managers. Secret-bearing jobs use the protected `production` environment, which permits only `main` deployments and does not allow administrator bypass. This protects environment-scoped credentials from arbitrary workflow refs. See getsentry/sentry#21930 for an example. README.md:99:`SENTRY_INTERNAL_APP_PRIVATE_KEY` and `SENTRY_RELEASE_BOT_PRIVATE_KEY` are organization-level secrets. Their availability to other repositories is an organization-wide security concern that this repository cannot narrow; Security must manage that boundary separately.
No blocker. I made no edits and ran no tests.
HEAD: 83d210b29553ea6f4d97508821724a834c43856eorigin/main: 7c60ddb7f43040fe8fbfea70efc833f689c04e75HEAD is the merge base, so origin/main is exactly one commit ahead.git apply --check found direct conflicts in both workflows, cocoapods-keepalive.yml, details-from-context.js, update-issue.js, and inputs.js. Package, lock, docs, and new files apply textually, but several tests conflict semantically with the security work.package.json + yarn.lock
└─ peggy 5.1.0
└─ scripts/generate-publish-issue-title-parser.js
├─ src/modules/publish-issue-title.peggy
├─ src/modules/publish-issue-title.js generated output
└─ docs/publish-issue-format.md generated title section
publish-issue-title.js
├─ release-revision.js
│ ├─ details-from-context.js
│ └─ resolve-release-revision.js
└─ ci-poller-input.js
└─ resolve-ci-poller-input.js
publish-issue-validation.js + publish-location.js
├─ details-from-context.js
├─ ci-poller-input.js
├─ discover-location.js
└─ resolve-location.js
details-from-context.js target regexes
└─ update-issue.js
publish workflow:
inputs → revision → exact-SHA checkout → workspace discovery
→ state-file output → Craft → failure cleanup
security-only current dependency:
approval-authorizer.js → details-from-context.js::parsePublishTitle
That last dependency breaks if upstream details-from-context.js replaces the current file unchanged.
/home/byk/Code/getsentry/publish/package.json
Adds generate, check:generated, makes every yarn test run the generated-file check, and pins Peggy 5.1.0 (origin/main:18-30).
/home/byk/Code/getsentry/publish/yarn.lock
Adds Peggy and only its new dependency chain: @peggyjs/from-mem, commander, semver, and source-map-generator ...