microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
a6b7827fcd2db3bf604d42e235216c8d5b244f13

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/internal-build-utils/src/prerelease.ts

260lines · modecode

1/* eslint-disable no-console */
2import { lstat, readdir, readFile, stat, writeFile } from "fs/promises";
3import { join } from "path";
4import stripJsonComments from "strip-json-comments";
5
6interface RushChangeFile {
7 packageName: string;
8 changes: RushChange[];
9}
10
11interface RushChange {
12 packageName: string;
13 comment: string;
14 type: "major" | "minor" | "patch" | "none";
15}
16
17interface RushWorkspace {
18 projects: any[];
19}
20
21interface PackageJson {
22 name: string;
23 version: string;
24 dependencies?: Record<string, string>;
25 devDependencies?: Record<string, string>;
26 peerDependencies?: Record<string, string>;
27}
28
29interface BumpManifest {
30 packageJsonPath: string;
31 /**
32 * Old version
33 */
34 oldVersion: string;
35 /**
36 * Next stable version
37 */
38 nextVersion: string;
39
40 /**
41 * Current dev version
42 */
43 newVersion: string;
44 manifest: PackageJson;
45}
46
47async function getAllChanges(workspaceRoot: string): Promise<RushChangeFile[]> {
48 const changeDir = join(workspaceRoot, "common", "changes");
49 const files = await findAllFiles(changeDir);
50 return await Promise.all(files.map((x) => readJsonFile<RushChangeFile>(x)));
51}
52
53/**
54 * @returns map of package to number of changes.
55 */
56async function getChangeCountPerPackage(workspaceRoot: string) {
57 const changes = await getAllChanges(workspaceRoot);
58 console.log("Changes", changes);
59 const changeCounts: Record<string, number> = {};
60
61 for (const change of changes) {
62 if (!(change.packageName in changeCounts)) {
63 // Count all changes that are not "none"
64 changeCounts[change.packageName] = 0;
65 }
66 changeCounts[change.packageName] += change.changes.length;
67 }
68
69 return changeCounts;
70}
71
72async function getPackages(
73 workspaceRoot: string
74): Promise<Record<string, { path: string; version: string }>> {
75 const rushJson = await readJsonFile<RushWorkspace>(join(workspaceRoot, "rush.json"));
76
77 const paths: Record<string, { path: string; version: string }> = {};
78 for (const project of rushJson.projects) {
79 const packagePath = join(workspaceRoot, project.projectFolder);
80 const pkg = await readJsonFile<PackageJson>(join(packagePath, "package.json"));
81 paths[project.packageName] = {
82 path: packagePath,
83 version: pkg.version,
84 };
85 }
86 return paths;
87}
88
89/**
90 * Update the package dependencies to match the newly updated version.
91 * @param {*} packageManifest
92 * @param {*} updatedPackages
93 */
94function updateDependencyVersions(
95 packageManifest: PackageJson,
96 updatedPackages: Record<string, BumpManifest>
97) {
98 const clone: PackageJson = {
99 ...packageManifest,
100 };
101 for (const depType of ["dependencies", "devDependencies", "peerDependencies"] as const) {
102 const dependencies: Record<string, string> = {};
103 const currentDeps = packageManifest[depType];
104 if (currentDeps) {
105 for (const [name, currentVersion] of Object.entries(currentDeps)) {
106 const updatedPackage = updatedPackages[name];
107 if (updatedPackage) {
108 // Loose dependency accept anything above the last release. This make sure that preview release of only one package need to be bumped without needing all the other as well.
109 dependencies[name] = getDevVersionRange(updatedPackage);
110 // change to this line to have strict dependency for preview versions
111 // dependencies[name] = `~${updatedPackage.newVersion}`;
112 } else {
113 dependencies[name] = currentVersion;
114 }
115 }
116 }
117 clone[depType] = dependencies;
118 }
119
120 return clone;
121}
122
123function getDevVersionRange(manifest: BumpManifest) {
124 return `~${manifest.oldVersion} || >=${manifest.nextVersion}-dev <${manifest.nextVersion}`;
125}
126
127function getDevVersion(version: string, changeCount: number) {
128 const [_, _1, patch] = version.split(".").map((x) => parseInt(x, 10));
129 const nextVersion = getNextVersion(version);
130 const devVersion = `${nextVersion}-dev.${changeCount + patch}`;
131 console.log(`Bumping version ${version} to ${devVersion}`);
132 return devVersion;
133}
134
135function getNextVersion(version: string) {
136 const [major, minor] = version.split(".").map((x) => parseInt(x, 10));
137 return `${major}.${minor + 1}.0`;
138}
139
140async function addPrereleaseNumber(
141 changeCounts: Record<string, number>,
142 packages: Record<string, { path: string; version: string }>
143) {
144 const updatedManifests: Record<string, BumpManifest> = {};
145 for (const [packageName, packageInfo] of Object.entries(packages)) {
146 const changeCount = changeCounts[packageName] ?? 0;
147 const packageJsonPath = join(packageInfo.path, "package.json");
148 const packageJsonContent = await readJsonFile<PackageJson>(packageJsonPath);
149 const newVersion = getDevVersion(packageInfo.version, changeCount);
150
151 console.log(`Setting version for ${packageName} to '${newVersion}'`);
152 updatedManifests[packageName] = {
153 packageJsonPath,
154 oldVersion: packageJsonContent.version,
155 nextVersion: getNextVersion(packageInfo.version),
156 newVersion,
157 manifest: {
158 ...packageJsonContent,
159 version: newVersion,
160 },
161 };
162 }
163
164 for (const { packageJsonPath, manifest } of Object.values(updatedManifests)) {
165 const newManifest = updateDependencyVersions(manifest, updatedManifests);
166 await writeFile(packageJsonPath, JSON.stringify(newManifest, null, 2));
167 }
168}
169
170export async function bumpVersionsForPrerelease(workspaceRoots: string[]) {
171 let changeCounts = {};
172 let packages: Record<string, { path: string; version: string }> = {};
173 for (const workspaceRoot of workspaceRoots) {
174 changeCounts = { ...changeCounts, ...(await getChangeCountPerPackage(workspaceRoot)) };
175
176 packages = { ...packages, ...(await getPackages(workspaceRoot)) };
177 }
178 console.log("Change counts: ", changeCounts);
179 console.log("Packages", packages);
180
181 // Bumping with rush publish so rush computes from the changes what will be the next non prerelease version.
182 console.log("Adding prerelease number");
183 await addPrereleaseNumber(changeCounts, packages);
184}
185
186async function findAllFiles(dir: string): Promise<string[]> {
187 const files = [];
188 if (!(await isDirectory(dir))) {
189 return [];
190 }
191
192 for (const file of await readdir(dir)) {
193 const path = join(dir, file);
194 const stat = await lstat(path);
195 if (stat.isDirectory()) {
196 files.push(...(await findAllFiles(path)));
197 } else {
198 files.push(path);
199 }
200 }
201 return files;
202}
203
204async function readJsonFile<T>(filename: string): Promise<T> {
205 const content = await readFile(filename);
206 return JSON.parse(stripJsonComments(content.toString()));
207}
208
209async function isDirectory(path: string) {
210 try {
211 const stats = await stat(path);
212 return stats.isDirectory();
213 } catch (e: any) {
214 if (e.code === "ENOENT" || e.code === "ENOTDIR") {
215 return false;
216 }
217 throw e;
218 }
219}
220
221export async function bumpVersionsForPR(
222 workspaceRoot: string,
223 prNumber: number,
224 buildNumber: string
225) {
226 const packages = await getPackages(workspaceRoot);
227 console.log("Packages", packages);
228
229 // Bumping with rush publish so rush computes from the changes what will be the next non prerelease version.
230
231 const updatedManifests: Record<string, BumpManifest> = {};
232 for (const [packageName, packageInfo] of Object.entries(packages)) {
233 const packageJsonPath = join(packageInfo.path, "package.json");
234 const packageJsonContent = await readJsonFile<PackageJson>(packageJsonPath);
235 const newVersion = getPrVersion(packageInfo.version, prNumber, buildNumber);
236
237 console.log(`Setting version for ${packageName} to '${newVersion}'`);
238 updatedManifests[packageName] = {
239 packageJsonPath,
240 oldVersion: packageJsonContent.version,
241 nextVersion: getNextVersion(packageInfo.version),
242 newVersion,
243 manifest: {
244 ...packageJsonContent,
245 version: newVersion,
246 },
247 };
248 }
249
250 for (const { packageJsonPath, manifest } of Object.values(updatedManifests)) {
251 await writeFile(packageJsonPath, JSON.stringify(manifest, null, 2));
252 }
253}
254
255function getPrVersion(version: string, prNumber: number, buildNumber: string) {
256 const nextVersion = getNextVersion(version);
257 const devVersion = `${nextVersion}-pr-${prNumber}.${buildNumber}`;
258 console.log(`Bumping version ${version} to ${devVersion}`);
259 return devVersion;
260}
261