From 555008682a6c6b128e4f9eb01b8336dd1f18c5df Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 12 Feb 2026 17:39:03 +0000 Subject: [PATCH 1/7] Move `getDefaultCliVersion` out of `GitHubFeatureFlags` It doesn't need to be in there since it doesn't depend on the API itself and call `getDefaultCliVersionFromFlags` directly --- lib/analyze-action.js | 17 +++++++---------- lib/autobuild-action.js | 17 +++++++---------- lib/init-action-post.js | 17 +++++++---------- lib/init-action.js | 17 +++++++---------- lib/setup-codeql-action.js | 17 +++++++---------- lib/start-proxy-action.js | 17 +++++++---------- lib/upload-sarif-action.js | 17 +++++++---------- src/feature-flags.ts | 20 +++++++------------- 8 files changed, 56 insertions(+), 83 deletions(-) diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 7e4e89eeab..9c0210d286 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -107805,7 +107805,13 @@ var Features = class { } gitHubFeatureFlags; async getDefaultCliVersion(variant) { - return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return { + cliVersion, + tagName: bundleVersion + }; } /** * @@ -107912,15 +107918,6 @@ var GitHubFeatureFlags = class { } return version; } - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.getDefaultCliVersionFromFlags(); - } - return { - cliVersion, - tagName: bundleVersion - }; - } async getDefaultCliVersionFromFlags() { const response = await this.getAllFeatures(); const enabledFeatureFlagCliVersions = Object.entries(response).map( diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index e495cffbee..ce869f8eae 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -104138,7 +104138,13 @@ var Features = class { } gitHubFeatureFlags; async getDefaultCliVersion(variant) { - return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return { + cliVersion, + tagName: bundleVersion + }; } /** * @@ -104245,15 +104251,6 @@ var GitHubFeatureFlags = class { } return version; } - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.getDefaultCliVersionFromFlags(); - } - return { - cliVersion, - tagName: bundleVersion - }; - } async getDefaultCliVersionFromFlags() { const response = await this.getAllFeatures(); const enabledFeatureFlagCliVersions = Object.entries(response).map( diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 1f1869f412..78e19df587 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165131,7 +165131,13 @@ var Features = class { } gitHubFeatureFlags; async getDefaultCliVersion(variant) { - return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return { + cliVersion, + tagName: bundleVersion + }; } /** * @@ -165238,15 +165244,6 @@ var GitHubFeatureFlags = class { } return version; } - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.getDefaultCliVersionFromFlags(); - } - return { - cliVersion, - tagName: bundleVersion - }; - } async getDefaultCliVersionFromFlags() { const response = await this.getAllFeatures(); const enabledFeatureFlagCliVersions = Object.entries(response).map( diff --git a/lib/init-action.js b/lib/init-action.js index 34c83bc4ae..9ac4c39a04 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105338,7 +105338,13 @@ var Features = class { } gitHubFeatureFlags; async getDefaultCliVersion(variant) { - return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return { + cliVersion, + tagName: bundleVersion + }; } /** * @@ -105445,15 +105451,6 @@ var GitHubFeatureFlags = class { } return version; } - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.getDefaultCliVersionFromFlags(); - } - return { - cliVersion, - tagName: bundleVersion - }; - } async getDefaultCliVersionFromFlags() { const response = await this.getAllFeatures(); const enabledFeatureFlagCliVersions = Object.entries(response).map( diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index 445f3b5768..bdf540153a 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -104039,7 +104039,13 @@ var Features = class { } gitHubFeatureFlags; async getDefaultCliVersion(variant) { - return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return { + cliVersion, + tagName: bundleVersion + }; } /** * @@ -104146,15 +104152,6 @@ var GitHubFeatureFlags = class { } return version; } - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.getDefaultCliVersionFromFlags(); - } - return { - cliVersion, - tagName: bundleVersion - }; - } async getDefaultCliVersionFromFlags() { const response = await this.getAllFeatures(); const enabledFeatureFlagCliVersions = Object.entries(response).map( diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index 1aa9d0f634..9e53638eef 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -120832,7 +120832,13 @@ var Features = class { } gitHubFeatureFlags; async getDefaultCliVersion(variant) { - return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return { + cliVersion, + tagName: bundleVersion + }; } /** * @@ -120939,15 +120945,6 @@ var GitHubFeatureFlags = class { } return version; } - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.getDefaultCliVersionFromFlags(); - } - return { - cliVersion, - tagName: bundleVersion - }; - } async getDefaultCliVersionFromFlags() { const response = await this.getAllFeatures(); const enabledFeatureFlagCliVersions = Object.entries(response).map( diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index e326893e67..07141748cf 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -106993,7 +106993,13 @@ var Features = class { } gitHubFeatureFlags; async getDefaultCliVersion(variant) { - return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return { + cliVersion, + tagName: bundleVersion + }; } /** * @@ -107100,15 +107106,6 @@ var GitHubFeatureFlags = class { } return version; } - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.getDefaultCliVersionFromFlags(); - } - return { - cliVersion, - tagName: bundleVersion - }; - } async getDefaultCliVersionFromFlags() { const response = await this.getAllFeatures(); const enabledFeatureFlagCliVersions = Object.entries(response).map( diff --git a/src/feature-flags.ts b/src/feature-flags.ts index ea8cc1767b..ab5798ed32 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -378,7 +378,13 @@ export class Features implements FeatureEnablement { async getDefaultCliVersion( variant: util.GitHubVariant, ): Promise { - return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return { + cliVersion: defaults.cliVersion, + tagName: defaults.bundleVersion, + }; } /** @@ -524,18 +530,6 @@ class GitHubFeatureFlags { return version; } - async getDefaultCliVersion( - variant: util.GitHubVariant, - ): Promise { - if (supportsFeatureFlags(variant)) { - return await this.getDefaultCliVersionFromFlags(); - } - return { - cliVersion: defaults.cliVersion, - tagName: defaults.bundleVersion, - }; - } - async getDefaultCliVersionFromFlags(): Promise { const response = await this.getAllFeatures(); From 40fb3a7822f474a0ca4de2a385a38baa21f44e5c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 12 Feb 2026 17:42:40 +0000 Subject: [PATCH 2/7] Add `OfflineFeatures` class --- lib/analyze-action.js | 87 ++++++++++++++++++------ lib/autobuild-action.js | 87 ++++++++++++++++++------ lib/init-action-post.js | 87 ++++++++++++++++++------ lib/init-action.js | 87 ++++++++++++++++++------ lib/setup-codeql-action.js | 87 ++++++++++++++++++------ lib/start-proxy-action.js | 87 ++++++++++++++++++------ lib/upload-sarif-action.js | 87 ++++++++++++++++++------ src/feature-flags.ts | 136 +++++++++++++++++++++++++++---------- 8 files changed, 577 insertions(+), 168 deletions(-) diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 9c0210d286..4149ebb9da 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -107793,27 +107793,24 @@ var featureConfig = { } }; var FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; -var Features = class { - constructor(gitHubVersion, repositoryNwo, tempDir, logger) { +var OfflineFeatures = class { + constructor(logger) { this.logger = logger; - this.gitHubFeatureFlags = new GitHubFeatureFlags( - gitHubVersion, - repositoryNwo, - path5.join(tempDir, FEATURE_FLAGS_FILE_NAME), - logger - ); } - gitHubFeatureFlags; - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); - } + async getDefaultCliVersion(_variant) { return { cliVersion, tagName: bundleVersion }; } /** + * Gets the `FeatureConfig` for `feature`. + */ + getFeatureConfig(feature) { + return featureConfig[feature]; + } + /** + * Determines whether `feature` is enabled without consulting the GitHub API. * * @param feature The feature to check. * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the @@ -107826,7 +107823,17 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { - const config = featureConfig[feature]; + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } + return this.getDefaultValue(feature); + } + /** + * Determines whether `feature` is enabled using the CLI and environment variables. + */ + async getOfflineValue(feature, codeql) { + const config = this.getFeatureConfig(feature); if (!codeql && config.minimumVersion) { throw new Error( `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.` @@ -107876,6 +107883,52 @@ var Features = class { ); return true; } + return void 0; + } + /** Gets the default value of `feature`. */ + async getDefaultValue(feature) { + const config = this.getFeatureConfig(feature); + const defaultValue = config.defaultValue; + this.logger.debug( + `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` + ); + return defaultValue; + } +}; +var Features = class extends OfflineFeatures { + gitHubFeatureFlags; + constructor(gitHubVersion, repositoryNwo, tempDir, logger) { + super(logger); + this.gitHubFeatureFlags = new GitHubFeatureFlags( + gitHubVersion, + repositoryNwo, + path5.join(tempDir, FEATURE_FLAGS_FILE_NAME), + logger + ); + } + async getDefaultCliVersion(variant) { + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return super.getDefaultCliVersion(variant); + } + /** + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, the + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature, codeql) { + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } const apiValue = await this.gitHubFeatureFlags.getValue(feature); if (apiValue !== void 0) { this.logger.debug( @@ -107883,11 +107936,7 @@ var Features = class { ); return apiValue; } - const defaultValue = config.defaultValue; - this.logger.debug( - `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` - ); - return defaultValue; + return this.getDefaultValue(feature); } }; var GitHubFeatureFlags = class { diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index ce869f8eae..d75ce54a99 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -104126,27 +104126,24 @@ var featureConfig = { } }; var FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; -var Features = class { - constructor(gitHubVersion, repositoryNwo, tempDir, logger) { +var OfflineFeatures = class { + constructor(logger) { this.logger = logger; - this.gitHubFeatureFlags = new GitHubFeatureFlags( - gitHubVersion, - repositoryNwo, - path3.join(tempDir, FEATURE_FLAGS_FILE_NAME), - logger - ); } - gitHubFeatureFlags; - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); - } + async getDefaultCliVersion(_variant) { return { cliVersion, tagName: bundleVersion }; } /** + * Gets the `FeatureConfig` for `feature`. + */ + getFeatureConfig(feature) { + return featureConfig[feature]; + } + /** + * Determines whether `feature` is enabled without consulting the GitHub API. * * @param feature The feature to check. * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the @@ -104159,7 +104156,17 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { - const config = featureConfig[feature]; + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } + return this.getDefaultValue(feature); + } + /** + * Determines whether `feature` is enabled using the CLI and environment variables. + */ + async getOfflineValue(feature, codeql) { + const config = this.getFeatureConfig(feature); if (!codeql && config.minimumVersion) { throw new Error( `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.` @@ -104209,6 +104216,52 @@ var Features = class { ); return true; } + return void 0; + } + /** Gets the default value of `feature`. */ + async getDefaultValue(feature) { + const config = this.getFeatureConfig(feature); + const defaultValue = config.defaultValue; + this.logger.debug( + `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` + ); + return defaultValue; + } +}; +var Features = class extends OfflineFeatures { + gitHubFeatureFlags; + constructor(gitHubVersion, repositoryNwo, tempDir, logger) { + super(logger); + this.gitHubFeatureFlags = new GitHubFeatureFlags( + gitHubVersion, + repositoryNwo, + path3.join(tempDir, FEATURE_FLAGS_FILE_NAME), + logger + ); + } + async getDefaultCliVersion(variant) { + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return super.getDefaultCliVersion(variant); + } + /** + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, the + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature, codeql) { + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } const apiValue = await this.gitHubFeatureFlags.getValue(feature); if (apiValue !== void 0) { this.logger.debug( @@ -104216,11 +104269,7 @@ var Features = class { ); return apiValue; } - const defaultValue = config.defaultValue; - this.logger.debug( - `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` - ); - return defaultValue; + return this.getDefaultValue(feature); } }; var GitHubFeatureFlags = class { diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 78e19df587..353de2d8a2 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165119,27 +165119,24 @@ var featureConfig = { } }; var FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; -var Features = class { - constructor(gitHubVersion, repositoryNwo, tempDir, logger) { +var OfflineFeatures = class { + constructor(logger) { this.logger = logger; - this.gitHubFeatureFlags = new GitHubFeatureFlags( - gitHubVersion, - repositoryNwo, - path4.join(tempDir, FEATURE_FLAGS_FILE_NAME), - logger - ); } - gitHubFeatureFlags; - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); - } + async getDefaultCliVersion(_variant) { return { cliVersion, tagName: bundleVersion }; } /** + * Gets the `FeatureConfig` for `feature`. + */ + getFeatureConfig(feature) { + return featureConfig[feature]; + } + /** + * Determines whether `feature` is enabled without consulting the GitHub API. * * @param feature The feature to check. * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the @@ -165152,7 +165149,17 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { - const config = featureConfig[feature]; + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } + return this.getDefaultValue(feature); + } + /** + * Determines whether `feature` is enabled using the CLI and environment variables. + */ + async getOfflineValue(feature, codeql) { + const config = this.getFeatureConfig(feature); if (!codeql && config.minimumVersion) { throw new Error( `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.` @@ -165202,6 +165209,52 @@ var Features = class { ); return true; } + return void 0; + } + /** Gets the default value of `feature`. */ + async getDefaultValue(feature) { + const config = this.getFeatureConfig(feature); + const defaultValue = config.defaultValue; + this.logger.debug( + `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` + ); + return defaultValue; + } +}; +var Features = class extends OfflineFeatures { + gitHubFeatureFlags; + constructor(gitHubVersion, repositoryNwo, tempDir, logger) { + super(logger); + this.gitHubFeatureFlags = new GitHubFeatureFlags( + gitHubVersion, + repositoryNwo, + path4.join(tempDir, FEATURE_FLAGS_FILE_NAME), + logger + ); + } + async getDefaultCliVersion(variant) { + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return super.getDefaultCliVersion(variant); + } + /** + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, the + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature, codeql) { + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } const apiValue = await this.gitHubFeatureFlags.getValue(feature); if (apiValue !== void 0) { this.logger.debug( @@ -165209,11 +165262,7 @@ var Features = class { ); return apiValue; } - const defaultValue = config.defaultValue; - this.logger.debug( - `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` - ); - return defaultValue; + return this.getDefaultValue(feature); } }; var GitHubFeatureFlags = class { diff --git a/lib/init-action.js b/lib/init-action.js index 9ac4c39a04..e01ced1fb0 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105326,27 +105326,24 @@ var featureConfig = { } }; var FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; -var Features = class { - constructor(gitHubVersion, repositoryNwo, tempDir, logger) { +var OfflineFeatures = class { + constructor(logger) { this.logger = logger; - this.gitHubFeatureFlags = new GitHubFeatureFlags( - gitHubVersion, - repositoryNwo, - path6.join(tempDir, FEATURE_FLAGS_FILE_NAME), - logger - ); } - gitHubFeatureFlags; - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); - } + async getDefaultCliVersion(_variant) { return { cliVersion, tagName: bundleVersion }; } /** + * Gets the `FeatureConfig` for `feature`. + */ + getFeatureConfig(feature) { + return featureConfig[feature]; + } + /** + * Determines whether `feature` is enabled without consulting the GitHub API. * * @param feature The feature to check. * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the @@ -105359,7 +105356,17 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { - const config = featureConfig[feature]; + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } + return this.getDefaultValue(feature); + } + /** + * Determines whether `feature` is enabled using the CLI and environment variables. + */ + async getOfflineValue(feature, codeql) { + const config = this.getFeatureConfig(feature); if (!codeql && config.minimumVersion) { throw new Error( `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.` @@ -105409,6 +105416,52 @@ var Features = class { ); return true; } + return void 0; + } + /** Gets the default value of `feature`. */ + async getDefaultValue(feature) { + const config = this.getFeatureConfig(feature); + const defaultValue = config.defaultValue; + this.logger.debug( + `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` + ); + return defaultValue; + } +}; +var Features = class extends OfflineFeatures { + gitHubFeatureFlags; + constructor(gitHubVersion, repositoryNwo, tempDir, logger) { + super(logger); + this.gitHubFeatureFlags = new GitHubFeatureFlags( + gitHubVersion, + repositoryNwo, + path6.join(tempDir, FEATURE_FLAGS_FILE_NAME), + logger + ); + } + async getDefaultCliVersion(variant) { + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return super.getDefaultCliVersion(variant); + } + /** + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, the + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature, codeql) { + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } const apiValue = await this.gitHubFeatureFlags.getValue(feature); if (apiValue !== void 0) { this.logger.debug( @@ -105416,11 +105469,7 @@ var Features = class { ); return apiValue; } - const defaultValue = config.defaultValue; - this.logger.debug( - `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` - ); - return defaultValue; + return this.getDefaultValue(feature); } }; var GitHubFeatureFlags = class { diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index bdf540153a..4f197ac68a 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -104027,27 +104027,24 @@ var featureConfig = { } }; var FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; -var Features = class { - constructor(gitHubVersion, repositoryNwo, tempDir, logger) { +var OfflineFeatures = class { + constructor(logger) { this.logger = logger; - this.gitHubFeatureFlags = new GitHubFeatureFlags( - gitHubVersion, - repositoryNwo, - path4.join(tempDir, FEATURE_FLAGS_FILE_NAME), - logger - ); } - gitHubFeatureFlags; - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); - } + async getDefaultCliVersion(_variant) { return { cliVersion, tagName: bundleVersion }; } /** + * Gets the `FeatureConfig` for `feature`. + */ + getFeatureConfig(feature) { + return featureConfig[feature]; + } + /** + * Determines whether `feature` is enabled without consulting the GitHub API. * * @param feature The feature to check. * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the @@ -104060,7 +104057,17 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { - const config = featureConfig[feature]; + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } + return this.getDefaultValue(feature); + } + /** + * Determines whether `feature` is enabled using the CLI and environment variables. + */ + async getOfflineValue(feature, codeql) { + const config = this.getFeatureConfig(feature); if (!codeql && config.minimumVersion) { throw new Error( `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.` @@ -104110,6 +104117,52 @@ var Features = class { ); return true; } + return void 0; + } + /** Gets the default value of `feature`. */ + async getDefaultValue(feature) { + const config = this.getFeatureConfig(feature); + const defaultValue = config.defaultValue; + this.logger.debug( + `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` + ); + return defaultValue; + } +}; +var Features = class extends OfflineFeatures { + gitHubFeatureFlags; + constructor(gitHubVersion, repositoryNwo, tempDir, logger) { + super(logger); + this.gitHubFeatureFlags = new GitHubFeatureFlags( + gitHubVersion, + repositoryNwo, + path4.join(tempDir, FEATURE_FLAGS_FILE_NAME), + logger + ); + } + async getDefaultCliVersion(variant) { + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return super.getDefaultCliVersion(variant); + } + /** + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, the + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature, codeql) { + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } const apiValue = await this.gitHubFeatureFlags.getValue(feature); if (apiValue !== void 0) { this.logger.debug( @@ -104117,11 +104170,7 @@ var Features = class { ); return apiValue; } - const defaultValue = config.defaultValue; - this.logger.debug( - `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` - ); - return defaultValue; + return this.getDefaultValue(feature); } }; var GitHubFeatureFlags = class { diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index 9e53638eef..e4e10473c6 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -120820,27 +120820,24 @@ var featureConfig = { } }; var FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; -var Features = class { - constructor(gitHubVersion, repositoryNwo, tempDir, logger) { +var OfflineFeatures = class { + constructor(logger) { this.logger = logger; - this.gitHubFeatureFlags = new GitHubFeatureFlags( - gitHubVersion, - repositoryNwo, - path.join(tempDir, FEATURE_FLAGS_FILE_NAME), - logger - ); } - gitHubFeatureFlags; - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); - } + async getDefaultCliVersion(_variant) { return { cliVersion, tagName: bundleVersion }; } /** + * Gets the `FeatureConfig` for `feature`. + */ + getFeatureConfig(feature) { + return featureConfig[feature]; + } + /** + * Determines whether `feature` is enabled without consulting the GitHub API. * * @param feature The feature to check. * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the @@ -120853,7 +120850,17 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { - const config = featureConfig[feature]; + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } + return this.getDefaultValue(feature); + } + /** + * Determines whether `feature` is enabled using the CLI and environment variables. + */ + async getOfflineValue(feature, codeql) { + const config = this.getFeatureConfig(feature); if (!codeql && config.minimumVersion) { throw new Error( `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.` @@ -120903,6 +120910,52 @@ var Features = class { ); return true; } + return void 0; + } + /** Gets the default value of `feature`. */ + async getDefaultValue(feature) { + const config = this.getFeatureConfig(feature); + const defaultValue = config.defaultValue; + this.logger.debug( + `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` + ); + return defaultValue; + } +}; +var Features = class extends OfflineFeatures { + gitHubFeatureFlags; + constructor(gitHubVersion, repositoryNwo, tempDir, logger) { + super(logger); + this.gitHubFeatureFlags = new GitHubFeatureFlags( + gitHubVersion, + repositoryNwo, + path.join(tempDir, FEATURE_FLAGS_FILE_NAME), + logger + ); + } + async getDefaultCliVersion(variant) { + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return super.getDefaultCliVersion(variant); + } + /** + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, the + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature, codeql) { + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } const apiValue = await this.gitHubFeatureFlags.getValue(feature); if (apiValue !== void 0) { this.logger.debug( @@ -120910,11 +120963,7 @@ var Features = class { ); return apiValue; } - const defaultValue = config.defaultValue; - this.logger.debug( - `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` - ); - return defaultValue; + return this.getDefaultValue(feature); } }; var GitHubFeatureFlags = class { diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index 07141748cf..d831b132d1 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -106981,27 +106981,24 @@ var featureConfig = { } }; var FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; -var Features = class { - constructor(gitHubVersion, repositoryNwo, tempDir, logger) { +var OfflineFeatures = class { + constructor(logger) { this.logger = logger; - this.gitHubFeatureFlags = new GitHubFeatureFlags( - gitHubVersion, - repositoryNwo, - path4.join(tempDir, FEATURE_FLAGS_FILE_NAME), - logger - ); } - gitHubFeatureFlags; - async getDefaultCliVersion(variant) { - if (supportsFeatureFlags(variant)) { - return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); - } + async getDefaultCliVersion(_variant) { return { cliVersion, tagName: bundleVersion }; } /** + * Gets the `FeatureConfig` for `feature`. + */ + getFeatureConfig(feature) { + return featureConfig[feature]; + } + /** + * Determines whether `feature` is enabled without consulting the GitHub API. * * @param feature The feature to check. * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the @@ -107014,7 +107011,17 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { - const config = featureConfig[feature]; + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } + return this.getDefaultValue(feature); + } + /** + * Determines whether `feature` is enabled using the CLI and environment variables. + */ + async getOfflineValue(feature, codeql) { + const config = this.getFeatureConfig(feature); if (!codeql && config.minimumVersion) { throw new Error( `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.` @@ -107064,6 +107071,52 @@ var Features = class { ); return true; } + return void 0; + } + /** Gets the default value of `feature`. */ + async getDefaultValue(feature) { + const config = this.getFeatureConfig(feature); + const defaultValue = config.defaultValue; + this.logger.debug( + `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` + ); + return defaultValue; + } +}; +var Features = class extends OfflineFeatures { + gitHubFeatureFlags; + constructor(gitHubVersion, repositoryNwo, tempDir, logger) { + super(logger); + this.gitHubFeatureFlags = new GitHubFeatureFlags( + gitHubVersion, + repositoryNwo, + path4.join(tempDir, FEATURE_FLAGS_FILE_NAME), + logger + ); + } + async getDefaultCliVersion(variant) { + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return super.getDefaultCliVersion(variant); + } + /** + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, the + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature, codeql) { + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } const apiValue = await this.gitHubFeatureFlags.getValue(feature); if (apiValue !== void 0) { this.logger.debug( @@ -107071,11 +107124,7 @@ var Features = class { ); return apiValue; } - const defaultValue = config.defaultValue; - this.logger.debug( - `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` - ); - return defaultValue; + return this.getDefaultValue(feature); } }; var GitHubFeatureFlags = class { diff --git a/src/feature-flags.ts b/src/feature-flags.ts index ab5798ed32..0400eb40e0 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -354,33 +354,15 @@ type GitHubFeatureFlagsApiResponse = Partial>; export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; /** - * Determines the enablement status of a number of features. - * If feature enablement is not able to be determined locally, a request to the - * GitHub API is made to determine the enablement status. + * Determines the enablement status of a number of features locally without + * consulting the GitHub API. */ -export class Features implements FeatureEnablement { - private gitHubFeatureFlags: GitHubFeatureFlags; - - constructor( - gitHubVersion: util.GitHubVersion, - repositoryNwo: RepositoryNwo, - tempDir: string, - private readonly logger: Logger, - ) { - this.gitHubFeatureFlags = new GitHubFeatureFlags( - gitHubVersion, - repositoryNwo, - path.join(tempDir, FEATURE_FLAGS_FILE_NAME), - logger, - ); - } +export class OfflineFeatures implements FeatureEnablement { + constructor(protected readonly logger: Logger) {} async getDefaultCliVersion( - variant: util.GitHubVariant, + _variant: util.GitHubVariant, ): Promise { - if (supportsFeatureFlags(variant)) { - return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); - } return { cliVersion: defaults.cliVersion, tagName: defaults.bundleVersion, @@ -388,6 +370,16 @@ export class Features implements FeatureEnablement { } /** + * Gets the `FeatureConfig` for `feature`. + */ + getFeatureConfig(feature: Feature): FeatureConfig { + // Narrow the type to FeatureConfig to avoid type errors. To avoid unsafe use of `as`, we + // check that the required properties exist using `satisfies`. + return featureConfig[feature] satisfies FeatureConfig as FeatureConfig; + } + + /** + * Determines whether `feature` is enabled without consulting the GitHub API. * * @param feature The feature to check. * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the @@ -400,11 +392,22 @@ export class Features implements FeatureEnablement { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature: Feature, codeql?: CodeQL): Promise { - // Narrow the type to FeatureConfig to avoid type errors. To avoid unsafe use of `as`, we - // check that the required properties exist using `satisfies`. - const config = featureConfig[ - feature - ] satisfies FeatureConfig as FeatureConfig; + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== undefined) { + return offlineValue; + } + + return this.getDefaultValue(feature); + } + + /** + * Determines whether `feature` is enabled using the CLI and environment variables. + */ + protected async getOfflineValue( + feature: Feature, + codeql?: CodeQL, + ): Promise { + const config = this.getFeatureConfig(feature); if (!codeql && config.minimumVersion) { throw new Error( @@ -470,6 +473,74 @@ export class Features implements FeatureEnablement { return true; } + return undefined; + } + + /** Gets the default value of `feature`. */ + protected async getDefaultValue(feature: Feature): Promise { + const config = this.getFeatureConfig(feature); + const defaultValue = config.defaultValue; + this.logger.debug( + `Feature ${feature} is ${ + defaultValue ? "enabled" : "disabled" + } due to its default value.`, + ); + return defaultValue; + } +} + +/** + * Determines the enablement status of a number of features. + * If feature enablement is not able to be determined locally, a request to the + * GitHub API is made to determine the enablement status. + */ +export class Features extends OfflineFeatures { + private gitHubFeatureFlags: GitHubFeatureFlags; + + constructor( + gitHubVersion: util.GitHubVersion, + repositoryNwo: RepositoryNwo, + tempDir: string, + logger: Logger, + ) { + super(logger); + + this.gitHubFeatureFlags = new GitHubFeatureFlags( + gitHubVersion, + repositoryNwo, + path.join(tempDir, FEATURE_FLAGS_FILE_NAME), + logger, + ); + } + + async getDefaultCliVersion( + variant: util.GitHubVariant, + ): Promise { + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags(); + } + return super.getDefaultCliVersion(variant); + } + + /** + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, the + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature: Feature, codeql?: CodeQL): Promise { + // Check whether the feature is enabled locally. + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== undefined) { + return offlineValue; + } + // Ask the GitHub API if the feature is enabled. const apiValue = await this.gitHubFeatureFlags.getValue(feature); if (apiValue !== undefined) { @@ -481,13 +552,8 @@ export class Features implements FeatureEnablement { return apiValue; } - const defaultValue = config.defaultValue; - this.logger.debug( - `Feature ${feature} is ${ - defaultValue ? "enabled" : "disabled" - } due to its default value.`, - ); - return defaultValue; + // Return the default value. + return this.getDefaultValue(feature); } } From 47a21aaa5cd6d1a837e15121933cf770e88e21d6 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 12 Feb 2026 17:55:06 +0000 Subject: [PATCH 3/7] Abstract over `FeatureEnablement` implementations with `initFeatures` --- lib/analyze-action.js | 7 +++++-- lib/autobuild-action.js | 5 ++++- lib/init-action-post.js | 5 ++++- lib/init-action.js | 5 ++++- lib/setup-codeql-action.js | 5 ++++- lib/start-proxy-action.js | 5 ++++- lib/upload-sarif-action.js | 5 ++++- src/analyze-action.ts | 4 ++-- src/autobuild.ts | 4 ++-- src/diff-informed-analysis-utils.test.ts | 4 ++-- src/feature-flags.test.ts | 4 ++-- src/feature-flags.ts | 17 +++++++++++++++-- src/init-action-post.ts | 4 ++-- src/init-action.ts | 6 +++--- src/setup-codeql-action.ts | 4 ++-- src/start-proxy-action.ts | 6 +++--- src/upload-sarif-action.ts | 4 ++-- 17 files changed, 64 insertions(+), 30 deletions(-) diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 4149ebb9da..667f6dad88 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -108114,6 +108114,9 @@ var GitHubFeatureFlags = class { function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } +function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); +} // src/diff-informed-analysis-utils.ts async function getDiffInformedAnalysisBranches(codeql, features, logger) { @@ -110059,7 +110062,7 @@ async function setupCppAutobuild(codeql, logger) { const featureName = "C++ automatic installation of dependencies"; const gitHubVersion = await getGitHubVersion(); const repositoryNwo = getRepositoryNwo(); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), @@ -112958,7 +112961,7 @@ async function run(startedAt2) { const repositoryNwo = getRepositoryNwo(); const gitHubVersion = await getGitHubVersion(); checkActionVersion(getActionVersion(), gitHubVersion); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index d75ce54a99..dfe57f9ccf 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -104447,6 +104447,9 @@ var GitHubFeatureFlags = class { function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } +function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); +} // src/trap-caching.ts var actionsCache2 = __toESM(require_cache5()); @@ -105198,7 +105201,7 @@ async function setupCppAutobuild(codeql, logger) { const featureName = "C++ automatic installation of dependencies"; const gitHubVersion = await getGitHubVersion(); const repositoryNwo = getRepositoryNwo(); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 353de2d8a2..22a9594f73 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165440,6 +165440,9 @@ var GitHubFeatureFlags = class { function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } +function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); +} // src/diff-informed-analysis-utils.ts function getDiffRangesJsonFilePath() { @@ -169936,7 +169939,7 @@ async function run2(startedAt) { const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); const repositoryNwo = getRepositoryNwo(); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/lib/init-action.js b/lib/init-action.js index e01ced1fb0..7d3acdf0ee 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105647,6 +105647,9 @@ var GitHubFeatureFlags = class { function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } +function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); +} // src/diff-informed-analysis-utils.ts async function shouldPerformDiffInformedAnalysis(codeql, features, logger) { @@ -109139,7 +109142,7 @@ async function run(startedAt) { checkGitHubVersionInRange(gitHubVersion, logger); checkActionVersion(getActionVersion(), gitHubVersion); const repositoryNwo = getRepositoryNwo(); - features = new Features( + features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index 4f197ac68a..06db03fa8b 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -104348,6 +104348,9 @@ var GitHubFeatureFlags = class { function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } +function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); +} // src/init.ts var toolrunner4 = __toESM(require_toolrunner()); @@ -106442,7 +106445,7 @@ async function run(startedAt) { checkGitHubVersionInRange(gitHubVersion, logger); checkActionVersion(getActionVersion(), gitHubVersion); const repositoryNwo = getRepositoryNwo(); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index e4e10473c6..3c4ed6ebcd 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -121141,6 +121141,9 @@ var GitHubFeatureFlags = class { function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } +function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); +} // src/start-proxy.ts var path2 = __toESM(require("path")); @@ -121909,7 +121912,7 @@ async function run(startedAt) { core11.saveState("proxy-log-file", proxyLogFilePath); const repositoryNwo = getRepositoryNwo(); const gitHubVersion = await getGitHubVersion(); - features = new Features( + features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index d831b132d1..5f8ae526f3 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -107302,6 +107302,9 @@ var GitHubFeatureFlags = class { function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } +function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); +} // src/status-report.ts var os = __toESM(require("os")); @@ -111272,7 +111275,7 @@ async function run(startedAt) { checkActionVersion(getActionVersion(), gitHubVersion); persistInputs(); const repositoryNwo = getRepositoryNwo(); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/src/analyze-action.ts b/src/analyze-action.ts index 3cc1ad019a..4093e37869 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -30,7 +30,7 @@ import { } from "./dependency-caching"; import { getDiffInformedAnalysisBranches } from "./diff-informed-analysis-utils"; import { EnvVar } from "./environment"; -import { Features } from "./feature-flags"; +import { initFeatures } from "./feature-flags"; import { KnownLanguage } from "./languages"; import { getActionsLogger, Logger } from "./logging"; import { cleanupAndUploadOverlayBaseDatabaseToCache } from "./overlay-database-utils"; @@ -293,7 +293,7 @@ async function run(startedAt: Date) { util.checkActionVersion(actionsUtil.getActionVersion(), gitHubVersion); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, actionsUtil.getTemporaryDirectory(), diff --git a/src/autobuild.ts b/src/autobuild.ts index ce3d45cc4e..46935bba67 100644 --- a/src/autobuild.ts +++ b/src/autobuild.ts @@ -6,7 +6,7 @@ import { CodeQL, getCodeQL } from "./codeql"; import * as configUtils from "./config-utils"; import { DocUrl } from "./doc-url"; import { EnvVar } from "./environment"; -import { Feature, featureConfig, Features } from "./feature-flags"; +import { Feature, featureConfig, initFeatures } from "./feature-flags"; import { KnownLanguage, Language } from "./languages"; import { Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; @@ -117,7 +117,7 @@ export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) { const featureName = "C++ automatic installation of dependencies"; const gitHubVersion = await getGitHubVersion(); const repositoryNwo = getRepositoryNwo(); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/src/diff-informed-analysis-utils.test.ts b/src/diff-informed-analysis-utils.test.ts index eeb06cd1b6..2d98a5f639 100644 --- a/src/diff-informed-analysis-utils.test.ts +++ b/src/diff-informed-analysis-utils.test.ts @@ -8,7 +8,7 @@ import { shouldPerformDiffInformedAnalysis, exportedForTesting, } from "./diff-informed-analysis-utils"; -import { Feature, Features } from "./feature-flags"; +import { Feature, initFeatures } from "./feature-flags"; import { getRunnerLogger } from "./logging"; import { parseRepositoryNwo } from "./repository"; import { @@ -63,7 +63,7 @@ const testShouldPerformDiffInformedAnalysis = test.macro({ delete process.env.CODEQL_ACTION_DIFF_INFORMED_QUERIES; } - const features = new Features( + const features = initFeatures( testCase.gitHubVersion, parseRepositoryNwo("github/example"), tmpDir, diff --git a/src/feature-flags.test.ts b/src/feature-flags.test.ts index cdab85e279..37253d1212 100644 --- a/src/feature-flags.test.ts +++ b/src/feature-flags.test.ts @@ -8,10 +8,10 @@ import { Feature, featureConfig, FeatureEnablement, - Features, FEATURE_FLAGS_FILE_NAME, FeatureConfig, FeatureWithoutCLI, + initFeatures, } from "./feature-flags"; import { getRunnerLogger } from "./logging"; import { parseRepositoryNwo } from "./repository"; @@ -565,7 +565,7 @@ function setUpFeatureFlagTests( ): FeatureEnablement { setupActionsVars(tmpDir, tmpDir); - return new Features(gitHubVersion, testRepositoryNwo, tmpDir, logger); + return initFeatures(gitHubVersion, testRepositoryNwo, tmpDir, logger); } /** diff --git a/src/feature-flags.ts b/src/feature-flags.ts index 0400eb40e0..e0d6d1acc7 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -357,7 +357,7 @@ export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; * Determines the enablement status of a number of features locally without * consulting the GitHub API. */ -export class OfflineFeatures implements FeatureEnablement { +class OfflineFeatures implements FeatureEnablement { constructor(protected readonly logger: Logger) {} async getDefaultCliVersion( @@ -494,7 +494,7 @@ export class OfflineFeatures implements FeatureEnablement { * If feature enablement is not able to be determined locally, a request to the * GitHub API is made to determine the enablement status. */ -export class Features extends OfflineFeatures { +class Features extends OfflineFeatures { private gitHubFeatureFlags: GitHubFeatureFlags; constructor( @@ -798,3 +798,16 @@ function supportsFeatureFlags(githubVariant: util.GitHubVariant): boolean { githubVariant === util.GitHubVariant.GHEC_DR ); } + +/** + * Initialises an instance of a `FeatureEnablement` implementation. The implementation used + * is determined by the environment we are running in. + */ +export function initFeatures( + gitHubVersion: util.GitHubVersion, + repositoryNwo: RepositoryNwo, + tempDir: string, + logger: Logger, +): FeatureEnablement { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); +} diff --git a/src/init-action-post.ts b/src/init-action-post.ts index cfae096938..d5aab32f33 100644 --- a/src/init-action-post.ts +++ b/src/init-action-post.ts @@ -21,7 +21,7 @@ import { getDependencyCacheUsage, } from "./dependency-caching"; import { EnvVar } from "./environment"; -import { Features } from "./feature-flags"; +import { initFeatures } from "./feature-flags"; import * as gitUtils from "./git-utils"; import * as initActionPostHelper from "./init-action-post-helper"; import { getActionsLogger } from "./logging"; @@ -62,7 +62,7 @@ async function run(startedAt: Date) { checkGitHubVersionInRange(gitHubVersion, logger); const repositoryNwo = getRepositoryNwo(); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/src/init-action.ts b/src/init-action.ts index 5d459acaec..353485fc13 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -38,7 +38,7 @@ import { makeTelemetryDiagnostic, } from "./diagnostics"; import { EnvVar } from "./environment"; -import { Feature, FeatureEnablement, Features } from "./feature-flags"; +import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; import { loadPropertiesFromApi, RepositoryProperties, @@ -210,7 +210,7 @@ async function run(startedAt: Date) { let config: configUtils.Config | undefined; let configFile: string | undefined; let codeql: CodeQL; - let features: Features; + let features: FeatureEnablement; let sourceRoot: string; let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined; let toolsFeatureFlagsValid: boolean | undefined; @@ -237,7 +237,7 @@ async function run(startedAt: Date) { const repositoryNwo = getRepositoryNwo(); - features = new Features( + features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/src/setup-codeql-action.ts b/src/setup-codeql-action.ts index 31c8986679..bd504f3fd3 100644 --- a/src/setup-codeql-action.ts +++ b/src/setup-codeql-action.ts @@ -10,7 +10,7 @@ import { import { getGitHubVersion } from "./api-client"; import { CodeQL } from "./codeql"; import { EnvVar } from "./environment"; -import { Features } from "./feature-flags"; +import { initFeatures } from "./feature-flags"; import { initCodeQL } from "./init"; import { getActionsLogger, Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; @@ -114,7 +114,7 @@ async function run(startedAt: Date): Promise { const repositoryNwo = getRepositoryNwo(); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts index a9b355eaa5..4ca4e9ea51 100644 --- a/src/start-proxy-action.ts +++ b/src/start-proxy-action.ts @@ -6,7 +6,7 @@ import { pki } from "node-forge"; import * as actionsUtil from "./actions-util"; import { getGitHubVersion } from "./api-client"; -import { Feature, Features } from "./feature-flags"; +import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; import { KnownLanguage } from "./languages"; import { getActionsLogger, Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; @@ -99,7 +99,7 @@ async function run(startedAt: Date) { // possible, and only use safe functions outside. const logger = getActionsLogger(); - let features: Features | undefined; + let features: FeatureEnablement | undefined; let language: KnownLanguage | undefined; try { @@ -114,7 +114,7 @@ async function run(startedAt: Date) { // Initialise FFs. const repositoryNwo = getRepositoryNwo(); const gitHubVersion = await getGitHubVersion(); - features = new Features( + features = initFeatures( gitHubVersion, repositoryNwo, actionsUtil.getTemporaryDirectory(), diff --git a/src/upload-sarif-action.ts b/src/upload-sarif-action.ts index 5273909bad..cec41b2766 100644 --- a/src/upload-sarif-action.ts +++ b/src/upload-sarif-action.ts @@ -4,7 +4,7 @@ import * as actionsUtil from "./actions-util"; import { getActionVersion, getTemporaryDirectory } from "./actions-util"; import * as analyses from "./analyses"; import { getGitHubVersion } from "./api-client"; -import { Features } from "./feature-flags"; +import { initFeatures } from "./feature-flags"; import { Logger, getActionsLogger } from "./logging"; import { getRepositoryNwo } from "./repository"; import { @@ -70,7 +70,7 @@ async function run(startedAt: Date) { actionsUtil.persistInputs(); const repositoryNwo = getRepositoryNwo(); - const features = new Features( + const features = initFeatures( gitHubVersion, repositoryNwo, getTemporaryDirectory(), From 81143cc9e3c58d1133226566f36a571399f05866 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 12 Feb 2026 18:00:19 +0000 Subject: [PATCH 4/7] Return `OfflineFeatures` for CCR --- lib/analyze-action.js | 6 +++++- lib/autobuild-action.js | 10 +++++++++- lib/init-action-post.js | 6 +++++- lib/init-action.js | 6 +++++- lib/setup-codeql-action.js | 10 +++++++++- lib/start-proxy-action.js | 10 +++++++++- lib/upload-sarif-action.js | 6 +++++- src/feature-flags.test.ts | 17 +++++++++++++++++ src/feature-flags.ts | 7 ++++++- 9 files changed, 70 insertions(+), 8 deletions(-) diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 667f6dad88..29e37a23ba 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -108115,7 +108115,11 @@ function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { - return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + if (isCCR()) { + return new OfflineFeatures(logger); + } else { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + } } // src/diff-informed-analysis-utils.ts diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index dfe57f9ccf..3d947e3a2e 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -103130,6 +103130,10 @@ function getWorkflowRunAttempt() { function isSelfHostedRunner() { return process.env.RUNNER_ENVIRONMENT === "self-hosted"; } +var CCR_KEY_PREFIX = "dynamic/copilot-pull-request-reviewer"; +function isCCR() { + return process.env["CODEQL_ACTION_ANALYSIS_KEY" /* ANALYSIS_KEY */]?.startsWith(CCR_KEY_PREFIX) || false; +} function prettyPrintInvocation(cmd, args) { return [cmd, ...args].map((x) => x.includes(" ") ? `'${x}'` : x).join(" "); } @@ -104448,7 +104452,11 @@ function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { - return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + if (isCCR()) { + return new OfflineFeatures(logger); + } else { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + } } // src/trap-caching.ts diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 22a9594f73..690a2718fb 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165441,7 +165441,11 @@ function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { - return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + if (isCCR()) { + return new OfflineFeatures(logger); + } else { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + } } // src/diff-informed-analysis-utils.ts diff --git a/lib/init-action.js b/lib/init-action.js index 7d3acdf0ee..8a38c6c541 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105648,7 +105648,11 @@ function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { - return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + if (isCCR()) { + return new OfflineFeatures(logger); + } else { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + } } // src/diff-informed-analysis-utils.ts diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index 06db03fa8b..b4842f0e88 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -103247,6 +103247,10 @@ function isSelfHostedRunner() { function isDynamicWorkflow() { return getWorkflowEventName() === "dynamic"; } +var CCR_KEY_PREFIX = "dynamic/copilot-pull-request-reviewer"; +function isCCR() { + return process.env["CODEQL_ACTION_ANALYSIS_KEY" /* ANALYSIS_KEY */]?.startsWith(CCR_KEY_PREFIX) || false; +} function prettyPrintInvocation(cmd, args) { return [cmd, ...args].map((x) => x.includes(" ") ? `'${x}'` : x).join(" "); } @@ -104349,7 +104353,11 @@ function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { - return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + if (isCCR()) { + return new OfflineFeatures(logger); + } else { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + } } // src/init.ts diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index 3c4ed6ebcd..1e0559af89 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -120267,6 +120267,10 @@ function getWorkflowRunAttempt() { function isSelfHostedRunner() { return process.env.RUNNER_ENVIRONMENT === "self-hosted"; } +var CCR_KEY_PREFIX = "dynamic/copilot-pull-request-reviewer"; +function isCCR() { + return process.env["CODEQL_ACTION_ANALYSIS_KEY" /* ANALYSIS_KEY */]?.startsWith(CCR_KEY_PREFIX) || false; +} var persistedInputsKey = "persisted_inputs"; var persistInputs = function() { const inputEnvironmentVariables = Object.entries(process.env).filter( @@ -121142,7 +121146,11 @@ function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { - return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + if (isCCR()) { + return new OfflineFeatures(logger); + } else { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + } } // src/start-proxy.ts diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index 5f8ae526f3..59bb9ad3e4 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -107303,7 +107303,11 @@ function supportsFeatureFlags(githubVariant) { return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; } function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { - return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + if (isCCR()) { + return new OfflineFeatures(logger); + } else { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + } } // src/status-report.ts diff --git a/src/feature-flags.test.ts b/src/feature-flags.test.ts index 37253d1212..2cbb74a9cc 100644 --- a/src/feature-flags.test.ts +++ b/src/feature-flags.test.ts @@ -4,6 +4,7 @@ import * as path from "path"; import test, { ExecutionContext } from "ava"; import * as defaults from "./defaults.json"; +import { EnvVar } from "./environment"; import { Feature, featureConfig, @@ -542,6 +543,22 @@ test("non-legacy feature flags should not start with codeql_action_", async (t) } }); +test("initFeatures returns a `Features` instance by default", async (t) => { + await withTmpDir(async (tmpDir) => { + const features = setUpFeatureFlagTests(tmpDir); + t.is("Features", features.constructor.name); + }); +}); + +test("initFeatures returns an `OfflineFeatures` instance in CCR", async (t) => { + await withTmpDir(async (tmpDir) => { + process.env.GITHUB_EVENT_NAME = "dynamic"; + process.env[EnvVar.ANALYSIS_KEY] = "dynamic/copilot-pull-request-reviewer"; + const features = setUpFeatureFlagTests(tmpDir); + t.is("OfflineFeatures", features.constructor.name); + }); +}); + function assertAllFeaturesUndefinedInApi( t: ExecutionContext, loggedMessages: LoggedMessage[], diff --git a/src/feature-flags.ts b/src/feature-flags.ts index e0d6d1acc7..793fb0cbbf 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -3,6 +3,7 @@ import * as path from "path"; import * as semver from "semver"; +import { isCCR } from "./actions-util"; import { getApiClient } from "./api-client"; import type { CodeQL } from "./codeql"; import * as defaults from "./defaults.json"; @@ -809,5 +810,9 @@ export function initFeatures( tempDir: string, logger: Logger, ): FeatureEnablement { - return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + if (isCCR()) { + return new OfflineFeatures(logger); + } else { + return new Features(gitHubVersion, repositoryNwo, tempDir, logger); + } } From a827b58ecb4b41673f5879bb4e22f5ffef8fc7b6 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 12 Feb 2026 18:14:01 +0000 Subject: [PATCH 5/7] Move FF test utils out of main file --- src/feature-flags.test.ts | 70 +++-------------------------- src/feature-flags/testing-util.ts | 75 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 64 deletions(-) create mode 100644 src/feature-flags/testing-util.ts diff --git a/src/feature-flags.test.ts b/src/feature-flags.test.ts index 2cbb74a9cc..ce19a5db31 100644 --- a/src/feature-flags.test.ts +++ b/src/feature-flags.test.ts @@ -1,33 +1,30 @@ import * as fs from "fs"; import * as path from "path"; -import test, { ExecutionContext } from "ava"; +import test from "ava"; import * as defaults from "./defaults.json"; import { EnvVar } from "./environment"; import { Feature, featureConfig, - FeatureEnablement, FEATURE_FLAGS_FILE_NAME, FeatureConfig, - FeatureWithoutCLI, - initFeatures, } from "./feature-flags"; -import { getRunnerLogger } from "./logging"; -import { parseRepositoryNwo } from "./repository"; +import { + setUpFeatureFlagTests, + getFeatureIncludingCodeQlIfRequired, + assertAllFeaturesUndefinedInApi, +} from "./feature-flags/testing-util"; import { getRecordingLogger, initializeFeatures, LoggedMessage, mockCodeQLVersion, mockFeatureFlagApiEndpoint, - setupActionsVars, setupTests, stubFeatureFlagApiEndpoint, } from "./testing-utils"; -import { ToolsFeature } from "./tools-features"; -import * as util from "./util"; import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util"; setupTests(test); @@ -36,8 +33,6 @@ test.beforeEach(() => { initializeEnvironment("1.2.3"); }); -const testRepositoryNwo = parseRepositoryNwo("github/example"); - test(`All features are disabled if running against GHES`, async (t) => { await withTmpDir(async (tmpDir) => { const loggedMessages = []; @@ -558,56 +553,3 @@ test("initFeatures returns an `OfflineFeatures` instance in CCR", async (t) => { t.is("OfflineFeatures", features.constructor.name); }); }); - -function assertAllFeaturesUndefinedInApi( - t: ExecutionContext, - loggedMessages: LoggedMessage[], -) { - for (const feature of Object.keys(featureConfig)) { - t.assert( - loggedMessages.find( - (v) => - v.type === "debug" && - (v.message as string).includes(feature) && - (v.message as string).includes("undefined in API response"), - ) !== undefined, - ); - } -} - -function setUpFeatureFlagTests( - tmpDir: string, - logger = getRunnerLogger(true), - gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion, -): FeatureEnablement { - setupActionsVars(tmpDir, tmpDir); - - return initFeatures(gitHubVersion, testRepositoryNwo, tmpDir, logger); -} - -/** - * Returns an argument to pass to `getValue` that if required includes a CodeQL object meeting the - * minimum version or tool feature requirements specified by the feature. - */ -function getFeatureIncludingCodeQlIfRequired( - features: FeatureEnablement, - feature: Feature, -) { - const config = featureConfig[ - feature - ] satisfies FeatureConfig as FeatureConfig; - if ( - config.minimumVersion === undefined && - config.toolsFeature === undefined - ) { - return features.getValue(feature as FeatureWithoutCLI); - } - - return features.getValue( - feature, - mockCodeQLVersion( - "9.9.9", - Object.fromEntries(Object.values(ToolsFeature).map((v) => [v, true])), - ), - ); -} diff --git a/src/feature-flags/testing-util.ts b/src/feature-flags/testing-util.ts new file mode 100644 index 0000000000..91596c416e --- /dev/null +++ b/src/feature-flags/testing-util.ts @@ -0,0 +1,75 @@ +import { type ExecutionContext } from "ava"; + +import { + Feature, + featureConfig, + FeatureConfig, + FeatureEnablement, + FeatureWithoutCLI, + initFeatures, +} from "../feature-flags"; +import { getRunnerLogger } from "../logging"; +import { parseRepositoryNwo } from "../repository"; +import { + LoggedMessage, + mockCodeQLVersion, + setupActionsVars, +} from "../testing-utils"; +import { ToolsFeature } from "../tools-features"; +import { GitHubVariant } from "../util"; +import * as util from "../util"; + +const testRepositoryNwo = parseRepositoryNwo("github/example"); + +export function assertAllFeaturesUndefinedInApi( + t: ExecutionContext, + loggedMessages: LoggedMessage[], +) { + for (const feature of Object.keys(featureConfig)) { + t.assert( + loggedMessages.find( + (v) => + v.type === "debug" && + (v.message as string).includes(feature) && + (v.message as string).includes("undefined in API response"), + ) !== undefined, + ); + } +} + +export function setUpFeatureFlagTests( + tmpDir: string, + logger = getRunnerLogger(true), + gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion, +): FeatureEnablement { + setupActionsVars(tmpDir, tmpDir); + + return initFeatures(gitHubVersion, testRepositoryNwo, tmpDir, logger); +} + +/** + * Returns an argument to pass to `getValue` that if required includes a CodeQL object meeting the + * minimum version or tool feature requirements specified by the feature. + */ +export function getFeatureIncludingCodeQlIfRequired( + features: FeatureEnablement, + feature: Feature, +) { + const config = featureConfig[ + feature + ] satisfies FeatureConfig as FeatureConfig; + if ( + config.minimumVersion === undefined && + config.toolsFeature === undefined + ) { + return features.getValue(feature as FeatureWithoutCLI); + } + + return features.getValue( + feature, + mockCodeQLVersion( + "9.9.9", + Object.fromEntries(Object.values(ToolsFeature).map((v) => [v, true])), + ), + ); +} From c80b61187fda2e5942ecdad6afb8e2f8185736aa Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 12 Feb 2026 18:22:44 +0000 Subject: [PATCH 6/7] Add `mockCCR` helper to `testing-utils` --- src/actions-util.test.ts | 6 +++--- src/feature-flags.test.ts | 6 +++--- src/testing-utils.ts | 7 +++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/actions-util.test.ts b/src/actions-util.test.ts index 71a409da6d..ba5591a41c 100644 --- a/src/actions-util.test.ts +++ b/src/actions-util.test.ts @@ -12,7 +12,7 @@ import { import { computeAutomationID } from "./api-client"; import { EnvVar } from "./environment"; import { getRunnerLogger } from "./logging"; -import { setupTests } from "./testing-utils"; +import { mockCCR, setupTests } from "./testing-utils"; import { initializeEnvironment } from "./util"; setupTests(test); @@ -258,8 +258,8 @@ test("isDynamicWorkflow() returns true if event name is `dynamic`", (t) => { }); test("isCCR() returns true when expected", (t) => { - process.env.GITHUB_EVENT_NAME = "dynamic"; - process.env[EnvVar.ANALYSIS_KEY] = "dynamic/copilot-pull-request-reviewer"; + mockCCR(); + t.assert(isCCR()); t.false(isDefaultSetup()); }); diff --git a/src/feature-flags.test.ts b/src/feature-flags.test.ts index ce19a5db31..da4b097195 100644 --- a/src/feature-flags.test.ts +++ b/src/feature-flags.test.ts @@ -4,7 +4,6 @@ import * as path from "path"; import test from "ava"; import * as defaults from "./defaults.json"; -import { EnvVar } from "./environment"; import { Feature, featureConfig, @@ -20,6 +19,7 @@ import { getRecordingLogger, initializeFeatures, LoggedMessage, + mockCCR, mockCodeQLVersion, mockFeatureFlagApiEndpoint, setupTests, @@ -547,8 +547,8 @@ test("initFeatures returns a `Features` instance by default", async (t) => { test("initFeatures returns an `OfflineFeatures` instance in CCR", async (t) => { await withTmpDir(async (tmpDir) => { - process.env.GITHUB_EVENT_NAME = "dynamic"; - process.env[EnvVar.ANALYSIS_KEY] = "dynamic/copilot-pull-request-reviewer"; + mockCCR(); + const features = setUpFeatureFlagTests(tmpDir); t.is("OfflineFeatures", features.constructor.name); }); diff --git a/src/testing-utils.ts b/src/testing-utils.ts index aff7780436..1f78e5acbf 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -14,6 +14,7 @@ import { CachingKind } from "./caching-utils"; import * as codeql from "./codeql"; import { Config } from "./config-utils"; import * as defaults from "./defaults.json"; +import { EnvVar } from "./environment"; import { CodeQLDefaultVersionInfo, Feature, @@ -436,3 +437,9 @@ export function makeTestToken(length: number = 36) { "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return chars.repeat(Math.ceil(length / chars.length)).slice(0, length); } + +/** Sets the environment variables needed for isCCR() to be `true`. */ +export function mockCCR() { + process.env.GITHUB_EVENT_NAME = "dynamic"; + process.env[EnvVar.ANALYSIS_KEY] = "dynamic/copilot-pull-request-reviewer"; +} From 7c91acf6fc99a384c7afa94fe2449f1dcf001d06 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 12 Feb 2026 18:35:09 +0000 Subject: [PATCH 7/7] Add test to check that OfflineFeatures doesn't use the API client --- src/feature-flags/offline-features.test.ts | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/feature-flags/offline-features.test.ts diff --git a/src/feature-flags/offline-features.test.ts b/src/feature-flags/offline-features.test.ts new file mode 100644 index 0000000000..fac7686faf --- /dev/null +++ b/src/feature-flags/offline-features.test.ts @@ -0,0 +1,37 @@ +import test from "ava"; +import * as sinon from "sinon"; + +import * as apiClient from "../api-client"; +import { Feature, featureConfig } from "../feature-flags"; +import { mockCCR, setupTests } from "../testing-utils"; +import { initializeEnvironment, withTmpDir } from "../util"; + +import { + getFeatureIncludingCodeQlIfRequired, + setUpFeatureFlagTests, +} from "./testing-util"; + +setupTests(test); + +test.beforeEach(() => { + initializeEnvironment("1.2.3"); + mockCCR(); +}); + +test("OfflineFeatures makes no API requests", async (t) => { + await withTmpDir(async (tmpDir) => { + const features = setUpFeatureFlagTests(tmpDir); + t.is("OfflineFeatures", features.constructor.name); + + sinon + .stub(apiClient, "getApiClient") + .throws(new Error("Should not have called getApiClient")); + + for (const feature of Object.values(Feature)) { + t.deepEqual( + await getFeatureIncludingCodeQlIfRequired(features, feature), + featureConfig[feature].defaultValue, + ); + } + }); +});