From 619a9761fe81464ce04f61c09ef1a18f9b4f02e2 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Tue, 26 Aug 2025 23:39:17 +0800 Subject: [PATCH 1/8] correct feature flag and key vault reference examples --- .../app-configuration/package.json | 2 + .../samples-dev/featureFlag.ts | 164 ++++++------------ .../samples-dev/secretReference.ts | 94 +++------- .../samples/v1/javascript/featureFlag.js | 139 +++++---------- .../samples/v1/javascript/package.json | 2 + .../samples/v1/javascript/secretReference.js | 92 +++------- .../samples/v1/typescript/package.json | 2 + .../samples/v1/typescript/src/featureFlag.ts | 164 ++++++------------ .../v1/typescript/src/secretReference.ts | 94 +++------- 9 files changed, 226 insertions(+), 527 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/package.json b/sdk/appconfiguration/app-configuration/package.json index 926799859c88..23c887f1c3f4 100644 --- a/sdk/appconfiguration/app-configuration/package.json +++ b/sdk/appconfiguration/app-configuration/package.json @@ -84,6 +84,8 @@ "@azure/eventgrid": "^5.0.0", "@azure/identity": "catalog:internal", "@azure/keyvault-secrets": "^4.2.0", + "@azure/app-configuration-provider": "latest", + "@microsoft/feature-management": "latest", "@types/node": "catalog:", "@vitest/browser": "catalog:testing", "@vitest/coverage-istanbul": "catalog:testing", diff --git a/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts index a117c9d632f3..ef15739d900e 100644 --- a/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts +++ b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts @@ -10,11 +10,15 @@ import { AppConfigurationClient, ConfigurationSetting, featureFlagContentType, - FeatureFlagValue, - parseFeatureFlag, + featureFlagPrefix, + FeatureFlagValue } from "@azure/app-configuration"; import { DefaultAzureCredential } from "@azure/identity"; +// Use configuration provider and feature management library to consume feature flags +import { load } from "@azure/app-configuration-provider"; +import { ConfigurationMapFeatureFlagProvider, FeatureManager, ITargetingContext } from "@microsoft/feature-management"; + // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); @@ -22,8 +26,9 @@ dotenv.config(); export async function main() { console.log(`Running featureFlag sample`); - const originalFeatureFlag: ConfigurationSetting = { - key: `sample-feature-flag`, + const featureFlagName = "sample-feature-flag"; + const sampleFeatureFlag: ConfigurationSetting = { + key: `${featureFlagPrefix}${featureFlagName}`, isReadOnly: false, contentType: featureFlagContentType, value: { @@ -31,14 +36,6 @@ export async function main() { description: "I'm a description", conditions: { clientFilters: [ - { - // Time window filter - Use this filter to activate the feature for a time period - name: "Microsoft.TimeWindow", - parameters: { - Start: "Wed, 01 May 2021 13:59:59 GMT", - End: "Mon, 01 July 2022 00:00:00 GMT", - }, - }, { // Targeting filter - you can target users/groups of users using this filter name: "Microsoft.Targeting", @@ -52,9 +49,12 @@ export async function main() { }, }, // { - // // Percentage filter - activates a feature based on a percentage, to enable the feature flag for 50% of requests - // name: "Microsoft.Percentage", - // parameters: { Value: 50 } + // // Time window filter - Use this filter to activate the feature for a time period + // name: "Microsoft.TimeWindow", + // parameters: { + // Start: new Date().toUTCString(), + // End: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toUTCString(), // 30 days from now + // }, // }, // { name: "FilterX" }, // Custom filter ], @@ -67,77 +67,49 @@ export async function main() { const credential = new DefaultAzureCredential(); const appConfigClient = new AppConfigurationClient(endpoint, credential); - await cleanupSampleValues([originalFeatureFlag.key], appConfigClient); + await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient); - console.log(`Add a new featureFlag with key: ${originalFeatureFlag.key}`); - await appConfigClient.addConfigurationSetting(originalFeatureFlag); + console.log(`Add a new featureFlag with key: ${sampleFeatureFlag.key}`); + await appConfigClient.addConfigurationSetting(sampleFeatureFlag); - console.log(`Get the added configurationSetting with key: ${originalFeatureFlag.key}`); - const getResponse = await appConfigClient.getConfigurationSetting({ - key: originalFeatureFlag.key, + console.log(`Use configuration provider to load feature flags and enable dynamic refresh`); + const appConfigProvider = await load(endpoint, credential, { + featureFlagOptions: { + enabled: true, + refresh: { + enabled: true, + refreshIntervalInMs: 5000 + } + } }); - // You can use the `isFeatureFlag` global method to check if the content type is featureFlagContentType ("application/vnd.microsoft.appconfig.ff+json;charset=utf-8") - const newFeatureFlag = parseFeatureFlag(getResponse); // Converts the configurationsetting into featureflag - // Modify the props - for (const clientFilter of newFeatureFlag.value.conditions.clientFilters) { - clientFilter.parameters = clientFilter.parameters || {}; - console.log( - `\n...clientFilter - "${clientFilter.name}"...\nparams => ${JSON.stringify( - clientFilter.parameters, - null, - 1, - )}\n`, - ); - switch (clientFilter.name) { - // Tweak the client filters of the feature flag - case "Microsoft.Targeting": - // Adds a new user to the group - if (isTargetingClientFilter(clientFilter)) { - clientFilter.parameters.Audience.Users = - clientFilter.parameters.Audience.Users.concat("test2@contoso.com"); - } - break; - case "Microsoft.TimeWindow": - // Changes the start time - if (isTimeWindowClientFilter(clientFilter)) { - clientFilter.parameters.Start = "Wed, 01 June 2021 13:59:59 GMT"; - } - break; - // case "Microsoft.Percentage": - // // Changes the percentage value from 50 to 75 - to enable the feature flag for 75% of requests - // clientFilter.parameters.Value = 75; - // break; - default: - // Change the filter name for all other client filters - // clientFilter.name = "FilterY"; - break; - } - } + console.log(`Use feature management library to consume feature flags`); + const featureManager = new FeatureManager( + new ConfigurationMapFeatureFlagProvider(appConfigProvider)); + + let isEnabled = await featureManager.isEnabled(featureFlagName); + console.log(`Is featureFlag enabled? ${isEnabled}`); console.log(`========> Update the featureFlag <======== `); + + sampleFeatureFlag.value.enabled = true; + // Updating the config setting - await appConfigClient.setConfigurationSetting(newFeatureFlag); + await appConfigClient.setConfigurationSetting(sampleFeatureFlag); - // Get the config setting again - console.log(`Get the updated config setting with key: ${newFeatureFlag.key}`); - const getResponseAfterUpdate = await appConfigClient.getConfigurationSetting({ - key: newFeatureFlag.key, - }); + // Wait for refresh interval to elapse + await new Promise(resolve => setTimeout(resolve, 5000)); - // You can use the `isFeatureFlag` global method to check if the content type is featureFlagContentType ("application/vnd.microsoft.appconfig.ff+json;charset=utf-8") - const featureFlagAfterUpdate = parseFeatureFlag(getResponseAfterUpdate); // Converts the configurationsetting into featureflag - const conditions = featureFlagAfterUpdate.value.conditions; - for (const clientFilter of conditions.clientFilters) { - console.log( - `\n...clientFilter - "${clientFilter.name}"...\nparams => ${JSON.stringify( - clientFilter.parameters, - null, - 1, - )}\n`, - ); - } - await cleanupSampleValues([originalFeatureFlag.key], appConfigClient); + await appConfigProvider.refresh(); + + isEnabled = await featureManager.isEnabled(featureFlagName); + console.log(`Is featureFlag enabled? ${isEnabled}`); + + const targetingContext : ITargetingContext = { userId: "test@contoso.com" }; + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); + console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + + await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient); } async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) { @@ -150,44 +122,6 @@ async function cleanupSampleValues(keys: string[], client: AppConfigurationClien } } -/** - * typeguard - for targeting client filter - */ -function isTargetingClientFilter(clientFilter: any): clientFilter is { - parameters: { - Audience: { - Groups: Array<{ Name: string; RolloutPercentage: number }>; - Users: Array; - DefaultRolloutPercentage: number; - }; - }; -} { - return ( - clientFilter.name === "Microsoft.Targeting" && - clientFilter.parameters && - clientFilter.parameters["Audience"] && - Array.isArray(clientFilter.parameters["Audience"]["Groups"]) && - Array.isArray(clientFilter.parameters["Audience"]["Users"]) && - typeof clientFilter.parameters["Audience"]["DefaultRolloutPercentage"] === "number" - ); -} - -/** - * typeguard - for timewindow client filter - */ -export function isTimeWindowClientFilter( - clientFilter: any, -): clientFilter is { parameters: { Start: string; End: string } } { - return ( - clientFilter.name === "Microsoft.TimeWindow" && - clientFilter.parameters && - clientFilter.parameters["Start"] && - clientFilter.parameters["End"] && - typeof clientFilter.parameters["Start"] === "string" && - typeof clientFilter.parameters["End"] === "string" - ); -} - main().catch((err) => { console.error("Failed to run sample:", err); process.exit(1); diff --git a/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts b/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts index aaaec942d093..aa885466d291 100644 --- a/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts +++ b/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts @@ -11,11 +11,13 @@ import { SecretReferenceValue, secretReferenceContentType, ConfigurationSetting, - parseSecretReference, } from "@azure/app-configuration"; -import { parseKeyVaultSecretIdentifier, SecretClient } from "@azure/keyvault-secrets"; +import { SecretClient } from "@azure/keyvault-secrets"; import { DefaultAzureCredential } from "@azure/identity"; +// Use configuration provider and feature management library to consume secret reference +import { load } from "@azure/app-configuration-provider"; + // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); @@ -23,91 +25,47 @@ dotenv.config(); export async function main() { console.log(`Running secretReference sample`); - const key = `secret${new Date().getTime()}`; + const secretName = `secret${new Date().getTime()}`; + + const vaultUri = process.env["KEYVAULT_URI"] || ""; + const secretClient = new SecretClient(vaultUri, new DefaultAzureCredential()); - // setup method creates - // - a secret using `@azure/keyvault-secrets` - // - a corresponding secret reference config setting with `@azure/app-configuration` - await setup(key); + const secretId = await createKeyVaultSecret(secretName, secretClient); - console.log(`Get the added secretReference from App Config with key: ${key}`); - // Set the following environment variable or edit the value on the following line. - const endpoint = process.env["AZ_CONFIG_ENDPOINT"] || ""; + const endpoint = process.env["AZ_CONFIG_ENDPOINT"] || ""; const credential = new DefaultAzureCredential(); const appConfigClient = new AppConfigurationClient(endpoint, credential); - const getResponse = await appConfigClient.getConfigurationSetting({ - key, - }); - // You can use the `isSecretReference` global method to check if the content type is secretReferenceContentType ("application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8") - const parsedSecretReference = parseSecretReference(getResponse); - - // Get the name and vaultUrl from the secretId - const { name: secretName, vaultUrl } = parseKeyVaultSecretIdentifier( - parsedSecretReference.value.secretId, - ); + // creates the secret reference config setting + await createConfigSetting(secretName, secretId, appConfigClient); - const secretClient = new SecretClient(vaultUrl, new DefaultAzureCredential()); - try { - // Read the secret we created - const secret = await secretClient.getSecret(secretName); - console.log(`Get the secret from keyvault key: ${secretName}, value: ${secret.value}`); - } catch (err: any) { - const error = err as { code: string; statusCode: number }; - if (error.code === "SecretNotFound" && error.statusCode === 404) { - throw new Error( - `\n Secret is not found, make sure the secret ${parsedSecretReference.value.secretId} is present in your keyvault account;\n Original error - ${error}`, - ); - } else { - throw err; + console.log(`Use configuration provider to load and resolve secret reference`); + const appConfigProvider = await load(endpoint, credential, { + keyVaultOptions: { + credential: credential } - } + }); + + console.log(`Secret value: ${appConfigProvider.get(secretName)}`); - console.log(`Deleting the secret from keyvault`); await secretClient.beginDeleteSecret(secretName); - await cleanupSampleValues([key], appConfigClient); + await cleanupSampleValues([secretName], appConfigClient); } -async function setup(key: string) { - if ( - !process.env["AZURE_TENANT_ID"] || - !process.env["AZURE_CLIENT_ID"] || - !process.env["AZURE_CLIENT_SECRET"] || - !process.env["KEYVAULT_URI"] || - !process.env["APPCONFIG_CONNECTION_STRING"] - ) { - console.log( - `At least one of the AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, APPCONFIG_CONNECTION_STRING and KEYVAULT_URI variables is not present, - please add the missing ones in your environment and rerun the sample.`, - ); - return; - } - - // DefaultAzureCredential expects the following three environment variables: - // - AZURE_TENANT_ID: The tenant ID in Azure Active Directory - // - AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant - // - AZURE_CLIENT_SECRET: The client secret for the registered application - const secretClient = new SecretClient(process.env["KEYVAULT_URI"], new DefaultAzureCredential()); - const secretName = `secret-${Date.now()}`; +async function createKeyVaultSecret(secretName: string, client: SecretClient) { // Create a secret console.log(`Create a keyvault secret with key: ${secretName} and value: "MySecretValue"`); - const secret = await secretClient.setSecret(secretName, "MySecretValue"); + const secret = await client.setSecret(secretName, "MySecretValue"); if (!secret.properties.id) { throw new Error("Something went wrong - secret id is undefined"); } - // creates the secret reference config setting - await createConfigSetting(key, secret.properties.id); + return secret.properties.id; } -async function createConfigSetting(key: string, secretId: string) { - // Set the following environment variable or edit the value on the following line. - const endpoint = process.env["AZ_CONFIG_ENDPOINT"] || ""; - const credential = new DefaultAzureCredential(); - const appConfigClient = new AppConfigurationClient(endpoint, credential); - +async function createConfigSetting(key: string, secretId: string, client: AppConfigurationClient) { const secretReference: ConfigurationSetting = { key, value: { secretId }, @@ -115,12 +73,12 @@ async function createConfigSetting(key: string, secretId: string) { contentType: secretReferenceContentType, }; - await cleanupSampleValues([key], appConfigClient); + await cleanupSampleValues([key], client); console.log( `Add a new secretReference with key: ${key} and secretId: ${secretReference.value.secretId}`, ); - await appConfigClient.addConfigurationSetting(secretReference); + await client.addConfigurationSetting(secretReference); } async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) { diff --git a/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js b/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js index 5e8e49d67337..cddae312fc99 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js +++ b/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js @@ -8,6 +8,7 @@ const { AppConfigurationClient, featureFlagContentType, parseFeatureFlag, + featureFlagPrefix } = require("@azure/app-configuration"); const { DefaultAzureCredential } = require("@azure/identity"); @@ -17,8 +18,9 @@ require("dotenv").config(); async function main() { console.log(`Running featureFlag sample`); - const originalFeatureFlag = { - key: `sample-feature-flag`, + const featureFlagName = "sample-feature-flag"; + const sampleFeatureFlag = { + key: `${featureFlagPrefix}${featureFlagName}`, isReadOnly: false, contentType: featureFlagContentType, value: { @@ -26,14 +28,6 @@ async function main() { description: "I'm a description", conditions: { clientFilters: [ - { - // Time window filter - Use this filter to activate the feature for a time period - name: "Microsoft.TimeWindow", - parameters: { - Start: "Wed, 01 May 2021 13:59:59 GMT", - End: "Mon, 01 July 2022 00:00:00 GMT", - }, - }, { // Targeting filter - you can target users/groups of users using this filter name: "Microsoft.Targeting", @@ -47,9 +41,12 @@ async function main() { }, }, // { - // // Percentage filter - activates a feature based on a percentage, to enable the feature flag for 50% of requests - // name: "Microsoft.Percentage", - // parameters: { Value: 50 } + // // Time window filter - Use this filter to activate the feature for a time period + // name: "Microsoft.TimeWindow", + // parameters: { + // Start: new Date().toUTCString(), + // End: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toUTCString(), // 30 days from now + // }, // }, // { name: "FilterX" }, // Custom filter ], @@ -62,69 +59,49 @@ async function main() { const credential = new DefaultAzureCredential(); const appConfigClient = new AppConfigurationClient(endpoint, credential); - await cleanupSampleValues([originalFeatureFlag.key], appConfigClient); + await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient); - console.log(`Add a new featureFlag with key: ${originalFeatureFlag.key}`); - await appConfigClient.addConfigurationSetting(originalFeatureFlag); + console.log(`Add a new featureFlag with key: ${sampleFeatureFlag.key}`); + await appConfigClient.addConfigurationSetting(sampleFeatureFlag); - console.log(`Get the added configurationSetting with key: ${originalFeatureFlag.key}`); - const getResponse = await appConfigClient.getConfigurationSetting({ - key: originalFeatureFlag.key, + console.log(`Use configuration provider to load feature flags and enable dynamic refresh`); + const appConfigProvider = await load(endpoint, credential, { + featureFlagOptions: { + enabled: true, + refresh: { + enabled: true, + refreshIntervalInMs: 5000 + } + } }); - // You can use the `isFeatureFlag` global method to check if the content type is featureFlagContentType ("application/vnd.microsoft.appconfig.ff+json;charset=utf-8") - const newFeatureFlag = parseFeatureFlag(getResponse); // Converts the configurationsetting into featureflag - // Modify the props - for (const clientFilter of newFeatureFlag.value.conditions.clientFilters) { - clientFilter.parameters = clientFilter.parameters || {}; - console.log( - `\n...clientFilter - "${clientFilter.name}"...\nparams => ${JSON.stringify(clientFilter.parameters, null, 1)}\n`, - ); - switch (clientFilter.name) { - // Tweak the client filters of the feature flag - case "Microsoft.Targeting": - // Adds a new user to the group - if (isTargetingClientFilter(clientFilter)) { - clientFilter.parameters.Audience.Users = - clientFilter.parameters.Audience.Users.concat("test2@contoso.com"); - } - break; - case "Microsoft.TimeWindow": - // Changes the start time - if (isTimeWindowClientFilter(clientFilter)) { - clientFilter.parameters.Start = "Wed, 01 June 2021 13:59:59 GMT"; - } - break; - // case "Microsoft.Percentage": - // // Changes the percentage value from 50 to 75 - to enable the feature flag for 75% of requests - // clientFilter.parameters.Value = 75; - // break; - default: - // Change the filter name for all other client filters - // clientFilter.name = "FilterY"; - break; - } - } + console.log(`Use feature management library to consume feature flags`); + const featureManager = new FeatureManager( + new ConfigurationMapFeatureFlagProvider(appConfigProvider)); + + let isEnabled = await featureManager.isEnabled(featureFlagName); + console.log(`Is featureFlag enabled? ${isEnabled}`); console.log(`========> Update the featureFlag <======== `); + + sampleFeatureFlag.value.enabled = true; + // Updating the config setting - await appConfigClient.setConfigurationSetting(newFeatureFlag); + await appConfigClient.setConfigurationSetting(sampleFeatureFlag); - // Get the config setting again - console.log(`Get the updated config setting with key: ${newFeatureFlag.key}`); - const getResponseAfterUpdate = await appConfigClient.getConfigurationSetting({ - key: newFeatureFlag.key, - }); + // Wait for refresh interval to elapse + await new Promise(resolve => setTimeout(resolve, 5000)); - // You can use the `isFeatureFlag` global method to check if the content type is featureFlagContentType ("application/vnd.microsoft.appconfig.ff+json;charset=utf-8") - const featureFlagAfterUpdate = parseFeatureFlag(getResponseAfterUpdate); // Converts the configurationsetting into featureflag - const conditions = featureFlagAfterUpdate.value.conditions; - for (const clientFilter of conditions.clientFilters) { - console.log( - `\n...clientFilter - "${clientFilter.name}"...\nparams => ${JSON.stringify(clientFilter.parameters, null, 1)}\n`, - ); - } - await cleanupSampleValues([originalFeatureFlag.key], appConfigClient); + await appConfigProvider.refresh(); + + isEnabled = await featureManager.isEnabled(featureFlagName); + console.log(`Is featureFlag enabled? ${isEnabled}`); + + const targetingContext : ITargetingContext = { userId: "test@contoso.com" }; + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); + console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + + await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient); } async function cleanupSampleValues(keys, client) { @@ -137,34 +114,6 @@ async function cleanupSampleValues(keys, client) { } } -/** - * typeguard - for targeting client filter - */ -function isTargetingClientFilter(clientFilter) { - return ( - clientFilter.name === "Microsoft.Targeting" && - clientFilter.parameters && - clientFilter.parameters["Audience"] && - Array.isArray(clientFilter.parameters["Audience"]["Groups"]) && - Array.isArray(clientFilter.parameters["Audience"]["Users"]) && - typeof clientFilter.parameters["Audience"]["DefaultRolloutPercentage"] === "number" - ); -} - -/** - * typeguard - for timewindow client filter - */ -function isTimeWindowClientFilter(clientFilter) { - return ( - clientFilter.name === "Microsoft.TimeWindow" && - clientFilter.parameters && - clientFilter.parameters["Start"] && - clientFilter.parameters["End"] && - typeof clientFilter.parameters["Start"] === "string" && - typeof clientFilter.parameters["End"] === "string" - ); -} - main().catch((err) => { console.error("Failed to run sample:", err); process.exit(1); diff --git a/sdk/appconfiguration/app-configuration/samples/v1/javascript/package.json b/sdk/appconfiguration/app-configuration/samples/v1/javascript/package.json index be0799cbdbc4..44a415a0e286 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/javascript/package.json +++ b/sdk/appconfiguration/app-configuration/samples/v1/javascript/package.json @@ -27,6 +27,8 @@ "homepage": "https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/appconfiguration/app-configuration", "dependencies": { "@azure/app-configuration": "latest", + "@azure/app-configuration-provider": "latest", + "@microsoft/feature-management": "latest", "dotenv": "latest", "@azure/identity": "^4.4.1", "@azure/keyvault-secrets": "^4.2.0", diff --git a/sdk/appconfiguration/app-configuration/samples/v1/javascript/secretReference.js b/sdk/appconfiguration/app-configuration/samples/v1/javascript/secretReference.js index 4c05d0edee7d..3e1a9759e7e5 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/javascript/secretReference.js +++ b/sdk/appconfiguration/app-configuration/samples/v1/javascript/secretReference.js @@ -7,100 +7,60 @@ const { AppConfigurationClient, secretReferenceContentType, - parseSecretReference, } = require("@azure/app-configuration"); -const { parseKeyVaultSecretIdentifier, SecretClient } = require("@azure/keyvault-secrets"); +const { SecretClient } = require("@azure/keyvault-secrets"); const { DefaultAzureCredential } = require("@azure/identity"); +// Use configuration provider and feature management library to consume secret reference +const { load } = require("@azure/app-configuration-provider"); + // Load the .env file if it exists require("dotenv").config(); async function main() { console.log(`Running secretReference sample`); - const key = `secret${new Date().getTime()}`; + const secretName = `secret${new Date().getTime()}`; + + const vaultUri = process.env["KEYVAULT_URI"] || ""; + const secretClient = new SecretClient(vaultUri, new DefaultAzureCredential()); - // setup method creates - // - a secret using `@azure/keyvault-secrets` - // - a corresponding secret reference config setting with `@azure/app-configuration` - await setup(key); + const secretId = await createKeyVaultSecret(secretName, secretClient); - console.log(`Get the added secretReference from App Config with key: ${key}`); - // Set the following environment variable or edit the value on the following line. - const endpoint = process.env["AZ_CONFIG_ENDPOINT"] || ""; + const endpoint = process.env["AZ_CONFIG_ENDPOINT"] || ""; const credential = new DefaultAzureCredential(); const appConfigClient = new AppConfigurationClient(endpoint, credential); - const getResponse = await appConfigClient.getConfigurationSetting({ - key, - }); - // You can use the `isSecretReference` global method to check if the content type is secretReferenceContentType ("application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8") - const parsedSecretReference = parseSecretReference(getResponse); - - // Get the name and vaultUrl from the secretId - const { name: secretName, vaultUrl } = parseKeyVaultSecretIdentifier( - parsedSecretReference.value.secretId, - ); + // creates the secret reference config setting + await createConfigSetting(secretName, secretId, appConfigClient); - const secretClient = new SecretClient(vaultUrl, new DefaultAzureCredential()); - try { - // Read the secret we created - const secret = await secretClient.getSecret(secretName); - console.log(`Get the secret from keyvault key: ${secretName}, value: ${secret.value}`); - } catch (err) { - const error = err; - if (error.code === "SecretNotFound" && error.statusCode === 404) { - throw new Error( - `\n Secret is not found, make sure the secret ${parsedSecretReference.value.secretId} is present in your keyvault account;\n Original error - ${error}`, - ); - } else { - throw err; + console.log(`Use configuration provider to load and resolve secret reference`); + const appConfigProvider = await load(endpoint, credential, { + keyVaultOptions: { + credential: credential } - } + }); + + console.log(`Secret value: ${appConfigProvider.get(secretName)}`); - console.log(`Deleting the secret from keyvault`); await secretClient.beginDeleteSecret(secretName); - await cleanupSampleValues([key], appConfigClient); + await cleanupSampleValues([secretName], appConfigClient); } -async function setup(key) { - if ( - !process.env["AZURE_TENANT_ID"] || - !process.env["AZURE_CLIENT_ID"] || - !process.env["AZURE_CLIENT_SECRET"] || - !process.env["KEYVAULT_URI"] || - !process.env["APPCONFIG_CONNECTION_STRING"] - ) { - console.log(`At least one of the AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, APPCONFIG_CONNECTION_STRING and KEYVAULT_URI variables is not present, - please add the missing ones in your environment and rerun the sample.`); - return; - } - - // DefaultAzureCredential expects the following three environment variables: - // - AZURE_TENANT_ID: The tenant ID in Azure Active Directory - // - AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant - // - AZURE_CLIENT_SECRET: The client secret for the registered application - const secretClient = new SecretClient(process.env["KEYVAULT_URI"], new DefaultAzureCredential()); - const secretName = `secret-${Date.now()}`; +async function createKeyVaultSecret(secretName, client) { // Create a secret console.log(`Create a keyvault secret with key: ${secretName} and value: "MySecretValue"`); - const secret = await secretClient.setSecret(secretName, "MySecretValue"); + const secret = await client.setSecret(secretName, "MySecretValue"); if (!secret.properties.id) { throw new Error("Something went wrong - secret id is undefined"); } - // creates the secret reference config setting - await createConfigSetting(key, secret.properties.id); + return secret.properties.id; } -async function createConfigSetting(key, secretId) { - // Set the following environment variable or edit the value on the following line. - const endpoint = process.env["AZ_CONFIG_ENDPOINT"] || ""; - const credential = new DefaultAzureCredential(); - const appConfigClient = new AppConfigurationClient(endpoint, credential); - +async function createConfigSetting(key, secretId, client) { const secretReference = { key, value: { secretId }, @@ -108,12 +68,12 @@ async function createConfigSetting(key, secretId) { contentType: secretReferenceContentType, }; - await cleanupSampleValues([key], appConfigClient); + await cleanupSampleValues([key], client); console.log( `Add a new secretReference with key: ${key} and secretId: ${secretReference.value.secretId}`, ); - await appConfigClient.addConfigurationSetting(secretReference); + await client.addConfigurationSetting(secretReference); } async function cleanupSampleValues(keys, client) { diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/package.json b/sdk/appconfiguration/app-configuration/samples/v1/typescript/package.json index 5e95855eca95..6e220733326c 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/typescript/package.json +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/package.json @@ -31,6 +31,8 @@ "homepage": "https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/appconfiguration/app-configuration", "dependencies": { "@azure/app-configuration": "latest", + "@azure/app-configuration-provider": "latest", + "@microsoft/feature-management": "latest", "dotenv": "latest", "@azure/identity": "^4.4.1", "@azure/keyvault-secrets": "^4.2.0", diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts index beeee3456a9e..eefe83e9b0bb 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts @@ -8,11 +8,15 @@ import { AppConfigurationClient, ConfigurationSetting, featureFlagContentType, - FeatureFlagValue, - parseFeatureFlag, + featureFlagPrefix, + FeatureFlagValue } from "@azure/app-configuration"; import { DefaultAzureCredential } from "@azure/identity"; +// Use configuration provider and feature management library to consume feature flags +import { load } from "@azure/app-configuration-provider"; +import { ConfigurationMapFeatureFlagProvider, FeatureManager, ITargetingContext } from "@microsoft/feature-management"; + // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); @@ -20,8 +24,9 @@ dotenv.config(); export async function main() { console.log(`Running featureFlag sample`); - const originalFeatureFlag: ConfigurationSetting = { - key: `sample-feature-flag`, + const featureFlagName = "sample-feature-flag"; + const sampleFeatureFlag: ConfigurationSetting = { + key: `${featureFlagPrefix}${featureFlagName}`, isReadOnly: false, contentType: featureFlagContentType, value: { @@ -29,14 +34,6 @@ export async function main() { description: "I'm a description", conditions: { clientFilters: [ - { - // Time window filter - Use this filter to activate the feature for a time period - name: "Microsoft.TimeWindow", - parameters: { - Start: "Wed, 01 May 2021 13:59:59 GMT", - End: "Mon, 01 July 2022 00:00:00 GMT", - }, - }, { // Targeting filter - you can target users/groups of users using this filter name: "Microsoft.Targeting", @@ -50,9 +47,12 @@ export async function main() { }, }, // { - // // Percentage filter - activates a feature based on a percentage, to enable the feature flag for 50% of requests - // name: "Microsoft.Percentage", - // parameters: { Value: 50 } + // // Time window filter - Use this filter to activate the feature for a time period + // name: "Microsoft.TimeWindow", + // parameters: { + // Start: new Date().toUTCString(), + // End: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toUTCString(), // 30 days from now + // }, // }, // { name: "FilterX" }, // Custom filter ], @@ -65,77 +65,49 @@ export async function main() { const credential = new DefaultAzureCredential(); const appConfigClient = new AppConfigurationClient(endpoint, credential); - await cleanupSampleValues([originalFeatureFlag.key], appConfigClient); + await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient); - console.log(`Add a new featureFlag with key: ${originalFeatureFlag.key}`); - await appConfigClient.addConfigurationSetting(originalFeatureFlag); + console.log(`Add a new featureFlag with key: ${sampleFeatureFlag.key}`); + await appConfigClient.addConfigurationSetting(sampleFeatureFlag); - console.log(`Get the added configurationSetting with key: ${originalFeatureFlag.key}`); - const getResponse = await appConfigClient.getConfigurationSetting({ - key: originalFeatureFlag.key, + console.log(`Use configuration provider to load feature flags and enable dynamic refresh`); + const appConfigProvider = await load(endpoint, credential, { + featureFlagOptions: { + enabled: true, + refresh: { + enabled: true, + refreshIntervalInMs: 5000 + } + } }); - // You can use the `isFeatureFlag` global method to check if the content type is featureFlagContentType ("application/vnd.microsoft.appconfig.ff+json;charset=utf-8") - const newFeatureFlag = parseFeatureFlag(getResponse); // Converts the configurationsetting into featureflag - // Modify the props - for (const clientFilter of newFeatureFlag.value.conditions.clientFilters) { - clientFilter.parameters = clientFilter.parameters || {}; - console.log( - `\n...clientFilter - "${clientFilter.name}"...\nparams => ${JSON.stringify( - clientFilter.parameters, - null, - 1, - )}\n`, - ); - switch (clientFilter.name) { - // Tweak the client filters of the feature flag - case "Microsoft.Targeting": - // Adds a new user to the group - if (isTargetingClientFilter(clientFilter)) { - clientFilter.parameters.Audience.Users = - clientFilter.parameters.Audience.Users.concat("test2@contoso.com"); - } - break; - case "Microsoft.TimeWindow": - // Changes the start time - if (isTimeWindowClientFilter(clientFilter)) { - clientFilter.parameters.Start = "Wed, 01 June 2021 13:59:59 GMT"; - } - break; - // case "Microsoft.Percentage": - // // Changes the percentage value from 50 to 75 - to enable the feature flag for 75% of requests - // clientFilter.parameters.Value = 75; - // break; - default: - // Change the filter name for all other client filters - // clientFilter.name = "FilterY"; - break; - } - } + console.log(`Use feature management library to consume feature flags`); + const featureManager = new FeatureManager( + new ConfigurationMapFeatureFlagProvider(appConfigProvider)); + + let isEnabled = await featureManager.isEnabled(featureFlagName); + console.log(`Is featureFlag enabled? ${isEnabled}`); console.log(`========> Update the featureFlag <======== `); + + sampleFeatureFlag.value.enabled = true; + // Updating the config setting - await appConfigClient.setConfigurationSetting(newFeatureFlag); + await appConfigClient.setConfigurationSetting(sampleFeatureFlag); - // Get the config setting again - console.log(`Get the updated config setting with key: ${newFeatureFlag.key}`); - const getResponseAfterUpdate = await appConfigClient.getConfigurationSetting({ - key: newFeatureFlag.key, - }); + // Wait for refresh interval to elapse + await new Promise(resolve => setTimeout(resolve, 5000)); - // You can use the `isFeatureFlag` global method to check if the content type is featureFlagContentType ("application/vnd.microsoft.appconfig.ff+json;charset=utf-8") - const featureFlagAfterUpdate = parseFeatureFlag(getResponseAfterUpdate); // Converts the configurationsetting into featureflag - const conditions = featureFlagAfterUpdate.value.conditions; - for (const clientFilter of conditions.clientFilters) { - console.log( - `\n...clientFilter - "${clientFilter.name}"...\nparams => ${JSON.stringify( - clientFilter.parameters, - null, - 1, - )}\n`, - ); - } - await cleanupSampleValues([originalFeatureFlag.key], appConfigClient); + await appConfigProvider.refresh(); + + isEnabled = await featureManager.isEnabled(featureFlagName); + console.log(`Is featureFlag enabled? ${isEnabled}`); + + const targetingContext : ITargetingContext = { userId: "test@contoso.com" }; + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); + console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + + await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient); } async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) { @@ -148,44 +120,6 @@ async function cleanupSampleValues(keys: string[], client: AppConfigurationClien } } -/** - * typeguard - for targeting client filter - */ -function isTargetingClientFilter(clientFilter: any): clientFilter is { - parameters: { - Audience: { - Groups: Array<{ Name: string; RolloutPercentage: number }>; - Users: Array; - DefaultRolloutPercentage: number; - }; - }; -} { - return ( - clientFilter.name === "Microsoft.Targeting" && - clientFilter.parameters && - clientFilter.parameters["Audience"] && - Array.isArray(clientFilter.parameters["Audience"]["Groups"]) && - Array.isArray(clientFilter.parameters["Audience"]["Users"]) && - typeof clientFilter.parameters["Audience"]["DefaultRolloutPercentage"] === "number" - ); -} - -/** - * typeguard - for timewindow client filter - */ -export function isTimeWindowClientFilter( - clientFilter: any, -): clientFilter is { parameters: { Start: string; End: string } } { - return ( - clientFilter.name === "Microsoft.TimeWindow" && - clientFilter.parameters && - clientFilter.parameters["Start"] && - clientFilter.parameters["End"] && - typeof clientFilter.parameters["Start"] === "string" && - typeof clientFilter.parameters["End"] === "string" - ); -} - main().catch((err) => { console.error("Failed to run sample:", err); process.exit(1); diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts index 6862784c0f7d..49701f13d232 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts @@ -9,11 +9,13 @@ import { SecretReferenceValue, secretReferenceContentType, ConfigurationSetting, - parseSecretReference, } from "@azure/app-configuration"; -import { parseKeyVaultSecretIdentifier, SecretClient } from "@azure/keyvault-secrets"; +import { SecretClient } from "@azure/keyvault-secrets"; import { DefaultAzureCredential } from "@azure/identity"; +// Use configuration provider and feature management library to consume secret reference +import { load } from "@azure/app-configuration-provider"; + // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); @@ -21,91 +23,47 @@ dotenv.config(); export async function main() { console.log(`Running secretReference sample`); - const key = `secret${new Date().getTime()}`; + const secretName = `secret${new Date().getTime()}`; + + const vaultUri = process.env["KEYVAULT_URI"] || ""; + const secretClient = new SecretClient(vaultUri, new DefaultAzureCredential()); - // setup method creates - // - a secret using `@azure/keyvault-secrets` - // - a corresponding secret reference config setting with `@azure/app-configuration` - await setup(key); + const secretId = await createKeyVaultSecret(secretName, secretClient); - console.log(`Get the added secretReference from App Config with key: ${key}`); - // Set the following environment variable or edit the value on the following line. - const endpoint = process.env["AZ_CONFIG_ENDPOINT"] || ""; + const endpoint = process.env["AZ_CONFIG_ENDPOINT"] || ""; const credential = new DefaultAzureCredential(); const appConfigClient = new AppConfigurationClient(endpoint, credential); - const getResponse = await appConfigClient.getConfigurationSetting({ - key, - }); - // You can use the `isSecretReference` global method to check if the content type is secretReferenceContentType ("application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8") - const parsedSecretReference = parseSecretReference(getResponse); - - // Get the name and vaultUrl from the secretId - const { name: secretName, vaultUrl } = parseKeyVaultSecretIdentifier( - parsedSecretReference.value.secretId, - ); + // creates the secret reference config setting + await createConfigSetting(secretName, secretId, appConfigClient); - const secretClient = new SecretClient(vaultUrl, new DefaultAzureCredential()); - try { - // Read the secret we created - const secret = await secretClient.getSecret(secretName); - console.log(`Get the secret from keyvault key: ${secretName}, value: ${secret.value}`); - } catch (err: any) { - const error = err as { code: string; statusCode: number }; - if (error.code === "SecretNotFound" && error.statusCode === 404) { - throw new Error( - `\n Secret is not found, make sure the secret ${parsedSecretReference.value.secretId} is present in your keyvault account;\n Original error - ${error}`, - ); - } else { - throw err; + console.log(`Use configuration provider to load and resolve secret reference`); + const appConfigProvider = await load(endpoint, credential, { + keyVaultOptions: { + credential: credential } - } + }); + + console.log(`Secret value: ${appConfigProvider.get(secretName)}`); - console.log(`Deleting the secret from keyvault`); await secretClient.beginDeleteSecret(secretName); - await cleanupSampleValues([key], appConfigClient); + await cleanupSampleValues([secretName], appConfigClient); } -async function setup(key: string) { - if ( - !process.env["AZURE_TENANT_ID"] || - !process.env["AZURE_CLIENT_ID"] || - !process.env["AZURE_CLIENT_SECRET"] || - !process.env["KEYVAULT_URI"] || - !process.env["APPCONFIG_CONNECTION_STRING"] - ) { - console.log( - `At least one of the AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, APPCONFIG_CONNECTION_STRING and KEYVAULT_URI variables is not present, - please add the missing ones in your environment and rerun the sample.`, - ); - return; - } - - // DefaultAzureCredential expects the following three environment variables: - // - AZURE_TENANT_ID: The tenant ID in Azure Active Directory - // - AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant - // - AZURE_CLIENT_SECRET: The client secret for the registered application - const secretClient = new SecretClient(process.env["KEYVAULT_URI"], new DefaultAzureCredential()); - const secretName = `secret-${Date.now()}`; +async function createKeyVaultSecret(secretName: string, client: SecretClient) { // Create a secret console.log(`Create a keyvault secret with key: ${secretName} and value: "MySecretValue"`); - const secret = await secretClient.setSecret(secretName, "MySecretValue"); + const secret = await client.setSecret(secretName, "MySecretValue"); if (!secret.properties.id) { throw new Error("Something went wrong - secret id is undefined"); } - // creates the secret reference config setting - await createConfigSetting(key, secret.properties.id); + return secret.properties.id; } -async function createConfigSetting(key: string, secretId: string) { - // Set the following environment variable or edit the value on the following line. - const endpoint = process.env["AZ_CONFIG_ENDPOINT"] || ""; - const credential = new DefaultAzureCredential(); - const appConfigClient = new AppConfigurationClient(endpoint, credential); - +async function createConfigSetting(key: string, secretId: string, client: AppConfigurationClient) { const secretReference: ConfigurationSetting = { key, value: { secretId }, @@ -113,12 +71,12 @@ async function createConfigSetting(key: string, secretId: string) { contentType: secretReferenceContentType, }; - await cleanupSampleValues([key], appConfigClient); + await cleanupSampleValues([key], client); console.log( `Add a new secretReference with key: ${key} and secretId: ${secretReference.value.secretId}`, ); - await appConfigClient.addConfigurationSetting(secretReference); + await client.addConfigurationSetting(secretReference); } async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) { From 1285a612d8021bce77524f79ab869fe619bfd6ad Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Tue, 26 Aug 2025 23:39:57 +0800 Subject: [PATCH 2/8] check in pnpmlock --- pnpm-lock.yaml | 328 ++++++++----------------------------------------- 1 file changed, 49 insertions(+), 279 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9e9eaed8043..d2978c7006b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1373,6 +1373,9 @@ importers: '@azure-tools/test-recorder': specifier: workspace:^ version: link:../../test-utils/recorder + '@azure/app-configuration-provider': + specifier: latest + version: 2.2.0 '@azure/dev-tool': specifier: workspace:^ version: link:../../../common/tools/dev-tool @@ -1388,6 +1391,9 @@ importers: '@azure/keyvault-secrets': specifier: ^4.2.0 version: link:../../keyvault/keyvault-secrets + '@microsoft/feature-management': + specifier: latest + version: 2.2.0 '@types/node': specifier: 'catalog:' version: 20.19.11 @@ -6113,76 +6119,6 @@ importers: specifier: catalog:testing version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@20.19.11)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) - sdk/containerservice/arm-containerservicefleet/changelog-temp/package: - dependencies: - '@azure-rest/core-client': - specifier: ^2.3.1 - version: link:../../../../core/core-client-rest - '@azure/abort-controller': - specifier: ^2.1.2 - version: link:../../../../core/abort-controller - '@azure/core-auth': - specifier: ^1.9.0 - version: link:../../../../core/core-auth - '@azure/core-lro': - specifier: ^3.1.0 - version: link:../../../../core/core-lro - '@azure/core-rest-pipeline': - specifier: ^1.19.1 - version: link:../../../../core/core-rest-pipeline - '@azure/core-util': - specifier: ^1.11.0 - version: link:../../../../core/core-util - '@azure/logger': - specifier: ^1.1.4 - version: link:../../../../core/logger - tslib: - specifier: ^2.8.1 - version: 2.8.1 - devDependencies: - '@azure-tools/test-credential': - specifier: ^2.0.0 - version: link:../../../../test-utils/test-credential - '@azure-tools/test-recorder': - specifier: ^4.1.0 - version: link:../../../../test-utils/recorder - '@azure-tools/test-utils-vitest': - specifier: ^1.0.0 - version: 1.0.0(@types/debug@4.1.12)(@types/node@18.19.123)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) - '@azure/dev-tool': - specifier: ^1.0.0 - version: link:../../../../../common/tools/dev-tool - '@azure/eslint-plugin-azure-sdk': - specifier: ^3.0.0 - version: link:../../../../../common/tools/eslint-plugin-azure-sdk - '@azure/identity': - specifier: ^4.8.0 - version: link:../../../../identity/identity - '@types/node': - specifier: ^18.0.0 - version: 18.19.123 - '@vitest/browser': - specifier: ^3.0.9 - version: 3.2.4(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(playwright@1.55.0)(vite@7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4) - '@vitest/coverage-istanbul': - specifier: ^3.0.9 - version: 3.2.4(vitest@3.2.4) - dotenv: - specifier: ^16.0.0 - version: 16.6.1 - eslint: - specifier: ^9.9.0 - version: 9.34.0 - playwright: - specifier: ^1.51.1 - version: 1.55.0 - typescript: - specifier: ~5.8.2 - version: 5.8.3 - vitest: - specifier: ^3.0.9 - version: 3.2.4(@types/debug@4.1.12)(@types/node@18.19.123)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) - sdk/contentsafety/ai-content-safety-rest: dependencies: '@azure-rest/core-client': @@ -28207,14 +28143,6 @@ packages: resolution: {integrity: sha512-KMVIPxG6ygcQ1M2hKHahF7eddKejYsWTjoLIfTWiqnaj42dBkYzj4+S8rK9xxmlOaEHKZHcMrRbm0NfN4kgwHw==} engines: {node: '>=20.0.0'} - '@azure-tools/test-recorder@4.1.0': - resolution: {integrity: sha512-0E+ZQ3DCx61K4Hrf5e8DhQCAQBJjZKWMGGo2a4vF195tx4d5LuKqMgYq2LUk9thUP4g8UoyG/npRQstrjhWxdQ==} - engines: {node: '>=20.0.0'} - - '@azure-tools/test-utils-vitest@1.0.0': - resolution: {integrity: sha512-Uk66mlqDyp4Yj3AUpYxvuONIvolWfU+lmKyvxjciTeuaIyjsSklL9OgplJ0Cyi6jYa4kMSl0ZQiuOSIqk+R+Mw==} - engines: {node: '>=20.0.0'} - '@azure/abort-controller@1.1.0': resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==} engines: {node: '>=12.0.0'} @@ -28223,6 +28151,9 @@ packages: resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} engines: {node: '>=18.0.0'} + '@azure/app-configuration-provider@2.2.0': + resolution: {integrity: sha512-bcLtlREDWS+3CrW6ZnwGxAU9M8wV7nkO+00TU1ceOOxGsV2fVQopwhd1SF5XnS6gz0ukPaUi2t9VzrF3KLl18g==} + '@azure/app-configuration@1.8.0': resolution: {integrity: sha512-RO4IGZMa3hI1yVhvb5rPr+r+UDxe4VDxbntFZIc5fsUPGqZbKzmGR2wABEtlrC2SU5YX6tL+NS3xWb4vf1M9lQ==} engines: {node: '>=18.0.0'} @@ -28346,6 +28277,10 @@ packages: resolution: {integrity: sha512-eDT7iXoBTRZ2n3fLiftuGJFD+yjkiB1GNqzU2KbY1TLYeXeSPVTVgn2eJ5vmRTZ11978jy2Kg2wI7xa9Tyr8ag==} engines: {node: '>=18.0.0'} + '@azure/keyvault-secrets@4.10.0': + resolution: {integrity: sha512-WvXc3h2hqHL1pMzUU7ANE2RBKoxjK3JQc0YNn6GUFvOWQtf2ZR+sH4/5cZu8zAg62v9qLCduBN7065nHKl+AOA==} + engines: {node: '>=18.0.0'} + '@azure/logger@1.3.0': resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==} engines: {node: '>=20.0.0'} @@ -29087,6 +29022,9 @@ packages: '@microsoft/applicationinsights-web-snippet@1.2.2': resolution: {integrity: sha512-pIa6QiUaenVlKzNJ9PYMgHDm4PfIJjm5zW3Vq//xsSkRerNlFfcv7dJKHGtX7kYPlSeMRFwld303bwIoUijehQ==} + '@microsoft/feature-management@2.2.0': + resolution: {integrity: sha512-R21l0GM9+eyw+qFOtSsB8REhC02MmE4xw5UGPBOpPil3cTDltVYTe11MmhFkqLFrDs9tLNBpvCouYrQoU4Uwrg==} + '@microsoft/tsdoc-config@0.17.1': resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} @@ -31444,6 +31382,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -33200,46 +33141,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@azure-tools/test-recorder@4.1.0': - dependencies: - '@azure/core-auth': 1.10.0 - '@azure/core-client': 1.10.0 - '@azure/core-rest-pipeline': 1.22.0 - '@azure/core-util': 1.13.0 - '@azure/logger': 1.3.0 - transitivePeerDependencies: - - supports-color - - '@azure-tools/test-utils-vitest@1.0.0(@types/debug@4.1.12)(@types/node@18.19.123)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1)': - dependencies: - '@azure-tools/test-recorder': 4.1.0 - '@azure/abort-controller': 2.1.2 - '@azure/core-rest-pipeline': 1.22.0 - '@azure/core-tracing': 1.3.0 - '@opentelemetry/api': 1.9.0 - tslib: 2.8.1 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@18.19.123)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - - '@types/node' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - '@azure/abort-controller@1.1.0': dependencies: tslib: 2.8.1 @@ -33248,6 +33149,15 @@ snapshots: dependencies: tslib: 2.8.1 + '@azure/app-configuration-provider@2.2.0': + dependencies: + '@azure/app-configuration': 1.8.0 + '@azure/identity': 4.11.1 + '@azure/keyvault-secrets': 4.10.0 + jsonc-parser: 3.3.1 + transitivePeerDependencies: + - supports-color + '@azure/app-configuration@1.8.0': dependencies: '@azure/abort-controller': 2.1.2 @@ -33596,6 +33506,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@azure/keyvault-secrets@4.10.0': + dependencies: + '@azure-rest/core-client': 2.5.0 + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.0 + '@azure/core-http-compat': 2.3.0 + '@azure/core-lro': 2.7.2 + '@azure/core-paging': 1.6.2 + '@azure/core-rest-pipeline': 1.22.0 + '@azure/core-tracing': 1.3.0 + '@azure/core-util': 1.13.0 + '@azure/keyvault-common': 2.0.0 + '@azure/logger': 1.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + '@azure/logger@1.3.0': dependencies: '@typespec/ts-http-runtime': 0.3.0 @@ -34263,14 +34190,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/confirm@5.1.16(@types/node@18.19.123)': - dependencies: - '@inquirer/core': 10.2.0(@types/node@18.19.123) - '@inquirer/type': 3.0.8(@types/node@18.19.123) - optionalDependencies: - '@types/node': 18.19.123 - optional: true - '@inquirer/confirm@5.1.16(@types/node@20.19.11)': dependencies: '@inquirer/core': 10.2.0(@types/node@20.19.11) @@ -34279,20 +34198,6 @@ snapshots: '@types/node': 20.19.11 optional: true - '@inquirer/core@10.2.0(@types/node@18.19.123)': - dependencies: - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@18.19.123) - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 18.19.123 - optional: true - '@inquirer/core@10.2.0(@types/node@20.19.11)': dependencies: '@inquirer/figures': 1.0.13 @@ -34309,11 +34214,6 @@ snapshots: '@inquirer/figures@1.0.13': {} - '@inquirer/type@3.0.8(@types/node@18.19.123)': - optionalDependencies: - '@types/node': 18.19.123 - optional: true - '@inquirer/type@3.0.8(@types/node@20.19.11)': optionalDependencies: '@types/node': 20.19.11 @@ -34433,6 +34333,8 @@ snapshots: '@microsoft/applicationinsights-web-snippet@1.2.2': {} + '@microsoft/feature-management@2.2.0': {} + '@microsoft/tsdoc-config@0.17.1': dependencies: '@microsoft/tsdoc': 0.15.1 @@ -35473,25 +35375,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/browser@3.2.4(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(playwright@1.55.0)(vite@7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)': - dependencies: - '@testing-library/dom': 10.4.1 - '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/mocker': 3.2.4(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(vite@7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1)) - '@vitest/utils': 3.2.4 - magic-string: 0.30.18 - sirv: 3.0.1 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@18.19.123)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) - ws: 8.18.3 - optionalDependencies: - playwright: 1.55.0 - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - '@vitest/browser@3.2.4(msw@2.7.3(@types/node@20.19.11)(typescript@5.8.3))(playwright@1.55.0)(vite@7.1.3(@types/node@20.19.11)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.1 @@ -35535,15 +35418,6 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(vite@7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.18 - optionalDependencies: - msw: 2.7.3(@types/node@18.19.123)(typescript@5.8.3) - vite: 7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) - '@vitest/mocker@3.2.4(msw@2.7.3(@types/node@20.19.11)(typescript@5.8.3))(vite@7.1.3(@types/node@20.19.11)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 @@ -37185,6 +37059,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -37491,32 +37367,6 @@ snapshots: ms@2.1.3: {} - msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3): - dependencies: - '@bundled-es-modules/cookie': 2.0.1 - '@bundled-es-modules/statuses': 1.0.1 - '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.1.16(@types/node@18.19.123) - '@mswjs/interceptors': 0.37.6 - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/until': 2.1.0 - '@types/cookie': 0.6.0 - '@types/statuses': 2.0.6 - graphql: 16.11.0 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - strict-event-emitter: 0.5.1 - type-fest: 4.41.0 - yargs: 17.7.2 - optionalDependencies: - typescript: 5.8.3 - transitivePeerDependencies: - - '@types/node' - optional: true - msw@2.7.3(@types/node@20.19.11)(typescript@5.8.3): dependencies: '@bundled-es-modules/cookie': 2.0.1 @@ -38722,27 +38572,6 @@ snapshots: vary@1.1.2: {} - vite-node@3.2.4(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1): - dependencies: - cac: 6.7.14 - debug: 4.4.1 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-node@3.2.4(@types/node@20.19.11)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1): dependencies: cac: 6.7.14 @@ -38764,21 +38593,6 @@ snapshots: - tsx - yaml - vite@7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1): - dependencies: - esbuild: 0.25.9 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.48.1 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 18.19.123 - fsevents: 2.3.3 - terser: 5.39.0 - tsx: 4.20.5 - yaml: 2.8.1 - vite@7.1.3(@types/node@20.19.11)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1): dependencies: esbuild: 0.25.9 @@ -38794,50 +38608,6 @@ snapshots: tsx: 4.20.5 yaml: 2.8.1 - vitest@3.2.4(@types/debug@4.1.12)(@types/node@18.19.123)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(vite@7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.1 - expect-type: 1.2.2 - magic-string: 0.30.18 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 18.19.123 - '@vitest/browser': 3.2.4(msw@2.7.3(@types/node@18.19.123)(typescript@5.8.3))(playwright@1.55.0)(vite@7.1.3(@types/node@18.19.123)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4) - jsdom: 16.7.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@20.19.11)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 From 9422e401761ed5cdec35e1e8e8d73ac464129507 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 27 Aug 2025 13:12:07 +0800 Subject: [PATCH 3/8] resolve merge conflict --- pnpm-lock.yaml | 60 ++++++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 878c751f0434..5cc62de1014a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19683,7 +19683,7 @@ importers: sdk/playwright/arm-playwright: dependencies: '@azure-rest/core-client': - specifier: ^2.1.0 + specifier: ^2.3.1 version: link:../../core/core-client-rest '@azure/abort-controller': specifier: ^2.1.2 @@ -19692,16 +19692,16 @@ importers: specifier: ^1.9.0 version: link:../../core/core-auth '@azure/core-lro': - specifier: ^3.0.0 + specifier: ^3.1.0 version: link:../../core/core-lro '@azure/core-rest-pipeline': - specifier: ^1.18.2 + specifier: ^1.20.0 version: link:../../core/core-rest-pipeline '@azure/core-util': - specifier: ^1.11.0 + specifier: ^1.12.0 version: link:../../core/core-util '@azure/logger': - specifier: ^1.1.4 + specifier: ^1.2.0 version: link:../../core/logger tslib: specifier: ^2.8.1 @@ -29831,9 +29831,6 @@ packages: '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} - '@types/node@18.19.123': - resolution: {integrity: sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==} - '@types/node@20.19.11': resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==} @@ -32837,9 +32834,6 @@ packages: underscore@1.13.7: resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -35118,11 +35112,11 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/bunyan@1.8.11': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/chai-as-promised@8.0.2': dependencies: @@ -35134,7 +35128,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/cookie@0.6.0': optional: true @@ -35156,7 +35150,7 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 0.17.5 @@ -35188,12 +35182,12 @@ snapshots: '@types/fs-extra@8.1.5': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/http-errors@2.0.5': {} @@ -35204,18 +35198,18 @@ snapshots: '@types/is-buffer@2.0.2': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/json-schema@7.0.15': {} '@types/jsonfile@6.1.4': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/linkify-it@5.0.0': {} @@ -35240,15 +35234,11 @@ snapshots: '@types/mysql@2.15.26': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/node-forge@1.3.14': dependencies: - '@types/node': 18.19.123 - - '@types/node@18.19.123': - dependencies: - undici-types: 5.26.5 + '@types/node': 20.19.11 '@types/node@20.19.11': dependencies: @@ -35264,7 +35254,7 @@ snapshots: '@types/pg@8.6.1': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -35272,7 +35262,7 @@ snapshots: '@types/prompts@2.4.9': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 kleur: 3.0.3 '@types/qs@6.14.0': {} @@ -35288,12 +35278,12 @@ snapshots: '@types/send@0.17.5': dependencies: '@types/mime': 1.3.5 - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/serve-static@1.15.8': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/send': 0.17.5 '@types/shimmer@1.2.0': {} @@ -35303,7 +35293,7 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/tough-cookie@4.0.5': optional: true @@ -35318,11 +35308,11 @@ snapshots: '@types/ws@7.4.7': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/ws@8.18.1': dependencies: - '@types/node': 18.19.123 + '@types/node': 20.19.11 '@types/yargs-parser@21.0.3': {} @@ -37802,7 +37792,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 18.19.123 + '@types/node': 20.19.11 long: 5.3.2 proxy-addr@2.0.7: @@ -38570,8 +38560,6 @@ snapshots: underscore@1.13.7: {} - undici-types@5.26.5: {} - undici-types@6.21.0: {} undici@5.29.0: From 45588374d250f71e73d3cb293c4f47343197bebd Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 27 Aug 2025 13:17:04 +0800 Subject: [PATCH 4/8] revert change --- pnpm-lock.yaml | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cc62de1014a..c8fbe24059d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28213,6 +28213,14 @@ packages: resolution: {integrity: sha512-KMVIPxG6ygcQ1M2hKHahF7eddKejYsWTjoLIfTWiqnaj42dBkYzj4+S8rK9xxmlOaEHKZHcMrRbm0NfN4kgwHw==} engines: {node: '>=20.0.0'} + '@azure-tools/test-recorder@4.1.0': + resolution: {integrity: sha512-0E+ZQ3DCx61K4Hrf5e8DhQCAQBJjZKWMGGo2a4vF195tx4d5LuKqMgYq2LUk9thUP4g8UoyG/npRQstrjhWxdQ==} + engines: {node: '>=20.0.0'} + + '@azure-tools/test-utils-vitest@1.0.0': + resolution: {integrity: sha512-Uk66mlqDyp4Yj3AUpYxvuONIvolWfU+lmKyvxjciTeuaIyjsSklL9OgplJ0Cyi6jYa4kMSl0ZQiuOSIqk+R+Mw==} + engines: {node: '>=20.0.0'} + '@azure/abort-controller@1.1.0': resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==} engines: {node: '>=12.0.0'} @@ -33194,6 +33202,46 @@ snapshots: transitivePeerDependencies: - supports-color + '@azure-tools/test-recorder@4.1.0': + dependencies: + '@azure/core-auth': 1.10.0 + '@azure/core-client': 1.10.0 + '@azure/core-rest-pipeline': 1.22.0 + '@azure/core-util': 1.13.0 + '@azure/logger': 1.3.0 + transitivePeerDependencies: + - supports-color + + '@azure-tools/test-utils-vitest@1.0.0(@types/debug@4.1.12)(@types/node@20.19.11)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@20.19.11)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1)': + dependencies: + '@azure-tools/test-recorder': 4.1.0 + '@azure/abort-controller': 2.1.2 + '@azure/core-rest-pipeline': 1.22.0 + '@azure/core-tracing': 1.3.0 + '@opentelemetry/api': 1.9.0 + tslib: 2.8.1 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(@vitest/browser@3.2.4)(jsdom@16.7.0)(msw@2.7.3(@types/node@20.19.11)(typescript@5.8.3))(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@types/debug' + - '@types/node' + - '@vitest/browser' + - '@vitest/ui' + - happy-dom + - jiti + - jsdom + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + '@azure-rest/core-client@2.5.0': dependencies: '@azure/abort-controller': 2.1.2 From d1f7993c65bd09100c226e4b58fbbfbe57705305 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 27 Aug 2025 13:21:56 +0800 Subject: [PATCH 5/8] revert change --- pnpm-lock.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8fbe24059d4..77218144a4d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33202,6 +33202,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@azure-rest/core-client@2.5.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.0 + '@azure/core-rest-pipeline': 1.22.0 + '@azure/core-tracing': 1.3.0 + '@typespec/ts-http-runtime': 0.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + '@azure-tools/test-recorder@4.1.0': dependencies: '@azure/core-auth': 1.10.0 @@ -33242,17 +33253,6 @@ snapshots: - tsx - yaml - '@azure-rest/core-client@2.5.0': - dependencies: - '@azure/abort-controller': 2.1.2 - '@azure/core-auth': 1.10.0 - '@azure/core-rest-pipeline': 1.22.0 - '@azure/core-tracing': 1.3.0 - '@typespec/ts-http-runtime': 0.3.0 - tslib: 2.8.1 - transitivePeerDependencies: - - supports-color - '@azure/abort-controller@1.1.0': dependencies: tslib: 2.8.1 From 8549b5eba3c9002af1667e7acde74369304e2d34 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 1 Sep 2025 13:47:19 +0800 Subject: [PATCH 6/8] fix format --- .../samples-dev/featureFlag.ts | 23 +++++++++++-------- .../samples-dev/secretReference.ts | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts index ef15739d900e..e8f2b9b8e399 100644 --- a/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts +++ b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts @@ -11,13 +11,17 @@ import { ConfigurationSetting, featureFlagContentType, featureFlagPrefix, - FeatureFlagValue + FeatureFlagValue, } from "@azure/app-configuration"; import { DefaultAzureCredential } from "@azure/identity"; // Use configuration provider and feature management library to consume feature flags import { load } from "@azure/app-configuration-provider"; -import { ConfigurationMapFeatureFlagProvider, FeatureManager, ITargetingContext } from "@microsoft/feature-management"; +import { + ConfigurationMapFeatureFlagProvider, + FeatureManager, + ITargetingContext, +} from "@microsoft/feature-management"; // Load the .env file if it exists import * as dotenv from "dotenv"; @@ -78,14 +82,15 @@ export async function main() { enabled: true, refresh: { enabled: true, - refreshIntervalInMs: 5000 - } - } + refreshIntervalInMs: 5000, + }, + }, }); console.log(`Use feature management library to consume feature flags`); const featureManager = new FeatureManager( - new ConfigurationMapFeatureFlagProvider(appConfigProvider)); + new ConfigurationMapFeatureFlagProvider(appConfigProvider), + ); let isEnabled = await featureManager.isEnabled(featureFlagName); console.log(`Is featureFlag enabled? ${isEnabled}`); @@ -98,14 +103,14 @@ export async function main() { await appConfigClient.setConfigurationSetting(sampleFeatureFlag); // Wait for refresh interval to elapse - await new Promise(resolve => setTimeout(resolve, 5000)); + await new Promise((resolve) => setTimeout(resolve, 5000)); - await appConfigProvider.refresh(); + await appConfigProvider.refresh(); isEnabled = await featureManager.isEnabled(featureFlagName); console.log(`Is featureFlag enabled? ${isEnabled}`); - const targetingContext : ITargetingContext = { userId: "test@contoso.com" }; + const targetingContext: ITargetingContext = { userId: "test@contoso.com" }; isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); diff --git a/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts b/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts index aa885466d291..2a779d2bb40b 100644 --- a/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts +++ b/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts @@ -42,8 +42,8 @@ export async function main() { console.log(`Use configuration provider to load and resolve secret reference`); const appConfigProvider = await load(endpoint, credential, { keyVaultOptions: { - credential: credential - } + credential: credential, + }, }); console.log(`Secret value: ${appConfigProvider.get(secretName)}`); From 08f04f743e3c9d582dddb7312f1c9f80dfe56a25 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Tue, 16 Sep 2025 11:10:19 +0800 Subject: [PATCH 7/8] update --- .../samples-dev/featureFlag.ts | 31 ++++++++++++------- .../samples/v1/javascript/featureFlag.js | 31 ++++++++++++------- .../samples/v1/typescript/src/featureFlag.ts | 31 ++++++++++++------- 3 files changed, 57 insertions(+), 36 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts index e8f2b9b8e399..a1694064be5e 100644 --- a/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts +++ b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts @@ -82,7 +82,7 @@ export async function main() { enabled: true, refresh: { enabled: true, - refreshIntervalInMs: 5000, + // refreshIntervalInMs: 30_000, // Optional. Default: 30 seconds }, }, }); @@ -95,24 +95,31 @@ export async function main() { let isEnabled = await featureManager.isEnabled(featureFlagName); console.log(`Is featureFlag enabled? ${isEnabled}`); + const targetingContext: ITargetingContext = { userId: "test@contoso.com" }; + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); + console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + console.log(`========> Update the featureFlag <======== `); + // Update the feature flag to be enabled sampleFeatureFlag.value.enabled = true; // Updating the config setting await appConfigClient.setConfigurationSetting(sampleFeatureFlag); - // Wait for refresh interval to elapse - await new Promise((resolve) => setTimeout(resolve, 5000)); - - await appConfigProvider.refresh(); - - isEnabled = await featureManager.isEnabled(featureFlagName); - console.log(`Is featureFlag enabled? ${isEnabled}`); - - const targetingContext: ITargetingContext = { userId: "test@contoso.com" }; - isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); - console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + while (!isEnabled) { + console.log("Waiting for feature flag to be refreshed..."); + // Waiting for refresh interval to elapse + await new Promise((resolve) => setTimeout(resolve, 10_000)); + await appConfigProvider.refresh(); + + // The feature flag will not be enabled for everyone as targeting filter is configured + isEnabled = await featureManager.isEnabled(featureFlagName); + console.log(`Is featureFlag enabled? ${isEnabled}`); + + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); + console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + } await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient); } diff --git a/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js b/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js index cddae312fc99..1d2e415814bc 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js +++ b/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js @@ -70,7 +70,7 @@ async function main() { enabled: true, refresh: { enabled: true, - refreshIntervalInMs: 5000 + // refreshIntervalInMs: 30_000, // Optional. Default: 30 seconds } } }); @@ -82,24 +82,31 @@ async function main() { let isEnabled = await featureManager.isEnabled(featureFlagName); console.log(`Is featureFlag enabled? ${isEnabled}`); + const targetingContext: ITargetingContext = { userId: "test@contoso.com" }; + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); + console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + console.log(`========> Update the featureFlag <======== `); + // Update the feature flag to be enabled sampleFeatureFlag.value.enabled = true; // Updating the config setting await appConfigClient.setConfigurationSetting(sampleFeatureFlag); - // Wait for refresh interval to elapse - await new Promise(resolve => setTimeout(resolve, 5000)); - - await appConfigProvider.refresh(); - - isEnabled = await featureManager.isEnabled(featureFlagName); - console.log(`Is featureFlag enabled? ${isEnabled}`); - - const targetingContext : ITargetingContext = { userId: "test@contoso.com" }; - isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); - console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + while (!isEnabled) { + console.log("Waiting for feature flag to be refreshed..."); + // Waiting for refresh interval to elapse + await new Promise((resolve) => setTimeout(resolve, 10_000)); + await appConfigProvider.refresh(); + + // The feature flag will not be enabled for everyone as targeting filter is configured + isEnabled = await featureManager.isEnabled(featureFlagName); + console.log(`Is featureFlag enabled? ${isEnabled}`); + + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); + console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + } await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient); } diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts index eefe83e9b0bb..686c4a66a41c 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts @@ -76,7 +76,7 @@ export async function main() { enabled: true, refresh: { enabled: true, - refreshIntervalInMs: 5000 + // refreshIntervalInMs: 30_000, // Optional. Default: 30 seconds } } }); @@ -88,24 +88,31 @@ export async function main() { let isEnabled = await featureManager.isEnabled(featureFlagName); console.log(`Is featureFlag enabled? ${isEnabled}`); + const targetingContext: ITargetingContext = { userId: "test@contoso.com" }; + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); + console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + console.log(`========> Update the featureFlag <======== `); + // Update the feature flag to be enabled sampleFeatureFlag.value.enabled = true; // Updating the config setting await appConfigClient.setConfigurationSetting(sampleFeatureFlag); - // Wait for refresh interval to elapse - await new Promise(resolve => setTimeout(resolve, 5000)); - - await appConfigProvider.refresh(); - - isEnabled = await featureManager.isEnabled(featureFlagName); - console.log(`Is featureFlag enabled? ${isEnabled}`); - - const targetingContext : ITargetingContext = { userId: "test@contoso.com" }; - isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); - console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + while (!isEnabled) { + console.log("Waiting for feature flag to be refreshed..."); + // Waiting for refresh interval to elapse + await new Promise((resolve) => setTimeout(resolve, 10_000)); + await appConfigProvider.refresh(); + + // The feature flag will not be enabled for everyone as targeting filter is configured + isEnabled = await featureManager.isEnabled(featureFlagName); + console.log(`Is featureFlag enabled? ${isEnabled}`); + + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); + console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); + } await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient); } From 8a517d379492754408c3f2cb4bfd31b640359c00 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Tue, 16 Sep 2025 12:43:10 +0800 Subject: [PATCH 8/8] fix lint --- .../app-configuration/samples-dev/featureFlag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts index a1694064be5e..f22e6d8d5c54 100644 --- a/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts +++ b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts @@ -116,7 +116,7 @@ export async function main() { // The feature flag will not be enabled for everyone as targeting filter is configured isEnabled = await featureManager.isEnabled(featureFlagName); console.log(`Is featureFlag enabled? ${isEnabled}`); - + isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext); console.log(`Is featureFlag enabled for test@contoso.com? ${isEnabled}`); }