"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateReleaseManifest = validateReleaseManifest;
exports.validateReleaseInstallerSafety = validateReleaseInstallerSafety;
exports.validateInstallerPayloadShims = validateInstallerPayloadShims;
exports.isNodeVersionAtLeast20 = isNodeVersionAtLeast20;
exports.snapshotDirectory = snapshotDirectory;
exports.directoriesMatchSnapshot = directoriesMatchSnapshot;
exports.isUsageErrorLike = isUsageErrorLike;
// Mechanically extracted from output-lifecycle.ts to keep release responsibilities bounded.
const node_crypto_1 = __importDefault(require("node:crypto"));
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const cli_constants_1 = require("../../../lib/config/cli-constants");
const errors_1 = require("../../../lib/errors");
const version_1 = require("./version");
const output_lifecycle_checksums_1 = require("./output-lifecycle-checksums");
const output_lifecycle_manifest_1 = require("./output-lifecycle-manifest");
const output_lifecycle_payload_policy_1 = require("./output-lifecycle-payload-policy");
function validateReleaseManifest(options) {
    const manifest = (0, output_lifecycle_manifest_1.normalizeReleaseManifestData)(options.manifest);
    const parsedVersion = (0, version_1.parseReleaseVersion)(manifest.version);
    if (!parsedVersion.ok || parsedVersion.version !== options.expectedVersion) {
        throw (0, errors_1.createUsageError)('Release manifest contains malformed or inconsistent version metadata.', [
            'Reason: malformed-manifest-version',
            `Manifest version: ${manifest.version}`,
            `Expected version: ${options.expectedVersion}`,
            'Recovery: regenerate the manifest from explicit release version metadata.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if (manifest.command.installedName !== options.expectedInstalledCommandName) {
        throw (0, errors_1.createUsageError)('Release manifest command metadata is inconsistent.', [
            'Reason: manifest-command-name-mismatch',
            `Manifest command name: ${manifest.command.installedName}`,
            `Expected command name: ${options.expectedInstalledCommandName}`,
            'Recovery: regenerate the manifest from the configured release command name.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if ((0, output_lifecycle_payload_policy_1.normalizePayloadPath)(manifest.command.stagedCommandPath) !== (0, output_lifecycle_payload_policy_1.normalizePayloadPath)(options.expectedStagedCommandPath)) {
        throw (0, errors_1.createUsageError)('Release manifest staged command metadata is inconsistent.', [
            'Reason: manifest-staged-command-path-mismatch',
            `Manifest staged command path: ${manifest.command.stagedCommandPath}`,
            `Expected staged command path: ${options.expectedStagedCommandPath}`,
            'Recovery: regenerate the manifest from the release command invocation mapping.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if (manifest.status !== manifest.replacement.status) {
        throw (0, errors_1.createUsageError)('Release manifest status metadata is inconsistent.', [
            'Reason: manifest-status-mismatch',
            `Manifest status: ${manifest.status}`,
            `Replacement status: ${manifest.replacement.status}`,
            'Recovery: regenerate the manifest from release publication metadata.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if (manifest.command.invocations.development !== 'node dist/cli/index.js' ||
        manifest.command.invocations.stagedRelease !== (0, output_lifecycle_payload_policy_1.normalizePayloadPath)(options.expectedStagedCommandPath) ||
        manifest.command.invocations.installedUser !== options.expectedInstalledCommandName) {
        throw (0, errors_1.createUsageError)('Release manifest command invocation metadata is inconsistent.', [
            'Reason: manifest-command-invocation-mismatch',
            `Development invocation: ${manifest.command.invocations.development}`,
            `Staged invocation: ${manifest.command.invocations.stagedRelease}`,
            `Installed invocation: ${manifest.command.invocations.installedUser}`,
            'Recovery: regenerate the manifest from the release command invocation mapping.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if (manifest.runtime.model !== 'system-node') {
        throw (0, errors_1.createUsageError)('Release manifest uses an unsupported runtime model.', [
            'Reason: unsupported-runtime-model',
            `Runtime model: ${String(manifest.runtime.model)}`,
            'Recovery: use system-node runtime metadata for MVP release artifacts.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    (0, output_lifecycle_manifest_1.validateManifestWebsiteRelativePath)(manifest.installation.readmePath, 'installation README path');
    (0, output_lifecycle_manifest_1.validateManifestWebsiteRelativePath)(manifest.releaseNotes.path, 'release notes path');
    (0, output_lifecycle_manifest_1.validateManifestWebsiteDownloadUrl)(manifest.releaseNotes.url, options.expectedVersion, cli_constants_1.RELEASE_NOTES_FILENAME, 'release notes URL');
    (0, output_lifecycle_manifest_1.validateReleaseManifestSupportedPlatforms)(manifest.supportedPlatforms);
    if (manifest.releaseNotes.sourceSummary.status !== 'generated') {
        throw (0, errors_1.createUsageError)('Release manifest requires generated release notes evidence.', [
            'Reason: release-notes-not-generated',
            `Release notes: ${manifest.releaseNotes.path}`,
            `Status: ${manifest.releaseNotes.sourceSummary.status}`,
            'Recovery: generate release notes before publishing release manifest metadata.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if (manifest.installation.installerManifest !== undefined) {
        const installerManifestPath = validateReferencedReleaseMetadata({
            versionDirectory: options.versionDirectory,
            versionDirectoryRelative: options.versionDirectoryRelative,
            version: options.expectedVersion,
            reference: manifest.installation.installerManifest,
            label: 'installer manifest',
            expectedFilename: cli_constants_1.RELEASE_INSTALLER_MANIFEST_FILENAME,
        });
        const installerManifest = JSON.parse(node_fs_1.default.readFileSync(installerManifestPath, 'utf8'));
        const installerCommand = installerManifest.command;
        const installerRuntime = installerManifest.runtime;
        if (installerManifest.schemaVersion !== 1 ||
            installerManifest.version !== options.expectedVersion ||
            installerCommand?.installedName !== options.expectedInstalledCommandName ||
            installerRuntime?.model !== manifest.runtime.model ||
            installerRuntime.requiredNodeVersion !== manifest.runtime.requiredNodeVersion ||
            !installerArtifactMetadataMatchesManifest(installerManifest.artifacts, manifest)) {
            throw (0, errors_1.createUsageError)('Release installer manifest is inconsistent with release metadata.', [
                'Reason: malformed-installer-manifest',
                `Installer manifest: ${manifest.installation.installerManifest.path}`,
                'Recovery: regenerate compact installer metadata before the release manifest.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
    }
    if ('provenance' in manifest.releaseNotes.sourceSummary) {
        const provenancePath = validateReferencedReleaseMetadata({
            versionDirectory: options.versionDirectory,
            versionDirectoryRelative: options.versionDirectoryRelative,
            version: options.expectedVersion,
            reference: manifest.releaseNotes.sourceSummary.provenance,
            label: 'release-notes provenance',
            expectedFilename: cli_constants_1.RELEASE_NOTES_PROVENANCE_FILENAME,
        });
        const provenance = JSON.parse(node_fs_1.default.readFileSync(provenancePath, 'utf8'));
        const sourceSummary = provenance.sourceSummary;
        if (provenance.schemaVersion !== 1 ||
            provenance.version !== options.expectedVersion ||
            sourceSummary?.status !== manifest.releaseNotes.sourceSummary.status ||
            !releaseNotesCountsMatch(sourceSummary?.counts, manifest.releaseNotes.sourceSummary.counts) ||
            !Array.isArray(sourceSummary?.selectedSources) ||
            !Array.isArray(sourceSummary?.excludedSources) ||
            !Array.isArray(sourceSummary?.releasedSources) ||
            sourceSummary.selectedSources.length !== manifest.releaseNotes.sourceSummary.counts.selectedSources ||
            sourceSummary.excludedSources.length !== manifest.releaseNotes.sourceSummary.counts.excludedSources) {
            throw (0, errors_1.createUsageError)('Release-notes provenance is inconsistent with release metadata.', [
                'Reason: malformed-release-notes-provenance',
                `Release-notes provenance: ${manifest.releaseNotes.sourceSummary.provenance.path}`,
                'Recovery: regenerate release-note provenance before the release manifest.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
    }
    const releaseNotesFilePath = (0, output_lifecycle_checksums_1.resolveArtifactPathInsideVersionDirectory)({
        versionDirectory: options.versionDirectory,
        versionDirectoryRelative: options.versionDirectoryRelative,
        artifactPath: manifest.releaseNotes.path,
        label: 'release notes',
    });
    if (!node_fs_1.default.existsSync(releaseNotesFilePath) || !node_fs_1.default.statSync(releaseNotesFilePath).isFile()) {
        throw (0, errors_1.createUsageError)('Release manifest release notes file is missing.', [
            'Reason: missing-release-notes-file',
            `Release notes: ${manifest.releaseNotes.path}`,
            'Recovery: regenerate release notes before publishing release manifest metadata.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    for (const artifact of manifest.artifacts) {
        const artifactPath = (0, output_lifecycle_checksums_1.resolveArtifactPathInsideVersionDirectory)({
            versionDirectory: options.versionDirectory,
            versionDirectoryRelative: options.versionDirectoryRelative,
            artifactPath: artifact.path,
            label: 'manifest artifact',
        });
        if (!node_fs_1.default.existsSync(artifactPath) || !node_fs_1.default.statSync(artifactPath).isFile()) {
            throw (0, errors_1.createUsageError)('Release manifest artifact is missing.', [
                'Reason: missing-manifest-artifact-file',
                `Artifact: ${artifact.path}`,
                'Recovery: regenerate release archives before manifest generation.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
        (0, output_lifecycle_manifest_1.validateManifestWebsiteRelativePath)(artifact.downloadPath, 'artifact download path');
        (0, output_lifecycle_manifest_1.validateManifestWebsiteDownloadPath)(artifact.downloadPath, options.expectedVersion, artifact.filename, 'artifact download path');
        (0, output_lifecycle_manifest_1.validateManifestWebsiteDownloadUrl)(artifact.url, options.expectedVersion, artifact.filename, 'artifact URL');
        if (artifact.filename !== node_path_1.default.basename(artifactPath)) {
            throw (0, errors_1.createUsageError)('Release manifest artifact filename is inconsistent.', [
                'Reason: manifest-artifact-filename-mismatch',
                `Artifact: ${artifact.path}`,
                `Filename: ${artifact.filename}`,
                'Recovery: regenerate the manifest from checksum artifact records.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
        if (artifact.runtimeModel !== 'system-node') {
            throw (0, errors_1.createUsageError)('Release manifest artifact uses an unsupported runtime model.', [
                'Reason: unsupported-runtime-model',
                `Artifact: ${artifact.path}`,
                `Runtime model: ${String(artifact.runtimeModel)}`,
                'Recovery: use system-node runtime metadata for MVP release artifacts.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
        const artifactBytes = node_fs_1.default.readFileSync(artifactPath);
        const actualSha256 = node_crypto_1.default.createHash('sha256').update(artifactBytes).digest('hex');
        if (artifact.byteSize !== artifactBytes.length) {
            throw (0, errors_1.createUsageError)('Release manifest artifact byte size does not match current file evidence.', [
                'Reason: artifact-byte-size-mismatch',
                `Artifact: ${artifact.path}`,
                `Expected byte size: ${artifact.byteSize}`,
                `Actual byte size: ${artifactBytes.length}`,
                'Recovery: regenerate checksum evidence and the release manifest after final artifact writes complete.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
        if (artifact.sha256 !== actualSha256) {
            throw (0, errors_1.createUsageError)('Release manifest artifact SHA-256 does not match current file evidence.', [
                'Reason: artifact-checksum-mismatch',
                `Artifact: ${artifact.path}`,
                'Recovery: regenerate checksum evidence and the release manifest after final artifact writes complete.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
    }
    const artifactsByPath = new Map(manifest.artifacts.map((artifact) => [artifact.path, artifact]));
    const missingManifestArtifacts = options.expectedArtifactPaths.filter((artifactPath) => !artifactsByPath.has(artifactPath));
    if (missingManifestArtifacts.length > 0) {
        throw (0, errors_1.createUsageError)('Release manifest is missing expected artifact metadata.', [
            'Reason: missing-manifest-artifact',
            `Missing artifacts: ${missingManifestArtifacts.join(', ')}`,
            'Recovery: regenerate the manifest after archive and checksum generation completes.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
}
function validateReferencedReleaseMetadata(options) {
    if (options.reference.filename !== options.expectedFilename) {
        throw (0, errors_1.createUsageError)(`Release ${options.label} filename is inconsistent.`, [
            'Reason: release-metadata-filename-mismatch',
            `Filename: ${options.reference.filename}`,
            `Expected filename: ${options.expectedFilename}`,
            'Recovery: regenerate the referenced metadata artifact and release manifest together.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    (0, output_lifecycle_manifest_1.validateManifestWebsiteRelativePath)(options.reference.path, `${options.label} path`);
    (0, output_lifecycle_manifest_1.validateManifestWebsiteDownloadPath)(options.reference.downloadPath, options.version, options.reference.filename, `${options.label} download path`);
    (0, output_lifecycle_manifest_1.validateManifestWebsiteDownloadUrl)(options.reference.url, options.version, options.reference.filename, `${options.label} URL`);
    const filePath = (0, output_lifecycle_checksums_1.resolveArtifactPathInsideVersionDirectory)({
        versionDirectory: options.versionDirectory,
        versionDirectoryRelative: options.versionDirectoryRelative,
        artifactPath: options.reference.path,
        label: options.label,
    });
    if (node_path_1.default.basename(filePath) !== options.reference.filename) {
        throw (0, errors_1.createUsageError)(`Release ${options.label} path is inconsistent with its filename.`, [
            'Reason: release-metadata-filename-mismatch',
            `Artifact: ${options.reference.path}`,
            `Filename: ${options.reference.filename}`,
            'Recovery: regenerate the referenced metadata artifact and release manifest together.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if (!node_fs_1.default.existsSync(filePath) || !node_fs_1.default.statSync(filePath).isFile()) {
        throw (0, errors_1.createUsageError)(`Release ${options.label} file is missing.`, [
            'Reason: missing-release-metadata-file',
            `Artifact: ${options.reference.path}`,
            'Recovery: regenerate release metadata before manifest validation.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    const bytes = node_fs_1.default.readFileSync(filePath);
    const sha256 = node_crypto_1.default.createHash('sha256').update(bytes).digest('hex');
    if (bytes.length !== options.reference.byteSize || sha256 !== options.reference.sha256) {
        throw (0, errors_1.createUsageError)(`Release ${options.label} checksum metadata is inconsistent.`, [
            'Reason: release-metadata-checksum-mismatch',
            `Artifact: ${options.reference.path}`,
            'Recovery: regenerate the referenced metadata artifact and release manifest together.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    return filePath;
}
function installerArtifactMetadataMatchesManifest(value, manifest) {
    if (!Array.isArray(value)) {
        return false;
    }
    const expectedArtifacts = manifest.artifacts.filter((artifact) => artifact.kind === 'archive');
    const candidates = new Map();
    for (const artifact of value) {
        if (typeof artifact !== 'object' || artifact === null || Array.isArray(artifact)) {
            return false;
        }
        const candidate = artifact;
        if (typeof candidate.filename !== 'string' || candidates.has(candidate.filename)) {
            return false;
        }
        candidates.set(candidate.filename, candidate);
    }
    return candidates.size === expectedArtifacts.length && expectedArtifacts.every((expected) => {
        const candidate = candidates.get(expected.filename);
        return (candidate?.kind === expected.kind &&
            candidate.target === expected.target &&
            candidate.byteSize === expected.byteSize &&
            candidate.sha256 === expected.sha256 &&
            candidate.runtimeModel === expected.runtimeModel);
    });
}
function releaseNotesCountsMatch(left, right) {
    if (typeof left !== 'object' || left === null || Array.isArray(left) ||
        typeof right !== 'object' || right === null || Array.isArray(right)) {
        return false;
    }
    const countKeys = [
        'selectedImplementationSummaries',
        'selectedMilestoneReconciliations',
        'explicitIncludes',
        'excludedDraftOrPending',
        'excludedProposalOnly',
        'excludedUnsupported',
        'selectedSources',
        'excludedSources',
    ];
    const leftCounts = left;
    const rightCounts = right;
    return countKeys.every((key) => (typeof leftCounts[key] === 'number' &&
        Number.isSafeInteger(leftCounts[key]) &&
        leftCounts[key] >= 0 &&
        leftCounts[key] === rightCounts[key]));
}
function validateReleaseInstallerSafety(options) {
    const manifest = (0, output_lifecycle_manifest_1.normalizeReleaseManifestData)(options.manifest);
    const archiveArtifact = manifest.artifacts.find((artifact) => artifact.kind === 'archive' && artifact.target === options.target && artifact.runtimeModel === 'system-node');
    if (archiveArtifact === undefined) {
        throw (0, errors_1.createUsageError)('Release installer validation requires a matching system-node archive artifact.', [
            'Reason: installer-manifest-archive-missing',
            `Target: ${options.target}`,
            'Recovery: regenerate the manifest with archive metadata for the installer target.',
            'Exit behavior: exits before existing installation replacement.',
        ]);
    }
    validateReleaseManifest({
        versionDirectory: options.versionDirectory,
        versionDirectoryRelative: options.versionDirectoryRelative,
        expectedVersion: options.expectedVersion,
        expectedInstalledCommandName: options.expectedInstalledCommandName,
        expectedStagedCommandPath: options.expectedStagedCommandPath,
        expectedArtifactPaths: [archiveArtifact.path],
        manifest,
    });
    if (options.nodeVersion === null) {
        throw (0, errors_1.createUsageError)('Release installer validation requires system Node.js before installation changes.', [
            'Reason: missing-node',
            'Required Node.js version: >=20.0.0',
            'Recovery: install Node.js >=20.0.0 before running the installer.',
            'Exit behavior: exits before existing installation replacement.',
        ]);
    }
    if (!isNodeVersionAtLeast20(options.nodeVersion)) {
        throw (0, errors_1.createUsageError)('Release installer validation requires Node.js >=20.0.0 before installation changes.', [
            'Reason: unsupported-node-version',
            `Node.js version: ${options.nodeVersion}`,
            'Required Node.js version: >=20.0.0',
            'Recovery: upgrade Node.js before running the installer.',
            'Exit behavior: exits before existing installation replacement.',
        ]);
    }
    const payloadDirectory = node_path_1.default.resolve(options.payloadDirectory);
    if (!node_fs_1.default.existsSync(payloadDirectory) || !node_fs_1.default.statSync(payloadDirectory).isDirectory()) {
        throw (0, errors_1.createUsageError)('Release installer validation requires an unpacked archive payload directory.', [
            'Reason: installer-payload-missing',
            `Payload directory: ${(0, output_lifecycle_payload_policy_1.normalizePayloadPath)(options.payloadDirectory)}`,
            'Recovery: download, verify, and unpack the release archive before replacement.',
            'Exit behavior: exits before existing installation replacement.',
        ]);
    }
    const installRoot = node_path_1.default.resolve(options.installRoot);
    const currentInstallDirectory = node_path_1.default.join(installRoot, 'current');
    const stagedInstallDirectory = node_path_1.default.join(installRoot, '.day-shift-install-stage');
    const backupInstallDirectory = node_path_1.default.join(installRoot, '.day-shift-install-backup');
    const hadExistingInstall = node_fs_1.default.existsSync(currentInstallDirectory);
    const existingInstallSnapshot = hadExistingInstall ? snapshotDirectory(currentInstallDirectory) : [];
    node_fs_1.default.mkdirSync(installRoot, { recursive: true });
    node_fs_1.default.rmSync(stagedInstallDirectory, { recursive: true, force: true });
    node_fs_1.default.rmSync(backupInstallDirectory, { recursive: true, force: true });
    try {
        node_fs_1.default.cpSync(payloadDirectory, stagedInstallDirectory, { recursive: true });
        validateInstallerPayloadShims({
            target: options.target,
            installedCommandName: options.expectedInstalledCommandName,
            payloadDirectory: stagedInstallDirectory,
        });
    }
    catch (error) {
        node_fs_1.default.rmSync(stagedInstallDirectory, { recursive: true, force: true });
        throw error;
    }
    try {
        if (hadExistingInstall) {
            node_fs_1.default.renameSync(currentInstallDirectory, backupInstallDirectory);
        }
        if (options.simulateReplacementFailure === true) {
            throw (0, errors_1.createUsageError)('Release installer staged replacement failed.', [
                'Reason: installer-replacement-failed',
                `Install root: ${(0, output_lifecycle_payload_policy_1.normalizePayloadPath)(options.installRoot)}`,
                'Recovery: keep the existing installation and retry after fixing filesystem permissions or locks.',
                'Exit behavior: restores the existing installation before returning failure.',
            ]);
        }
        node_fs_1.default.renameSync(stagedInstallDirectory, currentInstallDirectory);
        node_fs_1.default.rmSync(backupInstallDirectory, { recursive: true, force: true });
    }
    catch (error) {
        node_fs_1.default.rmSync(currentInstallDirectory, { recursive: true, force: true });
        if (node_fs_1.default.existsSync(backupInstallDirectory)) {
            node_fs_1.default.renameSync(backupInstallDirectory, currentInstallDirectory);
        }
        node_fs_1.default.rmSync(stagedInstallDirectory, { recursive: true, force: true });
        if (isUsageErrorLike(error)) {
            throw error;
        }
        throw (0, errors_1.createUsageError)('Release installer staged replacement failed.', [
            'Reason: installer-replacement-failed',
            `Install root: ${(0, output_lifecycle_payload_policy_1.normalizePayloadPath)(options.installRoot)}`,
            'Recovery: keep the existing installation and retry after fixing filesystem permissions or locks.',
            'Exit behavior: restores the existing installation before returning failure.',
        ]);
    }
    return {
        status: 'passed',
        target: options.target,
        installRoot: (0, output_lifecycle_payload_policy_1.normalizePayloadPath)(options.installRoot),
        currentInstallDirectory: (0, output_lifecycle_payload_policy_1.normalizePayloadPath)(currentInstallDirectory),
        stagedInstallDirectory: (0, output_lifecycle_payload_policy_1.normalizePayloadPath)(stagedInstallDirectory),
        existingInstallPreserved: hadExistingInstall
            ? directoriesMatchSnapshot(currentInstallDirectory, existingInstallSnapshot) === false
            : true,
        installedFiles: (0, output_lifecycle_payload_policy_1.collectFiles)(currentInstallDirectory)
            .map((filePath) => (0, output_lifecycle_payload_policy_1.toRepositoryRelativePath)(currentInstallDirectory, filePath))
            .sort(),
    };
}
function validateInstallerPayloadShims(options) {
    const requiredShimPaths = options.target === 'macos-linux'
        ? [`bin/${options.installedCommandName}`]
        : [`bin/${options.installedCommandName}.cmd`, `bin/${options.installedCommandName}.ps1`];
    for (const shimPath of requiredShimPaths) {
        const fullPath = node_path_1.default.join(options.payloadDirectory, ...shimPath.split('/'));
        if (!node_fs_1.default.existsSync(fullPath) || !node_fs_1.default.statSync(fullPath).isFile()) {
            throw (0, errors_1.createUsageError)('Release installer validation requires executable launch shims before replacement.', [
                'Reason: missing-executable-shim',
                `Missing shim: ${shimPath}`,
                'Recovery: regenerate archive payloads with the configured launcher or shim files.',
                'Exit behavior: exits before existing installation replacement.',
            ]);
        }
        if (options.target === 'macos-linux' && (node_fs_1.default.statSync(fullPath).mode & 0o111) === 0) {
            throw (0, errors_1.createUsageError)('Release installer validation requires an executable POSIX launcher before replacement.', [
                'Reason: missing-executable-shim',
                `Missing executable bit: ${shimPath}`,
                'Recovery: regenerate archive payloads with an executable POSIX launcher.',
                'Exit behavior: exits before existing installation replacement.',
            ]);
        }
    }
}
function isNodeVersionAtLeast20(value) {
    const match = value.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[.-].*)?$/);
    if (match === null) {
        return false;
    }
    const major = Number(match[1]);
    const minor = Number(match[2]);
    const patch = Number(match[3]);
    return Number.isInteger(major) && Number.isInteger(minor) && Number.isInteger(patch) && major >= 20;
}
function snapshotDirectory(directoryPath) {
    if (!node_fs_1.default.existsSync(directoryPath)) {
        return [];
    }
    return (0, output_lifecycle_payload_policy_1.collectFiles)(directoryPath)
        .map((filePath) => ({
        path: (0, output_lifecycle_payload_policy_1.toRepositoryRelativePath)(directoryPath, filePath),
        contents: node_fs_1.default.readFileSync(filePath).toString('base64'),
    }))
        .sort((left, right) => left.path.localeCompare(right.path));
}
function directoriesMatchSnapshot(directoryPath, snapshot) {
    const currentSnapshot = snapshotDirectory(directoryPath);
    return JSON.stringify(currentSnapshot) === JSON.stringify(snapshot);
}
function isUsageErrorLike(value) {
    return (typeof value === 'object' &&
        value !== null &&
        'code' in value &&
        value.code === 'USAGE_ERROR' &&
        Array.isArray(value.details));
}
