Skip to content

Commit 137ebc2

Browse files
committed
fix labeler
1 parent e1447b4 commit 137ebc2

2 files changed

Lines changed: 157 additions & 14 deletions

File tree

.github/scripts/create-changeset-from-pr.mjs

Lines changed: 148 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env node
2-
import { mkdir, readdir, rm, writeFile } from 'node:fs/promises';
2+
import { execFileSync } from 'node:child_process';
3+
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
34
import path from 'node:path';
45
import process from 'node:process';
56
import { fileURLToPath } from 'node:url';
@@ -13,6 +14,8 @@ const prNumber = process.env.PR_NUMBER?.trim() ?? 'unknown';
1314
const prTitle = process.env.PR_TITLE?.trim() ?? '';
1415
const prBody = process.env.PR_BODY?.trim() ?? '';
1516
const labelName = process.env.LABEL_NAME?.trim().toLowerCase() ?? '';
17+
const baseSha = process.env.BASE_SHA?.trim() ?? '';
18+
const headSha = process.env.HEAD_SHA?.trim() ?? '';
1619

1720
if (!prTitle) {
1821
console.error('PR title is required.');
@@ -44,26 +47,157 @@ if (labelName === 'add-changeset-major') {
4447
releaseType = 'major';
4548
}
4649

47-
const fileName = `pr-${prNumber}.md`;
48-
const filePath = path.join(changesetDir, fileName);
50+
const workspacePackages = await getWorkspacePackages(repoRoot);
51+
const affectedPackages = await getAffectedPackages(
52+
repoRoot,
53+
workspacePackages,
54+
baseSha,
55+
headSha,
56+
);
4957

50-
const existingFiles = await readdir(changesetDir);
58+
if (affectedPackages.length === 0) {
59+
console.log('No workspace packages were detected from the PR diff; skipping changeset creation.');
60+
process.exit(0);
61+
}
5162

52-
await Promise.all(
53-
existingFiles
54-
.filter((name) => name.startsWith(`pr-${prNumber}`) && name.endsWith('.md'))
55-
.map((name) => rm(path.join(changesetDir, name), { force: true })),
56-
);
63+
await mkdir(changesetDir, { recursive: true });
64+
const existingFiles = await listChangesetFiles(changesetDir);
65+
66+
execFileSync('pnpm', ['exec', 'changeset', 'add', '--empty', '--message', prTitle], {
67+
cwd: repoRoot,
68+
stdio: 'pipe',
69+
});
70+
71+
const createdChangesetFile = await getNewestChangesetFile(changesetDir, existingFiles);
72+
if (!createdChangesetFile) {
73+
console.error('Changesets CLI did not create a changeset file.');
74+
process.exit(1);
75+
}
5776

58-
const packages = ['react-docgen', 'react-docgen-cli'];
59-
const frontmatter = packages
77+
const filePath = path.join(changesetDir, createdChangesetFile);
78+
const frontmatter = affectedPackages
6079
.map((pkg) => `"${pkg}": ${releaseType}`)
6180
.join('\n');
6281
const content = `---\n${frontmatter}\n---\n\n${prTitle}\n`;
63-
64-
await mkdir(changesetDir, { recursive: true });
6582
await writeFile(filePath, content, 'utf8');
6683

6784
console.log(
68-
`Created changeset ${path.relative(repoRoot, filePath)} with release type ${releaseType}.`,
85+
`Created changeset ${path.relative(repoRoot, filePath)} for ${affectedPackages.join(', ')} with release type ${releaseType}.`,
6986
);
87+
88+
async function getWorkspacePackages(repoRootPath) {
89+
const packagesDir = path.join(repoRootPath, 'packages');
90+
const entries = await readdir(packagesDir, { withFileTypes: true });
91+
92+
return (
93+
await Promise.all(
94+
entries
95+
.filter((entry) => entry.isDirectory())
96+
.map(async (entry) => {
97+
const packageJsonPath = path.join(packagesDir, entry.name, 'package.json');
98+
try {
99+
const packageJsonContent = await readFile(packageJsonPath, 'utf8');
100+
const packageJson = JSON.parse(packageJsonContent);
101+
return packageJson.name ? { name: packageJson.name, dirName: entry.name } : null;
102+
} catch {
103+
return null;
104+
}
105+
}),
106+
)
107+
).filter(Boolean);
108+
}
109+
110+
async function getAffectedPackages(repoRootPath, workspacePackagesList, baseShaValue, headShaValue) {
111+
const changedFiles = await getChangedFiles(repoRootPath, baseShaValue, headShaValue);
112+
const affectedPackageNames = new Set();
113+
const ignoredPackages = new Set(['@react-docgen-internal/benchmark', '@react-docgen-internal/website']);
114+
115+
for (const changedFile of changedFiles) {
116+
const normalizedPath = changedFile.replace(/\\/g, '/');
117+
const rootChanges = ['package.json', 'pnpm-workspace.yaml', 'nx.json', 'tsconfig.base.json', 'pnpm-lock.yaml'];
118+
119+
if (rootChanges.includes(normalizedPath)) {
120+
for (const workspacePackage of workspacePackagesList) {
121+
if (!ignoredPackages.has(workspacePackage.name)) {
122+
affectedPackageNames.add(workspacePackage.name);
123+
}
124+
}
125+
continue;
126+
}
127+
128+
for (const workspacePackage of workspacePackagesList) {
129+
const packagePrefix = `packages/${workspacePackage.dirName}/`;
130+
if (
131+
!ignoredPackages.has(workspacePackage.name) &&
132+
(normalizedPath === `packages/${workspacePackage.dirName}` || normalizedPath.startsWith(packagePrefix))
133+
) {
134+
affectedPackageNames.add(workspacePackage.name);
135+
}
136+
}
137+
}
138+
139+
return [...affectedPackageNames].sort();
140+
}
141+
142+
async function getChangedFiles(repoRootPath, baseShaValue, headShaValue) {
143+
const baseRef = baseShaValue || 'HEAD~1';
144+
const headRef = headShaValue || 'HEAD';
145+
146+
try {
147+
const diffOutput = execFileSync('git', ['diff', '--name-only', baseRef, headRef], {
148+
cwd: repoRootPath,
149+
encoding: 'utf8',
150+
});
151+
const files = diffOutput
152+
.split(/\r?\n/)
153+
.map((line) => line.trim())
154+
.filter(Boolean);
155+
if (files.length > 0) {
156+
return files;
157+
}
158+
} catch {
159+
// fall through to the working tree state below
160+
}
161+
162+
try {
163+
const statusOutput = execFileSync('git', ['status', '--porcelain'], {
164+
cwd: repoRootPath,
165+
encoding: 'utf8',
166+
});
167+
return statusOutput
168+
.split(/\r?\n/)
169+
.map((line) => line.trim())
170+
.filter(Boolean)
171+
.map((line) => line.slice(3).trim())
172+
.filter(Boolean);
173+
} catch {
174+
return [];
175+
}
176+
}
177+
178+
async function listChangesetFiles(changesetDirectory) {
179+
const entries = await readdir(changesetDirectory, { withFileTypes: true });
180+
return entries
181+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
182+
.map((entry) => entry.name)
183+
.sort();
184+
}
185+
186+
async function getNewestChangesetFile(changesetDirectory, existingFiles) {
187+
const currentFiles = await listChangesetFiles(changesetDirectory);
188+
const newFiles = currentFiles.filter((file) => !existingFiles.includes(file));
189+
190+
if (newFiles.length === 0) {
191+
return null;
192+
}
193+
194+
const filesWithStats = await Promise.all(
195+
newFiles.map(async (file) => ({
196+
name: file,
197+
mtimeMs: (await stat(path.join(changesetDirectory, file))).mtimeMs,
198+
})),
199+
);
200+
201+
filesWithStats.sort((left, right) => right.mtimeMs - left.mtimeMs);
202+
return filesWithStats[0]?.name ?? null;
203+
}

.github/workflows/add-changeset-on-label.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,26 @@ jobs:
2222
ref: ${{ github.event.pull_request.head.ref }}
2323
fetch-depth: 0
2424

25+
- name: Install pnpm
26+
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6
27+
2528
- name: Setup Node.js
2629
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
2730
with:
2831
node-version: 22
32+
cache: pnpm
33+
34+
- name: Install dependencies
35+
run: pnpm install --frozen-lockfile
2936

3037
- name: Create changeset file
3138
env:
3239
PR_NUMBER: ${{ github.event.pull_request.number }}
3340
PR_TITLE: ${{ github.event.pull_request.title }}
3441
PR_BODY: ${{ github.event.pull_request.body }}
3542
LABEL_NAME: ${{ github.event.label.name }}
43+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
44+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
3645
run: node .github/scripts/create-changeset-from-pr.mjs
3746

3847
- name: Commit changeset

0 commit comments

Comments
 (0)