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
21 changes: 14 additions & 7 deletions .changeset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Releases are managed by Changesets and `.github/workflows/release.yml`. On each
successful push to `main`, CI derives a temporary changeset from the
Conventional Commits since the latest package tags. The Changesets action keeps
one rolling `chore: version packages` pull request up to date. Merging that pull
request publishes all three public packages, creates package tags, and creates
request publishes all four public packages, creates package tags, and creates
GitHub Releases.

Contributor lists use a paginated GitHub comparison to map release commit SHAs
Expand All @@ -14,6 +14,7 @@ rendered; unlinked identities and bots are omitted.
The public packages form a fixed group and always share one version:

- `gitlab-ui-react`
- `@gitlab-ui-react/json-render`
- `@gitlab-ui-react/styles`
- `@gitlab-ui-react/tokens`

Expand All @@ -28,10 +29,16 @@ The public packages form a fixed group and always share one version:
| `type!` or a `BREAKING CHANGE` footer | minor, shown as a breaking change |
| all other types | none |

Major versions are deliberately never inferred. Before merging a version pull
request that should be a major release, run `pnpm changeset`, select all three
packages with a `major` bump, commit the generated Markdown file, and merge it
to `main`. The rolling version pull request will update automatically.
Major versions are deliberately never inferred. Use `pnpm release:version` to
apply changesets: the wrapper temporarily hides workspace peer dependencies
within the fixed package group from Changesets 2.x's automatic major escalation,
then restores them even if versioning fails. The fixed group still advances
together, and external peer dependencies remain unchanged.

Before merging a version pull request that should be a major release, run
`pnpm changeset`, select all four packages with a `major` bump, commit the
generated Markdown file, and merge it to `main`. The rolling version pull
request will update automatically.

## Pending release snapshots

Expand All @@ -48,10 +55,10 @@ state blocks publishing rather than silently dropping commits.
Before entering prerelease mode, publish all pending `0.x` changes.

1. Enter alpha with `pnpm changeset pre enter alpha`, add a manual major
changeset for all three packages, and commit both changes. The next version
changeset for all four packages, and commit both changes. The next version
is `1.0.0-alpha.0`.
2. To enter beta, change the `tag` in `.changeset/pre.json` from `alpha` to
`beta` and add a patch changeset for all three packages. The next version is
`beta` and add a patch changeset for all four packages. The next version is
`1.0.0-beta.0`. The version script resets Changesets' numeric prerelease
counter when it detects the tag change.
3. To publish `1.0.0`, run `pnpm changeset pre exit` and commit the result.
Expand Down
57 changes: 54 additions & 3 deletions scripts/release-snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function commit(root: string, message: string): string {
return git(root, "rev-parse", "HEAD");
}

function createRepository(): string {
function createRepository(version = "0.1.0"): string {
const root = mkdtempSync(join(tmpdir(), "gitlab-ui-snapshot-"));
temporaryDirectories.push(root);
mkdirSync(join(root, ".changeset"));
Expand Down Expand Up @@ -91,8 +91,15 @@ function createRepository(): string {
join(root, directory, "package.json"),
`${JSON.stringify({
name,
version: "0.1.0",
version,
...(dependencies ? { dependencies } : {}),
...(name === "@gitlab-ui-react/json-render" ? {
peerDependencies: {
"@gitlab-ui-react/styles": "workspace:^",
react: "^19.2.8",
},
devDependencies: { "@gitlab-ui-react/styles": "workspace:^" },
} : {}),
}, null, 2)}\n`,
);
}
Expand All @@ -102,7 +109,7 @@ function createRepository(): string {
git(root, "config", "user.name", "Release Test");
commit(root, "chore: create release fixture");
for(const [, name] of PACKAGES) {
git(root, "-c", "tag.gpgSign=false", "tag", `${name}@0.1.0`);
git(root, "-c", "tag.gpgSign=false", "tag", `${name}@${version}`);
}
return root;
}
Expand Down Expand Up @@ -152,6 +159,50 @@ function writeManualChangeset(
}

describe("pending release snapshots", () => {
it("versions internal workspace peers from 0.2.0 to 0.3.0 for a feature", () => {
const root = createRepository("0.2.0");
writeFileSync(join(root, "packages/styles/feature.css"), ".feature {}\n");
const featureSha = commit(root, "feat(styles): add a feature");
expect(createAutomaticChangeset(root).bumps).toEqual({
"@gitlab-ui-react/styles": "minor",
});

versionPackages(root, { runChangesetVersion: () => runChangesetVersion(root) });

expect(fixedVersion(root)).toBe("0.3.0");
expect(readReleaseSnapshot(root)).toMatchObject({
coveredThrough: featureSha,
version: "0.3.0",
});
const manifest = JSON.parse(
readFileSync(join(root, "packages/json-render/package.json"), "utf8"),
);
expect(manifest.peerDependencies).toEqual({
"@gitlab-ui-react/styles": "workspace:^",
react: "^19.2.8",
});
expect(manifest.devDependencies).toEqual({
"@gitlab-ui-react/styles": "workspace:^",
});
}, 30_000);

it("restores workspace peers when Changesets fails and keeps external peers visible", () => {
const root = createRepository();
const manifestPath = join(root, "packages/json-render/package.json");
const original = readFileSync(manifestPath, "utf8");

expect(() => versionPackages(root, {
runChangesetVersion: () => {
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
expect(manifest.peerDependencies).toEqual({ react: "^19.2.8" });
throw new Error("Changesets failed");
},
})).toThrow("Changesets failed");

expect(readFileSync(manifestPath, "utf8")).toBe(original);
expect(fixedVersion(root)).toBe("0.1.0");
});

it("rolls release-worthy commits after a stale snapshot into a new version", () => {
const root = createRepository();

Expand Down
54 changes: 45 additions & 9 deletions scripts/version.mts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,40 @@ function resetPrereleaseCounters(root: string, preState: PreState | null): void
}
}

function withHiddenFixedWorkspacePeers(root: string, operation: () => void): void {
const fixedNames = new Set(PUBLISHABLE_PACKAGES.map(({ name }) => name));
const originals = new Map<string, Record<string, string>>();

// Changesets 2.x promotes minor peer dependency updates to major releases.
// Our fixed group already versions together; its workspace peers must not
// override the bump selected by automatic or maintainer-authored changesets.
try {
for(const { directory } of PUBLISHABLE_PACKAGES) {
const manifestPath = join(root, directory, "package.json");
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as {
peerDependencies?: Record<string, string>;
};
if(!manifest.peerDependencies) continue;
const peers = Object.entries(manifest.peerDependencies);
const remaining = peers.filter(
([name, range]) => !fixedNames.has(name) || !range.startsWith("workspace:"),
);
if(remaining.length === peers.length) continue;

originals.set(manifestPath, manifest.peerDependencies);
manifest.peerDependencies = Object.fromEntries(remaining);
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
}
operation();
} finally {
for(const [manifestPath, peerDependencies] of originals) {
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
manifest.peerDependencies = peerDependencies;
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
}
}
}

export function run(root = REPOSITORY_ROOT, options: VersionRunOptions = {}): void {
const preState = readPreState(root);
const previousSnapshot = readReleaseSnapshot(root);
Expand All @@ -353,15 +387,17 @@ export function run(root = REPOSITORY_ROOT, options: VersionRunOptions = {}): vo

resetPrereleaseCounters(root, preState);
console.log("[release] Running `changeset version`.");
if(options.runChangesetVersion) {
options.runChangesetVersion();
} else {
const version = pnpmInvocation(["exec", "changeset", "version"]);
execFileSync(version.command, version.args, {
cwd: root,
stdio: "inherit",
});
}
withHiddenFixedWorkspacePeers(root, () => {
if(options.runChangesetVersion) {
options.runChangesetVersion();
} else {
const version = pnpmInvocation(["exec", "changeset", "version"]);
execFileSync(version.command, version.args, {
cwd: root,
stdio: "inherit",
});
}
});

const after = readVersions(root);
const nextFixedVersion = assertFixedVersions(after);
Expand Down
Loading