refactor: flatten Microsoft skills from nested to flat directory structure
Rewrote sync_microsoft_skills.py (v4) to use each SKILL.md's frontmatter 'name' field as the flat directory name under skills/, replacing the nested skills/official/microsoft/<lang>/<category>/<service>/ hierarchy. This fixes CI failures caused by the indexing, validation, and catalog scripts expecting skills/<id>/SKILL.md (depth 1). Changes: - Rewrite scripts/sync_microsoft_skills.py for flat output with collision detection - Update scripts/tests/inspect_microsoft_repo.py for flat name mapping - Update scripts/tests/test_comprehensive_coverage.py for name uniqueness checks - Delete skills/official/ nested directory - Add 129 Microsoft skills as flat directories (e.g. skills/azure-mgmt-botservice-dotnet/) - Move attribution files to docs/ (LICENSE-MICROSOFT, microsoft-skills-attribution.json) - Rebuild skills_index.json, CATALOG.md, README.md (845 total skills)
This commit is contained in:
349
skills/azure-appconfiguration-ts/SKILL.md
Normal file
349
skills/azure-appconfiguration-ts/SKILL.md
Normal file
@@ -0,0 +1,349 @@
|
||||
---
|
||||
name: azure-appconfiguration-ts
|
||||
description: Build applications using Azure App Configuration SDK for JavaScript (@azure/app-configuration). Use when working with configuration settings, feature flags, Key Vault references, dynamic refresh, or centralized configuration management.
|
||||
package: @azure/app-configuration
|
||||
---
|
||||
|
||||
# Azure App Configuration SDK for TypeScript
|
||||
|
||||
Centralized configuration management with feature flags and dynamic refresh.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Low-level CRUD SDK
|
||||
npm install @azure/app-configuration @azure/identity
|
||||
|
||||
# High-level provider (recommended for apps)
|
||||
npm install @azure/app-configuration-provider @azure/identity
|
||||
|
||||
# Feature flag management
|
||||
npm install @microsoft/feature-management
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_APPCONFIG_ENDPOINT=https://<your-resource>.azconfig.io
|
||||
# OR
|
||||
AZURE_APPCONFIG_CONNECTION_STRING=Endpoint=https://...;Id=...;Secret=...
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
```typescript
|
||||
import { AppConfigurationClient } from "@azure/app-configuration";
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
|
||||
// DefaultAzureCredential (recommended)
|
||||
const client = new AppConfigurationClient(
|
||||
process.env.AZURE_APPCONFIG_ENDPOINT!,
|
||||
new DefaultAzureCredential()
|
||||
);
|
||||
|
||||
// Connection string
|
||||
const client2 = new AppConfigurationClient(
|
||||
process.env.AZURE_APPCONFIG_CONNECTION_STRING!
|
||||
);
|
||||
```
|
||||
|
||||
## CRUD Operations
|
||||
|
||||
### Create/Update Settings
|
||||
|
||||
```typescript
|
||||
// Add new (fails if exists)
|
||||
await client.addConfigurationSetting({
|
||||
key: "app:settings:message",
|
||||
value: "Hello World",
|
||||
label: "production",
|
||||
contentType: "text/plain",
|
||||
tags: { environment: "prod" },
|
||||
});
|
||||
|
||||
// Set (create or update)
|
||||
await client.setConfigurationSetting({
|
||||
key: "app:settings:message",
|
||||
value: "Updated value",
|
||||
label: "production",
|
||||
});
|
||||
|
||||
// Update with optimistic concurrency
|
||||
const existing = await client.getConfigurationSetting({ key: "myKey" });
|
||||
existing.value = "new value";
|
||||
await client.setConfigurationSetting(existing, { onlyIfUnchanged: true });
|
||||
```
|
||||
|
||||
### Read Settings
|
||||
|
||||
```typescript
|
||||
// Get single setting
|
||||
const setting = await client.getConfigurationSetting({
|
||||
key: "app:settings:message",
|
||||
label: "production", // optional
|
||||
});
|
||||
console.log(setting.value);
|
||||
|
||||
// List with filters
|
||||
const settings = client.listConfigurationSettings({
|
||||
keyFilter: "app:*",
|
||||
labelFilter: "production",
|
||||
});
|
||||
|
||||
for await (const setting of settings) {
|
||||
console.log(`${setting.key}: ${setting.value}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Settings
|
||||
|
||||
```typescript
|
||||
await client.deleteConfigurationSetting({
|
||||
key: "app:settings:message",
|
||||
label: "production",
|
||||
});
|
||||
```
|
||||
|
||||
### Lock/Unlock (Read-Only)
|
||||
|
||||
```typescript
|
||||
// Lock
|
||||
await client.setReadOnly({ key: "myKey", label: "prod" }, true);
|
||||
|
||||
// Unlock
|
||||
await client.setReadOnly({ key: "myKey", label: "prod" }, false);
|
||||
```
|
||||
|
||||
## App Configuration Provider
|
||||
|
||||
### Load Configuration
|
||||
|
||||
```typescript
|
||||
import { load } from "@azure/app-configuration-provider";
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
|
||||
const appConfig = await load(
|
||||
process.env.AZURE_APPCONFIG_ENDPOINT!,
|
||||
new DefaultAzureCredential(),
|
||||
{
|
||||
selectors: [
|
||||
{ keyFilter: "app:*", labelFilter: "production" },
|
||||
],
|
||||
trimKeyPrefixes: ["app:"],
|
||||
}
|
||||
);
|
||||
|
||||
// Map-style access
|
||||
const value = appConfig.get("settings:message");
|
||||
|
||||
// Object-style access
|
||||
const config = appConfig.constructConfigurationObject({ separator: ":" });
|
||||
console.log(config.settings.message);
|
||||
```
|
||||
|
||||
### Dynamic Refresh
|
||||
|
||||
```typescript
|
||||
const appConfig = await load(endpoint, credential, {
|
||||
selectors: [{ keyFilter: "app:*" }],
|
||||
refreshOptions: {
|
||||
enabled: true,
|
||||
refreshIntervalInMs: 30_000, // 30 seconds
|
||||
},
|
||||
});
|
||||
|
||||
// Trigger refresh (non-blocking)
|
||||
appConfig.refresh();
|
||||
|
||||
// Listen for refresh events
|
||||
const disposer = appConfig.onRefresh(() => {
|
||||
console.log("Configuration refreshed!");
|
||||
});
|
||||
|
||||
// Express middleware pattern
|
||||
app.use((req, res, next) => {
|
||||
appConfig.refresh();
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
### Key Vault References
|
||||
|
||||
```typescript
|
||||
const appConfig = await load(endpoint, credential, {
|
||||
selectors: [{ keyFilter: "app:*" }],
|
||||
keyVaultOptions: {
|
||||
credential: new DefaultAzureCredential(),
|
||||
secretRefreshIntervalInMs: 7200_000, // 2 hours
|
||||
},
|
||||
});
|
||||
|
||||
// Secrets are automatically resolved
|
||||
const dbPassword = appConfig.get("database:password");
|
||||
```
|
||||
|
||||
## Feature Flags
|
||||
|
||||
### Create Feature Flag (Low-Level)
|
||||
|
||||
```typescript
|
||||
import {
|
||||
featureFlagPrefix,
|
||||
featureFlagContentType,
|
||||
FeatureFlagValue,
|
||||
ConfigurationSetting,
|
||||
} from "@azure/app-configuration";
|
||||
|
||||
const flag: ConfigurationSetting<FeatureFlagValue> = {
|
||||
key: `${featureFlagPrefix}Beta`,
|
||||
contentType: featureFlagContentType,
|
||||
value: {
|
||||
id: "Beta",
|
||||
enabled: true,
|
||||
description: "Beta feature",
|
||||
conditions: {
|
||||
clientFilters: [
|
||||
{
|
||||
name: "Microsoft.Targeting",
|
||||
parameters: {
|
||||
Audience: {
|
||||
Users: ["user@example.com"],
|
||||
Groups: [{ Name: "beta-testers", RolloutPercentage: 50 }],
|
||||
DefaultRolloutPercentage: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await client.addConfigurationSetting(flag);
|
||||
```
|
||||
|
||||
### Load and Evaluate Feature Flags
|
||||
|
||||
```typescript
|
||||
import { load } from "@azure/app-configuration-provider";
|
||||
import {
|
||||
ConfigurationMapFeatureFlagProvider,
|
||||
FeatureManager,
|
||||
} from "@microsoft/feature-management";
|
||||
|
||||
const appConfig = await load(endpoint, credential, {
|
||||
featureFlagOptions: {
|
||||
enabled: true,
|
||||
selectors: [{ keyFilter: "*" }],
|
||||
refresh: {
|
||||
enabled: true,
|
||||
refreshIntervalInMs: 30_000,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const featureProvider = new ConfigurationMapFeatureFlagProvider(appConfig);
|
||||
const featureManager = new FeatureManager(featureProvider);
|
||||
|
||||
// Simple check
|
||||
const isEnabled = await featureManager.isEnabled("Beta");
|
||||
|
||||
// With targeting context
|
||||
const isEnabledForUser = await featureManager.isEnabled("Beta", {
|
||||
userId: "user@example.com",
|
||||
groups: ["beta-testers"],
|
||||
});
|
||||
```
|
||||
|
||||
## Snapshots
|
||||
|
||||
```typescript
|
||||
// Create snapshot
|
||||
const snapshot = await client.beginCreateSnapshotAndWait({
|
||||
name: "release-v1.0",
|
||||
retentionPeriod: 2592000, // 30 days
|
||||
filters: [{ keyFilter: "app:*", labelFilter: "production" }],
|
||||
});
|
||||
|
||||
// Get snapshot
|
||||
const snap = await client.getSnapshot("release-v1.0");
|
||||
|
||||
// List settings in snapshot
|
||||
const settings = client.listConfigurationSettingsForSnapshot("release-v1.0");
|
||||
for await (const setting of settings) {
|
||||
console.log(`${setting.key}: ${setting.value}`);
|
||||
}
|
||||
|
||||
// Archive/recover
|
||||
await client.archiveSnapshot("release-v1.0");
|
||||
await client.recoverSnapshot("release-v1.0");
|
||||
|
||||
// Load from snapshot (provider)
|
||||
const config = await load(endpoint, credential, {
|
||||
selectors: [{ snapshotName: "release-v1.0" }],
|
||||
});
|
||||
```
|
||||
|
||||
## Labels
|
||||
|
||||
```typescript
|
||||
// Create settings with labels
|
||||
await client.setConfigurationSetting({
|
||||
key: "database:host",
|
||||
value: "dev-db.example.com",
|
||||
label: "development",
|
||||
});
|
||||
|
||||
await client.setConfigurationSetting({
|
||||
key: "database:host",
|
||||
value: "prod-db.example.com",
|
||||
label: "production",
|
||||
});
|
||||
|
||||
// Filter by label
|
||||
const prodSettings = client.listConfigurationSettings({
|
||||
keyFilter: "*",
|
||||
labelFilter: "production",
|
||||
});
|
||||
|
||||
// No label (null label)
|
||||
const noLabelSettings = client.listConfigurationSettings({
|
||||
labelFilter: "\0",
|
||||
});
|
||||
|
||||
// List available labels
|
||||
for await (const label of client.listLabels()) {
|
||||
console.log(label.name);
|
||||
}
|
||||
```
|
||||
|
||||
## Key Types
|
||||
|
||||
```typescript
|
||||
import {
|
||||
AppConfigurationClient,
|
||||
ConfigurationSetting,
|
||||
FeatureFlagValue,
|
||||
SecretReferenceValue,
|
||||
featureFlagPrefix,
|
||||
featureFlagContentType,
|
||||
secretReferenceContentType,
|
||||
ListConfigurationSettingsOptions,
|
||||
} from "@azure/app-configuration";
|
||||
|
||||
import { load } from "@azure/app-configuration-provider";
|
||||
|
||||
import {
|
||||
FeatureManager,
|
||||
ConfigurationMapFeatureFlagProvider,
|
||||
} from "@microsoft/feature-management";
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use provider for apps** - `@azure/app-configuration-provider` for runtime config
|
||||
2. **Use low-level for management** - `@azure/app-configuration` for CRUD operations
|
||||
3. **Enable refresh** - For dynamic configuration updates
|
||||
4. **Use labels** - Separate configurations by environment
|
||||
5. **Use snapshots** - For immutable release configurations
|
||||
6. **Sentinel pattern** - Use a sentinel key to trigger full refresh
|
||||
7. **RBAC roles** - `App Configuration Data Reader` for read-only access
|
||||
Reference in New Issue
Block a user