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 pack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# changesets/action/pack

TODO
15 changes: 15 additions & 0 deletions pack/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Changesets - Pack
description: Pack publishable packages into tarballs
runs:
using: node24
main: ../dist/pack.js
inputs:
publish-plan-artifact-id:
description: "Artifact id for a publish plan generated by the select-mode subaction"
required: false
outputs:
packed-artifact-id:
description: "Artifact id for the packed output directory"
branding:
icon: package
color: blue
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"release:pr": "node --experimental-strip-types ./scripts/release-pr.ts"
},
"dependencies": {
"@actions/artifact": "^6.2.1",
"@actions/core": "^3.0.1",
"@actions/exec": "^3.0.0",
"@actions/github": "^9.1.1",
Expand Down
945 changes: 943 additions & 2 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

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
29 changes: 29 additions & 0 deletions publish/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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
packed-artifact-id:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a good input name? maybe pack-dir-artifact-id would be better?

description: "Artifact id for packed publish output generated by the pack subaction"
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"
published-packages:
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
4 changes: 4 additions & 0 deletions rolldown.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import { defineConfig } from "rolldown";
export default defineConfig({
input: {
index: "src/index.ts",
pack: "src/pack/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
14 changes: 14 additions & 0 deletions select-mode/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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'."
publish-plan-artifact-id:
description: "Artifact id for the generated publish plan when mode is `publish`"
branding:
icon: package
color: blue
79 changes: 79 additions & 0 deletions src/pack/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import artifact from "@actions/artifact";
import * as core from "@actions/core";
import { downloadArtifact, execChangesetsCli } from "../utils.ts";

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

async function main() {
const publishPlanArtifactId = core.getInput("publish-plan-artifact-id");

// If the user needs to change the cwd, set `working-directory` in the step instead
const cwd = process.cwd();
const tmpDir = process.env.RUNNER_TEMP ?? (await fs.realpath(os.tmpdir()));
const outDir = path.join(tmpDir, `changeset-pack-${Date.now()}`);

await pack(cwd, {
outDir,
publishPlanPath: publishPlanArtifactId
? await downloadPublishPlanArtifact(tmpDir, Number(publishPlanArtifactId))
: undefined,
});

const packedArtifact = await artifact.uploadArtifact(
`changeset-pack-${Date.now()}`,
await getFiles(outDir),
outDir,
);
if (packedArtifact.id === undefined) {
throw new Error("Packed artifact upload did not return an artifact id");
}
core.setOutput("packed-artifact-id", String(packedArtifact.id));
}

async function pack(
cwd: string,
args: {
outDir: string;
publishPlanPath?: string;
},
) {
const cliArgs = ["pack", "--out-dir", args.outDir];
if (args.publishPlanPath) {
cliArgs.push("--from-plan", args.publishPlanPath);
}

await execChangesetsCli(cliArgs, {
cwd,
env: process.env,
});
}

async function downloadPublishPlanArtifact(tmpDir: string, artifactId: number) {
const downloadPath = await downloadArtifact(
tmpDir,
artifactId,
"changeset-publish-plan",
);
return path.join(downloadPath, "publish-plan.json");
}

async function getFiles(dir: string): Promise<string[]> {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files = await Promise.all(
entries.map(async (entry) => {
const entryPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
return getFiles(entryPath);
}
return [entryPath];
}),
);
return files.flat();
}
67 changes: 67 additions & 0 deletions src/publish/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import fs from "node:fs/promises";
import os from "node:os";
import * as core from "@actions/core";
import { Git } from "../git.ts";
import { setupOctokit } from "../octokit.ts";
import { runPublish } from "../run.ts";
import { downloadArtifact } from "../utils.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 packedArtifactId = core.getInput("packed-artifact-id");
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 fromPackDir = packedArtifactId
? await downloadArtifact(
process.env.RUNNER_TEMP ?? (await fs.realpath(os.tmpdir())),
Number(packedArtifactId),
"changeset-pack",
)
: undefined;

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

if (result.published) {
core.setOutput("published", "true");
core.setOutput(
"published-packages",
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(", ")}`
: ""
}`,
);
}
}
52 changes: 32 additions & 20 deletions src/run.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
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";
import { Git } from "./git.ts";
import type { Octokit } from "./octokit.ts";
import readChangesetState from "./readChangesetState.ts";
import {
execChangesetsCli,
getChangedPackages,
getChangelogEntry,
getExecOutputChangesetsCli,
getVersionsByDirectory,
isErrorWithCode,
sortTheThings,
} from "./utils.ts";

const require = createRequire(import.meta.url);

// GitHub Issues/PRs messages have a max size limit on the
// message body payload.
// `body is too long (maximum is 65536 characters)`.
Expand Down Expand Up @@ -58,7 +62,8 @@ const createRelease = async (
};

type PublishOptions = {
script: string;
script?: string;
fromPackDir?: string;
githubToken: string;
octokit: Octokit;
createGithubReleases: boolean;
Expand All @@ -81,17 +86,36 @@ type PublishResult =

export async function runPublish({
script,
fromPackDir,
githubToken,
git,
octokit,
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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note how with a custom publish script we don't handle fromPackDir at all. My best idea to handle this would be to also add support for passing this argument~ through CLI args. That way the scripts could forward env to the underlying changesets publish kinda naturally - without messing with flags forwarding. Thoughts?

changesetPublishOutput = await getExecOutput(
script,
undefined,
execOptions,
);
} else {
const args = ["publish"];
if (fromPackDir) {
args.push("--from-pack-dir", fromPackDir);
}
changesetPublishOutput = await getExecOutputChangesetsCli(
args,
execOptions,
);
}

let { packages, tool } = await getPackages(cwd);
let releasedPackages: Package[] = [];
Expand Down Expand Up @@ -277,19 +301,7 @@ 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,
},
);
await execChangesetsCli(["version"], { cwd, env });
}

let changedPackages = await getChangedPackages(cwd, versionsByDirectory);
Expand Down
Loading