Dashboard › craft › Session 1IwgpKHTTZkV
1IwgpKHTTZkVsDo9lPerform a rigorous, read-only final adversarial review of the current uncommitted compact workspace publish-contract changes in BOTH worktrees:
Do not edit files, stage, commit, reset, or run destructive commands. Inspect the actual current worktrees versus their branch bases.
Critical contract facts:
getsentry/craft:latest. This is intentional and must not be reported as a mutable-image concern. Craft must release first, so latest contains workspace list and craft publish --rev.pnpm-lock.yaml in Publish is unrelated and must never be committed.Assess with exact file:line evidence:
craft publish --rev all use that exact revision and resolved location..craft.yml remains path behavior; discovery errors fail closed when root config exists.<path>/home/byk/Code/getsentry/craft-workspace-action-propagation</path> <type>directory</type> <entries> .craft.yml .dockerignore .eslintcache .git .github/ .gitignore .lore.md .nojekyll .npmignore .prettierignore .prettierrc.yml .tsbuildinfo .vscode/ action.yml AGENTS.md blog-post-draft.md build.mjs CHANGELOG.md CONTRIBUTING.md dist/ Dockerfile docs/ eslint.config.mjs img/ LICENSE node_modules/ package.json pnpm-lock.yaml README.md src/ tsconfig.build.json tsconfig.json vitest.config.mts (33 entries) </entries>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance</path> <type>directory</type> <entries> .eslintrc.js .git .github/ .gitignore AGENTS.md auto-approve-repos.txt docs/ LICENSE node_modules/ package.json pnpm-lock.yaml README.md scripts/ src/ vitest.config.js yarn.lock (16 entries) </entries>
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/AGENTS.md</path> <type>file</type> <content> 1: # AGENTS.md 2: 3: This file provides guidance for AI coding assistants working with the Craft codebase. 4: 5: ## Package Management 6: 7: - **Always use `pnpm`** for package management. Never use `npm` or `yarn`. 8: - Node.js version is managed by [Volta](https://volta.sh/) (currently v22.12.0). 9: - Install dependencies with `pnpm install --frozen-lockfile`. 10: 11: ## Development Commands 12: 13: | Command | Description | 14: | ------------ | ------------------------------------------- | 15: | `pnpm build` | Build the project (outputs to `dist/craft`) | 16: | `pnpm test` | Run tests | 17: | `pnpm lint` | Run ESLint | 18: | `pnpm fix` | Auto-fix lint issues | 19: 20: To manually test changes: 21: 22: ```bash 23: pnpm build && ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/AGENTS.md</path> <type>file</type> <content> 1: # AGENTS.md - Coding Agent Guidelines 2: 3: This document provides guidelines for AI coding agents working in this repository. 4: 5: ## Project Overview 6: 7: This is the **Sentry Publish Repository** - a GitHub Actions-based approval system for publishing releases across multiple Sentry projects using [Craft](https://github.com/getsentry/craft). It's a pure JavaScript (Node.js 24.0.0) project using CommonJS modules. 8: 9: ## Build/Lint/Test Commands 10: 11: ### Package Manager 12: 13: - **Yarn 1.22.22** (classic) - Do NOT use npm 14: - Version management via Volta (pinned in package.json) 15: 16: ### Commands 17: 18: ```bash 19: # Install dependencies 20: yarn install 21: 22: # Run all tests 23: yarn test 24: 25: # Run tests in watch mode 26: yarn test:watch 27: 28: # Run a single test file 29: yarn test src/modules/__tests__/update-issue.js 30: 31: # Run tests ...
## feat/workspace-action-propagation...origin/feat/workspace-action-propagation M action.yml M docs/src/content/docs/targets/github.md M src/__tests__/action.test.ts M src/__tests__/config.test.ts M src/commands/__tests__/publish.test.ts M src/commands/publish.ts M src/config.ts M src/index.ts M src/schemas/project_config.ts ?? src/commands/workspace.ts ?? src/commands/workspace_cmds/ feat/workspace-action-propagation 6dae341f9bc1378abde7a9022108caded2d22226 21270a1d12a60b461c51fba93bd076b90d9cf4af M .github/workflows/release.yml M action.yml M docs/src/content/docs/targets/github.md A src/__tests__/action.test.ts M src/__tests__/config.test.ts M src/commands/__tests__/publish.test.ts M src/commands/publish.ts M src/config.ts M src/index.ts M src/schemas/project_config.ts M action.yml M docs/src/content/docs/targets/github.md M src/__tests__/action.test.ts M src/__tests__/config.test.ts M src/commands/__tests__/publish.test.ts M src/commands/publish.ts M src/config.ts M src/in...
## feat/workspace-acceptance...origin/feat/workspace-acceptance M .github/workflows/publish.yml M docs/publish-issue-format.md M package.json M src/modules/__tests__/details-from-context.js M src/modules/__tests__/publish-workflow.js M src/modules/details-from-context.js M src/modules/post-workflow-details.js M src/publish/inputs.js M yarn.lock ?? pnpm-lock.yaml ?? scripts/ ?? src/modules/__tests__/publish-location.js ?? src/modules/__tests__/release-revision.js ?? src/modules/publish-issue-title.js ?? src/modules/publish-issue-title.peggy ?? src/modules/publish-location.js ?? src/modules/release-revision.js ?? src/publish/resolve-location.js ?? src/publish/resolve-release-revision.js feat/workspace-acceptance b658ffa0299fedf78c9dd9a2121cd740691ca157 fatal: Not a valid object name origin/master
origin https://github.com/getsentry/publish.git (fetch)
origin https://github.com/getsentry/publish.git (push)
* feat/workspace-acceptance
remotes/origin/feat/workspace-acceptance
origin/HEAD -> origin/main
origin/feat/ci-poller-variable-gate
origin/feat/ci-status-poller
origin/feat/workspace-acceptance
origin/fix/ci-poller-gh-token
origin/fix/ci-poller-variable-auth
origin/fix/react-native-release-8-14-craft-config
origin/main
origin/revert-twine-verbose
refs/remotes/origin/main
error: unknown option `remotes'
usage: git show-ref [--head] [-d | --dereference]
[-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
[--heads] [--] [<pattern>...]
or: git show-ref --verify [-q | --quiet] [-d | --dereference]
[-s | --hash[=<n>]] [--abbrev[=<n>]]
[--] [<ref>...]
or: git show-ref --exclude-existing[=<pattern>]
or: git show-ref --exists <ref>
--[no-]tags only show tags (can be ...
BASE=83d210b29553ea6f4d97508821724a834c43856e M .github/workflows/cocoapods-keepalive.yml M .github/workflows/publish.yml M README.md A docs/publish-issue-format.md M package.json M src/modules/__tests__/details-from-context.js A src/modules/__tests__/publish-workflow.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/update-issue.js M src/publish/inputs.js M yarn.lock --- worktree diff --- M .github/workflows/publish.yml M docs/publish-issue-format.md M package.json M src/modules/__tests__/details-from-context.js M src/modules/__tests__/publish-workflow.js M src/modules/details-from-context.js M src/modules/post-workflow-details.js M src/publish/inputs.js M yarn.lock --- staged diff --- --- recent history --- b658ffa (HEAD -> feat/workspace-acceptance, origin/feat/workspace-acceptance) fix: validate publish workspace JSON 33de3f1 feat: accept workspace publish requests 83d210b (origin/main, ...
action.yml | 18 ++---
docs/src/content/docs/targets/github.md | 36 ++++++++-
src/__tests__/action.test.ts | 125 +++++++++++++++++++++++++++---
src/__tests__/config.test.ts | 130 ++++++++++++++++++++++----------
src/commands/__tests__/publish.test.ts | 27 +++++++
src/commands/publish.ts | 18 ++++-
src/config.ts | 48 ++++++++----
src/index.ts | 2 +
src/schemas/project_config.ts | 63 +++++++++++-----
9 files changed, 370 insertions(+), 97 deletions(-)
diff --git a/action.yml b/action.yml
index 6344db2..7239ac1 100644
--- a/action.yml
+++ b/action.yml
@@ -74,10 +74,15 @@ runs:
- name: Validate workspace
shell: bash
env:
+ PATH_INPUT: ${{ inputs.path }}
WORKSPACE: ${{ inputs.workspace }}
run: |
- if [[ -n "$WORKSPACE" ]] && node -e ...
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/workspace_cmds/__tests__/list.test.ts /home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/workspace_cmds/list.ts
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/workspace.ts</path>
<type>file</type>
<content>
1: import { Argv, CommandBuilder } from 'yargs';
2:
3: import * as list from './workspace_cmds/list';
4:
5: export const command = ['workspace <command>'];
6: export const description = 'Manage release workspaces';
7:
8: export const builder: CommandBuilder = (yargs: Argv) =>
9: yargs.demandCommand().command(list);
10:
11: export const handler = (): void => {
12: /* pass */
13: };
(End of file - total 13 lines)
</content>
<system-reminder>
Instructions from: /home/byk/Code/getsentry/craft-workspace-action-propagation/AGENTS.md
# AGENTS.md
This file provides guidance for AI coding assistants working with the Craft codebase.
## Package Management
- **Always use `pnpm`** for package management. Never use `npm` or `yarn`.
- Node.js version is managed by [Volta](https://volta.sh/) (currently v22.12.0). ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/workspace_cmds/list.ts</path>
<type>file</type>
<content>
1: import { getWorkspaceNames } from '../../config';
2: import { formatJson } from '../../utils/strings';
3:
4: export const command = ['list'];
5: export const description = 'List defined release workspaces as a JSON array';
6:
7: export function handler(): void {
8: console.log(formatJson(getWorkspaceNames()));
9: }
(End of file - total 9 lines)
</content>
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/workspace_cmds/__tests__/list.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from 'vitest';
2:
3: vi.mock('../../../config', () => ({
4: getWorkspaceNames: vi.fn(),
5: }));
6: vi.mock('../../../utils/strings', () => ({
7: formatJson: vi.fn(value => JSON.stringify(value)),
8: }));
9:
10: import { getWorkspaceNames } from '../../../config';
11: import { handler } from '../list';
12:
13: describe('workspace list command', () => {
14: test('prints exact configured workspace names', () => {
15: vi.mocked(getWorkspaceNames).mockReturnValue(['cli', 'mcp.v2']);
16: const log = vi.spyOn(console, 'log').mockImplementation(() => {});
17:
18: handler();
19:
20: expect(log).toHaveBeenCalledWith('["cli","mcp.v2"]');
21: });
22:
23: test('prints an empty array when no workspaces are configured', () => {
24: ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/action.yml</path>
<type>file</type>
<content>
1: name: 'Craft Prepare Release'
2: description: 'Prepare a new release using Craft'
3:
4: inputs:
5: version:
6: description: >
7: Version to release. Can be a semver string (e.g., "1.2.3"),
8: a bump type ("major", "minor", "patch"), or "auto" for automatic detection.
9: required: false
10: merge_target:
11: description: Target branch to merge into. Uses the default branch as a fallback. ...
.github/workflows/release.yml | 9 ++
action.yml | 33 ++++-
docs/src/content/docs/targets/github.md | 36 ++++-
src/__tests__/action.test.ts | 249 ++++++++++++++++++++++++++++++++
src/__tests__/config.test.ts | 130 ++++++++++++-----
src/commands/__tests__/publish.test.ts | 74 ++++++++++
src/commands/publish.ts | 51 ++++++-
src/config.ts | 48 ++++--
src/index.ts | 2 +
src/schemas/project_config.ts | 63 +++++---
10 files changed, 613 insertions(+), 82 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4e6c11e..323e8ba 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,6 +12,9 @@ on:
force:
description: Force a release even when there are release-blockers (optional)
required: false
+ workspace:
+ description: Named ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/publish.ts</path>
<type>file</type>
<content>
1: import { Arguments, Argv, CommandBuilder } from 'yargs';
2: import chalk from 'chalk';
3: import { existsSync, mkdirSync, readFileSync } from 'fs';
4:
5: import { safeFs } from '../utils/dryRun';
6: import { dirname, join } from 'path';
7: import * as shellQuote from 'shell-quote';
8: import stringLength from 'string-length';
9:
10: import {
11: getConfiguration,
12: getStatusProviderFromConfig,
13: getArtifactProviderFromConfig,
14: DEFAULT_RELEASE_BRANCH_NAME,
15: getGlobalGitHubConfig,
16: expandWorkspaceTargets,
17: getNoMergeConfig,
18: getActiveWorkspace,
19: } from '../config';
20: import { formatTable, logger } from '../logger';
21: import {
22: type GitHubGlobalConfig,
23: TargetConfig,
24: } from '../schemas/project_config';
25: import { getAllTargetNames, getTargetByName, SpecialTarget } from '../targets';
26: import { ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/publish.ts</path>
<type>file</type>
<content>
560: *
561: * The command is usually for bumping the development version on master or
562: * cleanup tasks.
563: *
564: * @param newVersion Version being released
565: * @param postReleaseCommand Custom post-release command
566: */
567: export async function runPostReleaseCommand(
568: newVersion: string,
569: postReleaseCommand?: string,
570: ): Promise<boolean> {
571: let sysCommand: shellQuote.ParseEntry;
572: let args: shellQuote.ParseEntry[];
573: if (postReleaseCommand !== undefined && postReleaseCommand.length === 0) {
574: // Not running post-release command
575: logger.debug('Not running the post-release command: no command specified');
576: return false;
577: } else if (postReleaseCommand) {
578: [sysCommand, ...args] = shellQuote.parse(postReleaseCommand);
579: } else if ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/config.ts</path>
<type>file</type>
<content>
1: import { existsSync, lstatSync, readFileSync } from 'fs';
2: import path from 'path';
3:
4: import { load } from 'js-yaml';
5: import GitUrlParse from 'git-url-parse';
6: import { createGitClient } from './utils/git';
7: import { ZodError } from 'zod';
8:
9: import { logger } from './logger';
10: import {
11: CraftProjectConfig,
12: CraftProjectConfigSchema,
13: GitHubGlobalConfig,
14: ArtifactProviderName,
15: StatusProviderName,
16: TargetConfig,
17: ChangelogPolicy,
18: VersioningPolicy,
19: Workspace,
20: } from './schemas/project_config';
21: import { ConfigurationError } from './utils/errors';
22: import { isCompiledGitHubAction } from './utils/detection';
23: import {
24: getPackageVersion,
25: parseVersion,
26: versionGreaterOrEqualThan,
27: SemVer,
28: } from './utils/version';
29: // Note: We import getTargetByName lazily in ...
.github/workflows/publish.yml | 59 ++++++++++++++-------------
docs/publish-issue-format.md | 55 +++++++++++++++----------
package.json | 5 ++-
src/modules/__tests__/details-from-context.js | 20 +++++++++
src/modules/__tests__/publish-workflow.js | 36 +++++++++++++++-
src/modules/details-from-context.js | 19 +++++----
src/modules/post-workflow-details.js | 2 +-
src/publish/inputs.js | 2 +-
yarn.lock | 31 ++++++++++++++
9 files changed, 167 insertions(+), 62 deletions(-)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index c283b79..3158904 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -141,29 +141,12 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node .__publish__/src/publish/post-workflow-details.js
- # Setting the target repo ...
/home/byk/Code/getsentry/publish-workspace-acceptance/scripts/generate-publish-issue-title-parser.js
/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/release-revision.js /home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-location.js /home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.js /home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.peggy
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-release-revision.js /home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-location.js /home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/release-revision.js /home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-location.js /home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/release-revision.js /home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/publish-location.js
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.peggy</path>
<type>file</type>
<content>
1: {
2: function join(characters) {
3: return characters.join("");
4: }
5: }
6:
7: // Canonical grammar for publish issue titles. A path suffix is syntactic only:
8: // the controller resolves a single segment as a workspace after checking out
9: // the CI-approved revision.
10: PublishIssueTitle
11: = "publish: " "getsentry/"? repo:Repository path:Path? workspace:LegacyWorkspace? "@" version:Version !. {
12: return {
13: repo,
14: path: path || "",
15: version,
16: ...(workspace ? { workspace } : {}),
17: };
18: }
19:
20: Repository
21: = characters:RepositoryCharacter+ { return join(characters); }
22:
23: RepositoryCharacter
24: = [A-Za-z0-9_.-]
25:
26: Path
27: = segments:("/" segment:PathSegment { return `/${segment}`; })+ { return join(segments); }
28:
29: PathSegment
30: = ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.js</path>
<type>file</type>
<content>
1: /* eslint-disable */
2: // @generated by Peggy 5.1.0.
3: //
4: // https://peggyjs.org/
5:
6: "use strict";
7:
8: class peg$SyntaxError extends SyntaxError {
9: constructor(message, expected, found, location) {
10: super(message);
11: this.expected = expected;
12: this.found = found;
13: this.location = location;
14: this.name = "SyntaxError";
15: }
16:
17: format(sources) {
18: let str = "Error: " + this.message;
19: if (this.location) {
20: let src = null;
21: const st = sources.find((s) => s.source === this.location.source);
22: if (st) {
23: src = st.text.split(/\r\n|\n|\r/g);
24: }
25: const s = this.location.start;
26: const offset_s =
27: this.location.source &&
28: typeof this.location.source.offset === "function"
29: ? ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/scripts/generate-publish-issue-title-parser.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const path = require("path");
3: const peggy = require("peggy");
4: const prettier = require("prettier");
5:
6: const grammarPath = path.join(
7: __dirname,
8: "..",
9: "src",
10: "modules",
11: "publish-issue-title.peggy"
12: );
13: const outputPath = path.join(
14: __dirname,
15: "..",
16: "src",
17: "modules",
18: "publish-issue-title.js"
19: );
20: const grammar = fs.readFileSync(grammarPath, "utf8");
21: const parser = peggy.generate(grammar, {
22: format: "commonjs",
23: grammarSource: "publish-issue-title.peggy",
24: output: "source",
25: });
26:
27: const generatedParser = prettier.format(`/* eslint-disable */\n${parser}`, {
28: filepath: outputPath,
29: });
30:
31: if (process.argv.includes("--check")) {
32: const currentParser = fs.readFileSync(outputPath, "utf8");
33: ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/release-revision.js</path>
<type>file</type>
<content>
1: const CHECK_RUNS_LINK =
2: /^- \[View check runs\]\(https:\/\/github\.com\/getsentry\/(?<repo>[A-Za-z0-9_.-]+)\/commit\/(?<revision>[0-9a-f]{40})\/checks\/?\)\r?$/m;
3:
4: function getReleaseRevision({ issueBody, repo }) {
5: const match = issueBody.match(CHECK_RUNS_LINK);
6: if (!match?.groups || match.groups.repo !== repo) {
7: throw new Error(
8: `Expected a View check runs link for getsentry/${repo} in the publish issue body.`
9: );
10: }
11:
12: return match.groups.revision;
13: }
14:
15: module.exports = { getReleaseRevision };
(End of file - total 15 lines)
</content>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-location.js</path>
<type>file</type>
<content>
1: function needsWorkspaceDiscovery({ path, workspace }) {
2: return !workspace && /^\.\/[A-Za-z0-9_.-]+$/.test(path);
3: }
4:
5: function resolvePublishLocation({ path, workspace, workspaceNames }) {
6: if (workspace) {
7: if (path !== ".") {
8: throw new Error("A publish workspace must use the repository root path.");
9: }
10: return { path, workspace };
11: }
12:
13: if (!needsWorkspaceDiscovery({ path, workspace })) {
14: return { path };
15: }
16:
17: if (!workspaceNames.every(isWorkspaceName)) {
18: throw new Error(
19: "Craft workspace discovery returned an invalid workspace list."
20: );
21: }
22:
23: const segments = path.slice(2).split("/");
24: if (
25: path.startsWith("./") &&
26: segments.length === 1 &&
27: workspaceNames.includes(segments[0])
28: ) {
29: return { path: ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-release-revision.js</path>
<type>file</type>
<content>
1: const core = require("@actions/core");
2: const { getReleaseRevision } = require("../modules/release-revision");
3:
4: function resolveReleaseRevision() {
5: const { repo } = JSON.parse(process.env.PUBLISH_ARGS || "");
6: core.setOutput(
7: "revision",
8: getReleaseRevision({
9: issueBody: process.env.PUBLISH_ISSUE_BODY || "",
10: repo,
11: })
12: );
13: }
14:
15: resolveReleaseRevision();
(End of file - total 15 lines)
</content>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-location.js</path>
<type>file</type>
<content>
1: const core = require("@actions/core");
2: const {
3: needsWorkspaceDiscovery,
4: resolvePublishLocation,
5: } = require("../modules/publish-location");
6:
7: function resolveLocation() {
8: const input = JSON.parse(process.env.PUBLISH_ARGS || "");
9: const workspaceNames = needsWorkspaceDiscovery(input)
10: ? JSON.parse(process.env.CRAFT_WORKSPACE_NAMES || "")
11: : [];
12:
13: if (!Array.isArray(workspaceNames)) {
14: throw new Error(
15: "Craft workspace discovery returned an invalid workspace list."
16: );
17: }
18:
19: core.setOutput(
20: "result",
21: resolvePublishLocation({
22: path: input.path,
23: workspace: input.workspace,
24: workspaceNames,
25: })
26: );
27: }
28:
29: resolveLocation();
(End of file - total 29 lines)
</content>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/release-revision.js</path>
<type>file</type>
<content>
1: import { expect, test } from "vitest";
2:
3: const { getReleaseRevision } = require("../release-revision.js");
4:
5: const REVISION = "7e5ca7ed5581552de066e2a8bc295b8306be38ac";
6:
7: test("gets the CI-approved revision for the publish repository", () => {
8: expect(
9: getReleaseRevision({
10: repo: "toolkit",
11: issueBody: `Quick links:
12: - [View check runs](https://github.com/getsentry/toolkit/commit/${REVISION}/checks/)`,
13: })
14: ).toBe(REVISION);
15: });
16:
17: test("gets the CI-approved revision from a CRLF issue body", () => {
18: expect(
19: getReleaseRevision({
20: repo: "toolkit",
21: issueBody: `Quick links:\r\n- [View check runs](https://github.com/getsentry/toolkit/commit/${REVISION}/checks/)\r\n`,
22: })
23: ).toBe(REVISION);
24: });
25:
26: test("rejects a check-runs link ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/publish-location.js</path>
<type>file</type>
<content>
1: import { expect, test } from "vitest";
2:
3: const {
4: needsWorkspaceDiscovery,
5: resolvePublishLocation,
6: } = require("../publish-location.js");
7:
8: test.each([
9: [{ path: "./cli" }, true],
10: [{ path: "./packages/cli" }, false],
11: [{ path: "." }, false],
12: [{ path: "./cli", workspace: "cli" }, false],
13: ])("workspace discovery is %s for %j", (input, expected) => {
14: expect(needsWorkspaceDiscovery(input)).toBe(expected);
15: });
16:
17: test("classifies an exact one-segment workspace without normalizing it", () => {
18: expect(
19: resolvePublishLocation({
20: path: "./CLI",
21: workspaceNames: ["cli", "CLI"],
22: })
23: ).toStrictEqual({ path: ".", workspace: "CLI" });
24: });
25:
26: test("keeps a non-workspace suffix as a checkout path", () => {
27: expect(
28: ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/details-from-context.js</path>
<type>file</type>
<content>
1: const { parse: parsePublishIssueTitle } = require("./publish-issue-title");
2:
3: /**
4: * Matches the entire "Targets" section of a github publish issue body.
5: */
6: const TARGETS_SECTION_PARSER_REGEX =
7: /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m;
8:
9: /**
10: * Matches all targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
11: * The "id" of the targets is captured within a capture group.
12: */
13: const TARGETS_PARSER_REGEX = /^\s*- \[[ x]\] (\S+)/gim;
14:
15: /**
16: * Matches checked targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
17: * The "id" of the targets is captured within a capture group. ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.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 ...
.github/workflows/cocoapods-keepalive.yml | 2 +- .github/workflows/publish.yml | 82 +++++---- README.md | 5 + docs/publish-issue-format.md | 92 ++++++++++ package.json | 5 +- src/modules/__tests__/details-from-context.js | 250 +++++++++++++++++++++++++- src/modules/__tests__/publish-workflow.js | 114 ++++++++++++ src/modules/__tests__/update-issue.js | 15 +- src/modules/details-from-context.js | 40 ++++- src/modules/post-workflow-details.js | 2 +- src/modules/update-issue.js | 54 +++--- src/publish/inputs.js | 2 +- yarn.lock | 31 ++++ 13 files changed, 623 insertions(+), 71 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cff6bc3..3158904 100644 --- a/.github/workflows/publish.yml +++ ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/utils/publishState.ts</path> <type>file</type> <content> 1: /** 2: * Helpers for locating Craft's publish-state file in a path that is NOT 3: * writable by the repository being published. 4: * 5: * Background: `craft publish` writes a small JSON file listing targets 6: * that have completed so a resumed run can skip them. Before this module 7: * existed, the file lived at `.craft-publish-<version>.json` in the 8: * project's cwd. That path is inside the repository checkout, so any 9: * committed file at the same path (or any earlier CI step) could 10: * pre-populate the "published" set and trick Craft into silently 11: * skipping targets. 12: * 13: * The file now lives under `$XDG_STATE_HOME/craft/` (falling back to 14: * `$HOME/.local/state/craft/`). The filename is keyed on 15: * owner, repo, a hash of cwd (to disambiguate monorepo subpaths), and 16: * the version being published. ...
No files found
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github</path> <type>directory</type> <entries> CODEOWNERS workflows/ (2 entries) </entries>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows</path> <type>directory</type> <entries> auto-approve.yml ci-poller.yml cocoapods-keepalive.yml publish.yml test.yml (5 entries) </entries>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows/test.yml</path>
<type>file</type>
<content>
1: name: Test
2: on:
3: push:
4: branches:
5: - main
6: pull_request:
7:
8: permissions:
9: contents: read
10:
11: defaults:
12: run:
13: shell: bash
14: jobs:
15: unit-test:
16: runs-on: ubuntu-latest
17: name: unit tests
18: steps:
19: - uses: actions/checkout@v6
20: - uses: actions/setup-node@v6
21: with:
22: node-version: 24
23: - uses: actions/cache@v4
24: id: cache
25: with:
26: path: node_modules
27: key: ${{ runner.os }}-node_modules-${{ hashFiles('package.json', 'yarn.lock') }}
28:
29: - name: Install Dependencies
30: if: steps.cache.outputs.cache-hit != 'true'
31: run: yarn install --frozen-lockfile
32:
33: - name: Test
34: run: yarn test
(End of file - total 34 lines)
</content>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/update-issue.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3: TARGETS_SECTION_PARSER_REGEX,
4: TARGETS_PARSER_REGEX,
5: } = require("./details-from-context");
6:
7: async function updateTargets({
8: octokit,
9: stateFilePath,
10: publishRepo,
11: issue_number,
12: }) {
13: if (!stateFilePath || !fs.existsSync(stateFilePath)) {
14: return;
15: }
16:
17: const issueRequest = octokit.rest.issues.get({
18: ...publishRepo,
19: issue_number,
20: });
21:
22: const craftStateRequest = fs.promises
23: .readFile(stateFilePath, { encoding: "utf-8" })
24: .then((data) => JSON.parse(data));
25:
26: const [{ data: issue }, craftState] = await Promise.all([
27: issueRequest,
28: craftStateRequest,
29: ]);
30:
31: const newIssueBody = transformIssueBody(craftState, issue.body);
32:
33: await octokit.rest.issues.update({
34: ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/inputs.js</path>
<type>file</type>
<content>
1: const github = require('@actions/github');
2: const core = require('@actions/core');
3: const { detailsFromContext } = require('../modules/details-from-context');
4:
5: async function inputs() {
6: const result = await detailsFromContext({
7: context: github.context,
8: });
9: core.setOutput('result', result);
10: }
11:
12:
13: inputs();
(End of file - total 13 lines)
</content>
Usage: realpath [OPTION]... FILE...
Print the resolved absolute file name;
all but the last component must exist
-e, --canonicalize-existing all components of the path must exist
-m, --canonicalize-missing no path components need exist or be a directory
-L, --logical resolve '..' components before symlinks
-P, --physical resolve symlinks as encountered (default)
-q, --quiet suppress most error messages
--relative-to=DIR print the resolved path relative to DIR
--relative-base=DIR print absolute paths unless paths below DIR
-s, --strip, --no-symlinks don't expand symlinks
-z, --zero end each output line with NUL, not newline
--help display this help and exit
--version output version information and exit
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Full documentation <https://www.gnu.org/software/coreutils/realpath>
or available ...
diff --git a/.github/workflows/cocoapods-keepalive.yml b/.github/workflows/cocoapods-keepalive.yml index 21c0696..9c11b0e 100644 --- a/.github/workflows/cocoapods-keepalive.yml +++ b/.github/workflows/cocoapods-keepalive.yml @@ -1,4 +1,4 @@ -name: "CocoaPods Token Keep-Alive" +name: CocoaPods Token Keep-Alive on: schedule: --- committed files per commit --- 33de3f1 feat: accept workspace publish requests .github/workflows/publish.yml | 27 +++- src/modules/__tests__/details-from-context.js | 212 +++++++++++++++++++++++++- src/modules/__tests__/publish-workflow.js | 82 ++++++++++ src/modules/__tests__/update-issue.js | 15 +- src/modules/details-from-context.js | 27 +++- src/modules/update-issue.js | 54 ++++--- 6 files changed, 377 insertions(+), 40 deletions(-) b658ffa fix: validate publish workspace JSON .github/workflows/cocoapods-keepalive.yml | 2 +- README.md | 5 ++ ...
> @sentry/craft@2.31.0-dev.0 test /home/byk/Code/getsentry/craft-workspace-action-propagation > vitest run RUN v4.1.8 /home/byk/Code/getsentry/craft-workspace-action-propagation [info] [[target/github]] No previous release found [info] [[target/github]] Deleted orphaned draft release: v1.0.0 [info] [[target/github]] No previous release found [warn] [[target/github]] Failed to clean up release "v1.0.0": Error: Cleanup failed [info] [[target/github]] No previous release found [info] [[target/github]] No previous release found [info] [[target/github]] Release for tag "1.0.0" already exists and is published. Skipping GitHub release creation (likely from a previous run). [info] [[target/github]] No previous release found [info] [[target/github]] createRelease returned 422 for tag "1.0.0". Looking for a leftover draft release to clean up... [info] [[target/github]] Deleting leftover draft release (id=789) for tag "1.0.0"... ...
yarn run v1.22.22 $ yarn check:generated && vitest run $ node scripts/generate-publish-issue-title-parser.js --check [1m[46m RUN [49m[22m [36mv4.1.0 [39m[90m/home/byk/Code/getsentry/publish-workspace-acceptance[39m [2m Test Files [22m [1m[32m8 passed[39m[22m[90m (8)[39m [2m Tests [22m [1m[32m45 passed[39m[22m[90m (45)[39m [2m Start at [22m 13:15:43 [2m Duration [22m 1.33s[2m (transform 246ms, setup 0ms, import 1.02s, tests 300ms, environment 3ms)[22m Done in 2.84s.
M action.yml M docs/src/content/docs/targets/github.md M src/__tests__/action.test.ts M src/__tests__/config.test.ts M src/commands/__tests__/publish.test.ts M src/commands/publish.ts M src/config.ts M src/index.ts M src/schemas/project_config.ts ?? src/commands/workspace.ts ?? src/commands/workspace_cmds/ 8 10 action.yml 32 4 docs/src/content/docs/targets/github.md 115 10 src/__tests__/action.test.ts 92 38 src/__tests__/config.test.ts 27 0 src/commands/__tests__/publish.test.ts 15 3 src/commands/publish.ts 35 13 src/config.ts 2 0 src/index.ts 44 19 src/schemas/project_config.ts src/commands/workspace.ts src/commands/workspace_cmds/__tests__/list.test.ts src/commands/workspace_cmds/list.ts
M .github/workflows/publish.yml M docs/publish-issue-format.md M package.json M src/modules/__tests__/details-from-context.js M src/modules/__tests__/publish-workflow.js M src/modules/details-from-context.js M src/modules/post-workflow-details.js M src/publish/inputs.js M yarn.lock ?? pnpm-lock.yaml ?? scripts/ ?? src/modules/__tests__/publish-location.js ?? src/modules/__tests__/release-revision.js ?? src/modules/publish-issue-title.js ?? src/modules/publish-issue-title.peggy ?? src/modules/publish-location.js ?? src/modules/release-revision.js ?? src/publish/resolve-location.js ?? ...
Found 2 matches
/home/byk/Code/getsentry/publish-workspace-acceptance/node_modules/@actions/core/lib/core.js:
Line 52: exports.setOutput = setOutput;
Line 215: function setOutput(name, value) {
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/node_modules/@actions/core/lib/core.js</path>
<type>file</type>
<content>
200: const val = getInput(name, options);
201: if (trueValue.includes(val))
202: return true;
203: if (falseValue.includes(val))
204: return false;
205: throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` +
206: `Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
207: }
208: /**
209: * Sets the value of an output.
210: *
211: * @param name name of the output to set
212: * @param value value to store. Non-string values will be converted to a string via JSON.stringify
213: */
214: // eslint-disable-next-line @typescript-eslint/no-explicit-any
215: function setOutput(name, value) {
216: const filePath = process.env['GITHUB_OUTPUT'] || '';
217: if (filePath) {
218: return (0, file_command_1.issueFileCommand)('OUTPUT', ...
Found 100 matches (more matches available)
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/utils/workspaces.ts:
Line 15: /** Information about a workspace package */
Line 25: /** Dependencies that are also workspace packages */
Line 26: workspaceDependencies: string[];
Line 29: /** Result of workspace discovery */
Line 31: /** The type of workspace manager detected */
Line 37: /** Structure of pnpm-workspace.yaml */
Line 45: workspaces?: string[] | { packages?: string[] };
Line 93: * Extract workspaces array from package.json workspaces field
Line 97: workspaces: string[] | { packages?: string[] } | undefined,
Line 99: if (!workspaces) {
Line 102: if (Array.isArray(workspaces)) {
Line 103: return workspaces;
Line 105: return workspaces.packages || [];
Line 115: // First: collect all workspace package names and locations
Line 116: const workspaceLocations: Array<{
Line 120: const workspaceNames = ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/utils/helpers.ts</path>
<type>file</type>
<content>
1: import { appendFileSync, mkdirSync, writeFileSync } from 'fs';
2: import path from 'path';
3: import { parseArgs } from 'node:util';
4:
5: import prompts from 'prompts';
6: import { logger, LogLevel, setLevel } from '../logger';
7:
8: /**
9: * Maximum size (in bytes) for step output values passed through GITHUB_OUTPUT.
10: * Values exceeding this are truncated to avoid E2BIG errors when GitHub Actions
11: * expands them into environment variables for subsequent steps.
12: *
13: * 64 KB is well under the ~2 MB ARG_MAX kernel limit and also under GitHub's
14: * ~65 536-character issue body limit, so truncated changelogs still render. ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/schemas/project_config.ts</path>
<type>file</type>
<content>
150: calver: CalVerConfigSchema.optional(),
151: });
152:
153: export type VersioningConfig = z.infer<typeof VersioningConfigSchema>;
154:
155: /**
156: * Changelog configuration
157: */
158: export const ChangelogConfigSchema = z.union([
159: z.string(),
160: z.object({
161: filePath: z.string().optional(),
162: policy: z.enum(['auto', 'simple', 'none']).optional(),
163: scopeGrouping: z.boolean().optional(),
164: }),
165: ]);
166:
167: /**
168: * Fields that describe how a single release unit is built and published.
169: *
170: * These are shared between the top-level config (the implicit/default release
171: * unit) and each entry under the top-level `workspaces` map (an explicit,
172: * independently-versioned release unit). A workspace inherits the top-level
173: * values as defaults and overrides the fields it declares. ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows/ci-poller.yml</path> <type>file</type> <content> 1: name: CI Status Poller 2: 3: on: 4: schedule: 5: - cron: "*/5 * * * *" 6: workflow_dispatch: 7: inputs: 8: attempt: 9: description: Self-dispatch attempt counter (internal use) 10: required: false 11: default: "0" 12: 13: permissions: 14: contents: read 15: issues: read 16: 17: jobs: 18: check-ci: 19: runs-on: ubuntu-latest 20: environment: production 21: # Skip entirely (no runner provisioned) when there's nothing to check. 22: # Set to "true" by ci-pending.yml, reset to "false" here when done. 23: # Always allow workflow_dispatch for manual recovery. 24: if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch' 25: concurrency: 26: group: ci-status-poller 27: cancel-in-progress: false 28: steps: 29: # sentry-internal-app token ...
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/pnpm-lock.yaml</path> <type>file</type> <content> 1: lockfileVersion: '9.0' 2: 3: settings: 4: autoInstallPeers: true 5: excludeLinksFromLockfile: false 6: 7: overrides: 8: undici: ^6.23.0 9: 10: importers: 11: 12: .: 13: dependencies: 14: '@actions/core': 15: specifier: ^2.0.0 16: version: 2.0.3 17: '@actions/github': 18: specifier: ^7.0.0 19: version: 7.0.0 20: '@sentry/node': 21: specifier: ^10.0.0 22: version: 10.71.0(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1)) 23: devDependencies: 24: eslint: 25: specifier: ^8.9.0 26: version: 8.57.1 27: eslint-config-prettier: 28: specifier: ^8.3.0 29: version: 8.10.2(eslint@8.57.1) 30: eslint-plugin-yml: 31: specifier: ^0.13.0 32: version: 0.13.0(eslint@8.57.1) 33: peggy: 34: specifier: 5.1.0 35: version: ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/docs/publish-issue-format.md</path>
<type>file</type>
<content>
1: # Publish Issue Format
2:
3: The Publish workflow treats a publish issue as a release request. The title identifies
4: the release; the body supplies the merge target and selected targets. Do not edit the
5: title by hand unless it remains valid under this format.
6:
7: ## Title
8:
9: Every title starts with `publish: `. This EBNF is canonical:
10:
11: ```text
12: title = "publish: ", [ "getsentry/" ], repository, [ path ],
13: [ legacy-workspace ], "@", version ;
14: repository = token, { token } ;
15: path = "/", path-segment, { "/", path-segment } ;
16: path-segment = token, { token } ;
17: legacy-workspace = " [workspace: ", json-string, "] " ;
18: version = version-character, { version-character } ;
19: token = ? ASCII letter, digit, ".", "_", or "-" ? ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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: "generate": "node scripts/generate-publish-issue-title-parser.js",
19: "check:generated": "node scripts/generate-publish-issue-title-parser.js --check",
20: "test": "yarn check:generated && vitest run",
21: "test:watch": "vitest",
22: "lint": "eslint src .github --ignore-pattern '!.github'",
23: "prettier": "prettier --write src"
24: },
25: "devDependencies": {
26: "eslint": ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/__tests__/config.test.ts</path>
<type>file</type>
<content>
120: const data = { noMerge: false };
121: expect(validateConfiguration(data)).toEqual(data);
122: });
123:
124: test('noMerge defaults to undefined when not specified', () => {
125: const data = { github: { owner: 'getsentry', repo: 'craft' } };
126: const result = validateConfiguration(data);
127: expect(result.noMerge).toBeUndefined();
128: });
129:
130: test('fails with invalid noMerge type', () => {
131: expect(() => validateConfiguration({ noMerge: 'yes' })).toThrow(/noMerge/);
132: });
133:
134: test('parses configuration with workspaces', () => {
135: const data = {
136: minVersion: '2.27.0',
137: github: { owner: 'getsentry', repo: 'toolkit' },
138: workspaces: {
139: cli: {
140: releaseBranchPrefix: 'release/cli',
141: targets: [{ name: 'github', tagPrefix: ...
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/__tests__/action.test.ts</path>
<type>file</type>
<content>
150: test('passes option-looking workspace names inline to Craft', () => {
151: const environment = createActionEnvironment();
152: const workspace = '--config-from=untrusted';
153:
154: expect(runActionStep('Craft Prepare', workspace, environment).status).toBe(0);
155: expect(
156: runActionStep('Read Craft Targets', workspace, environment).status,
157: ).toBe(0);
158:
159: expect(readFileSync(environment.craftCalls, 'utf8')).toBe(
160: 'prepare --workspace=--config-from=untrusted\ntargets --workspace=--config-from=untrusted\n',
161: );
162: });
163:
164: test('forwards workspace input to every Craft command', () => {
165: expect(getActionStep('Validate workspace').env?.PATH_INPUT).toBe(
166: '${{ inputs.path }}',
167: );
168: expect(getActionStep('Craft Prepare').env?.WORKSPACE).toBe(
169: '${{ inputs.workspace ...
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.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: workflow_dispatch:
10:
11: permissions:
12: contents: read
13:
14: jobs:
15: keepalive:
16: name: Refresh CocoaPods Session
17: runs-on: macos-15
18: environment: production
19: env:
20: COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
21:
22: steps:
23: - name: Install CocoaPods
24: run: gem install cocoapods
25: - name: Print CocoaPods Version
26: run: pod ...
Reviewed both full branch ranges and current dirty trees read-only.
A decoy same-repo check-runs link can select a different revision.
getReleaseRevision() accepts the first matching link anywhere in the issue body, not the canonical Quick links entry: publish/src/modules/release-revision.js:1-12. That output drives both checkout and craft publish --rev: publish/.github/workflows/publish.yml:164-167,247-249.
Read-only reproduction: an earlier getsentry/toolkit decoy returned its aaaaaaaa… SHA instead of the later Quick-links SHA. Repository equality is enforced, but a different CI-green commit in the same repository can still be published.
The CI poller has the same broad extraction flaw: publish/.github/workflows/ci-poller.yml:107-110. Tests cover missing, wrong-repo, and uppercase links, but not decoys or duplicates: publish/src/modules/__tests__/release-revision.js:26-49.
Fix: Parse exactly one check-runs link from the canonical Quick links block; reject missing, duplicate, or out-of-block links. Apply the same rule in the CI poller. Update docs: docs/publish-issue-format.md:91-92 incorrectly calls this now-security-critical link “informational.”
Craft accepts the workspace name .., but Publish rejects its compact title.
Craft permits .. through WorkspaceNameSchema: craft/src/schemas/project_config.ts:218-225, and action validation accepts it: craft/action.yml:84. Craft then creates …/..@version: craft/action.yml:268. Publish converts that to ./.. and rejects it as traversal: publish/src/modules/details-from-context.js:53-55.
This makes a configured workspace unreleasable after craft prepare has already run. Neither config nor action tests cover ./..: craft/src/__tests__/config.test.ts:163-178, craft/src/__tests__/action.test.ts:207-234.
Fix: Reject . and .. as workspace names consistently in Craft’s schema, action validation, and ...
Date: Aug 29, 2026 * 🟡 (13:20) `publish-workspace-acceptance/package.json` specifies package `publish` version `0.0.1`, private, Apache-2.0, repository `git@github.com:getsentry/publish.git`; Volta pins Node `24.0.0` and Yarn `1.22.22`; `undici` resolution is `^6.23.0`. Scripts: `generate` runs `node scripts/generate-publish-issue-title-parser.js`; `check:generated` adds `--check`; `test` runs `…
Date: Aug 29, 2026 * 🔴 (13:19) User asserted that new Craft publish requests always include the checkout repository identity. * 🔴 (13:19) User asserted that the controller never normalizes names; workspace names preserve exact spelling. * 🟡 (13:19) `docs/publish-issue-format.md` defines canonical publish-title EBNF: `title = "publish: ", [ "getsentry/" ], repository, [ path ], [ legacy-workspa…
Date: Aug 29, 2026 * 🟡 (13:14) Inspected repository history in `/home/byk/Code/getsentry/publish-workspace-acceptance`: commit `33de3f1` (`feat: accept workspace publish requests`) changed `.github/workflows/publish.yml`, `src/modules/details-from-context.js`, `src/modules/update-issue.js`, and related tests (377 insertions, 40 deletions); commit `b658ffa` (`fix: validate publish workspace JSON`…
Date: Aug 29, 2026 * 🟡 (13:12) Inspected `/home/byk/Code/getsentry/craft-workspace-action-propagation/src/utils/publishState.ts`: module moves Craft publish-state files out of the published repository checkout to `$XDG_STATE_HOME/craft/`, falling back to `$HOME/.local/state/craft/`, preventing committed `.craft-publish-<version>.json` files or earlier CI steps from pre-populating completed targe…
Date: Aug 29, 2026 * 🟡 (13:07) Tool output inspected `/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/publish-location.js`: `needsWorkspaceDiscovery()` is tested true only for `{ path: "./cli" }`, and false for `{ path: "./packages/cli" }`, `{ path: "." }`, and `{ path: "./cli", workspace: "cli" }`. * 🟡 (13:07) `publish-location.js` tests require exact, case-preservi…
Date: Aug 29, 2026 * 🟡 (13:05) Tool output enumerated publish-workspace-acceptance files: `/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-release-revision.js`, `/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-location.js`, `/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/release-revision.js`, `/home/byk/Code/getsentry/publish…
Date: Aug 29, 2026 * 🟡 (13:04) Tool output identified publish-workspace-acceptance module files: `/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/release-revision.js`, `/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-location.js`, `/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.js`, and `/home/byk/Code/getsentry/p…
Date: Aug 29, 2026 * 🔴 (13:04) User specified that workspace min-version compatibility relaxation applies only to the CURRENT Craft version side, never the configured `minVersion` side. * 🔴 (13:04) User specified that new Craft publish requests always include the checkout repository identity. * 🔴 (13:04) User specified that the publish controller never normalizes workspace names; it preserves …
Date: Aug 29, 2026 * 🟡 (13:03) Workspace propagation change modifies 10 files with 613 insertions and 82 deletions: `.github/workflows/release.yml`, `action.yml`, `docs/src/content/docs/targets/github.md`, `src/__tests__/action.test.ts`, `src/__tests__/config.test.ts`, `src/commands/__tests__/publish.test.ts`, `src/commands/publish.ts`, `src/config.ts`, `src/index.ts`, and `src/schemas/project_c…
Date: Aug 29, 2026 * 🟡 (13:02) Craft `action.yml` defines composite GitHub Action `Craft Prepare Release` with inputs `version`, `merge_target`, `force` (default `'false'`), `blocker_label` (default `'release-blocker'`), `publish_repo`, `git_user_name`, `git_user_email`, `path` (default `'.'`), `workspace`, `craft_config_from_merge_target` (default `'false'`), and `craft_version` (default `''`);…
Date: Aug 29, 2026 * 🔴 (12:57) User stated Publish repository error-handling convention: always throw for unexpected values, including a `default` switch case such as `throw new Error(\`Unknown status: '${status}'\`)`. * 🟡 (12:57) Publish worktree `AGENTS.md` describes the Sentry Publish repository as a GitHub Actions approval system for releases across Sentry projects using Craft; it is a Comm…
Date: Aug 29, 2026 * 🔴 [requested-review] (12:48) User requested a rigorous, read-only final adversarial review of uncommitted compact workspace publish-contract changes in both worktrees: `/home/byk/Code/getsentry/craft-workspace-action-propagation` on branch `feat/workspace-action-propagation`, and `/home/byk/Code/getsentry/publish-workspace-acceptance` on branch `feat/workspace-acceptance`. U…