mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-02 08:15:56 +01:00
* Don't write ahp commit hash to every file Co-authored-by: Copilot <copilot@github.com> * undo --------- Co-authored-by: Copilot <copilot@github.com>
235 lines
8.4 KiB
TypeScript
235 lines
8.4 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
// Copies type definitions from the sibling `agent-host-protocol` repo into
|
|
// `src/vs/platform/agentHost/common/state/protocol/`. Run via:
|
|
//
|
|
// npx tsx scripts/sync-agent-host-protocol.ts
|
|
//
|
|
// Transformations applied:
|
|
// 1. Converts 2-space indentation to tabs.
|
|
// 2. Merges duplicate imports from the same module.
|
|
// 3. Formats with the project's tsfmt.json settings.
|
|
// 4. Adds Microsoft copyright header.
|
|
//
|
|
// URI stays as `string` (the protocol's canonical representation). VS Code code
|
|
// should call `URI.parse()` at point-of-use where a URI class is needed.
|
|
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { execSync } from 'child_process';
|
|
import * as ts from 'typescript';
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const PROTOCOL_REPO = path.resolve(ROOT, '../agent-host-protocol');
|
|
const TYPES_DIR = path.join(PROTOCOL_REPO, 'types');
|
|
const DEST_DIR = path.join(ROOT, 'src/vs/platform/agentHost/common/state/protocol');
|
|
|
|
// Load tsfmt.json formatting options once
|
|
const TSFMT_PATH = path.join(ROOT, 'tsfmt.json');
|
|
const FORMAT_OPTIONS: ts.FormatCodeSettings = JSON.parse(fs.readFileSync(TSFMT_PATH, 'utf-8'));
|
|
|
|
/**
|
|
* Formats a TypeScript source string using the TypeScript language service
|
|
* formatter with the project's tsfmt.json settings.
|
|
*/
|
|
function formatTypeScript(content: string, fileName: string): string {
|
|
const host: ts.LanguageServiceHost = {
|
|
getCompilationSettings: () => ({}),
|
|
getScriptFileNames: () => [fileName],
|
|
getScriptVersion: () => '1',
|
|
getScriptSnapshot: (name: string) => name === fileName ? ts.ScriptSnapshot.fromString(content) : undefined,
|
|
getCurrentDirectory: () => ROOT,
|
|
getDefaultLibFileName: () => '',
|
|
fileExists: () => false,
|
|
readFile: () => undefined,
|
|
};
|
|
const ls = ts.createLanguageService(host);
|
|
const edits = ls.getFormattingEditsForDocument(fileName, FORMAT_OPTIONS);
|
|
// Apply edits in reverse order to preserve offsets
|
|
for (let i = edits.length - 1; i >= 0; i--) {
|
|
const edit = edits[i];
|
|
content = content.substring(0, edit.span.start) + edit.newText + content.substring(edit.span.start + edit.span.length);
|
|
}
|
|
ls.dispose();
|
|
return content;
|
|
}
|
|
|
|
const COPYRIGHT = `/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/`;
|
|
|
|
const BANNER = '// allow-any-unicode-comment-file\n// DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts';
|
|
|
|
// Files to copy. All go into protocol/.
|
|
const FILES: { src: string; dest: string }[] = [
|
|
{ src: 'state.ts', dest: 'state.ts' },
|
|
{ src: 'actions.ts', dest: 'actions.ts' },
|
|
{ src: 'action-origin.generated.ts', dest: 'action-origin.generated.ts' },
|
|
{ src: 'reducers.ts', dest: 'reducers.ts' },
|
|
{ src: 'commands.ts', dest: 'commands.ts' },
|
|
{ src: 'errors.ts', dest: 'errors.ts' },
|
|
{ src: 'notifications.ts', dest: 'notifications.ts' },
|
|
{ src: 'messages.ts', dest: 'messages.ts' },
|
|
{ src: 'version/registry.ts', dest: 'version/registry.ts' },
|
|
];
|
|
|
|
function getSourceCommitHash(): string {
|
|
try {
|
|
return execSync('git rev-parse --short HEAD', { cwd: PROTOCOL_REPO, encoding: 'utf-8' }).trim();
|
|
} catch {
|
|
return 'unknown';
|
|
}
|
|
}
|
|
|
|
function stripExistingHeader(content: string): string {
|
|
return content.replace(/^\/\*\*?[\s\S]*?\*\/\s*/, '');
|
|
}
|
|
|
|
function convertIndentation(content: string): string {
|
|
const lines = content.split('\n');
|
|
return lines.map(line => {
|
|
const match = line.match(/^( +)/);
|
|
if (!match) {
|
|
return line;
|
|
}
|
|
const spaces = match[1].length;
|
|
const tabs = Math.floor(spaces / 2);
|
|
const remainder = spaces % 2;
|
|
return '\t'.repeat(tabs) + ' '.repeat(remainder) + line.slice(spaces);
|
|
}).join('\n');
|
|
}
|
|
|
|
/**
|
|
* Merges duplicate imports from the same module.
|
|
* Combines `import type { A }` and `import { B }` from the same module into
|
|
* `import { B, type A }` to satisfy the no-duplicate-imports lint rule.
|
|
*/
|
|
function mergeDuplicateImports(content: string): string {
|
|
// Collapse multi-line imports into single lines first
|
|
content = content.replace(/import\s+(type\s+)?\{([^}]+)\}\s+from\s+'([^']+)';/g, (_match, typeKeyword, names, mod) => {
|
|
const collapsed = names.replace(/\s+/g, ' ').trim();
|
|
return typeKeyword ? `import type { ${collapsed} } from '${mod}';` : `import { ${collapsed} } from '${mod}';`;
|
|
});
|
|
|
|
const importsByModule = new Map<string, { typeNames: string[]; valueNames: string[] }>();
|
|
const otherLines: string[] = [];
|
|
const seenModules = new Set<string>();
|
|
|
|
for (const line of content.split('\n')) {
|
|
const typeMatch = line.match(/^import type \{([^}]+)\} from '([^']+)';$/);
|
|
const valueMatch = line.match(/^import \{([^}]+)\} from '([^']+)';$/);
|
|
|
|
if (typeMatch) {
|
|
const [, names, mod] = typeMatch;
|
|
if (!importsByModule.has(mod)) {
|
|
importsByModule.set(mod, { typeNames: [], valueNames: [] });
|
|
}
|
|
importsByModule.get(mod)!.typeNames.push(...names.split(',').map(s => s.trim()).filter(s => s.length > 0));
|
|
if (!seenModules.has(mod)) {
|
|
seenModules.add(mod);
|
|
otherLines.push(`__IMPORT_PLACEHOLDER__${mod}`);
|
|
}
|
|
} else if (valueMatch) {
|
|
const [, names, mod] = valueMatch;
|
|
if (!importsByModule.has(mod)) {
|
|
importsByModule.set(mod, { typeNames: [], valueNames: [] });
|
|
}
|
|
importsByModule.get(mod)!.valueNames.push(...names.split(',').map(s => s.trim()).filter(s => s.length > 0));
|
|
if (!seenModules.has(mod)) {
|
|
seenModules.add(mod);
|
|
otherLines.push(`__IMPORT_PLACEHOLDER__${mod}`);
|
|
}
|
|
} else {
|
|
otherLines.push(line);
|
|
}
|
|
}
|
|
|
|
return otherLines.map(line => {
|
|
if (line.startsWith('__IMPORT_PLACEHOLDER__')) {
|
|
const mod = line.substring('__IMPORT_PLACEHOLDER__'.length);
|
|
const entry = importsByModule.get(mod)!;
|
|
const uniqueTypes = [...new Set(entry.typeNames)];
|
|
const uniqueValues = [...new Set(entry.valueNames)];
|
|
|
|
if (uniqueValues.length > 0 && uniqueTypes.length > 0) {
|
|
const allNames = [...uniqueValues, ...uniqueTypes.map(n => `type ${n}`)];
|
|
return `import { ${allNames.join(', ')} } from '${mod}';`;
|
|
} else if (uniqueValues.length > 0) {
|
|
return `import { ${uniqueValues.join(', ')} } from '${mod}';`;
|
|
} else {
|
|
return `import type { ${uniqueTypes.join(', ')} } from '${mod}';`;
|
|
}
|
|
}
|
|
return line;
|
|
}).join('\n');
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function processFile(src: string, dest: string): void {
|
|
let content = fs.readFileSync(src, 'utf-8');
|
|
content = stripExistingHeader(content);
|
|
|
|
// Merge duplicate imports from the same module
|
|
content = mergeDuplicateImports(content);
|
|
|
|
content = convertIndentation(content);
|
|
content = content.split('\n').map(line => line.trimEnd()).join('\n');
|
|
|
|
const header = `${COPYRIGHT}\n\n${BANNER}\n`;
|
|
content = header + '\n' + content;
|
|
|
|
if (!content.endsWith('\n')) {
|
|
content += '\n';
|
|
}
|
|
|
|
const destPath = path.join(DEST_DIR, dest);
|
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
content = formatTypeScript(content, dest);
|
|
fs.writeFileSync(destPath, content, 'utf-8');
|
|
console.log(` ${dest}`);
|
|
}
|
|
|
|
// ---- Main -------------------------------------------------------------------
|
|
|
|
function main() {
|
|
if (!fs.existsSync(TYPES_DIR)) {
|
|
console.error(`ERROR: Cannot find ${TYPES_DIR}`);
|
|
console.error('Clone agent-host-protocol as a sibling of the VS Code repo:');
|
|
console.error(' git clone git@github.com:microsoft/agent-host-protocol.git ../agent-host-protocol');
|
|
process.exit(1);
|
|
}
|
|
|
|
const commitHash = getSourceCommitHash();
|
|
console.log(`Syncing from agent-host-protocol @ ${commitHash}`);
|
|
console.log(` Source: ${TYPES_DIR}`);
|
|
console.log(` Dest: ${DEST_DIR}`);
|
|
console.log();
|
|
|
|
// Copy protocol files
|
|
for (const file of FILES) {
|
|
const srcPath = path.join(TYPES_DIR, file.src);
|
|
if (!fs.existsSync(srcPath)) {
|
|
console.error(` SKIP (not found): ${file.src}`);
|
|
continue;
|
|
}
|
|
processFile(srcPath, file.dest);
|
|
}
|
|
|
|
// Write the source commit hash to a single version file
|
|
const versionFile = path.join(DEST_DIR, '.ahp-version');
|
|
fs.writeFileSync(versionFile, commitHash + '\n', 'utf-8');
|
|
console.log(` .ahp-version -> ${commitHash}`);
|
|
|
|
console.log();
|
|
console.log('Done.');
|
|
}
|
|
|
|
main();
|