UNPKG

@nx/web

Version:

The Nx Plugin for Web Components contains generators for managing Web Component applications and libraries within an Nx workspace. It provides: - Integration with libraries such as Jest, Playwright, Cypress, and Storybook. - Scaffolding for creating bu

223 lines (222 loc) • 8.52 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = fileServerExecutor; const internal_1 = require("@nx/devkit/internal"); const child_process_1 = require("child_process"); const pc = require("picocolors"); const devkit_1 = require("@nx/devkit"); const fs_1 = require("fs"); const os_1 = require("os"); const path_1 = require("path"); const package_json_1 = require("nx/src/utils/package-json"); const client_1 = require("nx/src/daemon/client/client"); const utils_1 = require("nx/src/tasks-runner/utils"); const detectPort = require('detect-port'); // platform specific command name const pmCmd = (0, os_1.platform)() === 'win32' ? `npx.cmd` : 'npx'; function getHttpServerArgs(options) { const { buildTarget, parallel, host, proxyUrl, ssl, sslCert, sslKey, proxyOptions, watch, spa, cacheSeconds, ...rest } = options; const args = [`-c${options.cacheSeconds}`]; for (const [key, value] of Object.entries(rest)) { if (typeof value === 'boolean' && value) { args.push(`--${key}`); } else if (typeof value === 'string') { args.push(`--${key}=${value}`); } } if (host) { args.push(`-a=${host}`); } if (ssl) { args.push(`-S`); } if (sslCert) { args.push(`-C=${sslCert}`); } if (sslKey) { args.push(`-K=${sslKey}`); } if (proxyUrl) { args.push(`-P=${proxyUrl}`); } if (proxyOptions) { Object.keys(options.proxyOptions).forEach((key) => { args.push(`--proxy-options.${key}=${options.proxyOptions[key]}`); }); } return args; } function getBuildTargetCommand(options, context) { const target = (0, devkit_1.parseTargetString)(options.buildTarget, context); const cmd = ['nx', 'run']; if (target.configuration) { cmd.push(`${target.project}:${target.target}:${target.configuration}`); } else { cmd.push(`${target.project}:${target.target}`); } if (options.parallel) { cmd.push(`--parallel`); } if (options.maxParallel) { cmd.push(`--maxParallel=${options.maxParallel}`); } return cmd; } function getBuildTargetOutputPath(options, context) { if (options.staticFilePath) { return options.staticFilePath; } let outputPath; try { const target = (0, devkit_1.parseTargetString)(options.buildTarget, context); const buildOptions = (0, devkit_1.readTargetOptions)(target, context); if (buildOptions?.outputPath) { outputPath = buildOptions.outputPath; } else { const project = context.projectGraph.nodes[context.projectName]; const buildTarget = project.data.targets[target.target]; outputPath = buildTarget.outputs?.[0]; if (outputPath) outputPath = (0, utils_1.interpolate)(outputPath, { projectName: project.data.name, projectRoot: project.data.root, }); } } catch (e) { throw new Error(`Invalid buildTarget: ${options.buildTarget}`); } if (!outputPath) { throw new Error(`Unable to get the outputPath from buildTarget ${options.buildTarget}. Make sure ${options.buildTarget} has an outputPath property or manually provide an staticFilePath property`); } return outputPath; } function createFileWatcher(project, changeHandler) { return client_1.daemonClient.registerFileWatcher({ watchProjects: project ? [project] : 'all', includeGlobalWorkspaceFiles: true, includeDependentProjects: true, }, async (error, val) => { if (error === 'closed') { throw new Error('Watch error: Daemon closed the connection'); } else if (error) { throw new Error(`Watch error: ${error?.message ?? 'Unknown'}`); } else if (val?.changedFiles.length > 0) { changeHandler(); } }); } async function* fileServerExecutor(options, context) { if (!options.buildTarget && !options.staticFilePath) { throw new Error("You must set either 'buildTarget' or 'staticFilePath'."); } if (options.watch && !options.buildTarget) { throw new Error("Watch error: You can only specify 'watch' when 'buildTarget' is set."); } let running = false; let disposeWatch; if (options.buildTarget) { const run = () => { if (!running) { running = true; /** * Expose a variable to the build target to know if it's being run by the serve-static executor * This is useful because a config might need to change if it's being run by serve-static without the user's input * or if being ran by another executor (eg. E2E tests) * */ process.env.NX_SERVE_STATIC_BUILD_RUNNING = 'true'; try { const args = getBuildTargetCommand(options, context); (0, child_process_1.execFileSync)(pmCmd, args, { stdio: [0, 1, 2], shell: true, windowsHide: false, }); } catch { throw new Error(`Build target failed: ${pc.bold(options.buildTarget)}`); } finally { process.env.NX_SERVE_STATIC_BUILD_RUNNING = undefined; running = false; } } }; if (!client_1.daemonClient.enabled() && options.watch) { devkit_1.output.warn({ title: 'Nx Daemon is not enabled. Static server is not watching for changes.', }); } if (client_1.daemonClient.enabled() && options.watch) { disposeWatch = await createFileWatcher(context.projectName, run); } // perform initial run run(); } const port = await detectPort(options.port || 8080); const outputPath = getBuildTargetOutputPath(options, context); if (options.spa) { const src = (0, path_1.join)(outputPath, 'index.html'); const dst = (0, path_1.join)(outputPath, '404.html'); // See: https://github.com/http-party/http-server#magic-files (0, fs_1.copyFileSync)(src, dst); // We also need to ensure the proxyUrl is set, otherwise the browser will continue to throw a 404 error // This can cause unexpected behaviors and failures especially in automated test suites options.proxyUrl ??= `http${options.ssl ? 's' : ''}://localhost:${port}?`; } const args = getHttpServerArgs(options); const { path: pathToHttpServerPkgJson, packageJson } = (0, package_json_1.readModulePackageJson)('http-server', module.paths); const pathToHttpServerBin = packageJson.bin['http-server']; const pathToHttpServer = (0, path_1.resolve)(pathToHttpServerPkgJson.replace('package.json', ''), pathToHttpServerBin); // detect port as close to when used to prevent port being used by another process // when running in parallel args.push(`-p=${port}`); const serve = (0, child_process_1.fork)(pathToHttpServer, [outputPath, ...args], { stdio: 'pipe', cwd: context.root, env: { FORCE_COLOR: 'true', ...process.env, }, }); const processExitListener = () => { serve.kill(); if (disposeWatch) { disposeWatch(); } if (options.spa) { (0, fs_1.unlinkSync)((0, path_1.join)(outputPath, '404.html')); } }; process.on('exit', processExitListener); process.on('SIGTERM', processExitListener); serve.stdout.on('data', (chunk) => { if (chunk.toString().indexOf('GET') === -1) { process.stdout.write(chunk); } }); serve.stderr.on('data', (chunk) => { process.stderr.write(chunk); }); yield { success: true, baseUrl: `${options.ssl ? 'https' : 'http'}://${options.host}:${port}`, }; return new Promise((res) => { serve.on('exit', (code, signal) => { if (code === null) code = (0, internal_1.signalToCode)(signal); if (code == 0) { res({ success: true }); } else { res({ success: false }); } }); }); }