import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import { singletonPanel, getToolWebviewStyles, handleClipboardAndOpen } from './shared'; interface FileEntry { filename: string; url: string; description?: string; } const store: { panel: vscode.WebviewPanel | null } = { panel: null }; function gdriveToDirectUrl(url: string): string { const fileMatch = url.match(/\/file\/d\/([a-zA-Z0-9_-]+)/); if (fileMatch) { return `https://drive.google.com/uc?export=download&id=${fileMatch[1]}`; } const idMatch = url.match(/[?&]id=([a-zA-Z0-9_-]+)/); if (idMatch) { return `https://drive.google.com/uc?export=download&id=${idMatch[1]}`; } return url; } async function fetchManifest(url: string): Promise { const res = await fetch(gdriveToDirectUrl(url), { headers: { 'User-Agent': 'Mozilla/5.0' } }); if (!res.ok) { throw new Error(`HTTP ${res.status} ${res.statusText}`); } const json = await res.json() as unknown; if (!Array.isArray(json)) { throw new Error('Manifest must be a JSON array of { filename, url } objects.'); } return (json as FileEntry[]).filter(e => e.filename && e.url); } async function downloadFile(url: string, destPath: string): Promise { const directUrl = gdriveToDirectUrl(url); const res = await fetch(directUrl, { headers: { 'User-Agent': 'Mozilla/5.0' }, redirect: 'follow' }); if (!res.ok) { throw new Error(`HTTP ${res.status} ${res.statusText}`); } const contentType = res.headers.get('content-type') ?? ''; const text = await res.text(); const ext = path.extname(destPath).toLowerCase(); if (contentType.includes('text/html') && ext !== '.html' && ext !== '.htm') { throw new Error( 'Got an HTML page instead of a file — the Google Drive link may require sign-in or a download confirmation. Make sure the file is set to "Anyone with the link can view".' ); } await fs.promises.writeFile(destPath, text, 'utf8'); } function getHtml(): string { return `

Initialize Project

Fetches the shared file list from your team manifest and downloads selected files into the chosen folder.

Files

Download folder

`; } export function openProjectInit(context: vscode.ExtensionContext) { const { panel, isNew } = singletonPanel(store, 'hplToolbox.projectInit', 'Initialize Project'); if (!isNew) { return; } panel.webview.html = getHtml(); async function sendManifest() { const manifestUrl = vscode.workspace.getConfiguration('hplToolbox.projectInit').get('manifestUrl', ''); if (!manifestUrl) { panel.webview.postMessage({ type: 'manifestError', text: 'No manifest URL configured. Click ⚙ to set it in Settings.', }); return; } try { const files = await fetchManifest(manifestUrl); panel.webview.postMessage({ type: 'manifest', files, manifestUrl }); } catch (err: any) { panel.webview.postMessage({ type: 'manifestError', text: `Failed to load manifest: ${err.message}` }); } } panel.webview.onDidReceiveMessage(async (msg) => { if (handleClipboardAndOpen(msg)) { return; } if (msg.type === 'fetchManifest') { await sendManifest(); } if (msg.type === 'openSettings') { vscode.commands.executeCommand('workbench.action.openSettings', 'hplToolbox.projectInit.manifestUrl'); } if (msg.type === 'pickFolder') { const picked = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false, canSelectMany: false, openLabel: 'Select Destination Folder', defaultUri: vscode.workspace.workspaceFolders?.[0]?.uri, }); if (picked?.[0]) { panel.webview.postMessage({ type: 'folder', path: picked[0].fsPath }); } } if (msg.type === 'init') { const files: FileEntry[] = msg.files; const dest: string = msg.destFolder || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || ''; if (!dest) { panel.webview.postMessage({ type: 'result', ok: false, text: 'No destination folder selected and no workspace is open.' }); return; } const errors: string[] = []; for (const f of files) { try { await downloadFile(f.url, path.join(dest, f.filename)); } catch (err: any) { errors.push(`${f.filename}: ${err.message}`); } } if (errors.length) { panel.webview.postMessage({ type: 'result', ok: false, text: `${files.length - errors.length}/${files.length} downloaded.\n\nErrors:\n${errors.join('\n')}` }); } else { panel.webview.postMessage({ type: 'result', ok: true, text: `Done! ${files.length} file(s) downloaded to: ${dest}` }); } } }, undefined, context.subscriptions); }