|
4 | 4 | */ |
5 | 5 |
|
6 | 6 | import { strict as assert } from "node:assert"; |
| 7 | +import { readFile, writeFile } from "node:fs/promises"; |
7 | 8 | import type { Machine } from "jssm"; |
8 | 9 | import chalk from "picocolors"; |
9 | 10 |
|
@@ -191,3 +192,109 @@ export const doReleaseGroupBump: StateHandlerFunction = async ( |
191 | 192 | BaseStateHandler.signalSuccess(machine, state); |
192 | 193 | return true; |
193 | 194 | }; |
| 195 | + |
| 196 | +/** |
| 197 | + * Updates the generation used for layer compatibility. |
| 198 | + * |
| 199 | + * @param state - The current state machine state. |
| 200 | + * @param machine - The state machine. |
| 201 | + * @param testMode - Set to true to run function in test mode. |
| 202 | + * @param log - A logger that the function can use for logging. |
| 203 | + * @param data - An object with handler-specific contextual data. |
| 204 | + * @returns True if the state was handled; false otherwise. |
| 205 | + */ |
| 206 | +export const doLayerGenerationUpdate: StateHandlerFunction = async ( |
| 207 | + state: MachineState, |
| 208 | + machine: Machine<unknown>, |
| 209 | + testMode: boolean, |
| 210 | + log: CommandLogger, |
| 211 | + data: FluidReleaseStateHandlerData, |
| 212 | +): Promise<boolean> => { |
| 213 | + if (testMode) return true; |
| 214 | + |
| 215 | + const { bumpType } = data; |
| 216 | + if (bumpType === "patch") { |
| 217 | + log.info(`Generation update is not needed for patch release`); |
| 218 | + BaseStateHandler.signalSuccess(machine, state); |
| 219 | + return true; |
| 220 | + } |
| 221 | + |
| 222 | + log.info(`Updating layerGeneration.ts for a ${bumpType} release`); |
| 223 | + |
| 224 | + // eslint-disable-next-line no-warning-comments |
| 225 | + // TODO: Is it okay to read a file from this folder? |
| 226 | + // The file that stores information of the current generation and release date. |
| 227 | + const filename = "packages/common/client-utils/src/layerGeneration.ts"; |
| 228 | + |
| 229 | + // eslint-disable-next-line no-warning-comments |
| 230 | + // TODO: This should ideally be read from a common location in client utils. What is the best way to do this? |
| 231 | + // The minimum compatibility window in months that is supported across all layers. |
| 232 | + const minimumCompatWindowMonths = 3; |
| 233 | + |
| 234 | + const today = new Date(); |
| 235 | + let previousGeneration = -1; |
| 236 | + let newGeneration = 1; |
| 237 | + |
| 238 | + let fileContents: string | undefined; |
| 239 | + try { |
| 240 | + fileContents = await readFile(filename, "utf8"); |
| 241 | + } catch { |
| 242 | + log.info(`File ${filename} doesn't exist yet`); |
| 243 | + } |
| 244 | + |
| 245 | + if (fileContents !== undefined) { |
| 246 | + const match = fileContents.match( |
| 247 | + /.*\nexport const generation = (\d+);[\n\r]*export const releaseDate = "((0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2})";.*/m, |
| 248 | + ); |
| 249 | + if (match === null) { |
| 250 | + log.errorLog(`${filename} content not as expected`); |
| 251 | + BaseStateHandler.signalFailure(machine, state); |
| 252 | + return false; |
| 253 | + } |
| 254 | + |
| 255 | + previousGeneration = Number(match[1]); |
| 256 | + const previousReleaseDateString = match[2]; |
| 257 | + const previousReleaseDate = new Date(previousReleaseDateString); |
| 258 | + const daysBetweenReleases = Math.round( |
| 259 | + (today.getTime() - previousReleaseDate.getTime()) / (1000 * 60 * 60 * 24), |
| 260 | + ); |
| 261 | + |
| 262 | + const monthsBetweenReleases = Math.floor(daysBetweenReleases / 30); |
| 263 | + newGeneration = |
| 264 | + previousGeneration + Math.min(monthsBetweenReleases, minimumCompatWindowMonths - 1); |
| 265 | + |
| 266 | + log.info(`Previous release date: ${previousReleaseDate}, Today: ${today}`); |
| 267 | + log.info(`Days between releases: ${daysBetweenReleases}`); |
| 268 | + log.info(`Months between releases: ${monthsBetweenReleases}`); |
| 269 | + } |
| 270 | + |
| 271 | + if (newGeneration === previousGeneration) { |
| 272 | + log.info(`Generation does not need to be bumped`); |
| 273 | + BaseStateHandler.signalSuccess(machine, state); |
| 274 | + return true; |
| 275 | + } |
| 276 | + |
| 277 | + const yyyy = today.getFullYear(); |
| 278 | + const mmNumber = today.getMonth() + 1; // Months start at 0! |
| 279 | + const ddNumber = today.getDate(); |
| 280 | + |
| 281 | + const dd = ddNumber < 10 ? `0${ddNumber}` : ddNumber.toString(); |
| 282 | + const mm = mmNumber < 10 ? `0${mmNumber}` : mmNumber.toString(); |
| 283 | + const releaseDateFormatted = `${mm}/${dd}/${yyyy}`; |
| 284 | + |
| 285 | + const output = `/*! |
| 286 | + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. |
| 287 | + * Licensed under the MIT License. |
| 288 | + * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY |
| 289 | + */ |
| 290 | +
|
| 291 | +export const generation = ${newGeneration}; |
| 292 | +export const releaseDate = "${releaseDateFormatted}"; |
| 293 | +`; |
| 294 | + |
| 295 | + log.info(`Bumping generation from ${previousGeneration} to ${newGeneration}`); |
| 296 | + await writeFile(filename, output); |
| 297 | + |
| 298 | + BaseStateHandler.signalSuccess(machine, state); |
| 299 | + return true; |
| 300 | +}; |
0 commit comments