// Copyright (c) 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. const fs = require('fs'); const http = require('http'); const path = require('path'); const parseURL = require('url').parse; const {argv} = require('yargs'); const serverPort = parseInt(process.env.PORT, 10) || 8090; const target = argv.target || 'Default'; const devtoolsFrontendFolder = path.resolve(path.join(__dirname, '..', '..', 'out', target, 'gen', 'front_end')); if (!fs.existsSync(devtoolsFrontendFolder)) { console.error(`ERROR: Generated front_end folder (${devtoolsFrontendFolder}) does not exist.`); console.log( 'The components server works from the built Ninja output; you may need to run Ninja to update your built DevTools.'); console.log('If you build to a target other than default, you need to pass --target=X as an argument'); process.exit(1); } http.createServer(requestHandler).listen(serverPort); console.log(`Started components server at http://localhost:${serverPort}\n`); function createComponentIndexFile(componentPath, componentExamples) { const componentName = componentPath.replace('/', '').replace('_', ' '); // clang-format off return ` DevTools component: ${componentName}

${componentName}

${componentExamples.map(example => { const fullPath = path.join('component_docs', componentPath, example); return `

${example}

`; }).join('\n')} `; // clang-format on } function createServerIndexFile(componentNames) { // clang-format off return ` DevTools components

DevTools components

`; // clang-format on } async function getExamplesForPath(filePath) { const componentDirectory = path.join(devtoolsFrontendFolder, 'component_docs', filePath); const contents = await fs.promises.readdir(componentDirectory); return createComponentIndexFile(filePath, contents); } function respondWithHtml(response, html) { response.setHeader('Content-Type', 'text/html; charset=utf-8'); response.writeHead(200); response.write(html, 'utf8'); response.end(); } function send404(response, message) { response.writeHead(404); response.write(message, 'utf8'); response.end(); } async function checkFileExists(filePath) { try { const errorsAccessingFile = await fs.promises.access(filePath, fs.constants.R_OK); return !errorsAccessingFile; } catch (e) { return false; } } async function requestHandler(request, response) { const filePath = parseURL(request.url).pathname; if (filePath === '/favicon.ico') { send404(response, '404, no favicon'); return; } if (filePath === '/' || filePath === '/index.html') { const components = await fs.promises.readdir(path.join(devtoolsFrontendFolder, 'component_docs')); const html = createServerIndexFile(components); respondWithHtml(response, html); } else if (path.extname(filePath) === '') { // This means it's a component path like /breadcrumbs. const componentHtml = await getExamplesForPath(filePath); respondWithHtml(response, componentHtml); } else { // This means it's an asset like a JS file. const fullPath = path.join(devtoolsFrontendFolder, filePath); if (!fullPath.startsWith(devtoolsFrontendFolder)) { console.error(`Path ${fullPath} is outside the DevTools Frontend root dir.`); process.exit(1); } const fileExists = await checkFileExists(fullPath); if (!fileExists) { send404(response, '404, File not found'); return; } let encoding = 'utf8'; if (fullPath.endsWith('.wasm') || fullPath.endsWith('.png') || fullPath.endsWith('.jpg')) { encoding = 'binary'; } const fileContents = await fs.promises.readFile(fullPath, encoding); encoding = 'utf8'; if (fullPath.endsWith('.js')) { response.setHeader('Content-Type', 'text/javascript; charset=utf-8'); } else if (fullPath.endsWith('.css')) { response.setHeader('Content-Type', 'text/css; charset=utf-8'); } else if (fullPath.endsWith('.wasm')) { response.setHeader('Content-Type', 'application/wasm'); encoding = 'binary'; } else if (fullPath.endsWith('.svg')) { response.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); } else if (fullPath.endsWith('.png')) { response.setHeader('Content-Type', 'image/png'); encoding = 'binary'; } else if (fullPath.endsWith('.jpg')) { response.setHeader('Content-Type', 'image/jpg'); encoding = 'binary'; } response.writeHead(200); response.write(fileContents, encoding); response.end(); } }