#!/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);});
#!/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);
});