nx
Version:
447 lines (446 loc) • 23.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.openSockets = void 0;
exports.handleResult = handleResult;
exports.startServer = startServer;
const fs_1 = require("fs");
const net_1 = require("net");
const path_1 = require("path");
const perf_hooks_1 = require("perf_hooks");
const file_hasher_1 = require("../../hasher/file-hasher");
const native_1 = require("../../native");
const consume_messages_from_socket_1 = require("../../utils/consume-messages-from-socket");
const versions_1 = require("../../utils/versions");
const workspace_context_1 = require("../../utils/workspace-context");
const workspace_root_1 = require("../../utils/workspace-root");
const cache_1 = require("../cache");
const is_nx_version_mismatch_1 = require("../is-nx-version-mismatch");
const socket_utils_1 = require("../socket-utils");
const file_watcher_sockets_1 = require("./file-watching/file-watcher-sockets");
const project_graph_listener_sockets_1 = require("./project-graph-listener-sockets");
const handle_hash_tasks_1 = require("./handle-hash-tasks");
const handle_outputs_tracking_1 = require("./handle-outputs-tracking");
const handle_process_in_background_1 = require("./handle-process-in-background");
const handle_request_project_graph_1 = require("./handle-request-project-graph");
const handle_request_shutdown_1 = require("./handle-request-shutdown");
const logger_1 = require("./logger");
const outputs_tracking_1 = require("./outputs-tracking");
const project_graph_incremental_recomputation_1 = require("./project-graph-incremental-recomputation");
const shutdown_utils_1 = require("./shutdown-utils");
const watcher_1 = require("./watcher");
const handle_glob_1 = require("./handle-glob");
const glob_1 = require("../message-types/glob");
const get_nx_workspace_files_1 = require("../message-types/get-nx-workspace-files");
const handle_nx_workspace_files_1 = require("./handle-nx-workspace-files");
const get_context_file_data_1 = require("../message-types/get-context-file-data");
const handle_context_file_data_1 = require("./handle-context-file-data");
const get_files_in_directory_1 = require("../message-types/get-files-in-directory");
const handle_get_files_in_directory_1 = require("./handle-get-files-in-directory");
const hash_glob_1 = require("../message-types/hash-glob");
const handle_hash_glob_1 = require("./handle-hash-glob");
const task_history_1 = require("../message-types/task-history");
const handle_task_history_1 = require("./handle-task-history");
const force_shutdown_1 = require("../message-types/force-shutdown");
const handle_force_shutdown_1 = require("./handle-force-shutdown");
const get_sync_generator_changes_1 = require("../message-types/get-sync-generator-changes");
const handle_get_sync_generator_changes_1 = require("./handle-get-sync-generator-changes");
const sync_generators_1 = require("./sync-generators");
const file_change_events_1 = require("./file-watching/file-change-events");
const get_registered_sync_generators_1 = require("../message-types/get-registered-sync-generators");
const handle_get_registered_sync_generators_1 = require("./handle-get-registered-sync-generators");
const update_workspace_context_1 = require("../message-types/update-workspace-context");
const handle_update_workspace_context_1 = require("./handle-update-workspace-context");
const flush_sync_generator_changes_to_disk_1 = require("../message-types/flush-sync-generator-changes-to-disk");
const handle_flush_sync_generator_changes_to_disk_1 = require("./handle-flush-sync-generator-changes-to-disk");
const run_tasks_execution_hooks_1 = require("../message-types/run-tasks-execution-hooks");
const handle_tasks_execution_hooks_1 = require("./handle-tasks-execution-hooks");
const register_project_graph_listener_1 = require("../message-types/register-project-graph-listener");
const nx_console_1 = require("../message-types/nx-console");
const handle_nx_console_1 = require("./handle-nx-console");
const v8_1 = require("v8");
let performanceObserver;
let workspaceWatcherError;
let outputsWatcherError;
global.NX_DAEMON = true;
let numberOfOpenConnections = 0;
exports.openSockets = new Set();
const server = (0, net_1.createServer)(async (socket) => {
numberOfOpenConnections += 1;
exports.openSockets.add(socket);
logger_1.serverLogger.log(`Established a connection. Number of open connections: ${numberOfOpenConnections}`);
(0, shutdown_utils_1.resetInactivityTimeout)(handleInactivityTimeout);
if (!performanceObserver) {
performanceObserver = new perf_hooks_1.PerformanceObserver((list) => {
const entry = list.getEntries()[0];
logger_1.serverLogger.log(`Time taken for '${entry.name}'`, `${entry.duration}ms`);
});
performanceObserver.observe({ entryTypes: ['measure'] });
}
socket.on('data', (0, consume_messages_from_socket_1.consumeMessagesFromSocket)(async (message) => {
await handleMessage(socket, message);
}));
socket.on('error', (e) => {
logger_1.serverLogger.log('Socket error');
console.error(e);
});
socket.on('close', () => {
numberOfOpenConnections -= 1;
exports.openSockets.delete(socket);
logger_1.serverLogger.log(`Closed a connection. Number of open connections: ${numberOfOpenConnections}`);
(0, file_watcher_sockets_1.removeRegisteredFileWatcherSocket)(socket);
(0, project_graph_listener_sockets_1.removeRegisteredProjectGraphListenerSocket)(socket);
});
});
registerProcessTerminationListeners();
async function handleMessage(socket, data) {
if (workspaceWatcherError) {
await (0, shutdown_utils_1.respondWithErrorAndExit)(socket, `File watcher error in the workspace '${workspace_root_1.workspaceRoot}'.`, workspaceWatcherError);
}
const outdated = daemonIsOutdated();
if (outdated) {
await (0, shutdown_utils_1.respondWithErrorAndExit)(socket, `Daemon outdated`, new Error(outdated));
}
(0, shutdown_utils_1.resetInactivityTimeout)(handleInactivityTimeout);
const unparsedPayload = data;
let payload;
let mode = 'json';
logger_1.serverLogger.log(`Received raw message of length ${unparsedPayload.length}`);
try {
// JSON Message
if ((0, consume_messages_from_socket_1.isJsonMessage)(unparsedPayload)) {
payload = JSON.parse(unparsedPayload);
}
else {
// V8 Serialized Message
payload = (0, v8_1.deserialize)(Buffer.from(unparsedPayload, 'binary'));
mode = 'v8';
}
}
catch (e) {
await (0, shutdown_utils_1.respondWithErrorAndExit)(socket, `Invalid payload from the client`, new Error(`Unsupported payload sent to daemon server: ${unparsedPayload}`));
}
logger_1.serverLogger.log(`Received ${mode} message of type ${payload.type}`);
if (payload.type === 'PING') {
await handleResult(socket, 'PING', () => Promise.resolve({ response: true, description: 'ping' }), mode);
}
else if (payload.type === 'REQUEST_PROJECT_GRAPH') {
await handleResult(socket, 'REQUEST_PROJECT_GRAPH', () => (0, handle_request_project_graph_1.handleRequestProjectGraph)(), mode);
}
else if (payload.type === 'HASH_TASKS') {
await handleResult(socket, 'HASH_TASKS', () => (0, handle_hash_tasks_1.handleHashTasks)(payload), mode);
}
else if (payload.type === 'PROCESS_IN_BACKGROUND') {
await handleResult(socket, 'PROCESS_IN_BACKGROUND', () => (0, handle_process_in_background_1.handleProcessInBackground)(payload), mode);
}
else if (payload.type === 'RECORD_OUTPUTS_HASH') {
await handleResult(socket, 'RECORD_OUTPUTS_HASH', () => (0, handle_outputs_tracking_1.handleRecordOutputsHash)(payload), mode);
}
else if (payload.type === 'OUTPUTS_HASHES_MATCH') {
await handleResult(socket, 'OUTPUTS_HASHES_MATCH', () => (0, handle_outputs_tracking_1.handleOutputsHashesMatch)(payload), mode);
}
else if (payload.type === 'REQUEST_SHUTDOWN') {
await handleResult(socket, 'REQUEST_SHUTDOWN', () => (0, handle_request_shutdown_1.handleRequestShutdown)(server, numberOfOpenConnections), mode);
}
else if (payload.type === 'REGISTER_FILE_WATCHER') {
file_watcher_sockets_1.registeredFileWatcherSockets.push({ socket, config: payload.config });
}
else if ((0, register_project_graph_listener_1.isRegisterProjectGraphListenerMessage)(payload)) {
project_graph_listener_sockets_1.registeredProjectGraphListenerSockets.push(socket);
}
else if ((0, glob_1.isHandleGlobMessage)(payload)) {
await handleResult(socket, glob_1.GLOB, () => (0, handle_glob_1.handleGlob)(payload.globs, payload.exclude), mode);
}
else if ((0, glob_1.isHandleMultiGlobMessage)(payload)) {
await handleResult(socket, glob_1.MULTI_GLOB, () => (0, handle_glob_1.handleMultiGlob)(payload.globs, payload.exclude), mode);
}
else if ((0, get_nx_workspace_files_1.isHandleNxWorkspaceFilesMessage)(payload)) {
await handleResult(socket, get_nx_workspace_files_1.GET_NX_WORKSPACE_FILES, () => (0, handle_nx_workspace_files_1.handleNxWorkspaceFiles)(payload.projectRootMap), mode);
}
else if ((0, get_files_in_directory_1.isHandleGetFilesInDirectoryMessage)(payload)) {
await handleResult(socket, get_files_in_directory_1.GET_FILES_IN_DIRECTORY, () => (0, handle_get_files_in_directory_1.handleGetFilesInDirectory)(payload.dir), mode);
}
else if ((0, get_context_file_data_1.isHandleContextFileDataMessage)(payload)) {
await handleResult(socket, get_context_file_data_1.GET_CONTEXT_FILE_DATA, () => (0, handle_context_file_data_1.handleContextFileData)(), mode);
}
else if ((0, hash_glob_1.isHandleHashGlobMessage)(payload)) {
await handleResult(socket, hash_glob_1.HASH_GLOB, () => (0, handle_hash_glob_1.handleHashGlob)(payload.globs, payload.exclude), mode);
}
else if ((0, hash_glob_1.isHandleHashMultiGlobMessage)(payload)) {
await handleResult(socket, hash_glob_1.HASH_GLOB, () => (0, handle_hash_glob_1.handleHashMultiGlob)(payload.globGroups), mode);
}
else if ((0, task_history_1.isHandleGetFlakyTasksMessage)(payload)) {
await handleResult(socket, task_history_1.GET_FLAKY_TASKS, () => (0, handle_task_history_1.handleGetFlakyTasks)(payload.hashes), mode);
}
else if ((0, task_history_1.isHandleGetEstimatedTaskTimings)(payload)) {
await handleResult(socket, task_history_1.GET_ESTIMATED_TASK_TIMINGS, () => (0, handle_task_history_1.handleGetEstimatedTaskTimings)(payload.targets), mode);
}
else if ((0, task_history_1.isHandleWriteTaskRunsToHistoryMessage)(payload)) {
await handleResult(socket, task_history_1.RECORD_TASK_RUNS, () => (0, handle_task_history_1.handleRecordTaskRuns)(payload.taskRuns), mode);
}
else if ((0, force_shutdown_1.isHandleForceShutdownMessage)(payload)) {
await handleResult(socket, 'FORCE_SHUTDOWN', () => (0, handle_force_shutdown_1.handleForceShutdown)(server), mode);
}
else if ((0, get_sync_generator_changes_1.isHandleGetSyncGeneratorChangesMessage)(payload)) {
await handleResult(socket, get_sync_generator_changes_1.GET_SYNC_GENERATOR_CHANGES, () => (0, handle_get_sync_generator_changes_1.handleGetSyncGeneratorChanges)(payload.generators), mode);
}
else if ((0, flush_sync_generator_changes_to_disk_1.isHandleFlushSyncGeneratorChangesToDiskMessage)(payload)) {
await handleResult(socket, flush_sync_generator_changes_to_disk_1.FLUSH_SYNC_GENERATOR_CHANGES_TO_DISK, () => (0, handle_flush_sync_generator_changes_to_disk_1.handleFlushSyncGeneratorChangesToDisk)(payload.generators), mode);
}
else if ((0, get_registered_sync_generators_1.isHandleGetRegisteredSyncGeneratorsMessage)(payload)) {
await handleResult(socket, get_registered_sync_generators_1.GET_REGISTERED_SYNC_GENERATORS, () => (0, handle_get_registered_sync_generators_1.handleGetRegisteredSyncGenerators)(), mode);
}
else if ((0, update_workspace_context_1.isHandleUpdateWorkspaceContextMessage)(payload)) {
await handleResult(socket, update_workspace_context_1.UPDATE_WORKSPACE_CONTEXT, () => (0, handle_update_workspace_context_1.handleUpdateWorkspaceContext)(payload.createdFiles, payload.updatedFiles, payload.deletedFiles), mode);
}
else if ((0, run_tasks_execution_hooks_1.isHandlePreTasksExecutionMessage)(payload)) {
await handleResult(socket, run_tasks_execution_hooks_1.PRE_TASKS_EXECUTION, () => (0, handle_tasks_execution_hooks_1.handleRunPreTasksExecution)(payload.context), mode);
}
else if ((0, run_tasks_execution_hooks_1.isHandlePostTasksExecutionMessage)(payload)) {
await handleResult(socket, run_tasks_execution_hooks_1.POST_TASKS_EXECUTION, () => (0, handle_tasks_execution_hooks_1.handleRunPostTasksExecution)(payload.context), mode);
}
else if ((0, nx_console_1.isHandleGetNxConsoleStatusMessage)(payload)) {
await handleResult(socket, nx_console_1.GET_NX_CONSOLE_STATUS, () => (0, handle_nx_console_1.handleGetNxConsoleStatus)(), mode);
}
else if ((0, nx_console_1.isHandleSetNxConsolePreferenceAndInstallMessage)(payload)) {
await handleResult(socket, nx_console_1.SET_NX_CONSOLE_PREFERENCE_AND_INSTALL, () => (0, handle_nx_console_1.handleSetNxConsolePreferenceAndInstall)(payload.preference), mode);
}
else {
await (0, shutdown_utils_1.respondWithErrorAndExit)(socket, `Invalid payload from the client`, new Error(`Unsupported payload sent to daemon server: ${unparsedPayload}`));
}
}
async function handleResult(socket, type, hrFn, mode) {
let hr;
const startMark = new Date();
try {
hr = await hrFn();
}
catch (error) {
hr = { description: `[${type}]`, error };
}
const doneHandlingMark = new Date();
if (hr.error) {
await (0, shutdown_utils_1.respondWithErrorAndExit)(socket, hr.description, hr.error);
}
else {
logger_1.serverLogger.log(`Serializing response for ${type} message in ${mode} mode`);
const response = typeof hr.response === 'string'
? hr.response
: serializeUnserializedResult(hr.response, mode);
logger_1.serverLogger.log(`Responding to ${type} message`);
await (0, shutdown_utils_1.respondToClient)(socket, response, hr.description);
}
const endMark = new Date();
logger_1.serverLogger.log(`Handled ${mode} message ${type}. Handling time: ${doneHandlingMark.getTime() - startMark.getTime()}. Response time: ${endMark.getTime() - doneHandlingMark.getTime()}.`);
}
function handleInactivityTimeout() {
if ((0, file_watcher_sockets_1.hasRegisteredFileWatcherSockets)() ||
(0, project_graph_listener_sockets_1.hasRegisteredProjectGraphListenerSockets)()) {
logger_1.serverLogger.log(`There are open file watchers or project graph listeners. Resetting inactivity timer.`);
(0, shutdown_utils_1.resetInactivityTimeout)(handleInactivityTimeout);
}
else {
(0, shutdown_utils_1.handleServerProcessTermination)({
server,
reason: `${shutdown_utils_1.SERVER_INACTIVITY_TIMEOUT_MS}ms of inactivity`,
sockets: exports.openSockets,
});
}
}
function registerProcessTerminationListeners() {
process
.on('SIGINT', () => (0, shutdown_utils_1.handleServerProcessTermination)({
server,
reason: 'received process SIGINT',
sockets: exports.openSockets,
}))
.on('SIGTERM', () => (0, shutdown_utils_1.handleServerProcessTermination)({
server,
reason: 'received process SIGTERM',
sockets: exports.openSockets,
}))
.on('SIGHUP', () => (0, shutdown_utils_1.handleServerProcessTermination)({
server,
reason: 'received process SIGHUP',
sockets: exports.openSockets,
}));
}
let existingLockHash;
function daemonIsOutdated() {
if ((0, is_nx_version_mismatch_1.isNxVersionMismatch)()) {
return 'NX_VERSION_CHANGED';
}
else if (lockFileHashChanged()) {
return 'LOCK_FILES_CHANGED';
}
return null;
}
function lockFileHashChanged() {
const lockHashes = [
(0, path_1.join)(workspace_root_1.workspaceRoot, 'package-lock.json'),
(0, path_1.join)(workspace_root_1.workspaceRoot, 'yarn.lock'),
(0, path_1.join)(workspace_root_1.workspaceRoot, 'pnpm-lock.yaml'),
(0, path_1.join)(workspace_root_1.workspaceRoot, 'bun.lockb'),
(0, path_1.join)(workspace_root_1.workspaceRoot, 'bun.lock'),
]
.filter((file) => (0, fs_1.existsSync)(file))
.map((file) => (0, native_1.hashFile)(file));
const newHash = (0, file_hasher_1.hashArray)(lockHashes);
if (existingLockHash && newHash != existingLockHash) {
existingLockHash = newHash;
return true;
}
else {
existingLockHash = newHash;
return false;
}
}
/**
* When applicable files in the workspaces are changed (created, updated, deleted),
* we need to recompute the cached serialized project graph so that it is readily
* available for the next client request to the server.
*/
const handleWorkspaceChanges = async (err, changeEvents) => {
if (workspaceWatcherError) {
logger_1.serverLogger.watcherLog('Skipping handleWorkspaceChanges because of a previously recorded watcher error.');
return;
}
try {
(0, shutdown_utils_1.resetInactivityTimeout)(handleInactivityTimeout);
const outdatedReason = daemonIsOutdated();
if (outdatedReason) {
await (0, shutdown_utils_1.handleServerProcessTermination)({
server,
reason: outdatedReason,
sockets: exports.openSockets,
});
return;
}
if (err) {
let error = typeof err === 'string' ? new Error(err) : err;
logger_1.serverLogger.watcherLog('Unexpected workspace watcher error', error.message);
console.error(error);
workspaceWatcherError = error;
return;
}
logger_1.serverLogger.watcherLog((0, watcher_1.convertChangeEventsToLogMessage)(changeEvents));
const updatedFilesToHash = [];
const createdFilesToHash = [];
const deletedFiles = [];
for (const event of changeEvents) {
if (event.type === 'delete') {
deletedFiles.push(event.path);
}
else {
try {
const s = (0, fs_1.statSync)((0, path_1.join)(workspace_root_1.workspaceRoot, event.path));
if (s.isFile()) {
if (event.type === 'update') {
updatedFilesToHash.push(event.path);
}
else {
createdFilesToHash.push(event.path);
}
}
}
catch (e) {
// this can happen when the update file was deleted right after
}
}
}
(0, project_graph_incremental_recomputation_1.addUpdatedAndDeletedFiles)(createdFilesToHash, updatedFilesToHash, deletedFiles);
}
catch (err) {
logger_1.serverLogger.watcherLog(`Unexpected workspace error`, err.message);
console.error(err);
workspaceWatcherError = err;
}
};
const handleOutputsChanges = async (err, changeEvents) => {
try {
if (err || !changeEvents || !changeEvents.length) {
let error = typeof err === 'string' ? new Error(err) : err;
logger_1.serverLogger.watcherLog('Unexpected outputs watcher error', error.message);
console.error(error);
outputsWatcherError = error;
(0, outputs_tracking_1.disableOutputsTracking)();
return;
}
if (outputsWatcherError) {
return;
}
logger_1.serverLogger.watcherLog('Processing file changes in outputs');
(0, outputs_tracking_1.processFileChangesInOutputs)(changeEvents);
}
catch (err) {
logger_1.serverLogger.watcherLog(`Unexpected outputs watcher error`, err.message);
console.error(err);
outputsWatcherError = err;
(0, outputs_tracking_1.disableOutputsTracking)();
}
};
async function startServer() {
(0, workspace_context_1.setupWorkspaceContext)(workspace_root_1.workspaceRoot);
const socketPath = (0, socket_utils_1.getFullOsSocketPath)();
// Persist metadata about the background process so that it can be cleaned up later if needed
await (0, cache_1.writeDaemonJsonProcessCache)({
processId: process.pid,
socketPath,
nxVersion: versions_1.nxVersion,
});
// See notes in socket-command-line-utils.ts on OS differences regarding clean up of existings connections.
if (!socket_utils_1.isWindows) {
(0, socket_utils_1.killSocketOrPath)();
}
setInterval(() => {
if ((0, cache_1.getDaemonProcessIdSync)() !== process.pid) {
return (0, shutdown_utils_1.handleServerProcessTermination)({
server,
reason: 'this process is no longer the current daemon (native)',
sockets: exports.openSockets,
});
}
}, 20).unref();
return new Promise(async (resolve, reject) => {
try {
server.listen(socketPath, async () => {
try {
logger_1.serverLogger.log(`Started listening on: ${socketPath}`);
// this triggers the storage of the lock file hash
daemonIsOutdated();
if (!(0, shutdown_utils_1.getWatcherInstance)()) {
(0, shutdown_utils_1.storeWatcherInstance)(await (0, watcher_1.watchWorkspace)(server, handleWorkspaceChanges));
logger_1.serverLogger.watcherLog(`Subscribed to changes within: ${workspace_root_1.workspaceRoot} (native)`);
}
if (!(0, shutdown_utils_1.getOutputWatcherInstance)()) {
(0, shutdown_utils_1.storeOutputWatcherInstance)(await (0, watcher_1.watchOutputFiles)(server, handleOutputsChanges));
}
// listen for project graph recomputation events to collect and schedule sync generators
(0, project_graph_incremental_recomputation_1.registerProjectGraphRecomputationListener)(sync_generators_1.collectAndScheduleSyncGenerators);
// register file change listener to invalidate sync generator cache
(0, file_change_events_1.registerFileChangeListener)(sync_generators_1.clearSyncGeneratorsCache);
// trigger an initial project graph recomputation
(0, project_graph_incremental_recomputation_1.addUpdatedAndDeletedFiles)([], [], []);
// Kick off Nx Console check in background to prime the cache
(0, handle_nx_console_1.handleGetNxConsoleStatus)().catch(() => {
// Ignore errors, this is a background operation
});
return resolve(server);
}
catch (err) {
await handleWorkspaceChanges(err, []);
}
});
}
catch (err) {
reject(err);
}
});
}
function serializeUnserializedResult(response, mode) {
if (mode === 'json') {
return JSON.stringify(response);
}
else {
return (0, v8_1.serialize)(response).toString('binary');
}
}