microsoft/typespec

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
3e669c74c1e16a47afe2b98706bdcf8dad4434af

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

180lines · modecode

1/* eslint-disable no-console */
2import { lstat, readdir, readFile, 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 oldVersion: string;
32 newVersion: string;
33 manifest: PackageJson;
34}
35
36async function getAllChanges(workspaceRoot: string): Promise<RushChangeFile[]> {
37 const changeDir = join(workspaceRoot, "common", "changes");
38 const files = await findAllFiles(changeDir);
39 return await Promise.all(files.map((x) => readJsonFile<RushChangeFile>(x)));
40}
41
42/**
43 * @returns map of package to number of changes.
44 */
45async function getChangeCountPerPackage(workspaceRoot: string) {
46 const changes = await getAllChanges(workspaceRoot);
47 console.log("Changes", changes);
48 const changeCounts: Record<string, number> = {};
49
50 for (const change of changes) {
51 if (!(change.packageName in changeCounts)) {
52 // Count all changes that are not "none"
53 changeCounts[change.packageName] = 0;
54 }
55 changeCounts[change.packageName] += change.changes.length;
56 }
57
58 return changeCounts;
59}
60
61async function getPackages(
62 workspaceRoot: string
63): Promise<Record<string, { path: string; version: string }>> {
64 const rushJson = await readJsonFile<RushWorkspace>(join(workspaceRoot, "rush.json"));
65
66 const paths: Record<string, { path: string; version: string }> = {};
67 for (const project of rushJson.projects) {
68 const packagePath = join(workspaceRoot, project.projectFolder);
69 const pkg = await readJsonFile<PackageJson>(join(packagePath, "package.json"));
70 paths[project.packageName] = {
71 path: packagePath,
72 version: pkg.version,
73 };
74 }
75 return paths;
76}
77
78/**
79 * Update the package dependencies to match the newly updated version.
80 * @param {*} packageManifest
81 * @param {*} updatedPackages
82 */
83function updateDependencyVersions(packageManifest: PackageJson, updatedPackages: any) {
84 const clone: PackageJson = {
85 ...packageManifest,
86 };
87 for (const depType of ["dependencies", "devDependencies", "peerDependencies"] as const) {
88 const dependencies: Record<string, string> = {};
89 const currentDeps = packageManifest[depType];
90 if (currentDeps) {
91 for (const [name, currentVersion] of Object.entries(currentDeps)) {
92 const updatedPackage = updatedPackages[name];
93 if (updatedPackage) {
94 // 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.
95 dependencies[name] = `>=${updatedPackage.oldVersion}`;
96 // change to this line to have strict dependency for preview versions
97 // dependencies[name] = `~${updatedPackage.newVersion}`;
98 } else {
99 dependencies[name] = currentVersion;
100 }
101 }
102 }
103 clone[depType] = dependencies;
104 }
105
106 return clone;
107}
108
109function bumpVersion(version: string, changeCount: number) {
110 if (changeCount === 0) {
111 return version;
112 }
113 const [major, minor] = version.split(".").map((x) => parseInt(x, 10));
114 console.log(`Bumping version ${version} to ${major}.${minor + 1}.0-dev.${changeCount}`);
115 return `${major}.${minor + 1}.0-dev.${changeCount}`;
116}
117
118async function addPrereleaseNumber(
119 changeCounts: Record<string, number>,
120 packages: Record<string, { path: string; version: string }>
121) {
122 const updatedManifests: Record<string, BumpManifest> = {};
123 for (const [packageName, packageInfo] of Object.entries(packages)) {
124 const changeCount = changeCounts[packageName] ?? 0;
125 const packageJsonPath = join(packageInfo.path, "package.json");
126 const packageJsonContent = await readJsonFile<PackageJson>(packageJsonPath);
127 const newVersion = bumpVersion(packageInfo.version, changeCount);
128
129 console.log(`Setting version for ${packageName} to '${newVersion}'`);
130 updatedManifests[packageName] = {
131 packageJsonPath,
132 oldVersion: packageJsonContent.version,
133 newVersion: newVersion,
134 manifest: {
135 ...packageJsonContent,
136 version: newVersion,
137 },
138 };
139 }
140
141 for (const { packageJsonPath, manifest } of Object.values(updatedManifests)) {
142 const newManifest = updateDependencyVersions(manifest, updatedManifests);
143 await writeFile(packageJsonPath, JSON.stringify(newManifest, null, 2));
144 }
145}
146
147export async function bumpVersionsForPrerelease(workspaceRoots: string[]) {
148 let changeCounts = {};
149 let packages: Record<string, { path: string; version: string }> = {};
150 for (const workspaceRoot of workspaceRoots) {
151 changeCounts = { ...changeCounts, ...(await getChangeCountPerPackage(workspaceRoot)) };
152
153 packages = { ...packages, ...(await getPackages(workspaceRoot)) };
154 }
155 console.log("Change counts: ", changeCounts);
156 console.log("Packages", packages);
157
158 // Bumping with rush publish so rush computes from the changes what will be the next non prerelease version.
159 console.log("Adding prerelease number");
160 await addPrereleaseNumber(changeCounts, packages);
161}
162
163async function findAllFiles(dir: string): Promise<string[]> {
164 const files = [];
165 for (const file of await readdir(dir)) {
166 const path = join(dir, file);
167 const stat = await lstat(path);
168 if (stat.isDirectory()) {
169 files.push(...(await findAllFiles(path)));
170 } else {
171 files.push(path);
172 }
173 }
174 return files;
175}
176
177async function readJsonFile<T>(filename: string): Promise<T> {
178 const content = await readFile(filename);
179 return JSON.parse(stripJsonComments(content.toString()));
180}