nx
Version:
174 lines (173 loc) • 6.91 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BatchFunctionRunner = void 0;
exports.watch = watch;
const child_process_1 = require("child_process");
const client_1 = require("../../daemon/client/client");
const output_1 = require("../../utils/output");
const DEFAULT_PROJECT_NAME_ENV = 'NX_PROJECT_NAME';
const DEFAULT_FILE_CHANGES_ENV = 'NX_FILE_CHANGES';
class BatchFunctionRunner {
get _verbose() {
return process.env.NX_VERBOSE_LOGGING === 'true';
}
get hasPending() {
return this.pendingProjects.size > 0 || this.pendingFiles.size > 0;
}
constructor(callback) {
this.callback = callback;
this.running = false;
this.pendingProjects = new Set();
this.pendingFiles = new Set();
}
enqueue(projectNames, fileChanges) {
projectNames.forEach((projectName) => {
this.pendingProjects.add(projectName);
});
fileChanges.forEach((fileChange) => {
this.pendingFiles.add(fileChange.path);
});
return this.process();
}
async process() {
if (!this.running && this.hasPending) {
this.running = true;
// Clone the pending projects and files before clearing
const projects = new Set(this.pendingProjects);
const files = new Set(this.pendingFiles);
// Clear the pending projects and files
this.pendingProjects.clear();
this.pendingFiles.clear();
return this.callback(projects, files).then(() => {
this.running = false;
this.process();
});
}
else {
this._verbose &&
this.running &&
output_1.output.logSingleLine('waiting for function to finish executing');
this._verbose &&
!this.hasPending &&
output_1.output.logSingleLine('no more function to process');
}
}
}
exports.BatchFunctionRunner = BatchFunctionRunner;
class BatchCommandRunner extends BatchFunctionRunner {
constructor(command, projectNameEnv = DEFAULT_PROJECT_NAME_ENV, fileChangesEnv = DEFAULT_FILE_CHANGES_ENV) {
super((projects, files) => {
// process all pending commands together
const envs = this.createCommandEnvironments(projects, files);
return this.run(envs);
});
this.command = command;
this.projectNameEnv = projectNameEnv;
this.fileChangesEnv = fileChangesEnv;
}
createCommandEnvironments(projects, files) {
const commandsToRun = [];
if (projects.size > 0) {
projects.forEach((projectName) => {
commandsToRun.push({
[this.projectNameEnv]: projectName,
[this.fileChangesEnv]: Array.from(files).join(' '),
});
});
}
else {
commandsToRun.push({
[this.projectNameEnv]: '',
[this.fileChangesEnv]: Array.from(files).join(' '),
});
}
return commandsToRun;
}
async run(envs) {
this._verbose &&
output_1.output.logSingleLine('about to run commands with these environments: ' + JSON.stringify(envs));
return Promise.all(envs.map((env) => {
return new Promise((resolve, reject) => {
const commandExec = (0, child_process_1.spawn)(this.command, {
stdio: 'inherit',
shell: true,
cwd: process.cwd(),
env: {
...process.env,
[this.projectNameEnv]: env[this.projectNameEnv],
[this.fileChangesEnv]: env[this.fileChangesEnv],
},
windowsHide: false,
});
commandExec.on('close', () => {
resolve();
});
commandExec.on('exit', () => {
resolve();
});
});
})).then((r) => {
this._verbose &&
output_1.output.logSingleLine('running complete, processing the next batch');
return r;
});
}
}
async function watch(args) {
const projectReplacementRegex = new RegExp(args.projectNameEnvName ?? DEFAULT_PROJECT_NAME_ENV, 'g');
if (!client_1.daemonClient.enabled()) {
output_1.output.error({
title: 'Daemon is not running. The watch command is not supported without the Nx Daemon.',
});
process.exit(1);
}
if (args.includeGlobalWorkspaceFiles &&
args.command.match(projectReplacementRegex)) {
output_1.output.error({
title: 'Invalid command',
bodyLines: [
'The command you provided has a replacement for projects ($NX_PROJECT_NAME), but you are also including global workspace files.',
'You cannot use a replacement for projects when including global workspace files because there will be scenarios where there are file changes not associated with a project.',
],
});
process.exit(1);
}
args.verbose &&
output_1.output.logSingleLine('running with args: ' + JSON.stringify(args));
args.verbose && output_1.output.logSingleLine('starting watch process');
const whatToWatch = args.all ? 'all' : args.projects;
const batchQueue = new BatchCommandRunner(args.command ?? '', args.projectNameEnvName, args.fileChangesEnvName);
await client_1.daemonClient.registerFileWatcher({
watchProjects: whatToWatch,
includeDependentProjects: args.includeDependentProjects,
includeGlobalWorkspaceFiles: args.includeGlobalWorkspaceFiles,
}, async (err, data) => {
if (err === 'closed') {
output_1.output.error({
title: 'Watch connection closed',
bodyLines: [
'The daemon has closed the connection to this watch process.',
'Please restart your watch command.',
],
});
process.exit(1);
}
else if (err !== null) {
output_1.output.error({
title: 'Watch error',
bodyLines: [
'An error occurred during the watch process:',
err.message,
],
});
}
// Only pass the projects to the queue if the command has a replacement for projects
if (args.command.match(projectReplacementRegex)) {
batchQueue.enqueue(data.changedProjects, data.changedFiles);
}
else {
batchQueue.enqueue([], data.changedFiles);
}
});
args.verbose && output_1.output.logSingleLine('watch process waiting...');
}