"use strict";
exports.id = "ProjectWatcher";
exports.ids = ["ProjectWatcher"];
exports.modules = {

/***/ 700843
/*!******************************************************!*\
  !*** ./lib-intermediate-esm/logic/ProjectWatcher.js ***!
  \******************************************************/
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   ProjectWatcher: () => (/* binding */ ProjectWatcher)
/* harmony export */ });
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! node:fs */ 973024);
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_fs__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! node:os */ 848161);
/* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(node_os__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var node_readline__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! node:readline */ 380481);
/* harmony import */ var node_readline__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(node_readline__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var node_events__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! node:events */ 478474);
/* harmony import */ var node_events__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(node_events__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _rushstack_package_deps_hash__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @rushstack/package-deps-hash */ 119369);
/* harmony import */ var _rushstack_package_deps_hash__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_rushstack_package_deps_hash__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @rushstack/node-core-library */ 277275);
/* harmony import */ var _rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var _rushstack_terminal__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @rushstack/terminal */ 367587);
/* harmony import */ var _rushstack_terminal__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_rushstack_terminal__WEBPACK_IMPORTED_MODULE_6__);
/* harmony import */ var _Git__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./Git */ 435982);
/* harmony import */ var _utilities_executionUtilities__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../utilities/executionUtilities */ 953844);
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.









/**
 * This class is for incrementally watching a set of projects in the repository for changes.
 *
 * We are manually using fs.watch() instead of `chokidar` because all we want from the file system watcher is a boolean
 * signal indicating that "at least 1 file in a watched project changed". We then defer to getInputsSnapshotAsync (which
 * is responsible for change detection in all incremental builds) to determine what actually chanaged.
 *
 * Calling `waitForChange()` will return a promise that resolves when the package-deps of one or
 * more projects differ from the value the previous time it was invoked. The first time will always resolve with the full selection.
 */
class ProjectWatcher {
    constructor(options) {
        this._forceChangedProjects = new Map();
        this.isPaused = false;
        const { abortSignal, getInputsSnapshotAsync: snapshotProvider, debounceMs = 1000, rushConfiguration, projectsToWatch, terminal, initialSnapshot: initialState } = options;
        this._abortSignal = abortSignal;
        abortSignal.addEventListener('abort', () => {
            var _a;
            (_a = this._onAbort) === null || _a === void 0 ? void 0 : _a.call(this);
        });
        this._debounceMs = debounceMs;
        this._rushConfiguration = rushConfiguration;
        this._projectsToWatch = projectsToWatch;
        this._terminal = terminal;
        const gitPath = new _Git__WEBPACK_IMPORTED_MODULE_7__.Git(rushConfiguration).getGitPathOrThrow();
        this._repoRoot = _rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5__.Path.convertToSlashes((0,_rushstack_package_deps_hash__WEBPACK_IMPORTED_MODULE_4__.getRepoRoot)(rushConfiguration.rushJsonFolder, gitPath));
        this._resolveIfChanged = undefined;
        this._onAbort = undefined;
        this._initialSnapshot = initialState;
        this._previousSnapshot = initialState;
        this._renderedStatusLines = 0;
        this._getPromptLines = undefined;
        this._getInputsSnapshotAsync = snapshotProvider;
    }
    pause() {
        this.isPaused = true;
        this._setStatus('Project watcher paused.');
    }
    resume() {
        this.isPaused = false;
        this._setStatus('Project watcher resuming...');
        if (this._resolveIfChanged) {
            this._resolveIfChanged().catch(() => {
                // Suppress unhandled promise rejection error
            });
        }
    }
    invalidateProject(project, reason) {
        if (this._forceChangedProjects.has(project)) {
            return false;
        }
        this._forceChangedProjects.set(project, reason);
        return true;
    }
    invalidateAll(reason) {
        for (const project of this._projectsToWatch) {
            this.invalidateProject(project, reason);
        }
    }
    clearStatus() {
        this._renderedStatusLines = 0;
    }
    rerenderStatus() {
        var _a;
        this._setStatus((_a = this._lastStatus) !== null && _a !== void 0 ? _a : 'Waiting for changes...');
    }
    setPromptGenerator(promptGenerator) {
        this._getPromptLines = promptGenerator;
    }
    /**
     * Waits for a change to the package-deps of one or more of the selected projects, since the previous invocation.
     * Will return immediately the first time it is invoked, since no state has been recorded.
     * If no change is currently present, watches the source tree of all selected projects for file changes.
     * `waitForChange` is not allowed to be called multiple times concurrently.
     */
    async waitForChangeAsync(onWatchingFiles) {
        if (this.isPaused) {
            this._setStatus(`Project watcher paused.`);
            await new Promise((resolve) => {
                this._resolveIfChanged = async () => resolve();
            });
        }
        const initialChangeResult = await this._computeChangedAsync();
        // Ensure that the new state is recorded so that we don't loop infinitely
        this._commitChanges(initialChangeResult.inputsSnapshot);
        if (initialChangeResult.changedProjects.size) {
            // We can't call `clear()` here due to the async tick in the end of _computeChanged
            for (const project of initialChangeResult.changedProjects) {
                this._forceChangedProjects.delete(project);
            }
            // TODO: _forceChangedProjects might be non-empty here, which will result in an immediate rerun after the next
            // run finishes. This is suboptimal, but the latency of _computeChanged is probably high enough that in practice
            // all invalidations will have been picked up already.
            return initialChangeResult;
        }
        const previousState = initialChangeResult.inputsSnapshot;
        const repoRoot = _rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5__.Path.convertToSlashes(this._rushConfiguration.rushJsonFolder);
        // Map of path to whether config for the path
        const pathsToWatch = new Map();
        // Node 12 supports the "recursive" parameter to fs.watch only on win32 and OSX
        // https://nodejs.org/docs/latest-v12.x/api/fs.html#fs_caveats
        const useNativeRecursiveWatch = _utilities_executionUtilities__WEBPACK_IMPORTED_MODULE_8__.IS_WINDOWS || node_os__WEBPACK_IMPORTED_MODULE_1__.platform() === 'darwin';
        if (useNativeRecursiveWatch) {
            // Watch the root non-recursively
            pathsToWatch.set(repoRoot, { recurse: false });
            // Watch the rush config folder non-recursively
            pathsToWatch.set(_rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5__.Path.convertToSlashes(this._rushConfiguration.commonRushConfigFolder), {
                recurse: false
            });
            for (const project of this._projectsToWatch) {
                // Use recursive watch in individual project folders
                pathsToWatch.set(_rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5__.Path.convertToSlashes(project.projectFolder), { recurse: true });
            }
        }
        else {
            for (const project of this._projectsToWatch) {
                const projectState = previousState.getTrackedFileHashesForOperation(project);
                const prefixLength = project.projectFolder.length - repoRoot.length - 1;
                // Watch files in the root of the project, or
                for (const pathToWatch of ProjectWatcher._enumeratePathsToWatch(projectState.keys(), prefixLength)) {
                    pathsToWatch.set(`${this._repoRoot}/${pathToWatch}`, { recurse: true });
                }
            }
        }
        if (this._abortSignal.aborted) {
            return initialChangeResult;
        }
        const watchers = new Map();
        const closePromises = [];
        const watchedResult = await new Promise((resolve, reject) => {
            let timeout;
            let terminated = false;
            const terminal = this._terminal;
            const debounceMs = this._debounceMs;
            const abortSignal = this._abortSignal;
            this.clearStatus();
            this._onAbort = function onAbort() {
                if (timeout) {
                    clearTimeout(timeout);
                }
                terminated = true;
                resolve(initialChangeResult);
            };
            if (abortSignal.aborted) {
                return this._onAbort();
            }
            const resolveIfChanged = (this._resolveIfChanged = async () => {
                timeout = undefined;
                if (terminated) {
                    return;
                }
                try {
                    if (this.isPaused) {
                        this._setStatus(`Project watcher paused.`);
                        return;
                    }
                    this._setStatus(`Evaluating changes to tracked files...`);
                    const result = await this._computeChangedAsync();
                    this._setStatus(`Finished analyzing.`);
                    // Need an async tick to allow for more file system events to be handled
                    process.nextTick(() => {
                        if (timeout) {
                            // If another file has changed, wait for another pass.
                            this._setStatus(`More file changes detected, aborting.`);
                            return;
                        }
                        // Since there are multiple async ticks since the projects were enumerated in _computeChanged,
                        // more could have been added in the interaval. Check and debounce.
                        for (const project of this._forceChangedProjects.keys()) {
                            if (!result.changedProjects.has(project)) {
                                this._setStatus(`More invalidations occurred, aborting.`);
                                timeout = setTimeout(resolveIfChanged, debounceMs);
                                return;
                            }
                        }
                        this._commitChanges(result.inputsSnapshot);
                        const hasForcedChanges = this._forceChangedProjects.size > 0;
                        if (hasForcedChanges) {
                            this._setStatus(`Projects were invalidated: ${Array.from(new Set(this._forceChangedProjects.values())).join(', ')}`);
                            this.clearStatus();
                        }
                        this._forceChangedProjects.clear();
                        if (result.changedProjects.size) {
                            terminated = true;
                            terminal.writeLine();
                            resolve(result);
                        }
                        else {
                            this._setStatus(`No changes detected to tracked files.`);
                        }
                    });
                }
                catch (err) {
                    // eslint-disable-next-line require-atomic-updates
                    terminated = true;
                    terminal.writeLine();
                    reject(err);
                }
            });
            for (const [pathToWatch, { recurse }] of pathsToWatch) {
                addWatcher(pathToWatch, recurse);
            }
            if (onWatchingFiles) {
                onWatchingFiles();
            }
            this._setStatus(`Waiting for changes...`);
            function onError(err) {
                if (terminated) {
                    return;
                }
                terminated = true;
                terminal.writeLine();
                reject(err);
            }
            function addWatcher(watchedPath, recursive) {
                if (watchers.has(watchedPath)) {
                    return;
                }
                const listener = changeListener(watchedPath, recursive);
                const watcher = node_fs__WEBPACK_IMPORTED_MODULE_0__.watch(watchedPath, {
                    encoding: 'utf-8',
                    recursive: recursive && useNativeRecursiveWatch,
                    signal: abortSignal
                }, listener);
                watchers.set(watchedPath, watcher);
                watcher.once('error', (err) => {
                    watcher.close();
                    onError(err);
                });
                closePromises.push((0,node_events__WEBPACK_IMPORTED_MODULE_3__.once)(watcher, 'close').then(() => {
                    watchers.delete(watchedPath);
                    watcher.removeAllListeners();
                    watcher.unref();
                }));
            }
            function innerListener(root, recursive, event, fileName) {
                try {
                    if (terminated) {
                        return;
                    }
                    if (fileName === '.git' || fileName === 'node_modules') {
                        return;
                    }
                    // Handling for added directories
                    if (recursive && !useNativeRecursiveWatch) {
                        const decodedName = fileName ? fileName.toString() : '';
                        const normalizedName = decodedName && _rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5__.Path.convertToSlashes(decodedName);
                        const fullName = normalizedName && `${root}/${normalizedName}`;
                        if (fullName && !watchers.has(fullName)) {
                            try {
                                const stat = _rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5__.FileSystem.getStatistics(fullName);
                                if (stat.isDirectory()) {
                                    addWatcher(fullName, true);
                                }
                            }
                            catch (err) {
                                const code = err.code;
                                if (code !== 'ENOENT' && code !== 'ENOTDIR') {
                                    throw err;
                                }
                            }
                        }
                    }
                    // Use a timeout to debounce changes, e.g. bulk copying files into the directory while the watcher is running.
                    if (timeout) {
                        clearTimeout(timeout);
                    }
                    timeout = setTimeout(resolveIfChanged, debounceMs);
                }
                catch (err) {
                    terminated = true;
                    terminal.writeLine();
                    reject(err);
                }
            }
            function changeListener(root, recursive) {
                return innerListener.bind(0, root, recursive);
            }
        }).finally(() => {
            this._onAbort = undefined;
            this._resolveIfChanged = undefined;
        });
        this._terminal.writeDebugLine(`Closing watchers...`);
        for (const watcher of watchers.values()) {
            watcher.close();
        }
        await Promise.all(closePromises);
        this._terminal.writeDebugLine(`Closed ${closePromises.length} watchers`);
        return watchedResult;
    }
    _setStatus(status) {
        var _a, _b;
        const statusLines = [
            `[${this.isPaused ? 'PAUSED' : 'WATCHING'}] Watch Status: ${status}`,
            ...((_b = (_a = this._getPromptLines) === null || _a === void 0 ? void 0 : _a.call(this, this.isPaused)) !== null && _b !== void 0 ? _b : [])
        ];
        if (this._renderedStatusLines > 0) {
            node_readline__WEBPACK_IMPORTED_MODULE_2__.cursorTo(process.stdout, 0);
            node_readline__WEBPACK_IMPORTED_MODULE_2__.moveCursor(process.stdout, 0, -this._renderedStatusLines);
            node_readline__WEBPACK_IMPORTED_MODULE_2__.clearScreenDown(process.stdout);
        }
        this._renderedStatusLines = statusLines.length;
        this._lastStatus = status;
        this._terminal.writeLine(_rushstack_terminal__WEBPACK_IMPORTED_MODULE_6__.Colorize.bold(_rushstack_terminal__WEBPACK_IMPORTED_MODULE_6__.Colorize.cyan(statusLines.join('\n'))));
    }
    /**
     * Determines which, if any, projects (within the selection) have new hashes for files that are not in .gitignore
     */
    async _computeChangedAsync() {
        const currentSnapshot = await this._getInputsSnapshotAsync();
        if (!currentSnapshot) {
            throw new _rushstack_node_core_library__WEBPACK_IMPORTED_MODULE_5__.AlreadyReportedError();
        }
        const previousSnapshot = this._previousSnapshot;
        if (!previousSnapshot) {
            return {
                changedProjects: this._projectsToWatch,
                inputsSnapshot: currentSnapshot
            };
        }
        const changedProjects = new Set();
        for (const project of this._projectsToWatch) {
            const previous = previousSnapshot.getTrackedFileHashesForOperation(project);
            const current = currentSnapshot.getTrackedFileHashesForOperation(project);
            if (ProjectWatcher._haveProjectDepsChanged(previous, current)) {
                // May need to detect if the nature of the change will break the process, e.g. changes to package.json
                changedProjects.add(project);
            }
        }
        for (const project of this._forceChangedProjects.keys()) {
            changedProjects.add(project);
        }
        return {
            changedProjects,
            inputsSnapshot: currentSnapshot
        };
    }
    _commitChanges(state) {
        this._previousSnapshot = state;
        if (!this._initialSnapshot) {
            this._initialSnapshot = state;
        }
    }
    /**
     * Tests for inequality of the passed Maps. Order invariant.
     *
     * @returns `true` if the maps are different, `false` otherwise
     */
    static _haveProjectDepsChanged(prev, next) {
        if (!prev && !next) {
            return false;
        }
        if (!prev || !next) {
            return true;
        }
        if (prev.size !== next.size) {
            return true;
        }
        for (const [key, value] of prev) {
            if (next.get(key) !== value) {
                return true;
            }
        }
        return false;
    }
    static *_enumeratePathsToWatch(paths, prefixLength) {
        for (const path of paths) {
            const rootSlashIndex = path.indexOf('/', prefixLength);
            if (rootSlashIndex < 0) {
                yield path;
                return;
            }
            yield path.slice(0, rootSlashIndex);
            let slashIndex = path.indexOf('/', rootSlashIndex + 1);
            while (slashIndex >= 0) {
                yield path.slice(0, slashIndex);
                slashIndex = path.indexOf('/', slashIndex + 1);
            }
        }
    }
}
//# sourceMappingURL=ProjectWatcher.js.map

/***/ }

};
;
//# sourceMappingURL=ProjectWatcher.js.map