|
| 1 | +import { createHash } from 'node:crypto'; |
| 2 | +import * as fs from 'node:fs'; |
| 3 | +import { mkdir, readFile, writeFile } from 'node:fs/promises'; |
| 4 | +import * as path from 'node:path'; |
| 5 | +import { fileURLToPath } from 'node:url'; |
| 6 | + |
| 7 | +interface LockItem { |
| 8 | + file: string; |
| 9 | + url: string; |
| 10 | + checksum?: string; |
| 11 | +} |
| 12 | + |
| 13 | +const CHECKSUM_PREFIX = 'sha256-'; |
| 14 | + |
| 15 | +function computeChecksum(content: string | Buffer): string { |
| 16 | + const buffer = typeof content === 'string' ? Buffer.from(content) : content; |
| 17 | + return CHECKSUM_PREFIX + createHash('sha256').update(buffer).digest('hex'); |
| 18 | +} |
| 19 | + |
| 20 | +async function fetchWithChecksum(url: string) { |
| 21 | + console.log(`Downloading ${url}...`); |
| 22 | + const res = await fetch(url); |
| 23 | + if (!res.ok) { |
| 24 | + throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`); |
| 25 | + } |
| 26 | + const content = await res.text(); |
| 27 | + return { content, checksum: computeChecksum(content) }; |
| 28 | +} |
| 29 | + |
| 30 | +async function fileChecksum(filePath: string): Promise<string | null> { |
| 31 | + if (!fs.existsSync(filePath)) { |
| 32 | + return null; |
| 33 | + } |
| 34 | + const content = await readFile(filePath); |
| 35 | + return computeChecksum(content); |
| 36 | +} |
| 37 | + |
| 38 | +async function safeWriteFile(filePath: string, content: string | Buffer) { |
| 39 | + await mkdir(path.dirname(filePath), { recursive: true }); |
| 40 | + await writeFile(filePath, content); |
| 41 | +} |
| 42 | + |
| 43 | +async function processItem(dirPath: string, item: LockItem, update: boolean): Promise<boolean> { |
| 44 | + if (!item.file || !item.url) { |
| 45 | + throw new Error('Lock item must include "file" and "url".'); |
| 46 | + } |
| 47 | + |
| 48 | + const filePath = path.resolve(dirPath, item.file); |
| 49 | + |
| 50 | + if (update || !item.checksum) { |
| 51 | + const { content, checksum } = await fetchWithChecksum(item.url); |
| 52 | + await safeWriteFile(filePath, content); |
| 53 | + const changed = item.checksum !== checksum; |
| 54 | + item.checksum = checksum; |
| 55 | + return changed || update; |
| 56 | + } |
| 57 | + |
| 58 | + const expected = item.checksum; |
| 59 | + const localChecksum = await fileChecksum(filePath); |
| 60 | + if (localChecksum === expected) { |
| 61 | + return false; |
| 62 | + } |
| 63 | + |
| 64 | + const { content, checksum } = await fetchWithChecksum(item.url); |
| 65 | + if (checksum !== expected) { |
| 66 | + throw new Error( |
| 67 | + `Checksum mismatch for ${item.file}. Expected ${expected}, got ${checksum}. |
| 68 | +Please run "pnpm test:prepare -u" to update the lock hash.`, |
| 69 | + ); |
| 70 | + } |
| 71 | + |
| 72 | + if (checksum !== localChecksum) { |
| 73 | + await safeWriteFile(filePath, content); |
| 74 | + } |
| 75 | + |
| 76 | + return false; |
| 77 | +} |
| 78 | + |
| 79 | +const dir = path.dirname(fileURLToPath(import.meta.url)); |
| 80 | +const dirPath = path.resolve(dir, '../tests/embeddedGrammars'); |
| 81 | +const lockPath = path.resolve(dirPath, '_lock.json'); |
| 82 | +const update = !fs.existsSync(lockPath); |
| 83 | +const lock: LockItem[] = update |
| 84 | + ? [ |
| 85 | + { |
| 86 | + 'file': 'css.tmLanguage.json', |
| 87 | + 'url': 'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/css/syntaxes/css.tmLanguage.json', |
| 88 | + }, |
| 89 | + { |
| 90 | + 'file': 'html.tmLanguage.json', |
| 91 | + 'url': 'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/html/syntaxes/html.tmLanguage.json', |
| 92 | + }, |
| 93 | + { |
| 94 | + 'file': 'javascript.tmLanguage.json', |
| 95 | + 'url': |
| 96 | + 'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/javascript/syntaxes/JavaScript.tmLanguage.json', |
| 97 | + }, |
| 98 | + { |
| 99 | + 'file': 'scss.tmLanguage.json', |
| 100 | + 'url': 'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/scss/syntaxes/scss.tmLanguage.json', |
| 101 | + }, |
| 102 | + { |
| 103 | + 'file': 'typescript.tmLanguage.json', |
| 104 | + 'url': |
| 105 | + 'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json', |
| 106 | + }, |
| 107 | + ] |
| 108 | + : JSON.parse(await readFile(lockPath, 'utf8')); |
| 109 | +if (!Array.isArray(lock)) { |
| 110 | + throw new Error('_lock.json must contain an array of lock items.'); |
| 111 | +} |
| 112 | +await Promise.all(lock.map(item => processItem(dirPath, item, update))); |
| 113 | +await mkdir(dirPath, { recursive: true }); |
| 114 | +await writeFile(lockPath, JSON.stringify(lock, null, '\t') + '\n', 'utf8'); |
0 commit comments