"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.SplitLogFileWritable = exports.JsonLFileWritable = void 0;
exports.initializeProjectLogFilesAsync = initializeProjectLogFilesAsync;
exports.getLogfileBaseNames = getLogfileBaseNames;
exports.getProjectLogFolders = getProjectLogFolders;
exports.getProjectLogFilePaths = getProjectLogFilePaths;
const node_core_library_1 = require("@rushstack/node-core-library");
const terminal_1 = require("@rushstack/terminal");
const PackageNameParsers_1 = require("../../api/PackageNameParsers");
const RushConstants_1 = require("../RushConstants");
const LOG_CHUNKS_FOLDER_RELATIVE_PATH = `${RushConstants_1.RushConstants.projectRushFolderName}/${RushConstants_1.RushConstants.rushTempFolderName}/chunked-rush-logs`;
/**
 * A terminal stream that writes all log chunks to a JSONL format so they can be faithfully reconstructed
 *  during build cache restores. This is used for adding warning + error messages in cobuilds where the original
 *  logs cannot be completely restored from the existing `all.log` and `error.log` files.
 *
 * Example output:
 * libraries/rush-lib/.rush/temp/operations/rush-lib._phase_build.chunks.jsonl
 * ```
 * {"kind":"O","text":"Invoking: heft run --only build -- --clean \n"}
 * {"kind":"O","text":" ---- build started ---- \n"}
 * {"kind":"O","text":"[build:clean] Deleted 0 files and 5 folders\n"}
 * {"kind":"O","text":"[build:typescript] Using TypeScript version 5.4.2\n"}
 * {"kind":"O","text":"[build:lint] Using ESLint version 8.57.0\n"}
 * {"kind":"E","text":"[build:lint] Warning: libraries/rush-lib/src/logic/operations/LogChunksWritable.ts:15:7 - (@typescript-eslint/typedef) Expected test to have a type annotation.\n"}
 * {"kind":"E","text":"[build:lint] Warning: libraries/rush-lib/src/logic/operations/LogChunksWritable.ts:15:7 - (@typescript-eslint/no-unused-vars) 'test' is assigned a value but never used.\n"}
 * {"kind":"O","text":"[build:typescript] Copied 1138 folders or files and linked 0 files\n"}
 * {"kind":"O","text":"[build:webpack] Using Webpack version 5.82.1\n"}
 * {"kind":"O","text":"[build:webpack] Running Webpack compilation\n"}
 * {"kind":"O","text":"[build:api-extractor] Using API Extractor version 7.43.1\n"}
 * {"kind":"O","text":"[build:api-extractor] Analysis will use the bundled TypeScript version 5.4.2\n"}
 * {"kind":"O","text":"[build:copy-mock-flush-telemetry-plugin] Copied 1260 folders or files and linked 5 files\n"}
 * {"kind":"O","text":" ---- build finished (6.856s) ---- \n"}
 * {"kind":"O","text":"-------------------- Finished (6.858s) --------------------\n"}
 * ```
 */
class JsonLFileWritable extends terminal_1.TerminalWritable {
    constructor(logPath) {
        super();
        this.logPath = logPath;
        this._writer = node_core_library_1.FileWriter.open(logPath);
    }
    // Override writeChunk function to throw custom error
    writeChunk(chunk) {
        if (!this._writer) {
            throw new node_core_library_1.InternalError(`Log writer was closed for ${this.logPath}`);
        }
        // Stderr can always get written to a error log writer
        super.writeChunk(chunk);
    }
    onWriteChunk(chunk) {
        if (!this._writer) {
            throw new node_core_library_1.InternalError(`Log writer was closed for ${this.logPath}`);
        }
        this._writer.write(JSON.stringify(chunk) + '\n');
    }
    onClose() {
        if (this._writer) {
            try {
                this._writer.close();
            }
            catch (error) {
                throw new node_core_library_1.InternalError('Failed to close file handle for ' + this._writer.filePath);
            }
            this._writer = undefined;
        }
    }
}
exports.JsonLFileWritable = JsonLFileWritable;
/**
 * A terminal stream that writes two text log files: one with interleaved stdout and stderr, and one with just stderr.
 */
class SplitLogFileWritable extends terminal_1.TerminalWritable {
    constructor(logPath, errorLogPath) {
        super();
        this._logWriter = undefined;
        this._errorLogWriter = undefined;
        this.logPath = logPath;
        this.errorLogPath = errorLogPath;
        this._logWriter = node_core_library_1.FileWriter.open(logPath);
        this._errorLogWriter = undefined;
    }
    // Override writeChunk function to throw custom error
    writeChunk(chunk) {
        if (!this._logWriter) {
            throw new node_core_library_1.InternalError(`Log writer was closed for ${this.logPath}`);
        }
        // Stderr can always get written to a error log writer
        super.writeChunk(chunk);
    }
    onWriteChunk(chunk) {
        if (!this._logWriter) {
            throw new node_core_library_1.InternalError('Output file was closed');
        }
        // Both stderr and stdout get written to *.<phaseName>.log
        this._logWriter.write(chunk.text);
        if (chunk.kind === terminal_1.TerminalChunkKind.Stderr) {
            // Only stderr gets written to *.<phaseName>.error.log
            if (!this._errorLogWriter) {
                this._errorLogWriter = node_core_library_1.FileWriter.open(this.errorLogPath);
            }
            this._errorLogWriter.write(chunk.text);
        }
    }
    onClose() {
        if (this._logWriter) {
            try {
                this._logWriter.close();
            }
            catch (error) {
                throw new node_core_library_1.InternalError('Failed to close file handle for ' + this._logWriter.filePath);
            }
            this._logWriter = undefined;
        }
        if (this._errorLogWriter) {
            try {
                this._errorLogWriter.close();
            }
            catch (error) {
                throw new node_core_library_1.InternalError('Failed to close file handle for ' + this._errorLogWriter.filePath);
            }
            this._errorLogWriter = undefined;
        }
    }
}
exports.SplitLogFileWritable = SplitLogFileWritable;
/**
 * Initializes the project log files for a project. Produces a combined log file, an error log file, and optionally a
 * chunks file that can be used to reconstrct the original console output.
 * @param options - The options to initialize the project log files.
 * @returns The terminal writable stream that will write to the log files.
 */
async function initializeProjectLogFilesAsync(options) {
    const { logFilePaths, enableChunkedOutput = false } = options;
    const { textFolder: logFolderPath, jsonlFolder: jsonlFolderPath, text: logPath, error: errorLogPath, jsonl: jsonlPath } = logFilePaths;
    await Promise.all([
        node_core_library_1.FileSystem.ensureFolderAsync(logFolderPath),
        enableChunkedOutput && node_core_library_1.FileSystem.ensureFolderAsync(jsonlFolderPath),
        node_core_library_1.FileSystem.deleteFileAsync(logPath),
        node_core_library_1.FileSystem.deleteFileAsync(errorLogPath),
        node_core_library_1.FileSystem.deleteFileAsync(jsonlPath)
    ]);
    const splitLog = new terminal_1.TextRewriterTransform({
        destination: new SplitLogFileWritable(logPath, errorLogPath),
        removeColors: true,
        normalizeNewlines: node_core_library_1.NewlineKind.OsDefault
    });
    if (enableChunkedOutput) {
        const chunksFile = new JsonLFileWritable(jsonlPath);
        const splitter = new terminal_1.SplitterTransform({
            destinations: [splitLog, chunksFile]
        });
        return splitter;
    }
    return splitLog;
}
/**
 * @internal
 *
 * @param packageName - The raw package name
 * @param logFilenameIdentifier - The identifier to append to the log file name (typically the phase name)
 * @returns The base names of the log files
 */
function getLogfileBaseNames(packageName, logFilenameIdentifier) {
    const unscopedProjectName = PackageNameParsers_1.PackageNameParsers.permissive.getUnscopedName(packageName);
    const logFileBaseName = `${unscopedProjectName}.${logFilenameIdentifier}`;
    return {
        textFileName: `${logFileBaseName}.log`,
        jsonlFileName: `${logFileBaseName}.chunks.jsonl`,
        errorFileName: `${logFileBaseName}.error.log`
    };
}
/**
 * @internal
 *
 * @param projectFolder - The absolute path of the project folder
 * @returns The absolute paths of the log folders for regular and chunked logs
 */
function getProjectLogFolders(projectFolder) {
    const textFolder = `${projectFolder}/${RushConstants_1.RushConstants.rushLogsFolderName}`;
    const jsonlFolder = `${projectFolder}/${LOG_CHUNKS_FOLDER_RELATIVE_PATH}`;
    return { textFolder, jsonlFolder };
}
/**
 * @internal
 *
 * @param options - The options to get the log file paths
 * @returns All information about log file paths for the project and log identifier
 */
function getProjectLogFilePaths(options) {
    const { project: { projectFolder, packageName }, logFilenameIdentifier } = options;
    const { textFolder, jsonlFolder } = getProjectLogFolders(projectFolder);
    const { textFileName: textLog, jsonlFileName: jsonlLog, errorFileName: errorLog } = getLogfileBaseNames(packageName, logFilenameIdentifier);
    const textPath = `${textFolder}/${textLog}`;
    const errorPath = `${textFolder}/${errorLog}`;
    const jsonlPath = `${jsonlFolder}/${jsonlLog}`;
    return {
        textFolder,
        jsonlFolder,
        text: textPath,
        error: errorPath,
        jsonl: jsonlPath
    };
}
//# sourceMappingURL=ProjectLogWritable.js.map