Emit NDJSON `git-tag` events when `CHANGESETS_OUTPUT` is set by Andarist · Pull Request #2129 · changesets/changesets · GitHub
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
3 changes: 1 addition & 2 deletions .changeset/pre.json
5 changes: 5 additions & 0 deletions .changeset/real-apples-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/cli": minor
---

Commands supporting `--output` (such as `status` and `publish-plan`) can now be invoked with `CHANGESETS_OUTPUT=path/to/file` environment variable. This has the same effect as calling them with `--output=path/to/file`
5 changes: 5 additions & 0 deletions .changeset/strong-parrots-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/cli": minor
---

`changeset publish` and `changeset tag` can now be invoked with `CHANGESETS_OUTPUT=path/to/file` environment variable. They produce output in the NDJSON format.
5 changes: 0 additions & 5 deletions .changeset/yummy-garlics-carry.md

This file was deleted.

33 changes: 31 additions & 2 deletions packages/cli/src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, test, vi } from "vitest";
import { afterEach, describe, expect, test, vi } from "vitest";
import { cli } from "./cli.ts";
import { add } from "./commands/add/index.ts";
import { gitTag } from "./commands/git-tag/index.ts";
Expand All @@ -20,6 +20,10 @@ vi.mock("./commands/status/index.ts");
vi.mock("./commands/git-tag/index.ts");
vi.mock("./commands/pre/index.ts");

afterEach(() => {
vi.unstubAllEnvs();
});

interface CommandTest {
command: string;
fn: (...args: any[]) => unknown;
Expand All @@ -29,6 +33,7 @@ interface CommandTest {
interface CommandCase {
args: string[];
options: Record<string, unknown>;
env?: Record<string, string>;
}

const tests: CommandTest[] = [
Expand Down Expand Up @@ -145,6 +150,16 @@ const tests: CommandTest[] = [
gitTag: true,
},
},
{
args: [],
env: {
CHANGESETS_OUTPUT: "output.ndjson",
},
options: {
gitTag: true,
output: "output.ndjson",
},
},
],
},
{
Expand Down Expand Up @@ -220,6 +235,15 @@ const tests: CommandTest[] = [
args: [],
options: {},
},
{
args: [],
env: {
CHANGESETS_OUTPUT: "output.ndjson",
},
options: {
output: "output.ndjson",
},
},
],
},
{
Expand Down Expand Up @@ -251,9 +275,14 @@ const tests: CommandTest[] = [

for (const { command, fn, cases } of tests) {
describe(`changeset ${command}`, () => {
for (const { args, options } of cases) {
for (const { args, options, env } of cases) {
test(`${args.join(" ") || "<no args>"}`, async () => {
vi.clearAllMocks();
if (env) {
for (const [name, value] of Object.entries(env)) {
vi.stubEnv(name, value);
}
}
cli.parse(["node", "changeset", command, ...args], { run: false });
await cli.runMatchedCommand();

Expand Down
26 changes: 20 additions & 6 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ cli.help((sections) => {
cli.globalCommand.outputVersion = () => console.info(manifest.version);

function normalizeOptions(
options: Record<string, any>,
options: Record<string, unknown>,
{ array }: { array?: string[] } = {},
) {
// Do not allow positional arguments in options
Expand All @@ -33,7 +33,13 @@ function normalizeOptions(
// If the flag is expected to be an array, ensure it's an array.
if (array?.includes(key)) {
const v = options[key];
options[key] = Array.isArray(v) ? v.map(String) : [String(v)];
options[key] = Array.isArray(v)
? v.map(String)
: [
// those won't actually be objects and we want to stringify other primitive types
// eslint-disable-next-line @typescript-eslint/no-base-to-string
String(v),
];
}
// If a flag is passed multiple times (becoming an array), only take the last value.
else if (Array.isArray(options[key])) {
Expand All @@ -46,6 +52,14 @@ function normalizeOptions(
options[key] = String(options[key]);
}
}

return options;
}

function withEnvOptions(options: Record<string, unknown>) {
if (options.output == null && process.env.CHANGESETS_OUTPUT) {
options.output = process.env.CHANGESETS_OUTPUT;
}
}

cli
Expand Down Expand Up @@ -103,7 +117,7 @@ cli
.option("--from-pack-dir <dir>", "Publish from a packed output directory")
.option("--git-tag", "Create a git tag for the release", { default: true })
.action(async (options) => {
normalizeOptions(options);
withEnvOptions(normalizeOptions(options));
const { publish } = await import("./commands/publish/index.ts");
await publish(options);
});
Expand All @@ -115,7 +129,7 @@ cli
"Output the publish plan as JSON to a file [experimental]",
)
.action(async (options) => {
normalizeOptions(options);
withEnvOptions(normalizeOptions(options));
const { publishPlan } = await import("./commands/publish-plan/index.ts");
await publishPlan(options);
});
Expand Down Expand Up @@ -144,7 +158,7 @@ cli
.option("-v, --verbose", "Show more information about the changesets")
.option("-o, --output <file>", "Output the status as JSON to a file")
.action(async (options) => {
normalizeOptions(options);
withEnvOptions(normalizeOptions(options));
const { status } = await import("./commands/status/index.ts");
await status(options);
});
Expand All @@ -158,7 +172,7 @@ cli
"The 'tag' command is deprecated. Please use 'git-tag' instead.",
);
}
normalizeOptions(options);
withEnvOptions(normalizeOptions(options));
const { gitTag } = await import("./commands/git-tag/index.ts");
await gitTag(options);
});
Expand Down
42 changes: 42 additions & 0 deletions packages/cli/src/commands/git-tag/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fs from "node:fs/promises";
import path from "node:path";
import * as git from "@changesets/git";
import { silenceLogsInBlock, testdir } from "@changesets/test-utils";
import { describe, expect, it, type Mock, vi } from "vitest";
Expand Down Expand Up @@ -39,6 +41,46 @@ describe("git-tag command", () => {
expect((git.tag as Mock).mock.calls[1][0]).toEqual("pkg-b@1.0.0");
});

it("writes tag events when output path is provided", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"package-lock.json": "",
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
}),
"packages/pkg-b/package.json": JSON.stringify({
name: "pkg-b",
version: "1.0.0",
}),
".changeset/config.json": JSON.stringify({}),
});
const outputFile = path.join(cwd, "output.ndjson");

(git.getAllTags as Mock).mockReturnValue(new Set());

await gitTag({ cwd, output: outputFile });

await expect(fs.readFile(outputFile, "utf8")).resolves.toBe(
[
JSON.stringify({
type: "git-tag",
tag: "pkg-a@1.0.0",
packageName: "pkg-a",
}),
JSON.stringify({
type: "git-tag",
tag: "pkg-b@1.0.0",
packageName: "pkg-b",
}),
"",
].join("\n"),
);
});

it("skips tags that already exist", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/src/commands/git-tag/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { shouldSkipPackage } from "@changesets/should-skip-package";
import { log, progress } from "@clack/prompts";
import { getPackages, type Tool } from "@manypkg/get-packages";
import { getUntaggedPackages } from "../../utils/getUntaggedPackages.ts";
import { createOutputReport } from "../../utils/output.ts";
import { readConfig } from "../../utils/read-config.ts";
import { ensureChangesetFolder } from "../shared.ts";

Expand All @@ -24,6 +25,7 @@ function buildTagMessage(

export interface GitTagOptions {
cwd?: string;
output?: string;
}

export async function gitTag(options?: GitTagOptions) {
Expand Down Expand Up @@ -58,11 +60,18 @@ export async function gitTag(options?: GitTagOptions) {
const p = progress({ max: untaggedPackages.length - skippedTags.length });
p.start("Creating tags...");

await using reporter = await createOutputReport(options?.output);

for (const pkg of untaggedPackages) {
const tag = buildTag(packages.tool, pkg);
if (allExistingTags.has(tag)) continue;

await git.tag(tag, packages.rootDir);
reporter?.write({
type: "git-tag",
tag,
packageName: pkg.name,
});

p.advance(1, buildTagMessage(packages.tool, pkg));
}
Expand Down
50 changes: 50 additions & 0 deletions packages/cli/src/commands/publish/__tests__/index.test.ts
Loading