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
15 changes: 15 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ jobs:
STEPS_GET_VERSION_OUTPUTS_IS_PRERELEASE: ${{ steps.get_version.outputs.is_prerelease }}
run: |
VERSION="${STEPS_GET_VERSION_OUTPUTS_PACKAGE_VERSION}"

# On the pull_request merge event RELEASE_PR_BODY is the merged "Version Packages"
# PR body, which holds the changelog. On a workflow_dispatch re-run (the recovery
# path after a failed merge-triggered run) there is no pull_request context, so it's
# empty. Fall back to the body of the most recently merged changeset-release/main PR
# so the "What's changed" section is still populated.
if [ -z "${RELEASE_PR_BODY}" ]; then
echo "RELEASE_PR_BODY is empty (workflow_dispatch re-run); fetching merged changeset-release/main PR body"
RELEASE_PR_BODY="$(gh pr list --repo triggerdotdev/trigger.dev \
--head changeset-release/main --state merged \
--json body,mergedAt --limit 10 \
-q 'sort_by(.mergedAt) | reverse | .[0].body')"
fi
export RELEASE_PR_BODY

node scripts/generate-github-release.mjs "$VERSION" > /tmp/release-body.md
PRERELEASE_FLAG=""
if [ "${STEPS_GET_VERSION_OUTPUTS_IS_PRERELEASE}" = "true" ]; then
Expand Down
114 changes: 93 additions & 21 deletions scripts/enhance-release-pr.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -183,28 +183,8 @@ async function getPrForCommit(commitSha) {
// --- Parse .server-changes/ files ---

async function parseServerChanges() {
const dir = join(ROOT_DIR, ".server-changes");
const entries = [];

let files;
try {
files = await fs.readdir(dir);
} catch {
return entries;
}

// Collect file info and look up commits in parallel
const fileData = [];
for (const file of files) {
if (!file.endsWith(".md") || file === "README.md") continue;

const filePath = join(".server-changes", file);
const content = await fs.readFile(join(dir, file), "utf-8");
const parsed = parseFrontmatter(content);
if (!parsed.body.trim()) continue;

fileData.push({ filePath, parsed });
}
const fileData = await getServerChangeFileData();

// Look up commits for all files in parallel
const commits = await Promise.all(fileData.map((f) => getCommitForFile(f.filePath)));
Expand Down Expand Up @@ -232,6 +212,98 @@ async function parseServerChanges() {
return entries;
}

async function getServerChangeFileData() {
// The changesets version command deletes .server-changes before this script
// enhances the release PR body. We combine files still live on disk with the
// ones recovered from the release branch diff, deduped by filename, rather
// than picking one source or the other. This is additive so a partial cleanup
// (some files deleted, some still live) can't silently drop entries. Live
// files win on collision since they are the current on-disk truth.
const [live, deleted] = await Promise.all([
getLiveServerChangeFileData(),
getDeletedServerChangeFileDataFromReleaseBranch(),
]);

const byName = new Map();
for (const fileData of deleted) {
byName.set(fileData.filePath.split("/").pop(), fileData);
}
for (const fileData of live) {
byName.set(fileData.filePath.split("/").pop(), fileData);
}

return [...byName.values()].sort((a, b) => a.filePath.localeCompare(b.filePath));
}
Comment thread
claude[bot] marked this conversation as resolved.

async function getLiveServerChangeFileData() {
const dir = join(ROOT_DIR, ".server-changes");

let files;
try {
files = await fs.readdir(dir);
} catch {
return [];
}

const fileData = [];
for (const file of files.sort()) {
if (!file.endsWith(".md") || file === "README.md") continue;

const filePath = join(".server-changes", file);
const content = await fs.readFile(join(dir, file), "utf-8");
const parsed = parseFrontmatter(content);
if (!parsed.body.trim()) continue;

fileData.push({ filePath, parsed });
}

return fileData;
}

async function getDeletedServerChangeFileDataFromReleaseBranch() {
const baseRef = process.env.SERVER_CHANGES_BASE_REF || "origin/main";
const releaseRef = process.env.SERVER_CHANGES_RELEASE_REF || "origin/changeset-release/main";

let mergeBase;
let deletedFiles;
try {
mergeBase = await gitExec(["merge-base", baseRef, releaseRef]);
deletedFiles = await gitExec([
"diff",
"--name-only",
"--diff-filter=D",
`${mergeBase}..${releaseRef}`,
"--",
".server-changes",
]);
} catch (err) {
console.error(
"[enhance-release-pr] failed to recover deleted server-changes from release branch:",
err
);
return [];
}

const fileData = [];
for (const filePath of deletedFiles.split("\n").filter(Boolean).sort()) {
const file = filePath.split("/").pop();
if (!file?.endsWith(".md") || file === "README.md") continue;

try {
const content = await gitExec(["show", `${mergeBase}:${filePath}`]);
const parsed = parseFrontmatter(content);
if (!parsed.body.trim()) continue;
fileData.push({ filePath, parsed });
} catch (err) {
// If an individual file cannot be recovered, skip it rather than hiding
// all other server changes.
console.error(`[enhance-release-pr] failed to read deleted server-change ${filePath}:`, err);
}
}

return fileData;
}

function parseFrontmatter(content) {
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
if (!match) return { frontmatter: {}, body: content };
Expand Down
15 changes: 15 additions & 0 deletions scripts/generate-github-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,21 @@ async function main() {
}

const changesContent = extractChangesFromPrBody(prBody);

// Fail loudly when a stable (non-prerelease) release resolved no changelog. This
// guards against publishing an empty "What's changed" section, which previously
// happened silently on workflow_dispatch re-runs where RELEASE_PR_BODY was empty.
// Prereleases (any version with a hyphen, e.g. 4.5.0-rc.0) are allowed to be empty.
const isPrerelease = version.includes("-");
if (!changesContent && !isPrerelease) {
console.error(
`Error: no "What's changed" content could be resolved for v${version}. ` +
`RELEASE_PR_BODY was empty or contained no changelog entries. ` +
`Refusing to publish a release with an empty changelog.`
);
process.exit(1);
}
Comment thread
claude[bot] marked this conversation as resolved.

const contributors = getContributors(getPreviousVersion(version));
const packages = getPublishedPackages();

Expand Down
Loading