153 lines · 5.2 KB · JavaScript
RawDownload
#!/usr/bin/env node import fs from "node:fs/promises";import os from "node:os";import path from "node:path";import vm from "node:vm";import { execFile } from "node:child_process";import { promisify } from "node:util"; const execFileAsync = promisify(execFile); const VERSION = process.argv[2] ?? "12.17.0";const VERSION_TAG = `v${VERSION}`;const VSIX_URL = `https://open-vsx.org/api/vscode-icons-team/vscode-icons/${VERSION}/file/vscode-icons-team.vscode-icons-${VERSION}.vsix`;const LANGUAGES_URL = `https://raw.githubusercontent.com/vscode-icons/vscode-icons/${VERSION_TAG}/src/iconsManifest/languages.ts`; const REPO_ROOT = process.cwd();const MANIFEST_PATH = path.join(REPO_ROOT, "apps/web/src/vscode-icons-manifest.json");const ASSOCIATIONS_PATH = path.join(  REPO_ROOT,  "apps/web/src/vscode-icons-language-associations.json",); function normalizeExtension(value) {  return value.trim().toLowerCase().replace(/^\./, "");} function normalizeFileName(value) {  return value.trim().toLowerCase();} function putIfAbsent(target, key, value) {  if (!(key in target)) {    target[key] = value;  }} async function downloadVsix(tmpDir) {  const vsixPath = path.join(tmpDir, `vscode-icons-${VERSION}.vsix`);  const response = await fetch(VSIX_URL);  if (!response.ok) {    throw new Error(`Failed to download VSIX: ${response.status} ${response.statusText}`);  }  const bytes = Buffer.from(await response.arrayBuffer());  await fs.writeFile(vsixPath, bytes);  return vsixPath;} async function extractManifestFromVsix(vsixPath) {  const { stdout } = await execFileAsync("unzip", [    "-p",    vsixPath,    "extension/dist/src/vsicons-icon-theme.json",  ]);  if (!stdout || stdout.trim().length === 0) {    throw new Error("Could not extract vsicons-icon-theme.json from VSIX");  }  return JSON.parse(stdout);} async function loadLanguagesCollection() {  const response = await fetch(LANGUAGES_URL);  if (!response.ok) {    throw new Error(`Failed to fetch languages.ts: ${response.status} ${response.statusText}`);  }  const rawSource = await response.text();  const source = rawSource    .replace(/^import[^;]+;\s*/gm, "")    .replace(/export const languages(?:\s*:\s*[^=]+)?\s*=/, "const languages =")    .replace(/\}\s*satisfies\s*Record<[^;]+>;/, "};");   const context = {};  vm.createContext(context);  vm.runInContext(`${source}\n;globalThis.__languages = languages;`, context);  const languages = context.__languages;  if (!languages || typeof languages !== "object") {    throw new Error("Failed to parse languages.ts into a collection");  }  return languages;} function buildLanguageAssociations(manifest, languages) {  const availableLanguageIds = new Set(Object.keys(manifest.languageIds ?? {}));  const extensionToLanguageId = {};  const fileNameToLanguageId = {};   for (const entry of Object.values(languages)) {    if (!entry || typeof entry !== "object") continue;    const idsRaw = entry.ids;    const ids = Array.isArray(idsRaw) ? idsRaw : [idsRaw];    const knownExtensions = Array.isArray(entry.knownExtensions) ? entry.knownExtensions : [];    const knownFilenames = Array.isArray(entry.knownFilenames) ? entry.knownFilenames : [];     for (const idValue of ids) {      if (typeof idValue !== "string") continue;      const languageId = idValue.trim();      if (languageId.length === 0) continue;      if (!availableLanguageIds.has(languageId)) continue;       for (const extensionValue of knownExtensions) {        if (typeof extensionValue !== "string") continue;        const extension = normalizeExtension(extensionValue);        if (extension.length === 0) continue;        putIfAbsent(extensionToLanguageId, extension, languageId);      }       for (const fileNameValue of knownFilenames) {        if (typeof fileNameValue !== "string") continue;        const fileName = normalizeFileName(fileNameValue);        if (fileName.length === 0) continue;        putIfAbsent(fileNameToLanguageId, fileName, languageId);      }    }  }   return {    version: VERSION,    source: LANGUAGES_URL,    generatedAt: new Date().toISOString(),    extensionToLanguageId,    fileNameToLanguageId,  };} async function main() {  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "t3-vscode-icons-sync-"));  try {    const vsixPath = await downloadVsix(tmpDir);    const manifest = await extractManifestFromVsix(vsixPath);    const languages = await loadLanguagesCollection();    const associations = buildLanguageAssociations(manifest, languages);     await fs.writeFile(MANIFEST_PATH, `${JSON.stringify(manifest)}\n`, "utf8");    await fs.writeFile(ASSOCIATIONS_PATH, `${JSON.stringify(associations)}\n`, "utf8");     process.stdout.write(      [        `Synced vscode-icons ${VERSION}`,        `manifest: ${MANIFEST_PATH}`,        `language associations: ${ASSOCIATIONS_PATH}`,        `extension mappings: ${Object.keys(associations.extensionToLanguageId).length}`,        `filename mappings: ${Object.keys(associations.fileNameToLanguageId).length}`,      ].join("\n") + "\n",    );  } finally {    await fs.rm(tmpDir, { recursive: true, force: true });  }} main().catch((error) => {  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);  process.exit(1);});