Skip to content
Closed
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
39 changes: 25 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

# /snapit

> Create a snapshot NPM release with `/snapit` comment in a PR
> Create a snapshot NPM release from a `/snapit` comment or manual workflow run

This GitHub action allows for automation of [Changesets Snapshot Release](https://github.com/changesets/changesets/blob/main/docs/snapshot-releases.md) with the comment `/snapit` in a pull request. Snapshot releases are a way to release your changes for testing without updating the versions.
This GitHub action automates [Changesets Snapshot Releases](https://github.com/changesets/changesets/blob/main/docs/snapshot-releases.md) from a `/snapit` pull request comment or a manual workflow run. Snapshot releases are a way to release your changes for testing without updating the versions.

## Usage

Expand All @@ -25,6 +25,12 @@ on:
issue_comment:
types:
- created
workflow_dispatch:
inputs:
pull_request_number:
description: Pull request number to snapshot
required: true
type: number

jobs:
snapit:
Expand All @@ -51,10 +57,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: true
with:
pull_request_number: ${{ inputs.pull_request_number }}
build_script: pnpm build # Optional
trigger_comment: /snapit # Default value not required
```

Once this workflow is on the default branch, run it from the Actions page by entering a pull request number, or with `gh workflow run snapit.yml -f pull_request_number=123`.

**Deploy to branch**

This is useful when orchestrating releases outside of GitHub actions or with other package registries.
Expand Down Expand Up @@ -102,18 +111,20 @@ Use [NPM Trusted Publishers](https://docs.npmjs.com/trusted-publishers) with OID

## GitHub Action Inputs

| Inputs (All Optional) | Description |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trigger_comment` | Default: `/snapit`. Comma seperated list of comments to write to trigger the creation of a snapshot. |
| `comment_prefix` | Custom message to add to the beginning of the release GitHub comment. Default: "Test the snapshots by updating your package.json with the newly published versions:" |
| `comment_suffix` | Custom message to add to the end of the release GitHub comment. |
| `comment_packages` | Filter to include specific packages in the release GitHub comment. Separate multiple packages with commas. |
| `comment_is_global` | If true, the generated GitHub comment will show the command to install your packages globally. Otherwise, shows a JSON example to update local dependencies. |
| `cwd` | If specified, the action will run all commands for snapit in the specified directory. |
| `branch` | Push the changes to a branch instead of publishing to the NPM registry. |
| `post_install_script` | If specified, will run a script after dependencies are installed. |
| `build_script` | The build script to run before publishing. |
| `release_branch` | Default: `changeset-release/main`. If specified, will use this branch name in place of the default |
| Inputs (All Optional) | Description |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pull_request_number` | Pull request to snapshot. Required when invoked by `workflow_dispatch`. |
| `trigger_comment` | Default: `/snapit`. Comma seperated list of comments to write to trigger the creation of a snapshot. |
| `comment_prefix` | Custom message to add to the beginning of the release GitHub comment. Default: "Test the snapshots by updating your package.json with the newly published versions:" |
| `comment_suffix` | Custom message to add to the end of the release GitHub comment. |
| `comment_packages` | Filter to include specific packages in the release GitHub comment. Separate multiple packages with commas. |
| `include_private_packages` | Include private packages in the generated snapshot output. Default: `false`. |
| `comment_is_global` | If true, the generated GitHub comment will show the command to install your packages globally. Otherwise, shows a JSON example to update local dependencies. |
| `cwd` | If specified, the action will run all commands for snapit in the specified directory. |
| `branch` | Push the changes to a branch instead of publishing to the NPM registry. |
| `post_install_script` | If specified, will run a script after dependencies are installed. |
| `build_script` | The build script to run before publishing. |
| `release_branch` | Default: `changeset-release/main`. If specified, will use this branch name in place of the default |

## Contributing

Expand Down
7 changes: 6 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
name: snapit-release
description: Create a snapshot NPM release with `/snapit` comment in a PR
description: Create a snapshot NPM release from a `/snapit` comment or manual workflow run
author: Shopify
inputs:
pull_request_number:
description: Pull request to snapshot. Required when invoked by workflow_dispatch.
trigger_comment:
description: A comma seperated list of comment commands to trigger the action
default: '/snapit'
Expand All @@ -11,6 +13,9 @@ inputs:
description: Custom message added to the end of the snapit GitHub comment.
comment_packages:
description: Which packages to include in the final output. All pacakges are still published, but only those in this list are shown in the final message. If empty, all packages are included.
include_private_packages:
description: Include private packages in the generated snapshot output.
default: 'false'
comment_is_global:
description: If true, the generated GitHub comment will show the command to install your packages globally. Otherwise, shows a JSON example to update local dependencies.
cwd:
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

86 changes: 53 additions & 33 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,25 @@ try {
);
}

const {payload} = github.context;
if (
!payload.comment ||
!payload.repository ||
!payload.repository.owner.login ||
!payload.issue ||
!payload.issue.title
) {
throw new Error('No comment, repository, or issue found in the payload');
const {payload, eventName, actor} = github.context;
const isManual = eventName === 'workflow_dispatch';

if (!isManual && (!payload.comment || !payload.issue)) {
throw new Error('No comment or issue found in the payload');
}

const ownerRepo = {
owner: payload.repository.owner.login,
repo: payload.repository.name,
};
const ownerRepo = github.context.repo;
const pullRequestNumber = isManual
? Number(core.getInput('pull_request_number'))
: payload.issue.number;

if (!Number.isInteger(pullRequestNumber) || pullRequestNumber <= 0) {
throw new Error(
'A valid pull_request_number input is required for manual runs',
);
}

const triggeringUser = isManual ? actor : payload.comment.user.login;
const branch = core.getInput('branch');
const commentCommand = core.getInput('comment_command');
const commentPrefix = core.getInput('comment_prefix');
Expand All @@ -38,6 +41,8 @@ try {
const postInstallScript = core.getInput('post_install_script');
const buildScript = core.getInput('build_script');
const commentPackages = core.getInput('comment_packages');
const includePrivatePackages =
core.getInput('include_private_packages') === 'true';
const cwd = core.getInput('cwd');
const octokit = github.getOctokit(process.env.GITHUB_TOKEN);
const releaseBranch =
Expand All @@ -52,18 +57,23 @@ try {
const changesetBinary = path.join('node_modules/.bin/changeset');
const versionPrefix = 'snapshot';

if (commentCommand.split(',').indexOf(payload.comment.body) !== -1) {
await octokit.rest.reactions.createForIssueComment({
...ownerRepo,
comment_id: payload.comment.id,
content: 'eyes',
});
if (
isManual ||
commentCommand.split(',').indexOf(payload.comment.body) !== -1
) {
if (!isManual) {
await octokit.rest.reactions.createForIssueComment({
...ownerRepo,
comment_id: payload.comment.id,
content: 'eyes',
});
}

// Check if user has permissions to run /snapit
const actorPermission = (
await octokit.rest.repos.getCollaboratorPermissionLevel({
...ownerRepo,
username: payload.comment.user.login,
username: triggeringUser,
})
).data.permission;

Expand All @@ -72,7 +82,7 @@ try {
'Only users with write permission to the repository can run /snapit';
await octokit.rest.issues.createComment({
...ownerRepo,
issue_number: payload.issue.number,
issue_number: pullRequestNumber,
body: errorMessage,
});
throw new Error(errorMessage);
Expand All @@ -81,18 +91,21 @@ try {
// Check if pull request is from a forked repo
const pullRequest = await octokit.rest.pulls.get({
...ownerRepo,
pull_number: payload.issue.number,
pull_number: pullRequestNumber,
});

if (payload.repository.full_name !== pullRequest.data.head.repo.full_name) {
if (
`${ownerRepo.owner}/${ownerRepo.repo}` !==
pullRequest.data.head.repo.full_name
) {
throw new Error(
'`/snapit` is not supported on pull requests from forked repositories.',
);
}

await exec(
'gh',
['pr', 'checkout', payload.issue.number.toString()],
['pr', 'checkout', pullRequestNumber.toString()],
silentOption,
);

Expand Down Expand Up @@ -142,7 +155,12 @@ try {
const snapshots: Snapshot[] = [];
packages.forEach(({packageJson}) => {
const {name, version, private: isPrivate} = packageJson;
if (name && version && !isPrivate && version.includes(versionPrefix)) {
if (
name &&
version &&
(!isPrivate || includePrivatePackages) &&
version.includes(versionPrefix)
) {
const timestamp = version.split('-').at(-1);
snapshots.push({
package: name,
Expand Down Expand Up @@ -183,7 +201,7 @@ try {
await exec('git', [
'commit',
'-m',
`${payload.issue.title} ${versionPrefix}-${snapshotTimestamp}`,
`${pullRequest.data.title} ${versionPrefix}-${snapshotTimestamp}`,
]);
await exec('git', ['checkout', '-b', branch]);
await exec('git', ['push', '--force', 'origin', branch]);
Expand Down Expand Up @@ -235,22 +253,24 @@ try {
: `Test the snapshot${multiple ? 's' : ''} by updating your \`package.json\` with the newly published version${multiple ? 's' : ''}:`;

const body =
`🫰✨ **Thanks @${payload.comment.user.login}! ${introMessage}` +
`🫰✨ **Thanks @${triggeringUser}! ${introMessage}` +
`${commentPrefix ? commentPrefix + ' ' : ''}${defaultMessage}\n` +
`${commentIsGlobal ? `${globalPackagesMessage}` : `${localDependenciesMessage}`}` +
`${commentSuffix ? `\n\n${commentSuffix}` : ''}`;

await octokit.rest.issues.createComment({
...ownerRepo,
issue_number: payload.issue.number,
issue_number: pullRequestNumber,
body,
});

await octokit.rest.reactions.createForIssueComment({
...ownerRepo,
comment_id: payload.comment.id,
content: 'rocket',
});
if (!isManual) {
await octokit.rest.reactions.createForIssueComment({
...ownerRepo,
comment_id: payload.comment.id,
content: 'rocket',
});
}
}
} catch (error) {
core.setFailed(error.message);
Expand Down
Loading