Files
vscode/build/lib/policies/exportPolicyData.ts
Josh Spicer fcbabe4c73 Merge extension-provided policy (#306874)
* Add merge script for extension configuration policies from distro

Adds build/lib/policies/mergeExtensionPolicies.ts which fetches the
distro's product.json (at the commit pinned in package.json) and merges
extensionConfigurationPolicy entries into policyData.jsonc.

The --export-policy-data step only captures policies from the OSS
configuration registry. Extension policies (e.g., Copilot settings)
are defined in vscode-distro's product.json and are not available in
the OSS dev build. This merge script bridges that gap.

Workflow: ./scripts/code.sh --export-policy-data && node build/lib/policies/mergeExtensionPolicies.ts

Also updates:
- policyExport integration test to run the merge step
- add-policy skill documentation with extension policy lifecycle

* Simplify extension policy format: drop localization block from distro

- mergeExtensionPolicies.ts now reads only description/category from
  the distro and synthesizes localization key/value pairs internally
- Integration test gracefully skips merge step when distro is unavailable
- Updated SKILL.md to document the simplified distro format

* docs: use gh auth token in policy export command

* Handle old distro format missing description/category fields

* docs: add troubleshooting section for merge step in policy skill

* Require description and category fields in extension policy entries

* Add extension configuration policies to policyData.jsonc

* Address PR feedback: fix disclaimer, log targetPath, strip GITHUB_TOKEN from test

* fix: revert extension policies from policyData.jsonc, remove console.log from test

Extension policies should only be added after the distro PR merges.
Test runner disallows console output in tests.

* Add mock distro test for mergeExtensionPolicies, remove merge from export test

- mergeExtensionPolicies.ts: add DISTRO_PRODUCT_JSON env var to
  override the distro path (for testing without private repo access)
- New test: creates a mock product.json, runs the merge script,
  verifies policies are added correctly and idempotently
- Export test: no longer runs the merge step (avoids needing
  distro access or GITHUB_TOKEN in CI)

* Update distro and regenerate policyData.jsonc with extension policies

* Fix CI: add extension policy fixture for export test

The export test now runs mergeExtensionPolicies using a checked-in
fixture file instead of requiring distro access or GITHUB_TOKEN.
When extension policies change in the distro, regenerate both
policyData.jsonc and the fixture file.

* Integrate extension policy merge into --export-policy-data

- Removed mergeExtensionPolicies.ts — no separate CLI tool needed
- --export-policy-data now reads build/lib/policies/extensionPolicies.json
  and merges extension policies into the output automatically
- Single command workflow: ./scripts/code.sh --export-policy-data
- Simplified test — no merge step, no GITHUB_TOKEN, no distro access
- Updated SKILL.md to document the simplified workflow

* Dynamic distro reading: --export-policy-data fetches from distro directly

- policyExport.contribution.ts reads distro product.json dynamically:
  1. DISTRO_PRODUCT_JSON env var (for testing)
  2. .build/distro/mixin/stable/product.json (local checkout)
  3. GitHub API with GITHUB_TOKEN (remote fetch)
- New scripts/export-policy-data.sh wrapper: sets up GITHUB_TOKEN
  via gh CLI and runs the export
- Deleted build/lib/policies/extensionPolicies.json (no static copy)
- Test uses DISTRO_PRODUCT_JSON with a fixture file
- Uses sandbox process.env and VSBuffer (renderer-safe)

* Replace bash wrapper with cross-platform TS script

- New build/lib/policies/exportPolicyData.ts: handles transpilation,
  GITHUB_TOKEN setup via gh CLI, and runs --export-policy-data
- Added 'npm run export-policy-data' script to package.json
- Removed scripts/export-policy-data.sh
- Updated SKILL.md and test error message

* Use OAuth device flow for GitHub token acquisition

exportPolicyData.ts now acquires tokens via:
1. GITHUB_TOKEN env var (if set)
2. gh CLI (fast, non-interactive)
3. GitHub OAuth device flow (interactive, no dependencies)

Based on vscode-copilot-chat's getToken.mts pattern.

* Fix error messages to reference npm run export-policy-data

* Fix disclaimer to reference npm run export-policy-data, clean up stale refs

* Clarify test fixture is static and not expected to track distro changes

* Add inline comment in test clarifying fixture is static

* SKILL.md: add step to update distro commit hash in package.json

* Remove inline comment per PR feedback

* Remove local .build/distro/ fallback path per PR feedback
2026-04-01 00:07:47 +00:00

86 lines
3.2 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { execSync, execFileSync } from 'child_process';
import { resolve } from 'path';
const rootPath = resolve(import.meta.dirname, '..', '..', '..');
// VS Code OAuth app client ID (same as the GitHub Authentication extension)
const CLIENT_ID = '01ab8ac9400c4e429b23';
/**
* Acquires a GitHub token via the OAuth device flow.
* Opens the browser for the user to authorize, then polls for the token.
*/
async function acquireTokenViaDeviceFlow(): Promise<string> {
const response1 = await (await fetch('https://github.com/login/device/code', {
method: 'POST',
body: JSON.stringify({ client_id: CLIENT_ID, scope: 'repo' }),
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
})).json() as { user_code: string; device_code: string; verification_uri: string; expires_in: number; interval: number };
console.log(`\n Copy this code: ${response1.user_code}`);
console.log(` Then open: ${response1.verification_uri}`);
console.log(` Waiting for authorization (up to ${response1.expires_in}s)...\n`);
let expiresIn = response1.expires_in;
while (expiresIn > 0) {
await new Promise(resolve => setTimeout(resolve, 1000 * response1.interval));
expiresIn -= response1.interval;
const response2 = await (await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
body: JSON.stringify({
client_id: CLIENT_ID,
device_code: response1.device_code,
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
}),
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
})).json() as { access_token?: string };
if (response2.access_token) {
return response2.access_token;
}
}
throw new Error('Timed out waiting for GitHub authorization');
}
// Ensure sources are transpiled
console.log('Transpiling client sources...');
execSync('npm run transpile-client', { cwd: rootPath, stdio: 'inherit' });
// Set up GITHUB_TOKEN if not already set
if (!process.env['GITHUB_TOKEN'] && !process.env['DISTRO_PRODUCT_JSON']) {
// Try gh CLI first (fast, non-interactive)
let token: string | undefined;
try {
token = execFileSync('gh', ['auth', 'token'], { encoding: 'utf8' }).trim();
console.log('Set GITHUB_TOKEN from gh CLI.');
} catch {
// Fall back to OAuth device flow (interactive)
console.log('gh CLI not available, starting GitHub OAuth device flow...');
token = await acquireTokenViaDeviceFlow();
console.log('GitHub authorization successful.');
}
process.env['GITHUB_TOKEN'] = token;
}
// Run the export
console.log('Exporting policy data...');
const codeScript = process.platform === 'win32'
? resolve(rootPath, 'scripts', 'code.bat')
: resolve(rootPath, 'scripts', 'code.sh');
execSync(`"${codeScript}" --export-policy-data`, {
cwd: rootPath,
stdio: 'inherit',
env: process.env,
});
console.log('\nPolicy data exported to build/lib/policies/policyData.jsonc');