UNPKG

next

Version:

The React Framework

165 lines (164 loc) • 6.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "uploadTraceToBlob", { enumerable: true, get: function() { return uploadTraceToBlob; } }); const _path = /*#__PURE__*/ _interop_require_default(require("path")); const _promises = /*#__PURE__*/ _interop_require_default(require("fs/promises")); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const UPLOAD_TRACE_URL = 'https://nextjs.org/api/upload-trace'; // V8 CPU profiles are JSON objects starting with {"nodes": const CPUPROFILE_HEADER = Buffer.from('{"nodes":'); // Turbopack trace files start with this magic header (written by trace_writer.rs) const TURBOPACK_TRACE_HEADER = Buffer.from('TRACEv0'); const PROGRESS_CHUNK_SIZE = 64 * 1024 // 64 KB ; function getUploadUrl() { return process.env.__NEXT_UPLOAD_TRACE_URL_OVERRIDE || UPLOAD_TRACE_URL; } function formatBytes(bytes) { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } function renderProgressBar(current, total) { const width = 30; const ratio = Math.min(current / total, 1); const filled = Math.round(width * ratio); const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(width - filled); const percent = (ratio * 100).toFixed(0).padStart(3); return ` [${bar}] ${percent}% ${formatBytes(current)}/${formatBytes(total)}`; } function createProgressStream(content, onProgress) { let offset = 0; return new ReadableStream({ pull (controller) { if (offset >= content.length) { controller.close(); return; } const end = Math.min(offset + PROGRESS_CHUNK_SIZE, content.length); controller.enqueue(content.subarray(offset, end)); offset = end; onProgress(offset); } }); } function validateCpuProfile(header, file) { if (header.length < CPUPROFILE_HEADER.length || !header.subarray(0, CPUPROFILE_HEADER.length).equals(CPUPROFILE_HEADER)) { console.error(`Error: ${file} does not appear to be a valid V8 CPU profile.`); process.exit(1); } } function validateTurbopackTrace(header, file) { if (header.length < TURBOPACK_TRACE_HEADER.length || !header.subarray(0, TURBOPACK_TRACE_HEADER.length).equals(TURBOPACK_TRACE_HEADER)) { console.error(`Error: ${file} does not appear to be a valid Turbopack trace (missing TRACEv0 header).`); process.exit(1); } } async function uploadTraceToBlob(options) { const dir = options.directory || process.cwd(); const profilesDir = _path.default.join(dir, '.next-profiles'); let entries; try { entries = await _promises.default.readdir(profilesDir); } catch (err) { if (err.code === 'ENOENT') { console.error(`Error: Profiles directory not found at ${profilesDir}. Run "next build --experimental-cpu-prof" or "next build --internal-trace" first.`); process.exit(1); } throw err; } const uploadableFiles = entries.filter((f)=>f.endsWith('.cpuprofile') || f.endsWith('trace-turbopack.bin')); if (uploadableFiles.length === 0) { console.error(`Error: No profile or trace files found in ${profilesDir}.`); process.exit(1); } const uploadUrl = getUploadUrl(); console.log(`Found ${uploadableFiles.length} file(s) in ${profilesDir}.`); console.log(`Uploading to the Next.js team...`); const { put } = require('next/dist/compiled/@vercel/blob'); let sessionId; let sessionToken; let uploadedCount = 0; for (const file of uploadableFiles){ const filePath = _path.default.join(profilesDir, file); const stat = await _promises.default.stat(filePath); if (stat.size === 0) { console.warn(`Skipping ${file}: file is empty.`); continue; } const fd = await _promises.default.open(filePath, 'r'); const headerBuf = Buffer.alloc(16); await fd.read(headerBuf, 0, 16, 0); await fd.close(); if (file.endsWith('.cpuprofile')) { validateCpuProfile(headerBuf, file); } else if (file.endsWith('trace-turbopack.bin')) { validateTurbopackTrace(headerBuf, file); } const content = await _promises.default.readFile(filePath); const tokenRes = await fetch(uploadUrl, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ filename: file, ...sessionId && sessionToken ? { sessionId, sessionToken } : {} }) }); if (!tokenRes.ok) { console.error(`Error: Failed to get upload token for ${file} (${tokenRes.status} ${tokenRes.statusText})`); process.exit(1); } const tokenBody = await tokenRes.json(); if (!tokenBody.clientToken || !tokenBody.pathname) { console.error('Error: Invalid response from the upload endpoint.'); process.exit(1); } if (!sessionId) { sessionId = tokenBody.sessionId; sessionToken = tokenBody.sessionToken; } const totalSize = content.length; if (process.stdout.isTTY) { const stream = createProgressStream(content, (bytesRead)=>{ process.stdout.write(`\r${renderProgressBar(bytesRead, totalSize)}`); }); await put(tokenBody.pathname, stream, { access: 'private', token: tokenBody.clientToken }); process.stdout.write('\r' + ' '.repeat(80) + '\r'); } else { await put(tokenBody.pathname, content, { access: 'private', token: tokenBody.clientToken }); } uploadedCount++; console.log(`Uploaded ${file} (${formatBytes(totalSize)})`); } if (uploadedCount === 0) { console.error('Error: No files were uploaded (all candidates were empty).'); process.exit(1); } if (sessionId) { console.log(`\nUpload session: ${sessionId}`); } console.log('All files uploaded successfully.'); } //# sourceMappingURL=upload-trace.js.map