diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 0ab72d31c..914a4112d 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -90761,6 +90761,12 @@ var Features = class { ); } gitHubFeatureFlags; + // Tracks features that have been queried at some point and the outcome. + queriedFeatures = {}; + /** Gets a record of features that were queried and the corresponding outcomes. */ + getQueriedFeatures() { + return this.queriedFeatures; + } async getDefaultCliVersion(variant) { return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); } @@ -90777,6 +90783,11 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { + const value = await this.getValueInternal(feature, codeql); + this.queriedFeatures[feature] = { value }; + return value; + } + async getValueInternal(feature, codeql) { const config = featureConfig[feature]; if (!codeql && config.minimumVersion) { throw new Error( diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index 0a73443cb..c980b56c6 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -87094,6 +87094,12 @@ var Features = class { ); } gitHubFeatureFlags; + // Tracks features that have been queried at some point and the outcome. + queriedFeatures = {}; + /** Gets a record of features that were queried and the corresponding outcomes. */ + getQueriedFeatures() { + return this.queriedFeatures; + } async getDefaultCliVersion(variant) { return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); } @@ -87110,6 +87116,11 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { + const value = await this.getValueInternal(feature, codeql); + this.queriedFeatures[feature] = { value }; + return value; + } + async getValueInternal(feature, codeql) { const config = featureConfig[feature]; if (!codeql && config.minimumVersion) { throw new Error( diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 97748d394..88185fda5 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -131403,6 +131403,12 @@ var Features = class { ); } gitHubFeatureFlags; + // Tracks features that have been queried at some point and the outcome. + queriedFeatures = {}; + /** Gets a record of features that were queried and the corresponding outcomes. */ + getQueriedFeatures() { + return this.queriedFeatures; + } async getDefaultCliVersion(variant) { return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); } @@ -131419,6 +131425,11 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { + const value = await this.getValueInternal(feature, codeql); + this.queriedFeatures[feature] = { value }; + return value; + } + async getValueInternal(feature, codeql) { const config = featureConfig[feature]; if (!codeql && config.minimumVersion) { throw new Error( diff --git a/lib/init-action.js b/lib/init-action.js index ee50500d7..979c0bcc4 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -88252,6 +88252,12 @@ var Features = class { ); } gitHubFeatureFlags; + // Tracks features that have been queried at some point and the outcome. + queriedFeatures = {}; + /** Gets a record of features that were queried and the corresponding outcomes. */ + getQueriedFeatures() { + return this.queriedFeatures; + } async getDefaultCliVersion(variant) { return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); } @@ -88268,6 +88274,11 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { + const value = await this.getValueInternal(feature, codeql); + this.queriedFeatures[feature] = { value }; + return value; + } + async getValueInternal(feature, codeql) { const config = featureConfig[feature]; if (!codeql && config.minimumVersion) { throw new Error( diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index 7182336ae..cbb3dd29e 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -86995,6 +86995,12 @@ var Features = class { ); } gitHubFeatureFlags; + // Tracks features that have been queried at some point and the outcome. + queriedFeatures = {}; + /** Gets a record of features that were queried and the corresponding outcomes. */ + getQueriedFeatures() { + return this.queriedFeatures; + } async getDefaultCliVersion(variant) { return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); } @@ -87011,6 +87017,11 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { + const value = await this.getValueInternal(feature, codeql); + this.queriedFeatures[feature] = { value }; + return value; + } + async getValueInternal(feature, codeql) { const config = featureConfig[feature]; if (!codeql && config.minimumVersion) { throw new Error( diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index 4b775f252..a79bcadd4 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -89949,6 +89949,12 @@ var Features = class { ); } gitHubFeatureFlags; + // Tracks features that have been queried at some point and the outcome. + queriedFeatures = {}; + /** Gets a record of features that were queried and the corresponding outcomes. */ + getQueriedFeatures() { + return this.queriedFeatures; + } async getDefaultCliVersion(variant) { return await this.gitHubFeatureFlags.getDefaultCliVersion(variant); } @@ -89965,6 +89971,11 @@ var Features = class { * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. */ async getValue(feature, codeql) { + const value = await this.getValueInternal(feature, codeql); + this.queriedFeatures[feature] = { value }; + return value; + } + async getValueInternal(feature, codeql) { const config = featureConfig[feature]; if (!codeql && config.minimumVersion) { throw new Error( diff --git a/src/feature-flags.test.ts b/src/feature-flags.test.ts index cdab85e27..62760b316 100644 --- a/src/feature-flags.test.ts +++ b/src/feature-flags.test.ts @@ -92,6 +92,31 @@ test(`Feature flags are requested in GHEC-DR`, async (t) => { }); }); +test("Queried feature flags are recorded", async (t) => { + await withTmpDir(async (tmpDir) => { + const loggedMessages = []; + const features = setUpFeatureFlagTests( + tmpDir, + getRecordingLogger(loggedMessages), + { type: GitHubVariant.DOTCOM }, + ); + + mockFeatureFlagApiEndpoint(200, initializeFeatures(true)); + + // No features should have been queried initially. + t.is(Object.keys(features.getQueriedFeatures()).length, 0); + + // Query all features. + const allFeatures = Object.values(Feature); + for (const feature of allFeatures) { + await getFeatureIncludingCodeQlIfRequired(features, feature); + } + + // All features should have a been queried. + t.is(Object.keys(features.getQueriedFeatures()).length, allFeatures.length); + }); +}); + test("API response missing and features use default value", async (t) => { await withTmpDir(async (tmpDir) => { const loggedMessages: LoggedMessage[] = []; @@ -562,7 +587,7 @@ function setUpFeatureFlagTests( tmpDir: string, logger = getRunnerLogger(true), gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion, -): FeatureEnablement { +): Features { setupActionsVars(tmpDir, tmpDir); return new Features(gitHubVersion, testRepositoryNwo, tmpDir, logger); diff --git a/src/feature-flags.ts b/src/feature-flags.ts index 8de68a28d..b4cd391a1 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -345,6 +345,14 @@ export interface FeatureEnablement { */ type GitHubFeatureFlagsApiResponse = Partial>; +// Even though we are currently only tracking the value of queried features, we use an object +// here rather than just a boolean to keep open the possibility of also tracking the reason +// for why a particular value was resolved (i.e. because of an environment variable or API result) +// in the future. +export interface QueriedFeatureStatus { + value: boolean; +} + export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; /** @@ -355,6 +363,9 @@ export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; export class Features implements FeatureEnablement { private gitHubFeatureFlags: GitHubFeatureFlags; + // Tracks features that have been queried at some point and the outcome. + private queriedFeatures: Partial> = {}; + constructor( gitHubVersion: util.GitHubVersion, repositoryNwo: RepositoryNwo, @@ -369,6 +380,11 @@ export class Features implements FeatureEnablement { ); } + /** Gets a record of features that were queried and the corresponding outcomes. */ + public getQueriedFeatures() { + return this.queriedFeatures; + } + async getDefaultCliVersion( variant: util.GitHubVariant, ): Promise { @@ -388,6 +404,15 @@ 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 { + const value = await this.getValueInternal(feature, codeql); + this.queriedFeatures[feature] = { value }; + return value; + } + + private async getValueInternal( + 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[