forked from microsoft/fluentui-react-native
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchange.mts
More file actions
122 lines (101 loc) · 4.28 KB
/
change.mts
File metadata and controls
122 lines (101 loc) · 4.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env node
// @ts-ignore
import { parseArgs, styleText } from 'node:util';
import { $, echo, fs } from 'zx';
/**
* Wrapper around `changeset add` (default) and `changeset status` validation (--check).
*
* Without --check: runs `changeset add` interactively with the correct upstream remote
* auto-detected from package.json's repository URL, temporarily patched into config.json.
*
* With --check (CI mode): validates that all changed packages have changesets and that
* no major version bumps are introduced.
*/
interface ChangesetStatusOutput {
releases: Array<{
name: string;
type: 'major' | 'minor' | 'patch' | 'none';
oldVersion: string;
newVersion: string;
changesets: string[];
}>;
changesets: string[];
}
const log = {
error: (msg: string) => echo(styleText('red', msg)),
success: (msg: string) => echo(styleText('green', msg)),
warn: (msg: string) => echo(styleText('yellow', msg)),
info: (msg: string) => echo(msg),
};
/** Find the remote that matches the repo's own URL (works for forks and CI alike). */
async function getBaseBranch(): Promise<string> {
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));
const repoUrl: string = pkg.repository?.url ?? '';
// Extract "org/repo" from https://github.com/org/repo.git or git@github.com:org/repo.git
const repoPath = repoUrl.match(/github\.com[:/](.+?)(?:\.git)?$/)?.[1] ?? '';
const remotes = (await $`git remote -v`.quiet()).stdout;
const remote = (repoPath && remotes.match(new RegExp(`^(\\S+)\\s+.*${repoPath}`, 'm'))?.[1]) ?? 'origin';
return `${remote}/main`;
}
/** Run `changeset add` interactively, temporarily patching config.json with the correct baseBranch. */
async function runAdd(baseBranch: string): Promise<void> {
const configPath = '.changeset/config.json';
const config = fs.readJsonSync(configPath);
const originalBaseBranch: string = config.baseBranch;
config.baseBranch = baseBranch;
fs.writeJsonSync(configPath, config, { spaces: 2 });
try {
await $({ stdio: 'inherit' })`yarn changeset`;
} finally {
config.baseBranch = originalBaseBranch;
fs.writeJsonSync(configPath, config, { spaces: 2 });
}
}
function checkMajorBumps(releases: ChangesetStatusOutput['releases']): void {
const majorBumps = releases.filter((r) => r.type === 'major');
if (majorBumps.length === 0) return;
log.error('❌ Major version bumps detected!\n');
for (const release of majorBumps) {
log.error(` ${release.name}: major`);
if (release.changesets.length > 0) {
log.error(` (from changesets: ${release.changesets.join(', ')})`);
}
}
log.error('\nMajor version bumps are not allowed.');
log.warn('If you need to make a breaking change, please discuss with the team first.\n');
process.exit(1);
}
/** Validate that all changed packages have changesets and no major bumps are introduced. */
async function runCheck(baseBranch: string): Promise<void> {
const STATUS_FILE = 'changeset-status.json';
log.info(`\n${'='.repeat(60)}`);
log.info('Changesets Validation');
log.info(`${'='.repeat(60)}\n`);
log.info(`Comparing against ${baseBranch}\n`);
// Pre-write empty state so changeset status always has a file to overwrite
fs.writeJsonSync(STATUS_FILE, { releases: [], changesets: [] });
const statusResult = await $`yarn changeset status --since=${baseBranch} --output ${STATUS_FILE}`.nothrow();
const data: ChangesetStatusOutput = fs.readJsonSync(STATUS_FILE);
fs.removeSync(STATUS_FILE);
// Fail: packages changed but no changeset written
if (statusResult.exitCode !== 0) {
log.error('❌ Some packages have been changed but no changesets were found.');
log.warn('Run `yarn change` to create a changeset, or `yarn changeset --empty` if no release is needed.\n');
process.exit(1);
}
checkMajorBumps(data.releases);
// Pass
if (data.releases.length === 0) {
log.info('ℹ️ No public packages changed — no changeset required');
} else {
log.success(`✅ Changesets found (${data.releases.map((r) => r.name).join(', ')})`);
}
log.success('\nAll validations passed! ✅\n');
}
const { values: args } = parseArgs({ options: { check: { type: 'boolean', default: false } } });
const baseBranch = await getBaseBranch();
if (args.check) {
await runCheck(baseBranch);
} else {
await runAdd(baseBranch);
}