From 805129ea55f15e9893b9dd670019632307f2dff6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:11:36 +0000 Subject: [PATCH 01/19] Initial plan From e4e64e5655c3e5c7b59e53d5894799210f0ee2f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:21:27 +0000 Subject: [PATCH 02/19] Add CheckCompatLayerGeneration to release prepare command Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../build-cli/src/commands/release/prepare.ts | 2 + .../build-cli/src/handlers/checkFunctions.ts | 18 +++----- .../packages/build-cli/src/library/index.ts | 1 + .../src/library/releasePrepChecks.ts | 43 +++++++++++++++++++ 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/build-tools/packages/build-cli/src/commands/release/prepare.ts b/build-tools/packages/build-cli/src/commands/release/prepare.ts index 689f649de315..8bcbef886e23 100644 --- a/build-tools/packages/build-cli/src/commands/release/prepare.ts +++ b/build-tools/packages/build-cli/src/commands/release/prepare.ts @@ -8,6 +8,7 @@ import chalk from "picocolors"; import { findPackageOrReleaseGroup, packageOrReleaseGroupArg } from "../../args.js"; import { BaseCommand } from "../../library/index.js"; import { + CheckCompatLayerGeneration, CheckDependenciesInstalled, type CheckFunction, CheckHasNoPrereleaseDependencies, @@ -32,6 +33,7 @@ const allChecks: ReadonlyMap = new Map([ ["Has no pre-release Fluid dependencies", CheckHasNoPrereleaseDependencies], ["No repo policy violations", CheckNoPolicyViolations], ["No untagged asserts", CheckNoUntaggedAsserts], + ["Compatibility layer generation is up to date", CheckCompatLayerGeneration], ]); /** diff --git a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts index 608815809015..b673a6514415 100644 --- a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts +++ b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts @@ -22,6 +22,9 @@ import { getPreReleaseDependencies, getReleaseSourceForReleaseGroup, isReleased, + // library is overloaded with too much stuff now, and we should consider allowing interior imports. + // eslint-disable-next-line import/no-internal-modules + runCompatLayerGenerationCheck, } from "../library/index.js"; import type { CommandLogger } from "../logging.js"; import type { MachineState } from "../machines/index.js"; @@ -931,21 +934,12 @@ export const checkCompatLayerGeneration: StateHandlerFunction = async ( return true; } - // layerGeneration:gen should be run from the root. It will only update packages that have the layerGeneration:gen - // script defined in their package.json. - const result = await execa.command(`pnpm run -r layerGeneration:gen`, { - cwd: context.root, - }); - log.verbose(result.stdout); + const isUpToDate = await runCompatLayerGenerationCheck(context); - // check for policy check violation - const gitRepo = await context.getGitRepository(); - const afterPolicyCheckStatus = await gitRepo.gitClient.status(); - const isClean = afterPolicyCheckStatus.isClean(); - if (!isClean) { + if (!isUpToDate) { log.logHr(); log.errorLog( - `Layer generation needs to be updated. Please create a PR for the changes and merge before retrying.\n${afterPolicyCheckStatus.files.map((fileStatus) => `${fileStatus.index} ${fileStatus.path}`).join("\n")}`, + `Layer generation needs to be updated. Please create a PR for the changes and merge before retrying.`, ); BaseStateHandler.signalFailure(machine, state); return false; diff --git a/build-tools/packages/build-cli/src/library/index.ts b/build-tools/packages/build-cli/src/library/index.ts index 5e2c4070012a..cb0e7afd397b 100644 --- a/build-tools/packages/build-cli/src/library/index.ts +++ b/build-tools/packages/build-cli/src/library/index.ts @@ -65,3 +65,4 @@ export { } from "./release.js"; export { LayerGraph } from "./layerGraph.js"; export { type Handler, policyHandlers } from "./repoPolicyCheck/index.js"; +export { runCompatLayerGenerationCheck } from "./releasePrepChecks.js"; diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index af48f19b6e3e..5f400a2e631e 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -228,3 +228,46 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( }; } }; + +/** + * Runs the compatibility layer generation command and checks if it made any changes to the repository. + * This is a shared helper function used by both the prepare command checks and the state machine checks. + * + * @param context - The repository context. + * @returns `true` if no changes were made (i.e., layer generation is up to date), `false` otherwise. + */ +export async function runCompatLayerGenerationCheck(context: Context): Promise { + // layerGeneration:gen should be run from the root. It will only update packages that have the layerGeneration:gen + // script defined in their package.json. + await execa("pnpm", ["run", "-r", "layerGeneration:gen"], { + cwd: context.root, + }); + + // check if the command made any changes + const gitRepo = await context.getGitRepository(); + const afterGenStatus = await gitRepo.gitClient.status(); + const isClean = afterGenStatus.isClean(); + + // Reset any changes that were made + if (!isClean) { + await gitRepo.gitClient.reset(ResetMode.HARD); + } + + return isClean; +} + +/** + * Checks that the compatibility layer generation is up to date. Any necessary changes will return a failure result. + */ +export const CheckCompatLayerGeneration: CheckFunction = async ( + context: Context, +): Promise => { + const isUpToDate = await runCompatLayerGenerationCheck(context); + + if (!isUpToDate) { + return { + message: "Layer generation needs to be updated.", + fixCommand: "pnpm run -r layerGeneration:gen", + }; + } +}; From 05fec9f26ed3342781b945f40793ca3bce4104e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:24:20 +0000 Subject: [PATCH 03/19] Fix CheckCompatLayerGeneration function signature and return statement Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../packages/build-cli/src/library/releasePrepChecks.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index 5f400a2e631e..c476c0766521 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -261,6 +261,7 @@ export async function runCompatLayerGenerationCheck(context: Context): Promise => { const isUpToDate = await runCompatLayerGenerationCheck(context); @@ -270,4 +271,6 @@ export const CheckCompatLayerGeneration: CheckFunction = async ( fixCommand: "pnpm run -r layerGeneration:gen", }; } + + return; }; From 3b4a3dbab89948e0d42904d5714eea8521813a61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:25:50 +0000 Subject: [PATCH 04/19] Remove unnecessary eslint-disable comment Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- build-tools/packages/build-cli/src/handlers/checkFunctions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts index b673a6514415..b3d60e119e68 100644 --- a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts +++ b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts @@ -22,8 +22,6 @@ import { getPreReleaseDependencies, getReleaseSourceForReleaseGroup, isReleased, - // library is overloaded with too much stuff now, and we should consider allowing interior imports. - // eslint-disable-next-line import/no-internal-modules runCompatLayerGenerationCheck, } from "../library/index.js"; import type { CommandLogger } from "../logging.js"; From 7571073a10f782d1e90323286092f20fc62ef4ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:28:20 +0000 Subject: [PATCH 05/19] Mark unused releaseGroupOrPackage parameter with underscore prefix Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- build-tools/packages/build-cli/src/library/releasePrepChecks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index c476c0766521..b5f48cf0f136 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -261,7 +261,7 @@ export async function runCompatLayerGenerationCheck(context: Context): Promise => { const isUpToDate = await runCompatLayerGenerationCheck(context); From 2197af788ffb9a0c5b75bb7122afeada0d2f9e6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:56:25 +0000 Subject: [PATCH 06/19] Use direct function calls instead of running pnpm command for layer generation check Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../src/library/releasePrepChecks.ts | 60 ++++++++++++++----- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index b5f48cf0f136..4a0df0645ddd 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -230,30 +230,60 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( }; /** - * Runs the compatibility layer generation command and checks if it made any changes to the repository. + * Checks if any packages need a compatibility layer generation update. * This is a shared helper function used by both the prepare command checks and the state machine checks. * * @param context - The repository context. - * @returns `true` if no changes were made (i.e., layer generation is up to date), `false` otherwise. + * @returns `true` if all packages have up-to-date layer generation metadata, `false` if any updates are needed. */ export async function runCompatLayerGenerationCheck(context: Context): Promise { - // layerGeneration:gen should be run from the root. It will only update packages that have the layerGeneration:gen - // script defined in their package.json. - await execa("pnpm", ["run", "-r", "layerGeneration:gen"], { - cwd: context.root, - }); + const { maybeGetNewGeneration, isCurrentPackageVersionPatch } = await import( + // library is overloaded with too much stuff now, and we should consider allowing interior imports. + // eslint-disable-next-line import/no-internal-modules + "../commands/generate/layerCompatGeneration.js" + ); - // check if the command made any changes - const gitRepo = await context.getGitRepository(); - const afterGenStatus = await gitRepo.gitClient.status(); - const isClean = afterGenStatus.isClean(); + // Check all packages that have fluidCompatMetadata + for (const pkg of context.fullPackageMap.values()) { + const { fluidCompatMetadata } = pkg.packageJson; + + // Skip packages without compatibility metadata + if (fluidCompatMetadata === undefined) { + continue; + } - // Reset any changes that were made - if (!isClean) { - await gitRepo.gitClient.reset(ResetMode.HARD); + const currentPkgVersion = pkg.version; + + // Skip patch versions as they don't trigger generation updates + if (isCurrentPackageVersionPatch(currentPkgVersion)) { + continue; + } + + // Use a no-op logger since we don't want verbose output during checks + const noopLogger = { + verbose: () => {}, + info: () => {}, + warning: () => {}, + errorLog: () => {}, + }; + + // Check if this package needs a generation update + // Using default minimumCompatWindowMonths of 3 (same as the command default) + const newGeneration = maybeGetNewGeneration( + currentPkgVersion, + fluidCompatMetadata, + 3, // minimumCompatWindowMonths default + noopLogger, + ); + + // If any package needs an update, return false + if (newGeneration !== undefined) { + return false; + } } - return isClean; + // All packages are up to date + return true; } /** From 3b76f2c4eb0171d9b1d06e3d789d9336c816d788 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:58:26 +0000 Subject: [PATCH 07/19] Remove trailing whitespace and clarify comment Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../packages/build-cli/src/library/releasePrepChecks.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index 4a0df0645ddd..06e691291e47 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -230,7 +230,7 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( }; /** - * Checks if any packages need a compatibility layer generation update. + * Checks if any packages need a compatibility layer generation update by directly calling the layer generation logic. * This is a shared helper function used by both the prepare command checks and the state machine checks. * * @param context - The repository context. @@ -246,14 +246,14 @@ export async function runCompatLayerGenerationCheck(context: Context): Promise Date: Tue, 2 Dec 2025 21:00:31 +0000 Subject: [PATCH 08/19] Extract minimumCompatWindowMonths constant Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../packages/build-cli/src/library/releasePrepChecks.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index 06e691291e47..ceac301281ca 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -9,6 +9,12 @@ import { ResetMode } from "simple-git"; import type { Context } from "./context.js"; import { getPreReleaseDependencies } from "./package.js"; +/** + * The default minimum compatibility window in months for layer generation. + * This matches the default value used in the layerCompatGeneration command. + */ +const DEFAULT_MINIMUM_COMPAT_WINDOW_MONTHS = 3; + /** * An async function that executes a release preparation check. The function returns a {@link CheckResult} with details * about the results of the check. @@ -268,11 +274,10 @@ export async function runCompatLayerGenerationCheck(context: Context): Promise Date: Tue, 2 Dec 2025 21:03:16 +0000 Subject: [PATCH 09/19] Add missing log method to noop logger and clarify comment Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../packages/build-cli/src/library/releasePrepChecks.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index ceac301281ca..121b45ab2460 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -236,7 +236,7 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( }; /** - * Checks if any packages need a compatibility layer generation update by directly calling the layer generation logic. + * Checks if any packages need a compatibility layer generation update using the layer generation functions directly. * This is a shared helper function used by both the prepare command checks and the state machine checks. * * @param context - The repository context. @@ -267,6 +267,7 @@ export async function runCompatLayerGenerationCheck(context: Context): Promise {}, verbose: () => {}, info: () => {}, warning: () => {}, From 10cf5c43bdf471f64c4b2a2b2aebea036190f58e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 21:05:57 +0000 Subject: [PATCH 10/19] Add onlyConfiguredPackages parameter to filter packages by metadata configuration Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../src/library/releasePrepChecks.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index 121b45ab2460..3200a1c2bedd 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -239,10 +239,21 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( * Checks if any packages need a compatibility layer generation update using the layer generation functions directly. * This is a shared helper function used by both the prepare command checks and the state machine checks. * + * When the actual `layerGeneration:gen` command is run as a package script, it processes all packages and will + * initialize metadata for packages that don't have it. However, this check function is used to validate that + * packages are up-to-date before releasing, so by default it only validates packages that are already configured + * with fluidCompatMetadata. + * * @param context - The repository context. - * @returns `true` if all packages have up-to-date layer generation metadata, `false` if any updates are needed. + * @param onlyConfiguredPackages - If true (default), only checks packages that already have fluidCompatMetadata + * configured and skips packages without metadata. If false, treats packages without fluidCompatMetadata as + * needing updates (they would get generation 1 when the command runs). + * @returns `true` if all (configured) packages have up-to-date layer generation metadata, `false` if any updates are needed. */ -export async function runCompatLayerGenerationCheck(context: Context): Promise { +export async function runCompatLayerGenerationCheck( + context: Context, + onlyConfiguredPackages: boolean = true, +): Promise { const { maybeGetNewGeneration, isCurrentPackageVersionPatch } = await import( // library is overloaded with too much stuff now, and we should consider allowing interior imports. // eslint-disable-next-line import/no-internal-modules @@ -253,9 +264,16 @@ export async function runCompatLayerGenerationCheck(context: Context): Promise Date: Tue, 2 Dec 2025 21:08:03 +0000 Subject: [PATCH 11/19] Document how to set up unconfigured packages for layer compatibility Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../src/library/releasePrepChecks.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index 3200a1c2bedd..35dbff33eb1b 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -239,10 +239,21 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( * Checks if any packages need a compatibility layer generation update using the layer generation functions directly. * This is a shared helper function used by both the prepare command checks and the state machine checks. * - * When the actual `layerGeneration:gen` command is run as a package script, it processes all packages and will - * initialize metadata for packages that don't have it. However, this check function is used to validate that - * packages are up-to-date before releasing, so by default it only validates packages that are already configured - * with fluidCompatMetadata. + * **Setting up a new package for layer compatibility:** + * To add layer compatibility to a package that doesn't have it yet: + * 1. Add a `layerGeneration:gen` script to the package's package.json: + * `"layerGeneration:gen": "flub generate layerCompatGeneration --dir . -v"` + * 2. Run the command to initialize the package: `pnpm run layerGeneration:gen` + * 3. The command will create the `fluidCompatMetadata` field in package.json with generation 1 + * and generate the layer generation file (e.g., `src/layerGenerationState.ts`) + * + * **How this check works:** + * - When `onlyConfiguredPackages = true` (default): Only validates packages that already have + * `fluidCompatMetadata` configured. This is appropriate for release checks since we only want + * to validate packages that are already participating in layer compatibility. + * - When `onlyConfiguredPackages = false`: Treats packages without metadata as needing updates. + * This would fail if any package lacks metadata, which is usually not desired since not all + * packages need layer compatibility. * * @param context - The repository context. * @param onlyConfiguredPackages - If true (default), only checks packages that already have fluidCompatMetadata From 7bc2409a711f440547d9256c6336986737d5f652 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:14:11 +0000 Subject: [PATCH 12/19] Remove unused onlyConfiguredPackages parameter to simplify implementation Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../src/library/releasePrepChecks.ts | 33 +++++-------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index 35dbff33eb1b..e38749ffdc94 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -239,6 +239,10 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( * Checks if any packages need a compatibility layer generation update using the layer generation functions directly. * This is a shared helper function used by both the prepare command checks and the state machine checks. * + * Only validates packages that already have `fluidCompatMetadata` configured. This is appropriate for + * release checks since we only want to validate packages that are already participating in layer compatibility. + * Packages without metadata are skipped - not all packages need layer compatibility. + * * **Setting up a new package for layer compatibility:** * To add layer compatibility to a package that doesn't have it yet: * 1. Add a `layerGeneration:gen` script to the package's package.json: @@ -247,24 +251,10 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( * 3. The command will create the `fluidCompatMetadata` field in package.json with generation 1 * and generate the layer generation file (e.g., `src/layerGenerationState.ts`) * - * **How this check works:** - * - When `onlyConfiguredPackages = true` (default): Only validates packages that already have - * `fluidCompatMetadata` configured. This is appropriate for release checks since we only want - * to validate packages that are already participating in layer compatibility. - * - When `onlyConfiguredPackages = false`: Treats packages without metadata as needing updates. - * This would fail if any package lacks metadata, which is usually not desired since not all - * packages need layer compatibility. - * * @param context - The repository context. - * @param onlyConfiguredPackages - If true (default), only checks packages that already have fluidCompatMetadata - * configured and skips packages without metadata. If false, treats packages without fluidCompatMetadata as - * needing updates (they would get generation 1 when the command runs). - * @returns `true` if all (configured) packages have up-to-date layer generation metadata, `false` if any updates are needed. + * @returns `true` if all configured packages have up-to-date layer generation metadata, `false` if any updates are needed. */ -export async function runCompatLayerGenerationCheck( - context: Context, - onlyConfiguredPackages: boolean = true, -): Promise { +export async function runCompatLayerGenerationCheck(context: Context): Promise { const { maybeGetNewGeneration, isCurrentPackageVersionPatch } = await import( // library is overloaded with too much stuff now, and we should consider allowing interior imports. // eslint-disable-next-line import/no-internal-modules @@ -275,16 +265,9 @@ export async function runCompatLayerGenerationCheck( for (const pkg of context.fullPackageMap.values()) { const { fluidCompatMetadata } = pkg.packageJson; - // Handle packages without compatibility metadata + // Skip packages without compatibility metadata - not all packages need layer compatibility if (fluidCompatMetadata === undefined) { - if (onlyConfiguredPackages) { - // Skip packages without metadata when only checking configured packages - continue; - } else { - // Treat packages without metadata as needing updates - // (they would get generation 1 when the command runs) - return false; - } + continue; } const currentPkgVersion = pkg.version; From 4541d93e07777851c9821f0ca3b1a0969d9cf5e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 23:53:11 +0000 Subject: [PATCH 13/19] Move layer compatibility functions to library and update imports Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../generate/layerCompatGeneration.ts | 102 +----------------- .../build-cli/src/handlers/checkFunctions.ts | 2 +- .../packages/build-cli/src/library/index.ts | 1 - .../src/library/layerCompatibility.ts | 102 ++++++++++++++++++ .../src/library/releasePrepChecks.ts | 7 +- 5 files changed, 109 insertions(+), 105 deletions(-) create mode 100644 build-tools/packages/build-cli/src/library/layerCompatibility.ts diff --git a/build-tools/packages/build-cli/src/commands/generate/layerCompatGeneration.ts b/build-tools/packages/build-cli/src/commands/generate/layerCompatGeneration.ts index 3dbbff30ea39..8e4a6f0b2b6e 100644 --- a/build-tools/packages/build-cli/src/commands/generate/layerCompatGeneration.ts +++ b/build-tools/packages/build-cli/src/commands/generate/layerCompatGeneration.ts @@ -8,18 +8,17 @@ import path from "node:path"; import { updatePackageJsonFile } from "@fluid-tools/build-infrastructure"; import type { IFluidCompatibilityMetadata, - Logger, Package, PackageJson, } from "@fluidframework/build-tools"; import { Flags } from "@oclif/core"; -import { formatISO, isDate, isValid, parseISO } from "date-fns"; -import { diff, parse } from "semver"; +import { formatISO } from "date-fns"; import { PackageCommand } from "../../BasePackageCommand.js"; import type { PackageSelectionDefault } from "../../flags.js"; - -// Approximate month as 33 days to add some buffer and avoid over-counting months in longer spans. -export const daysInMonthApproximation = 33; +import { + isCurrentPackageVersionPatch, + maybeGetNewGeneration, +} from "../../library/layerCompatibility.js"; export default class UpdateGenerationCommand extends PackageCommand< typeof UpdateGenerationCommand @@ -94,29 +93,6 @@ export default class UpdateGenerationCommand extends PackageCommand< } } -/** - * Determines if the current package version represents a patch release. - * - * @param pkgVersion - The semantic version of the package (e.g., "2.0.1") - * @returns True if the version is a patch release, false otherwise - * - * @throws Error When the provided version string is not a valid semantic version - * - * @example - * ```typescript - * isCurrentPackageVersionPatch("2.0.1"); // returns true - * isCurrentPackageVersionPatch("2.1.0"); // returns false - * isCurrentPackageVersionPatch("3.0.0"); // returns false - * ``` - */ -export function isCurrentPackageVersionPatch(pkgVersion: string): boolean { - const parsed = parse(pkgVersion); - if (parsed === null) { - throw new Error(`Package version ${pkgVersion} is not a valid semver`); - } - return parsed.patch > 0; -} - /** * Generates the complete content for a layer generation TypeScript file. * @@ -144,71 +120,3 @@ export function generateLayerFileContent(generation: number): string { export const generation = ${generation}; `; } - -/** - * Determines if a new generation should be generated based on package version changes and time since - * the last release. - * - * This function parses an existing layer generation file and decides whether to increment the generation - * number based on: - * 1. Whether the package version has changed since the last update - * 2. How much time has elapsed since the previous release date - * 3. The minimum compatibility window constraints - * - * The generation increment is calculated as the number of months since the previous release, - * but capped at (minimumCompatWindowMonths - 1) to maintain compatibility requirements. - * - * @param currentPkgVersion - The current package version to compare against the stored version - * @param fluidCompatMetadata - The existing Fluid compatibility metadata from the previous generation - * @param minimumCompatWindowMonths - The maximum number of months of compatibility to maintain across layers - * @param log - Logger instance for verbose output about the calculation process - * @returns The new generation number if an update is needed, or undefined if no update is required - * - * @throws Error When the generation file content doesn't match the expected format - * @throws Error When the current date is older than the previous release date - */ -export function maybeGetNewGeneration( - currentPkgVersion: string, - fluidCompatMetadata: IFluidCompatibilityMetadata, - minimumCompatWindowMonths: number, - log: Logger, -): number | undefined { - // Only "minor" or "major" version changes trigger generation updates. - const result = diff(currentPkgVersion, fluidCompatMetadata.releasePkgVersion); - if (result === null || (result !== "minor" && result !== "major")) { - log.verbose(`No minor or major release since last update; skipping generation update.`); - return undefined; - } - - log.verbose( - `Previous package version: ${fluidCompatMetadata.releasePkgVersion}, Current package version: ${currentPkgVersion}`, - ); - - const previousReleaseDate = parseISO(fluidCompatMetadata.releaseDate); - if (!isValid(previousReleaseDate) || !isDate(previousReleaseDate)) { - throw new Error( - `Previous release date "${fluidCompatMetadata.releaseDate}" is not a valid date.`, - ); - } - - const today = new Date(); - const timeDiff = today.getTime() - previousReleaseDate.getTime(); - if (timeDiff < 0) { - throw new Error("Current date is older that previous release date"); - } - const daysBetweenReleases = Math.round(timeDiff / (1000 * 60 * 60 * 24)); - const monthsBetweenReleases = Math.floor(daysBetweenReleases / daysInMonthApproximation); - log.verbose(`Previous release date: ${previousReleaseDate}, Today: ${today}`); - log.verbose( - `Time between releases: ${daysBetweenReleases} day(s) or ~${monthsBetweenReleases} month(s)`, - ); - - const newGeneration = - fluidCompatMetadata.generation + - Math.min(monthsBetweenReleases, minimumCompatWindowMonths - 1); - if (newGeneration === fluidCompatMetadata.generation) { - log.verbose(`Generation remains the same (${newGeneration}); skipping generation update.`); - return undefined; - } - return newGeneration; -} diff --git a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts index b3d60e119e68..ad026b9e95ba 100644 --- a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts +++ b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts @@ -22,8 +22,8 @@ import { getPreReleaseDependencies, getReleaseSourceForReleaseGroup, isReleased, - runCompatLayerGenerationCheck, } from "../library/index.js"; +import { runCompatLayerGenerationCheck } from "../library/releasePrepChecks.js"; import type { CommandLogger } from "../logging.js"; import type { MachineState } from "../machines/index.js"; import { type ReleaseSource, isReleaseGroup } from "../releaseGroups.js"; diff --git a/build-tools/packages/build-cli/src/library/index.ts b/build-tools/packages/build-cli/src/library/index.ts index cb0e7afd397b..5e2c4070012a 100644 --- a/build-tools/packages/build-cli/src/library/index.ts +++ b/build-tools/packages/build-cli/src/library/index.ts @@ -65,4 +65,3 @@ export { } from "./release.js"; export { LayerGraph } from "./layerGraph.js"; export { type Handler, policyHandlers } from "./repoPolicyCheck/index.js"; -export { runCompatLayerGenerationCheck } from "./releasePrepChecks.js"; diff --git a/build-tools/packages/build-cli/src/library/layerCompatibility.ts b/build-tools/packages/build-cli/src/library/layerCompatibility.ts new file mode 100644 index 000000000000..26411ef281f8 --- /dev/null +++ b/build-tools/packages/build-cli/src/library/layerCompatibility.ts @@ -0,0 +1,102 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import type { IFluidCompatibilityMetadata, Logger } from "@fluidframework/build-tools"; +import { formatISO, isDate, isValid, parseISO } from "date-fns"; +import { diff, parse } from "semver"; + +// Approximate month as 33 days to add some buffer and avoid over-counting months in longer spans. +export const daysInMonthApproximation = 33; + +/** + * Determines if the current package version represents a patch release. + * + * @param pkgVersion - The semantic version of the package (e.g., "2.0.1") + * @returns True if the version is a patch release, false otherwise + * + * @throws Error When the provided version string is not a valid semantic version + * + * @example + * ```typescript + * isCurrentPackageVersionPatch("2.0.1"); // returns true + * isCurrentPackageVersionPatch("2.1.0"); // returns false + * isCurrentPackageVersionPatch("3.0.0"); // returns false + * ``` + */ +export function isCurrentPackageVersionPatch(pkgVersion: string): boolean { + const parsed = parse(pkgVersion); + if (parsed === null) { + throw new Error(`Package version ${pkgVersion} is not a valid semver`); + } + return parsed.patch > 0; +} + +/** + * Determines if a new generation should be generated based on package version changes and time since + * the last release. + * + * This function parses an existing layer generation file and decides whether to increment the generation + * number based on: + * 1. Whether the package version has changed since the last update + * 2. How much time has elapsed since the previous release date + * 3. The minimum compatibility window constraints + * + * The generation increment is calculated as the number of months since the previous release, + * but capped at (minimumCompatWindowMonths - 1) to maintain compatibility requirements. + * + * @param currentPkgVersion - The current package version to compare against the stored version + * @param fluidCompatMetadata - The existing Fluid compatibility metadata from the previous generation + * @param minimumCompatWindowMonths - The maximum number of months of compatibility to maintain across layers + * @param log - Logger instance for verbose output about the calculation process + * @returns The new generation number if an update is needed, or undefined if no update is required + * + * @throws Error When the generation file content doesn't match the expected format + * @throws Error When the current date is older than the previous release date + */ +export function maybeGetNewGeneration( + currentPkgVersion: string, + fluidCompatMetadata: IFluidCompatibilityMetadata, + minimumCompatWindowMonths: number, + log: Logger, +): number | undefined { + // Only "minor" or "major" version changes trigger generation updates. + const result = diff(currentPkgVersion, fluidCompatMetadata.releasePkgVersion); + if (result === null || (result !== "minor" && result !== "major")) { + log.verbose(`No minor or major release since last update; skipping generation update.`); + return undefined; + } + + log.verbose( + `Previous package version: ${fluidCompatMetadata.releasePkgVersion}, Current package version: ${currentPkgVersion}`, + ); + + const previousReleaseDate = parseISO(fluidCompatMetadata.releaseDate); + if (!isValid(previousReleaseDate) || !isDate(previousReleaseDate)) { + throw new Error( + `Previous release date "${fluidCompatMetadata.releaseDate}" is not a valid date.`, + ); + } + + const today = new Date(); + const timeDiff = today.getTime() - previousReleaseDate.getTime(); + if (timeDiff < 0) { + throw new Error("Current date is older that previous release date"); + } + const daysBetweenReleases = Math.round(timeDiff / (1000 * 60 * 60 * 24)); + const monthsBetweenReleases = Math.floor(daysBetweenReleases / daysInMonthApproximation); + log.verbose(`Previous release date: ${previousReleaseDate}, Today: ${today}`); + log.verbose( + `Time between releases: ${daysBetweenReleases} day(s) or ~${monthsBetweenReleases} month(s)`, + ); + + const newGeneration = + fluidCompatMetadata.generation + + Math.min(monthsBetweenReleases, minimumCompatWindowMonths - 1); + if (newGeneration === fluidCompatMetadata.generation) { + log.verbose(`Generation remains the same (${newGeneration}); skipping generation update.`); + return undefined; + } + return newGeneration; +} diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index e38749ffdc94..5666614c65d3 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -7,6 +7,7 @@ import { MonoRepo, type Package } from "@fluidframework/build-tools"; import execa from "execa"; import { ResetMode } from "simple-git"; import type { Context } from "./context.js"; +import { isCurrentPackageVersionPatch, maybeGetNewGeneration } from "./layerCompatibility.js"; import { getPreReleaseDependencies } from "./package.js"; /** @@ -255,12 +256,6 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( * @returns `true` if all configured packages have up-to-date layer generation metadata, `false` if any updates are needed. */ export async function runCompatLayerGenerationCheck(context: Context): Promise { - const { maybeGetNewGeneration, isCurrentPackageVersionPatch } = await import( - // library is overloaded with too much stuff now, and we should consider allowing interior imports. - // eslint-disable-next-line import/no-internal-modules - "../commands/generate/layerCompatGeneration.js" - ); - // Check all packages that have fluidCompatMetadata for (const pkg of context.fullPackageMap.values()) { const { fluidCompatMetadata } = pkg.packageJson; From 157bbd06cfd92d75821d476bba7d51af7a614f94 Mon Sep 17 00:00:00 2001 From: Tyler Butler Date: Wed, 3 Dec 2025 10:54:17 -0800 Subject: [PATCH 14/19] Update build-tools/packages/build-cli/src/handlers/checkFunctions.ts --- build-tools/packages/build-cli/src/handlers/checkFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts index ad026b9e95ba..1cfb7d3bcd6c 100644 --- a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts +++ b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts @@ -937,7 +937,7 @@ export const checkCompatLayerGeneration: StateHandlerFunction = async ( if (!isUpToDate) { log.logHr(); log.errorLog( - `Layer generation needs to be updated. Please create a PR for the changes and merge before retrying.`, + `Compat layer generation needs to be updated. Run "pnpm run layerGeneration:gen" from the repo root, then create a PR for the changes and merge before retrying.`, ); BaseStateHandler.signalFailure(machine, state); return false; From ef2a5a9f425fa3805365a2ff24c84a8a0e95621f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 19:40:49 +0000 Subject: [PATCH 15/19] Refactor layer compatibility functions based on code review feedback - Rename daysInMonthApproximation to DAYS_IN_MONTH_APPROXIMATION (constant) - Move DEFAULT_MINIMUM_COMPAT_WINDOW_MONTHS to layerCompatibility.ts as shared constant - Make logger parameter optional in maybeGetNewGeneration - Add currentDate parameter to maybeGetNewGeneration for testability - Refactor runCompatLayerGenerationCheck to take list of packages instead of context - Update CheckCompatLayerGeneration to use releaseGroupOrPackage parameter - Update all imports and references in tests Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../build-cli/src/handlers/checkFunctions.ts | 2 +- .../src/library/layerCompatibility.ts | 33 ++++++++------ .../src/library/releasePrepChecks.ts | 43 ++++++++----------- .../generate/layerCompatGeneration.test.ts | 12 +++--- 4 files changed, 48 insertions(+), 42 deletions(-) diff --git a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts index 1cfb7d3bcd6c..e0f80c6165ce 100644 --- a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts +++ b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts @@ -932,7 +932,7 @@ export const checkCompatLayerGeneration: StateHandlerFunction = async ( return true; } - const isUpToDate = await runCompatLayerGenerationCheck(context); + const isUpToDate = await runCompatLayerGenerationCheck(context.fullPackageMap.values()); if (!isUpToDate) { log.logHr(); diff --git a/build-tools/packages/build-cli/src/library/layerCompatibility.ts b/build-tools/packages/build-cli/src/library/layerCompatibility.ts index 26411ef281f8..979364cc9fe4 100644 --- a/build-tools/packages/build-cli/src/library/layerCompatibility.ts +++ b/build-tools/packages/build-cli/src/library/layerCompatibility.ts @@ -7,8 +7,16 @@ import type { IFluidCompatibilityMetadata, Logger } from "@fluidframework/build- import { formatISO, isDate, isValid, parseISO } from "date-fns"; import { diff, parse } from "semver"; -// Approximate month as 33 days to add some buffer and avoid over-counting months in longer spans. -export const daysInMonthApproximation = 33; +/** + * Approximate month as 33 days to add some buffer and avoid over-counting months in longer spans. + */ +export const DAYS_IN_MONTH_APPROXIMATION = 33; + +/** + * The default minimum compatibility window in months for layer generation. + * This matches the default value used in the layerCompatGeneration command. + */ +export const DEFAULT_MINIMUM_COMPAT_WINDOW_MONTHS = 3; /** * Determines if the current package version represents a patch release. @@ -49,7 +57,8 @@ export function isCurrentPackageVersionPatch(pkgVersion: string): boolean { * @param currentPkgVersion - The current package version to compare against the stored version * @param fluidCompatMetadata - The existing Fluid compatibility metadata from the previous generation * @param minimumCompatWindowMonths - The maximum number of months of compatibility to maintain across layers - * @param log - Logger instance for verbose output about the calculation process + * @param log - Optional logger instance for verbose output about the calculation process + * @param currentDate - Optional current date for testing purposes. Defaults to new Date() * @returns The new generation number if an update is needed, or undefined if no update is required * * @throws Error When the generation file content doesn't match the expected format @@ -59,16 +68,17 @@ export function maybeGetNewGeneration( currentPkgVersion: string, fluidCompatMetadata: IFluidCompatibilityMetadata, minimumCompatWindowMonths: number, - log: Logger, + log?: Logger, + currentDate: Date = new Date(), ): number | undefined { // Only "minor" or "major" version changes trigger generation updates. const result = diff(currentPkgVersion, fluidCompatMetadata.releasePkgVersion); if (result === null || (result !== "minor" && result !== "major")) { - log.verbose(`No minor or major release since last update; skipping generation update.`); + log?.verbose(`No minor or major release since last update; skipping generation update.`); return undefined; } - log.verbose( + log?.verbose( `Previous package version: ${fluidCompatMetadata.releasePkgVersion}, Current package version: ${currentPkgVersion}`, ); @@ -79,15 +89,14 @@ export function maybeGetNewGeneration( ); } - const today = new Date(); - const timeDiff = today.getTime() - previousReleaseDate.getTime(); + const timeDiff = currentDate.getTime() - previousReleaseDate.getTime(); if (timeDiff < 0) { throw new Error("Current date is older that previous release date"); } const daysBetweenReleases = Math.round(timeDiff / (1000 * 60 * 60 * 24)); - const monthsBetweenReleases = Math.floor(daysBetweenReleases / daysInMonthApproximation); - log.verbose(`Previous release date: ${previousReleaseDate}, Today: ${today}`); - log.verbose( + const monthsBetweenReleases = Math.floor(daysBetweenReleases / DAYS_IN_MONTH_APPROXIMATION); + log?.verbose(`Previous release date: ${previousReleaseDate}, Today: ${currentDate}`); + log?.verbose( `Time between releases: ${daysBetweenReleases} day(s) or ~${monthsBetweenReleases} month(s)`, ); @@ -95,7 +104,7 @@ export function maybeGetNewGeneration( fluidCompatMetadata.generation + Math.min(monthsBetweenReleases, minimumCompatWindowMonths - 1); if (newGeneration === fluidCompatMetadata.generation) { - log.verbose(`Generation remains the same (${newGeneration}); skipping generation update.`); + log?.verbose(`Generation remains the same (${newGeneration}); skipping generation update.`); return undefined; } return newGeneration; diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index 5666614c65d3..ae491952e78f 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -7,15 +7,13 @@ import { MonoRepo, type Package } from "@fluidframework/build-tools"; import execa from "execa"; import { ResetMode } from "simple-git"; import type { Context } from "./context.js"; -import { isCurrentPackageVersionPatch, maybeGetNewGeneration } from "./layerCompatibility.js"; +import { + DEFAULT_MINIMUM_COMPAT_WINDOW_MONTHS, + isCurrentPackageVersionPatch, + maybeGetNewGeneration, +} from "./layerCompatibility.js"; import { getPreReleaseDependencies } from "./package.js"; -/** - * The default minimum compatibility window in months for layer generation. - * This matches the default value used in the layerCompatGeneration command. - */ -const DEFAULT_MINIMUM_COMPAT_WINDOW_MONTHS = 3; - /** * An async function that executes a release preparation check. The function returns a {@link CheckResult} with details * about the results of the check. @@ -252,12 +250,14 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( * 3. The command will create the `fluidCompatMetadata` field in package.json with generation 1 * and generate the layer generation file (e.g., `src/layerGenerationState.ts`) * - * @param context - The repository context. + * @param packages - The list of packages to check. * @returns `true` if all configured packages have up-to-date layer generation metadata, `false` if any updates are needed. */ -export async function runCompatLayerGenerationCheck(context: Context): Promise { +export async function runCompatLayerGenerationCheck( + packages: Iterable, +): Promise { // Check all packages that have fluidCompatMetadata - for (const pkg of context.fullPackageMap.values()) { + for (const pkg of packages) { const { fluidCompatMetadata } = pkg.packageJson; // Skip packages without compatibility metadata - not all packages need layer compatibility @@ -272,21 +272,11 @@ export async function runCompatLayerGenerationCheck(context: Context): Promise {}, - verbose: () => {}, - info: () => {}, - warning: () => {}, - errorLog: () => {}, - }; - - // Check if this package needs a generation update + // Check if this package needs a generation update (no logger for checks) const newGeneration = maybeGetNewGeneration( currentPkgVersion, fluidCompatMetadata, DEFAULT_MINIMUM_COMPAT_WINDOW_MONTHS, - noopLogger, ); // If any package needs an update, return false @@ -303,10 +293,15 @@ export async function runCompatLayerGenerationCheck(context: Context): Promise => { - const isUpToDate = await runCompatLayerGenerationCheck(context); + const packagesToCheck = + releaseGroupOrPackage instanceof MonoRepo + ? releaseGroupOrPackage.packages + : [releaseGroupOrPackage]; + + const isUpToDate = await runCompatLayerGenerationCheck(packagesToCheck); if (!isUpToDate) { return { diff --git a/build-tools/packages/build-cli/src/test/commands/generate/layerCompatGeneration.test.ts b/build-tools/packages/build-cli/src/test/commands/generate/layerCompatGeneration.test.ts index 5b4a5781ccd7..d4ec4b0cd6fe 100644 --- a/build-tools/packages/build-cli/src/test/commands/generate/layerCompatGeneration.test.ts +++ b/build-tools/packages/build-cli/src/test/commands/generate/layerCompatGeneration.test.ts @@ -9,11 +9,13 @@ import { formatISO } from "date-fns"; import { describe, it } from "mocha"; import UpdateGenerationCommand, { - daysInMonthApproximation, generateLayerFileContent, +} from "../../../commands/generate/layerCompatGeneration.js"; +import { + DAYS_IN_MONTH_APPROXIMATION, isCurrentPackageVersionPatch, maybeGetNewGeneration, -} from "../../../commands/generate/layerCompatGeneration.js"; +} from "../../../library/layerCompatibility.js"; describe("generate:layerCompatGeneration", () => { const minimumCompatWindowMonths = UpdateGenerationCommand.flags.minimumCompatWindowMonths @@ -77,7 +79,7 @@ describe("generate:layerCompatGeneration", () => { // Create a date 2 months ago (should normally trigger increment) const oldDate = new Date(); - oldDate.setDate(oldDate.getDate() - 2 * daysInMonthApproximation); + oldDate.setDate(oldDate.getDate() - 2 * DAYS_IN_MONTH_APPROXIMATION); const oldDateString = formatISO(oldDate, { representation: "date" }); const mockMetadata: IFluidCompatibilityMetadata = { generation: previousGeneration, @@ -102,7 +104,7 @@ describe("generate:layerCompatGeneration", () => { // Create a date monthsSincePreviousRelease months ago const oldDate = new Date(); - oldDate.setDate(oldDate.getDate() - monthsSincePreviousRelease * daysInMonthApproximation); + oldDate.setDate(oldDate.getDate() - monthsSincePreviousRelease * DAYS_IN_MONTH_APPROXIMATION); const oldDateString = formatISO(oldDate, { representation: "date" }); const mockMetadata: IFluidCompatibilityMetadata = { generation: previousGeneration, @@ -126,7 +128,7 @@ describe("generate:layerCompatGeneration", () => { // Create a date monthsSincePreviousRelease months ago const oldDate = new Date(); - oldDate.setDate(oldDate.getDate() - monthsSincePreviousRelease * daysInMonthApproximation); + oldDate.setDate(oldDate.getDate() - monthsSincePreviousRelease * DAYS_IN_MONTH_APPROXIMATION); const oldDateString = formatISO(oldDate, { representation: "date" }); const mockMetadata: IFluidCompatibilityMetadata = { generation: previousGeneration, From afa24cf761877515c91266c9a489c5c6b41a9413 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:05:48 +0000 Subject: [PATCH 16/19] Fix layer generation check to scope to release group/package and update fixCommand - Update checkCompatLayerGeneration to check only packages in the release group or individual package being released, not all packages in repo - Change fixCommand from "pnpm run -r layerGeneration:gen" to "pnpm run layerGeneration:gen" to allow root package.json to handle the -r flag - Add Package import to checkFunctions.ts Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../packages/build-cli/src/handlers/checkFunctions.ts | 11 ++++++++--- .../build-cli/src/library/releasePrepChecks.ts | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts index e0f80c6165ce..9fcfc33b26b9 100644 --- a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts +++ b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts @@ -11,7 +11,7 @@ import execa from "execa"; import type { Machine } from "jssm"; import { bumpVersionScheme } from "@fluid-tools/version-tools"; -import { FluidRepo } from "@fluidframework/build-tools"; +import { FluidRepo, type Package } from "@fluidframework/build-tools"; import { generateBumpDepsBranchName, @@ -924,7 +924,7 @@ export const checkCompatLayerGeneration: StateHandlerFunction = async ( ): Promise => { if (testMode) return true; - const { context, bumpType } = data; + const { context, bumpType, releaseGroup } = data; if (bumpType === "patch") { log.verbose(`Skipping layer compat generation check for patch release.`); @@ -932,7 +932,12 @@ export const checkCompatLayerGeneration: StateHandlerFunction = async ( return true; } - const isUpToDate = await runCompatLayerGenerationCheck(context.fullPackageMap.values()); + // Get packages for the release group or individual package being released + const packagesToCheck = isReleaseGroup(releaseGroup) + ? context.packagesInReleaseGroup(releaseGroup) + : [context.fullPackageMap.get(releaseGroup)].filter((pkg): pkg is Package => pkg !== undefined); + + const isUpToDate = await runCompatLayerGenerationCheck(packagesToCheck); if (!isUpToDate) { log.logHr(); diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index ae491952e78f..cd1006ffbe36 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -306,7 +306,7 @@ export const CheckCompatLayerGeneration: CheckFunction = async ( if (!isUpToDate) { return { message: "Layer generation needs to be updated.", - fixCommand: "pnpm run -r layerGeneration:gen", + fixCommand: "pnpm run layerGeneration:gen", }; } From 03714cf46f23e5f2cee2dca4e09e49095c5a3bae Mon Sep 17 00:00:00 2001 From: Tyler Butler Date: Wed, 3 Dec 2025 13:42:02 -0800 Subject: [PATCH 17/19] format --- .../packages/build-cli/src/handlers/checkFunctions.ts | 4 +++- .../packages/build-cli/src/library/layerCompatibility.ts | 4 +++- .../test/commands/generate/compatLayerGeneration.test.ts | 8 ++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts index 9fcfc33b26b9..a6bb43966544 100644 --- a/build-tools/packages/build-cli/src/handlers/checkFunctions.ts +++ b/build-tools/packages/build-cli/src/handlers/checkFunctions.ts @@ -935,7 +935,9 @@ export const checkCompatLayerGeneration: StateHandlerFunction = async ( // Get packages for the release group or individual package being released const packagesToCheck = isReleaseGroup(releaseGroup) ? context.packagesInReleaseGroup(releaseGroup) - : [context.fullPackageMap.get(releaseGroup)].filter((pkg): pkg is Package => pkg !== undefined); + : [context.fullPackageMap.get(releaseGroup)].filter( + (pkg): pkg is Package => pkg !== undefined, + ); const isUpToDate = await runCompatLayerGenerationCheck(packagesToCheck); diff --git a/build-tools/packages/build-cli/src/library/layerCompatibility.ts b/build-tools/packages/build-cli/src/library/layerCompatibility.ts index 979364cc9fe4..72713a322df1 100644 --- a/build-tools/packages/build-cli/src/library/layerCompatibility.ts +++ b/build-tools/packages/build-cli/src/library/layerCompatibility.ts @@ -104,7 +104,9 @@ export function maybeGetNewGeneration( fluidCompatMetadata.generation + Math.min(monthsBetweenReleases, minimumCompatWindowMonths - 1); if (newGeneration === fluidCompatMetadata.generation) { - log?.verbose(`Generation remains the same (${newGeneration}); skipping generation update.`); + log?.verbose( + `Generation remains the same (${newGeneration}); skipping generation update.`, + ); return undefined; } return newGeneration; diff --git a/build-tools/packages/build-cli/src/test/commands/generate/compatLayerGeneration.test.ts b/build-tools/packages/build-cli/src/test/commands/generate/compatLayerGeneration.test.ts index f344c848e8ba..be7e4402989a 100644 --- a/build-tools/packages/build-cli/src/test/commands/generate/compatLayerGeneration.test.ts +++ b/build-tools/packages/build-cli/src/test/commands/generate/compatLayerGeneration.test.ts @@ -104,7 +104,9 @@ describe("generate:compatLayerGeneration", () => { // Create a date monthsSincePreviousRelease months ago const oldDate = new Date(); - oldDate.setDate(oldDate.getDate() - monthsSincePreviousRelease * DAYS_IN_MONTH_APPROXIMATION); + oldDate.setDate( + oldDate.getDate() - monthsSincePreviousRelease * DAYS_IN_MONTH_APPROXIMATION, + ); const oldDateString = formatISO(oldDate, { representation: "date" }); const mockMetadata: IFluidCompatibilityMetadata = { generation: previousGeneration, @@ -128,7 +130,9 @@ describe("generate:compatLayerGeneration", () => { // Create a date monthsSincePreviousRelease months ago const oldDate = new Date(); - oldDate.setDate(oldDate.getDate() - monthsSincePreviousRelease * DAYS_IN_MONTH_APPROXIMATION); + oldDate.setDate( + oldDate.getDate() - monthsSincePreviousRelease * DAYS_IN_MONTH_APPROXIMATION, + ); const oldDateString = formatISO(oldDate, { representation: "date" }); const mockMetadata: IFluidCompatibilityMetadata = { generation: previousGeneration, From 45ab3c3410a197f7b2d646e4a62af38b9612bcdc Mon Sep 17 00:00:00 2001 From: Tyler Butler Date: Wed, 3 Dec 2025 13:42:56 -0800 Subject: [PATCH 18/19] build --- build-tools/packages/build-cli/docs/release.md | 1 + 1 file changed, 1 insertion(+) diff --git a/build-tools/packages/build-cli/docs/release.md b/build-tools/packages/build-cli/docs/release.md index ed4cc7c65c72..db266eb3fdff 100644 --- a/build-tools/packages/build-cli/docs/release.md +++ b/build-tools/packages/build-cli/docs/release.md @@ -161,6 +161,7 @@ DESCRIPTION - Has no pre-release Fluid dependencies - No repo policy violations - No untagged asserts + - Compatibility layer generation is up to date ALIASES $ flub release prep From 43b8af40feb84ca9a08585d2b3470453b857f24b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 22:28:30 +0000 Subject: [PATCH 19/19] Update comments to reference compatLayerGeneration command name Co-authored-by: tylerbutler <19589+tylerbutler@users.noreply.github.com> --- .../packages/build-cli/src/library/layerCompatibility.ts | 2 +- build-tools/packages/build-cli/src/library/releasePrepChecks.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-tools/packages/build-cli/src/library/layerCompatibility.ts b/build-tools/packages/build-cli/src/library/layerCompatibility.ts index 72713a322df1..cdd0750b11cc 100644 --- a/build-tools/packages/build-cli/src/library/layerCompatibility.ts +++ b/build-tools/packages/build-cli/src/library/layerCompatibility.ts @@ -14,7 +14,7 @@ export const DAYS_IN_MONTH_APPROXIMATION = 33; /** * The default minimum compatibility window in months for layer generation. - * This matches the default value used in the layerCompatGeneration command. + * This matches the default value used in the compatLayerGeneration command. */ export const DEFAULT_MINIMUM_COMPAT_WINDOW_MONTHS = 3; diff --git a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts index cd1006ffbe36..f008c4902bce 100644 --- a/build-tools/packages/build-cli/src/library/releasePrepChecks.ts +++ b/build-tools/packages/build-cli/src/library/releasePrepChecks.ts @@ -245,7 +245,7 @@ export const CheckNoUntaggedAsserts: CheckFunction = async ( * **Setting up a new package for layer compatibility:** * To add layer compatibility to a package that doesn't have it yet: * 1. Add a `layerGeneration:gen` script to the package's package.json: - * `"layerGeneration:gen": "flub generate layerCompatGeneration --dir . -v"` + * `"layerGeneration:gen": "flub generate compatLayerGeneration --dir . -v"` * 2. Run the command to initialize the package: `pnpm run layerGeneration:gen` * 3. The command will create the `fluidCompatMetadata` field in package.json with generation 1 * and generate the layer generation file (e.g., `src/layerGenerationState.ts`)