Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/eight-clocks-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/action": minor
---

Add new `/select-mode`, `/version`, and `/publish` sub-actions to better control version and publish steps
3 changes: 3 additions & 0 deletions publish/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# changesets/action/publish

TODO
26 changes: 26 additions & 0 deletions publish/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Changesets - Publish
description: Publish packages to npm
runs:
using: node24
main: ../dist/publish.js
inputs:
github-token:
description: "The GitHub token to use for authentication. Defaults to the GitHub-provided token."
required: false
default: ${{ github.token }}
script:
description: "The command to use to publish packages"
required: false
create-github-releases:
description: "Whether to create Github releases after publish"
required: false
default: true
outputs:
published:
description: "A boolean value to indicate whether a publishing has happened or not"
publishedPackages:
description: >
A JSON array to present the published packages. The format is `[{"name": "@xx/xx", "version": "1.2.0"}, {"name": "@xx/xy", "version": "0.8.9"}]`
branding:
icon: package
color: blue
3 changes: 3 additions & 0 deletions rolldown.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export default defineConfig({
index: "src/index.ts",
["pr-status"]: "src/pr-status/index.ts",
["pr-comment"]: "src/pr-comment/index.ts",
["select-mode"]: "src/select-mode/index.ts",
version: "src/version/index.ts",
publish: "src/publish/index.ts",
},
output: {
dir: "dist",
Expand Down
3 changes: 3 additions & 0 deletions select-mode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# changesets/action/select-mode

TODO
12 changes: 12 additions & 0 deletions select-mode/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Changesets - Select Mode
description: Whether to version or publish in the current repo state
runs:
using: node24
main: ../dist/select-mode.js
inputs: {}
outputs:
mode:
description: "The mode to use for the current repo state: 'version', 'publish', or 'none'."
branding:
icon: package
color: blue
55 changes: 55 additions & 0 deletions src/publish/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as core from "@actions/core";
import { Git } from "../git.ts";
import { setupOctokit } from "../octokit.ts";
import { runPublish } from "../run.ts";

try {
await main();
} catch (err) {
core.setFailed((err as Error).message);
}

async function main() {
const githubToken = core.getInput("github-token", { required: true });
const script = core.getInput("script");
const createGithubReleases = core.getBooleanInput("create-github-releases");

// If the user needs to change the cwd, set `working-directory` in the step instead
const cwd = process.cwd();

const octokit = setupOctokit(githubToken);
// NOTE: Always pass octokit here as publish does not need a commit-mode
const git = new Git({ octokit, cwd });

const result = await runPublish({
script,
githubToken,
git,
octokit,
createGithubReleases,
cwd,
});

if (result.published) {
core.setOutput("published", "true");
core.setOutput(
"publishedPackages",
JSON.stringify(result.publishedPackages),
);
} else {
core.setOutput("published", "false");
}

if (result.exitCode !== 0) {
throw new Error(
`Publish command exited with code ${result.exitCode}${
result.published
? `, but some packages were published: ${result.publishedPackages
.map((p) => `${p.name}@${p.version}`)
.join(", ")}`
: ""
}`,
);
process.exit(result.exitCode);
}
}
48 changes: 31 additions & 17 deletions src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import fs from "node:fs/promises";
import { createRequire } from "node:module";
import path from "node:path";
import * as core from "@actions/core";
import { exec, getExecOutput } from "@actions/exec";
import {
exec,
getExecOutput,
type ExecOptions,
type ExecOutput,
} from "@actions/exec";
import * as github from "@actions/github";
import type { PreState } from "@changesets/types";
import { type Package, getPackages } from "@manypkg/get-packages";
Expand Down Expand Up @@ -58,7 +63,7 @@ const createRelease = async (
};

type PublishOptions = {
script: string;
script?: string;
githubToken: string;
octokit: Octokit;
createGithubReleases: boolean;
Expand Down Expand Up @@ -87,11 +92,29 @@ export async function runPublish({
createGithubReleases,
cwd,
}: PublishOptions): Promise<PublishResult> {
let changesetPublishOutput = await getExecOutput(script, undefined, {
let changesetPublishOutput: ExecOutput;
const execOptions: ExecOptions = {
cwd,
ignoreReturnCode: true,
env: { ...process.env, GITHUB_TOKEN: githubToken },
});
};

if (script) {
changesetPublishOutput = await getExecOutput(
script,
undefined,
execOptions,
);
} else {
const changesetsCliBin = require.resolve("@changesets/cli/bin.js", {
paths: [cwd],
});
changesetPublishOutput = await getExecOutput(
"node",
[changesetsCliBin, "publish"],
execOptions,
);
}

let { packages, tool } = await getPackages(cwd);
let releasedPackages: Package[] = [];
Expand Down Expand Up @@ -277,19 +300,10 @@ export async function runVersion({
if (script) {
await exec(script, undefined, { cwd, env });
} else {
await exec(
"node",
[
require.resolve("@changesets/cli/bin.js", {
paths: [cwd],
}),
"version",
],
{
cwd,
env,
},
);
const changesetsCliBin = require.resolve("@changesets/cli/bin.js", {
paths: [cwd],
});
await exec("node", [changesetsCliBin, "version"], { cwd, env });
}

let changedPackages = await getChangedPackages(cwd, versionsByDirectory);
Expand Down
30 changes: 30 additions & 0 deletions src/select-mode/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as core from "@actions/core";
import readChangesetState from "../readChangesetState.ts";

try {
await main();
} catch (err) {
core.setFailed((err as Error).message);
}

async function main() {
const mode = await getMode();
core.setOutput("mode", mode);
}

async function getMode(): Promise<"version" | "publish" | "none"> {
const { changesets } = await readChangesetState();

if (changesets.length > 0) {
const hasNonEmptyChangesets = changesets.some(
(changeset) => changeset.releases.length > 0,
);
if (hasNonEmptyChangesets) {
return "version";
} else {
return "none";
}
} else {
return "publish";
}
}
56 changes: 56 additions & 0 deletions src/version/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as core from "@actions/core";
import { Git } from "../git.ts";
import { setupOctokit } from "../octokit.ts";
import { runVersion } from "../run.ts";

try {
await main();
} catch (err) {
core.setFailed((err as Error).message);
}

async function main() {
const githubToken = core.getInput("github-token", { required: true });
const script = core.getInput("script");
const commitMessage = core.getInput("commit-message", { required: true });
const prTitle = core.getInput("pr-title", { required: true });
const prDraft = core.getInput("pr-draft") || undefined;
const baseBranch = core.getInput("base-branch");
const commitMode = core.getInput("commit-mode") || "git-cli";
const setupGitUser = core.getBooleanInput("setup-git-user");

// Validations
if (prDraft !== undefined && prDraft !== "always" && prDraft !== "create") {
throw new Error(`Invalid pr-draft input: ${prDraft}`);
}

// If the user needs to change the cwd, set `working-directory` in the step instead
const cwd = process.cwd();

const octokit = setupOctokit(githubToken);
const git = new Git({
octokit: commitMode === "github-api" ? octokit : undefined,
cwd,
});

if (setupGitUser) {
core.info("setting git user");
await git.setupUser();
}

const { pullRequestNumber } = await runVersion({
script,
githubToken,
git,
octokit,
cwd,
prTitle,
commitMessage,
// TODO: Use neutral message for PR description
hasPublishScript: true,
prDraft,
branch: baseBranch,
});

core.setOutput("pr-number", String(pullRequestNumber));
}
3 changes: 3 additions & 0 deletions version/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# changesets/action/version

TODO
43 changes: 43 additions & 0 deletions version/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Changesets - Version
description: Version packages
runs:
using: node24
main: ../dist/version.js
inputs:
github-token:
description: "The GitHub token to use for authentication. Defaults to the GitHub-provided token."
required: false
default: ${{ github.token }}
script:
description: "The command to use to publish packages"
required: false
commit-message:
description: "The commit message. Default to `Version Packages`"
required: false
default: "Version Packages"
pr-title:
description: "The pull request title. Default to `Version Packages`"
required: false
default: "Version Packages"
pr-draft:
description: "Controls draft PR behavior. Use 'create' to create new version PRs as draft, or 'always' to also convert existing version PRs back to draft when updating them."
required: false
base-branch:
description: "Sets the base branch of the PR. Defaults to `github.ref_name`."
required: false
commit-mode:
description: >
An enum to specify the commit mode. Use "git-cli" to push changes using the Git CLI,
or "github-api" to push changes via the GitHub API. When using "github-api",
all commits and tags are signed using GitHub's GPG key and attributed to the user
or app who owns the GITHUB_TOKEN.
required: false
default: "git-cli"
setup-git-user:
description: Sets up the git user for commits as `"github-actions[bot]"`. Default to `true`
required: false
default: true
outputs: {}
branding:
icon: package
color: blue