Skip to content

Unhide shopify app security commands #7843

Unhide shopify app security commands

Unhide shopify app security commands #7843

Workflow file for this run

name: Release
on:
# Trigger for snapit functionality
issue_comment:
types:
- created
# Trigger for changeset release functionality
push:
branches:
- main
- stable/*
# Trigger for manual/cron release functionality
schedule:
- cron: '0 6 * * *' # 6:00 AM UTC every day
workflow_dispatch:
inputs:
tag:
description: 'Tag'
default: 'nightly'
type: choice
options:
- nightly
- latest
- experimental
- snapshot
snapit_comment_id:
description: 'PR comment requesting a snapshot (set automatically by /snapit)'
type: string
snapit_sha:
description: 'PR commit to publish (set automatically by /snapit)'
type: string
concurrency:
group: changeset-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
env:
PNPM_VERSION: '10.11.1'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
# npm rejects issue_comment OIDC tokens, so comments only request a separate run.
snapit:
name: Request snapshot
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.comment.body == '/snapit' }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
actions: write
contents: read
pull-requests: write
steps:
- name: Request a snapshot release
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const {comment, issue, repository} = context.payload;
try {
const {data: collaborator} = await github.rest.repos.getCollaboratorPermissionLevel({
...context.repo,
username: comment.user.login,
});
if (!['write', 'admin'].includes(collaborator.permission)) {
throw new Error('Only users with write permission to the repository can run /snapit.');
}
const {data: pullRequest} = await github.rest.pulls.get({
...context.repo,
pull_number: issue.number,
});
if (pullRequest.state !== 'open') {
throw new Error('Snapshots can only be requested for open pull requests.');
}
if (pullRequest.head.repo?.full_name !== repository.full_name) {
throw new Error('/snapit is not supported on pull requests from forked repositories.');
}
await github.rest.actions.createWorkflowDispatch({
...context.repo,
workflow_id: 'release.yml',
ref: repository.default_branch,
inputs: {
tag: 'snapshot',
snapit_comment_id: String(comment.id),
snapit_sha: pullRequest.head.sha,
},
});
await github.rest.reactions.createForIssueComment({
...context.repo,
comment_id: comment.id,
content: 'eyes',
}).catch((error) => core.warning(`Snapshot requested, but the reaction failed: ${error.message}`));
core.info(`Requested a snapshot for PR #${issue.number} at ${pullRequest.head.sha}.`);
} catch (error) {
core.setFailed(error.message);
await github.rest.issues.createComment({
...context.repo,
issue_number: issue.number,
body: `Unable to request a snapshot: ${error.message}`,
});
}
snapshot-release:
name: Publish PR snapshot
if: ${{ github.event_name == 'workflow_dispatch' && (inputs.snapit_comment_id != '' || inputs.snapit_sha != '') }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- name: Validate snapshot request
id: request
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const {tag, snapit_comment_id: commentId, snapit_sha: sha} = context.payload.inputs;
if (
tag !== 'snapshot' ||
!/^[1-9]\d*$/.test(commentId) ||
!Number.isSafeInteger(Number(commentId)) ||
!/^[a-f0-9]{40}$/.test(sha)
) {
throw new Error('A snapshot request requires a comment ID and a full PR commit SHA.');
}
// Recheck the original request before checking out or executing any PR code.
const {data: comment} = await github.rest.issues.getComment({
...context.repo,
comment_id: Number(commentId),
});
if (comment.body !== '/snapit') {
throw new Error('The requesting comment must contain /snapit.');
}
const {data: collaborator} = await github.rest.repos.getCollaboratorPermissionLevel({
...context.repo,
username: comment.user.login,
});
if (!['write', 'admin'].includes(collaborator.permission)) {
throw new Error('Only users with write permission to the repository can run /snapit.');
}
const issueUrlPrefix = `https://api.github.com/repos/${context.repo.owner}/${context.repo.repo}/issues/`;
const pullRequestNumber = Number(comment.issue_url.slice(issueUrlPrefix.length));
if (
!comment.issue_url.startsWith(issueUrlPrefix) ||
!Number.isSafeInteger(pullRequestNumber) ||
pullRequestNumber < 1
) {
throw new Error('The requesting comment must belong to a PR in this repository.');
}
const {data: pullRequest} = await github.rest.pulls.get({
...context.repo,
pull_number: pullRequestNumber,
});
// Keep enough context to report failures for an authorized request.
core.setOutput('pull_request', pullRequestNumber);
core.setOutput('comment_id', comment.id);
core.setOutput('requester', comment.user.login);
core.setOutput('sha', sha);
if (
pullRequest.state !== 'open' ||
pullRequest.head.repo?.full_name !== `${context.repo.owner}/${context.repo.repo}`
) {
throw new Error('Snapshots require an open PR from this repository.');
}
if (pullRequest.head.sha !== sha) {
throw new Error('The PR changed after the request. Post /snapit again to publish the current commit.');
}
- name: Checkout PR commit
uses: actions/checkout@v6
with:
ref: ${{ steps.request.outputs.sha }}
fetch-depth: 0
persist-credentials: false
- name: Setup deps
uses: ./.github/actions/setup-cli-deps
with:
node-version: 24.12.0
- name: Publish snapshot
id: publish
run: |
pnpm release snapshot
VERSION=$(node -p "require('./packages/cli/package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ''
NPM_CONFIG_PROVENANCE: true
SHOPIFY_CLI_BUILD_REPO: ${{ github.repository }}
- name: Report snapshot result
if: ${{ always() && steps.request.outputs.pull_request != '' }}
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
SNAPSHOT_OUTCOME: ${{ steps.publish.outcome }}
SNAPSHOT_VERSION: ${{ steps.publish.outputs.version }}
SNAPSHOT_PR: ${{ steps.request.outputs.pull_request }}
SNAPSHOT_COMMENT: ${{ steps.request.outputs.comment_id }}
SNAPSHOT_REQUESTER: ${{ steps.request.outputs.requester }}
SNAPSHOT_SHA: ${{ steps.request.outputs.sha }}
with:
script: |
const {
SNAPSHOT_OUTCOME: outcome,
SNAPSHOT_VERSION: version,
SNAPSHOT_REQUESTER: requester,
SNAPSHOT_SHA: sha,
} = process.env;
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const published = outcome === 'success';
const body = published
? [
`🫰✨ **Thanks @${requester}! Your snapshot has been published to npm.**`,
`Built from \`${sha}\`. [Workflow run](${runUrl}).`,
'Test the snapshot by installing your package globally:',
[
'```bash',
`pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@${version}`,
'```',
].join('\n'),
[
'> [!CAUTION]',
'> After installing, validate the version by running `shopify version` in your terminal.',
"> If the versions don't match, you might have multiple global instances installed.",
'> Use `which shopify` to find out which one you are running and uninstall it.',
].join('\n'),
].join('\n\n')
: `The snapshot requested by @${requester} for \`${sha}\` could not be published. [View the workflow run](${runUrl}).`;
await github.rest.issues.createComment({
...context.repo,
issue_number: Number(process.env.SNAPSHOT_PR),
body,
});
await github.rest.reactions.createForIssueComment({
...context.repo,
comment_id: Number(process.env.SNAPSHOT_COMMENT),
content: published ? 'rocket' : 'confused',
});
# Changeset release job - runs on push to main or stable branches
changeset-release:
name: Changeset Release
if: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag == '' && inputs.snapit_comment_id == '' && inputs.snapit_sha == '') }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
with:
fetch-depth: 0
- name: Setup deps
uses: ./.github/actions/setup-cli-deps
with:
node-version: 24.12.0
- name: Create Release Pull Request
id: changesets
uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1
with:
version: pnpm changeset-manifests
title: Version Packages - ${{ github.ref_name }}
createGithubReleases: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish packages
if: steps.changesets.outputs.hasChangesets == 'false'
run: pnpm release latest
env:
NPM_TOKEN: ''
NPM_CONFIG_PROVENANCE: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SHOPIFY_CLI_BUILD_REPO: ${{ github.repository }}
- name: Get version
id: version
if: steps.changesets.outputs.hasChangesets == 'false'
run: |
VERSION=$(node -p "require('./packages/cli/package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Version: $VERSION"
- name: Create tag
if: steps.changesets.outputs.hasChangesets == 'false'
env:
TAG: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
if git ls-remote --exit-code --tags origin "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists, skipping"
exit 0
fi
git tag "$TAG"
git push origin "$TAG"
echo "Created tag $TAG"
- name: Create stable branch
if: steps.changesets.outputs.hasChangesets == 'false' && github.ref_name == 'main' && endsWith(steps.version.outputs.version, '.0')
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
MINOR=${VERSION%.0}
BRANCH="stable/$MINOR"
if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
echo "Branch $BRANCH already exists, skipping"
exit 0
fi
git push origin "HEAD:refs/heads/$BRANCH"
echo "Created branch $BRANCH"
- name: Create GitHub release
if: steps.changesets.outputs.hasChangesets == 'false'
env:
TAG: ${{ steps.version.outputs.version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
# gh release create does not support the REST API's make_latest=legacy option.
RELEASE_ID="$(gh release view "$TAG" --json databaseId --jq .databaseId 2>/dev/null || true)"
if [ -n "$RELEASE_ID" ]; then
gh api \
--method PATCH \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID" \
-f make_latest=legacy >/dev/null
echo "Release $TAG already exists; ensured make_latest=legacy"
exit 0
fi
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"repos/$GITHUB_REPOSITORY/releases" \
-f tag_name="$TAG" \
-f name="$TAG" \
-F generate_release_notes=true \
-f make_latest=legacy >/dev/null
echo "Created release $TAG"
# Manual/Cron release job - runs on schedule or manual trigger with tag
manual-cron-release:
name: Manual & Cron Release
if: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' && inputs.snapit_comment_id == '' && inputs.snapit_sha == '') }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
with:
fetch-depth: 0
- name: Setup deps
uses: ./.github/actions/setup-cli-deps
with:
node-version: 24.12.0
- name: Release
run: pnpm release ${{ github.event.inputs.tag || 'nightly' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ''
NPM_CONFIG_PROVENANCE: true
SHOPIFY_CLI_BUILD_REPO: ${{ github.repository }}