Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ on:
force:
description: Force a release even when there are release-blockers (optional)
required: false
workspace:
description: Named Craft release workspace to prepare and publish
required: false

# For external repos to call this workflow
workflow_call:
Expand Down Expand Up @@ -51,6 +54,10 @@ on:
type: string
required: false
default: '.'
workspace:
description: Named Craft release workspace to prepare and publish
type: string
required: false
craft_config_from_merge_target:
description: Use the craft config from the merge target branch
type: string
Expand Down Expand Up @@ -122,6 +129,7 @@ jobs:
with:
version: ${{ github.event.inputs.version }}
force: ${{ github.event.inputs.force }}
workspace: ${{ github.event.inputs.workspace }}

# For external repos: use published action
- name: Prepare release
Expand All @@ -139,4 +147,5 @@ jobs:
git_user_name: ${{ inputs.git_user_name }}
git_user_email: ${{ inputs.git_user_email }}
path: ${{ inputs.path }}
workspace: ${{ inputs.workspace }}
craft_config_from_merge_target: ${{ inputs.craft_config_from_merge_target }}
40 changes: 37 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ inputs:
description: The path that Craft will run inside
required: false
default: '.'
workspace:
description: Named Craft release workspace to prepare and publish
required: false
craft_config_from_merge_target:
description: Use the craft config from the merge target branch
required: false
Expand Down Expand Up @@ -68,6 +71,26 @@ outputs:
runs:
using: 'composite'
steps:
- name: Validate workspace
shell: bash
env:
LC_ALL: C
PATH_INPUT: ${{ inputs.path }}
WORKSPACE: ${{ inputs.workspace }}
run: |
if [[ -n "$WORKSPACE" && "$PATH_INPUT" != '.' ]]; then
echo "::error::The path and workspace inputs cannot be used together."
exit 1
fi
if ! node -e 'const path = process.env.PATH_INPUT; const segment = /^[A-Za-z0-9_.-]+$/; process.exit(path === "." || (typeof path === "string" && path.split("/").every(name => segment.test(name) && ![".", "..", "__proto__"].includes(name) && !name.startsWith("-"))) ? 0 : 1)'; then
echo "::error::The path input must use ASCII path segments containing only letters, digits, periods, underscores, and hyphens."
exit 1
fi
if [[ -n "$WORKSPACE" ]] && ! node -e 'const workspace = process.env.WORKSPACE; const segment = /^[A-Za-z0-9_.-]+$/; process.exit(workspace.split("/").every(name => segment.test(name) && ![".", "..", "__proto__"].includes(name) && !name.startsWith("-")) ? 0 : 1)'; then
echo "::error::Workspace names must use ASCII path segments containing only letters, digits, periods, underscores, and hyphens."
exit 1
fi

- id: killswitch
name: Check release blockers
shell: bash
Expand Down Expand Up @@ -162,8 +185,10 @@ runs:
CRAFT_CONFIG_FROM_MERGE_TARGET: ${{ inputs.craft_config_from_merge_target }}
MERGE_TARGET: ${{ inputs.merge_target }}
VERSION: ${{ inputs.version }}
WORKSPACE: ${{ inputs.workspace }}
working-directory: ${{ inputs.path }}
run: |
unset CRAFT_WORKSPACE
# Ensure we have origin/HEAD set
git remote set-head origin --auto

Expand All @@ -172,6 +197,9 @@ runs:
if [[ "$CRAFT_CONFIG_FROM_MERGE_TARGET" == 'true' && -n "$MERGE_TARGET" ]]; then
CRAFT_ARGS=(--config-from "$MERGE_TARGET")
fi
if [[ -n "$WORKSPACE" ]]; then
CRAFT_ARGS+=("--workspace=$WORKSPACE")
fi

# Version is optional - if not provided, Craft uses versioning.policy from config
VERSION_ARG=()
Expand All @@ -187,8 +215,14 @@ runs:
working-directory: ${{ inputs.path }}
env:
CRAFT_LOG_LEVEL: Warn
WORKSPACE: ${{ inputs.workspace }}
run: |
targets=$(craft targets | jq -r '.[]|" - [ ] \(.)"')
unset CRAFT_WORKSPACE
CRAFT_ARGS=()
if [[ -n "$WORKSPACE" ]]; then
CRAFT_ARGS=("--workspace=$WORKSPACE")
fi
targets=$(craft targets "${CRAFT_ARGS[@]}" | jq -r '.[]|" - [ ] \(.)"')

# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
echo "targets<<EOF" >> "$GITHUB_OUTPUT"
Expand All @@ -211,6 +245,7 @@ runs:
SUBDIRECTORY: ${{ inputs.path != '.' && format('/{0}', inputs.path) || '' }}
MERGE_TARGET: ${{ inputs.merge_target || '(default)' }}
PUBLISH_REPO: ${{ inputs.publish_repo || format('{0}/publish', github.repository_owner) }}
WORKSPACE: ${{ inputs.workspace }}
run: |
# Resolve "self" to the current repository
if [[ "$PUBLISH_REPO" == "self" ]]; then
Expand All @@ -221,7 +256,6 @@ runs:
echo "::error::Craft did not output a version. This is unexpected."
exit 1
fi

# Read changelog from file to avoid E2BIG.
# Produced by craft >= 2.22.0; older versions don't produce the file
# and the publish issue will be created without a changelog section.
Expand All @@ -238,7 +272,7 @@ runs:
CHANGELOG="${CHANGELOG:0:$MAX_CHANGELOG_CHARS}"$'\n\n---\n*Changelog truncated for issue body.*'
fi

title="publish: ${GITHUB_REPOSITORY}${SUBDIRECTORY}@${RESOLVED_VERSION}"
title="publish: ${GITHUB_REPOSITORY}${SUBDIRECTORY}${WORKSPACE:+/$WORKSPACE}@${RESOLVED_VERSION}"

# Check if issue already exists by listing all open issues and filtering by exact title match.
# We avoid GitHub search API to bypass indexing delays and query syntax edge cases.
Expand Down
42 changes: 38 additions & 4 deletions docs/src/content/docs/targets/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,46 @@ targets:

Releasing `1.2.3` for each product then produces the tags `cli@1.2.3` / `mcp@1.2.3` on release branches `release/cli/1.2.3` / `release/mcp/1.2.3` — no collisions.

:::note[Coming soon: first-class workspaces]
A single, target-agnostic top-level `workspaces:` model (with an explicit `--workspace` selector) is planned so that all of a repo's products can be managed from one `.craft.yml`. It will supersede the per-file convention above. Track progress in [getsentry/craft#842](https://github.com/getsentry/craft/issues/842).
:::
## Release Workspaces

Use top-level `workspaces:` to define independently versioned release units in
one repository. Keys may be literal paths or glob patterns, and Craft expands
patterns to concrete directories relative to `.craft.yml`. Select one concrete
path explicitly with `--workspace <path>` or
`CRAFT_WORKSPACE`:

```yaml
minVersion: 2.29.0
github:
owner: getsentry
repo: toolkit
workspaces:
Comment thread
BYK marked this conversation as resolved.
Resolved
packages/*:
releaseBranchPrefix: release/cli
targets:
- name: github
tagPrefix: "cli@"
tools/mcp:
releaseBranchPrefix: release/mcp
targets:
- name: github
tagPrefix: "mcp@"
```

Selected workspace paths use nonempty ASCII path segments containing letters,
digits, periods, underscores, and hyphens. Segments cannot be `.`, `..`,
`__proto__`, or start with `-`. They are release units, not npm package
workspaces. Craft ignores files matched by a configured glob and rejects
overlapping workspace keys. A workspace cannot use
`github.projectPath`, and a workflow cannot provide both a `path` and a
workspace. This keeps each publish request unambiguous.

The `craft workspace list` command prints the concrete workspace paths as a
JSON array for automation. Action publish titles always carry the full concrete
path.

:::caution
Declaring **multiple** `github` targets with **different** `tagPrefix` values in a *single* config is currently ambiguous: Craft uses the first prefix for read-path operations and logs a warning. Until workspaces land, use a separate `.craft.yml` per product.
Declaring **multiple** `github` targets with **different** `tagPrefix` values in a single release unit is ambiguous: Craft uses the first prefix for read-path operations and logs a warning. Use separate release workspaces for independent products.
:::

:::note[Known limitation: the GitHub "Latest" badge is repo-wide]
Expand Down
Loading
Loading