// deno-lint-ignore-file no-explicit-any
import * as dntShim from "../../_dnt.shims.js";
import { SEP, colors, log, path, yamlParseFile, yamlStringify, } from "../../deps.js";
import { defaultScriptMetadata, } from "../../bootstrap/script_bootstrap.js";
import { languagesWithRawReqsSupport, } from "./script_common.js";
import { inferContentTypeFromFilePath } from "./script_common.js";
import { exts, findGlobalDeps } from "../commands/script/script.js";
import { FSFSElement, findCodebase, yamlOptions, } from "../commands/sync/sync.js";
import { generateHash, readInlinePathSync, getHeaders, writeIfChanged, } from "./utils.js";
import { replaceInlineScripts } from "../../windmill-utils-internal/src/inline-scripts/replacer.js";
import { extractInlineScripts as extractInlineScriptsForFlows } from "../../windmill-utils-internal/src/inline-scripts/extractor.js";
import { argSigToJsonSchemaType } from "../../windmill-utils-internal/src/parse/parse-schema.js";
import { getIsWin } from "./utils.js";
export class LockfileGenerationError extends Error {
    constructor(message) {
        super(message);
        this.name = "LockfileGenerationError";
    }
}
export async function generateAllMetadata() { }
function findClosestRawReqs(lang, remotePath, globalDeps) {
    let bestCandidate = undefined;
    if (lang) {
        Object.entries(globalDeps.get(lang) ?? {}).forEach(([k, v]) => {
            if (remotePath.startsWith(k) &&
                k.length >= (bestCandidate?.k ?? "").length) {
                bestCandidate = { k, v };
            }
        });
    }
    // @ts-ignore
    return bestCandidate?.v;
}
const TOP_HASH = "__flow_hash";
async function generateFlowHash(rawReqs, folder, defaultTs) {
    const elems = await FSFSElement(path.join(dntShim.Deno.cwd(), folder), [], true);
    const hashes = {};
    for await (const f of elems.getChildren()) {
        if (exts.some((e) => f.path.endsWith(e))) {
            let reqs;
            if (rawReqs) {
                // Get language name from path
                const lang = inferContentTypeFromFilePath(f.path, defaultTs);
                // Get lock for that language
                [, reqs] =
                    Object.entries(rawReqs).find(([lang2, _]) => lang == lang2) ?? [];
            }
            // Embed lock into hash
            hashes[f.path] = await generateHash((await f.getContentText()) + (reqs ?? ""));
        }
    }
    return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) };
}
export async function generateFlowLockInternal(folder, dryRun, workspace, opts, justUpdateMetadataLock, noStaleMessage, useRawReqs) {
    if (folder.endsWith(SEP)) {
        folder = folder.substring(0, folder.length - 1);
    }
    const remote_path = folder
        .replaceAll(SEP, "/")
        .substring(0, folder.length - ".flow".length);
    if (!justUpdateMetadataLock && !noStaleMessage) {
        log.info(`Generating lock for flow ${folder} at ${remote_path}`);
    }
    let rawReqs = undefined;
    if (useRawReqs) {
        // Find all dependency files in the workspace
        const globalDeps = await findGlobalDeps();
        // Find closest dependency files for this flow
        rawReqs = {};
        // TODO: PERF: Only include raw reqs for the languages that are in the flow
        languagesWithRawReqsSupport.map((lang) => {
            const dep = findClosestRawReqs(lang, folder, globalDeps);
            if (dep) {
                // @ts-ignore
                rawReqs[lang.language] = dep;
            }
        });
    }
    let hashes = await generateFlowHash(rawReqs, folder, opts.defaultTs);
    const conf = await readLockfile();
    if (await checkifMetadataUptodate(folder, hashes[TOP_HASH], conf, TOP_HASH)) {
        if (!noStaleMessage) {
            log.info(colors.green(`Flow ${remote_path} metadata is up-to-date, skipping`));
        }
        return;
    }
    else if (dryRun) {
        return remote_path;
    }
    if (useRawReqs) {
        log.warn("If using local lockfiles, following redeployments from Web App will inevitably override generated lockfiles by CLI. To maintain your script's lockfiles you will need to redeploy only from CLI. (Behavior is subject to change)");
        log.info((await blueColor())(`Found raw requirements (${languagesWithRawReqsSupport
            .map((l) => l.rrFilename)
            .join("/")}) for ${folder}, using it`));
    }
    const flowValue = (await yamlParseFile(folder + SEP + "flow.yaml"));
    if (!justUpdateMetadataLock) {
        const changedScripts = [];
        //find hashes that do not correspond to previous hashes
        for (const [path, hash] of Object.entries(hashes)) {
            if (path == TOP_HASH) {
                continue;
            }
            if (!(await checkifMetadataUptodate(folder, hash, conf, path))) {
                changedScripts.push(path);
            }
        }
        log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${folder}`);
        await replaceInlineScripts(flowValue.value.modules, async (path) => await dntShim.Deno.readTextFile(folder + SEP + path), log, folder + SEP, SEP, changedScripts);
        //removeChangedLocks
        flowValue.value = await updateFlow(workspace, flowValue.value, remote_path, rawReqs);
        const inlineScripts = extractInlineScriptsForFlows(flowValue.value.modules, {}, SEP, opts.defaultTs);
        inlineScripts.forEach((s) => {
            writeIfChanged(dntShim.Deno.cwd() + SEP + folder + SEP + s.path, s.content);
        });
        // Overwrite `flow.yaml` with the new lockfile references
        writeIfChanged(dntShim.Deno.cwd() + SEP + folder + SEP + "flow.yaml", yamlStringify(flowValue));
    }
    hashes = await generateFlowHash(rawReqs, folder, opts.defaultTs);
    await clearGlobalLock(folder);
    for (const [path, hash] of Object.entries(hashes)) {
        await updateMetadataGlobalLock(folder, hash, path);
    }
    log.info(colors.green(`Flow ${remote_path} lockfiles updated`));
}
// on windows, when using powershell, blue is not readable
export async function blueColor() {
    const isWin = await getIsWin();
    return isWin ? colors.black : colors.blue;
}
export async function generateScriptMetadataInternal(scriptPath, workspace, opts, dryRun, noStaleMessage, globalDeps, codebases, justUpdateMetadataLock) {
    const remotePath = scriptPath
        .substring(0, scriptPath.indexOf("."))
        .replaceAll(SEP, "/");
    const language = inferContentTypeFromFilePath(scriptPath, opts.defaultTs);
    const metadataWithType = await parseMetadataFile(remotePath, undefined);
    // read script content
    const scriptContent = await dntShim.Deno.readTextFile(scriptPath);
    const metadataContent = await dntShim.Deno.readTextFile(metadataWithType.path);
    const rrLang = languagesWithRawReqsSupport.find((l) => language == l.language);
    const rawReqs = findClosestRawReqs(rrLang, scriptPath, globalDeps);
    let hash = await generateScriptHash(rawReqs, scriptContent, metadataContent);
    if (await checkifMetadataUptodate(remotePath, hash, undefined)) {
        if (!noStaleMessage) {
            log.info(colors.green(`Script ${remotePath} metadata is up-to-date, skipping`));
        }
        return;
    }
    else if (dryRun) {
        return `${remotePath} (${language})`;
    }
    if (!justUpdateMetadataLock) {
        log.info(colors.gray(`Generating metadata for ${scriptPath}`));
    }
    const metadataParsedContent = metadataWithType?.payload;
    if (!opts.lockOnly && !justUpdateMetadataLock) {
        await updateScriptSchema(scriptContent, language, metadataParsedContent, scriptPath);
    }
    if (!opts.schemaOnly && !justUpdateMetadataLock) {
        const hasCodebase = findCodebase(scriptPath, codebases) != undefined;
        if (!hasCodebase) {
            await updateScriptLock(workspace, scriptContent, language, remotePath, metadataParsedContent, rawReqs);
        }
        else {
            metadataParsedContent.lock = "";
        }
    }
    else {
        metadataParsedContent.lock =
            "!inline " + remotePath.replaceAll(SEP, "/") + ".script.lock";
    }
    let metaPath = remotePath + ".script.yaml";
    let newMetadataContent = yamlStringify(metadataParsedContent, yamlOptions);
    if (metadataWithType.isJson) {
        metaPath = remotePath + ".script.json";
        newMetadataContent = JSON.stringify(metadataParsedContent);
    }
    const metadataContentUsedForHash = newMetadataContent;
    hash = await generateScriptHash(rawReqs, scriptContent, metadataContentUsedForHash);
    await updateMetadataGlobalLock(remotePath, hash);
    if (!justUpdateMetadataLock) {
        await dntShim.Deno.writeTextFile(metaPath, newMetadataContent);
    }
    return `${remotePath} (${language})`;
}
export async function updateScriptSchema(scriptContent, language, metadataContent, path) {
    // infer schema from script content and update it inplace
    const result = await inferSchema(language, scriptContent, metadataContent.schema, path);
    metadataContent.schema = result.schema;
    if (result.has_preprocessor) {
        metadataContent.has_preprocessor = result.has_preprocessor;
    }
    else {
        delete metadataContent.has_preprocessor;
    }
    if (result.no_main_func) {
        metadataContent.no_main_func = result.no_main_func;
    }
    else {
        delete metadataContent.no_main_func;
    }
}
async function updateScriptLock(workspace, scriptContent, language, remotePath, metadataContent, rawDeps) {
    if (!(languagesWithRawReqsSupport.some((l) => l.language == language) ||
        language == "deno" ||
        language == "rust" ||
        language == "ansible")) {
        return;
    }
    if (rawDeps) {
        log.info(`Generating script lock for ${remotePath} with raw deps`);
    }
    // generate the script lock running a dependency job in Windmill and update it inplace
    // TODO: update this once the client is released
    const extraHeaders = getHeaders();
    const rawResponse = await fetch(`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/dependencies`, {
        method: "POST",
        headers: {
            Cookie: `token=${workspace.token}`,
            "Content-Type": "application/json",
            ...extraHeaders,
        },
        body: JSON.stringify({
            raw_scripts: [
                {
                    raw_code: scriptContent,
                    language: language,
                    script_path: remotePath,
                },
            ],
            raw_deps: rawDeps,
            entrypoint: remotePath,
        }),
    });
    let responseText = "reading response failed";
    try {
        responseText = await rawResponse.text();
        const response = JSON.parse(responseText);
        const lock = response.lock;
        if (lock === undefined) {
            if (response?.["error"]?.["message"]) {
                throw new LockfileGenerationError(`Failed to generate lockfile: ${response?.["error"]?.["message"]}`);
            }
            throw new LockfileGenerationError(`Failed to generate lockfile: ${JSON.stringify(response, null, 2)}`);
        }
        const lockPath = remotePath + ".script.lock";
        if (lock != "") {
            await dntShim.Deno.writeTextFile(lockPath, lock);
            metadataContent.lock = "!inline " + lockPath.replaceAll(SEP, "/");
        }
        else {
            try {
                if (await dntShim.Deno.stat(lockPath)) {
                    await dntShim.Deno.remove(lockPath);
                }
            }
            catch (e) {
                log.info(colors.yellow(`Error removing lock file ${lockPath}: ${e}`));
            }
            metadataContent.lock = "";
        }
    }
    catch (e) {
        if (e instanceof LockfileGenerationError) {
            throw e;
        }
        throw new LockfileGenerationError(`Failed to generate lockfile:${rawResponse.statusText}, ${responseText}, ${e}`);
    }
}
export async function updateFlow(workspace, flow_value, remotePath, rawDeps) {
    let rawResponse;
    if (rawDeps != undefined) {
        log.info(colors.blue("Using raw requirements for flow dependencies"));
        // generate the script lock running a dependency job in Windmill and update it inplace
        const extraHeaders = getHeaders();
        rawResponse = await fetch(`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`, {
            method: "POST",
            headers: {
                Cookie: `token=${workspace.token}`,
                "Content-Type": "application/json",
                ...extraHeaders,
            },
            body: JSON.stringify({
                flow_value,
                path: remotePath,
                use_local_lockfiles: true,
                raw_deps: rawDeps,
            }),
        });
    }
    else {
        // Standard dependency resolution on the server
        const extraHeaders = getHeaders();
        rawResponse = await fetch(`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`, {
            method: "POST",
            headers: {
                Cookie: `token=${workspace.token}`,
                "Content-Type": "application/json",
                ...extraHeaders,
            },
            body: JSON.stringify({
                flow_value,
                path: remotePath,
            }),
        });
    }
    let responseText = "reading response failed";
    try {
        const res = (await rawResponse.json());
        if (rawResponse.status != 200) {
            const msg = res?.["error"]?.["message"];
            if (msg) {
                throw new LockfileGenerationError(`Failed to generate lockfile: ${msg}`);
            }
            throw new LockfileGenerationError(`Failed to generate lockfile: ${rawResponse.statusText}, ${responseText}`);
        }
        return res.updated_flow_value;
    }
    catch (e) {
        try {
            responseText = await rawResponse.text();
        }
        catch {
            responseText = "";
        }
        throw new Error(`Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}`);
    }
}
////////////////////////////////////////////////////////////////////////////////////////////
// below functions copied from Windmill's FE inferArgs function. TODO: refactor           //
////////////////////////////////////////////////////////////////////////////////////////////
export async function inferSchema(language, content, currentSchema, path) {
    let inferedSchema;
    if (language === "python3") {
        const { parse_python } = await import("../../wasm/py/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_python(content));
    }
    else if (language === "nativets") {
        const { parse_deno } = await import("../../wasm/ts/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_deno(content));
    }
    else if (language === "bun") {
        const { parse_deno } = await import("../../wasm/ts/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_deno(content));
    }
    else if (language === "deno") {
        const { parse_deno } = await import("../../wasm/ts/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_deno(content));
    }
    else if (language === "go") {
        const { parse_go } = await import("../../wasm/go/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_go(content));
    }
    else if (language === "mysql") {
        const { parse_mysql } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_mysql(content));
        inferedSchema.args = [
            { name: "database", typ: { resource: "mysql" } },
            ...inferedSchema.args,
        ];
    }
    else if (language === "bigquery") {
        const { parse_bigquery } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_bigquery(content));
        inferedSchema.args = [
            { name: "database", typ: { resource: "bigquery" } },
            ...inferedSchema.args,
        ];
    }
    else if (language === "oracledb") {
        const { parse_oracledb } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_oracledb(content));
        inferedSchema.args = [
            { name: "database", typ: { resource: "oracledb" } },
            ...inferedSchema.args,
        ];
    }
    else if (language === "snowflake") {
        const { parse_snowflake } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_snowflake(content));
        inferedSchema.args = [
            { name: "database", typ: { resource: "snowflake" } },
            ...inferedSchema.args,
        ];
    }
    else if (language === "mssql") {
        const { parse_mssql } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_mssql(content));
        inferedSchema.args = [
            { name: "database", typ: { resource: "ms_sql_server" } },
            ...inferedSchema.args,
        ];
    }
    else if (language === "postgresql") {
        const { parse_sql } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_sql(content));
        inferedSchema.args = [
            { name: "database", typ: { resource: "postgresql" } },
            ...inferedSchema.args,
        ];
    }
    else if (language === "duckdb") {
        const { parse_duckdb } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_duckdb(content));
    }
    else if (language === "graphql") {
        const { parse_graphql } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_graphql(content));
        inferedSchema.args = [
            { name: "api", typ: { resource: "graphql" } },
            ...inferedSchema.args,
        ];
    }
    else if (language === "bash") {
        const { parse_bash } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_bash(content));
    }
    else if (language === "powershell") {
        const { parse_powershell } = await import("../../wasm/regex/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_powershell(content));
    }
    else if (language === "php") {
        const { parse_php } = await import("../../wasm/php/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_php(content));
    }
    else if (language === "rust") {
        const { parse_rust } = await import("../../wasm/rust/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_rust(content));
    }
    else if (language === "csharp") {
        const { parse_csharp } = await import("../../wasm/csharp/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_csharp(content));
    }
    else if (language === "nu") {
        const { parse_nu } = await import("../../wasm/nu/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_nu(content));
    }
    else if (language === "ansible") {
        const { parse_ansible } = await import("../../wasm/yaml/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_ansible(content));
    }
    else if (language === "java") {
        const { parse_java } = await import("../../wasm/java/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_java(content));
    }
    else if (language === "ruby") {
        const { parse_ruby } = await import("../../wasm/ruby/windmill_parser_wasm.js");
        inferedSchema = JSON.parse(parse_ruby(content));
        // for related places search: ADD_NEW_LANG
    }
    else {
        throw new Error("Invalid language: " + language);
    }
    if (inferedSchema.type == "Invalid") {
        log.info(colors.yellow(`Script ${path} invalid, it cannot be parsed to infer schema.`));
        return {
            schema: defaultScriptMetadata().schema,
            has_preprocessor: false,
            no_main_func: false,
        };
    }
    if (!currentSchema) {
        currentSchema = {};
    }
    currentSchema.required = [];
    const oldProperties = JSON.parse(JSON.stringify(currentSchema?.properties ?? {}));
    currentSchema.properties = {};
    for (const arg of inferedSchema.args) {
        if (!(arg.name in oldProperties)) {
            currentSchema.properties[arg.name] = { description: "", type: "" };
        }
        else {
            currentSchema.properties[arg.name] = oldProperties[arg.name];
        }
        currentSchema.properties[arg.name] = sortObject(currentSchema.properties[arg.name]);
        argSigToJsonSchemaType(arg.typ, currentSchema.properties[arg.name]);
        currentSchema.properties[arg.name].default = arg.default;
        if (!arg.has_default && !currentSchema.required.includes(arg.name)) {
            currentSchema.required.push(arg.name);
        }
    }
    return {
        schema: currentSchema,
        has_preprocessor: inferedSchema.has_preprocessor,
        no_main_func: inferedSchema.no_main_func,
    };
}
function sortObject(obj) {
    return Object.keys(obj)
        .sort()
        .reduce((acc, key) => ({
        ...acc,
        [key]: obj[key],
    }), {});
}
////////////////////////////////////////////////////////////////////////////////////////////
// end of refactoring TODO                                                                //
////////////////////////////////////////////////////////////////////////////////////////////
export function replaceLock(o) {
    if (Array.isArray(o?.lock)) {
        o.lock = o.lock.join("\n");
    }
    if (o?.lock?.startsWith("!inline ")) {
        try {
            const lockPath = o?.lock?.split(" ")[1];
            o.lock = readInlinePathSync(lockPath);
        }
        catch (e) {
            log.info(colors.yellow(`Failed to read lockfile, doing as if it was empty: ${e}`));
            o.lock = "";
        }
    }
}
export async function parseMetadataFile(scriptPath, generateMetadataIfMissing) {
    let metadataFilePath = scriptPath + ".script.json";
    try {
        await dntShim.Deno.stat(metadataFilePath);
        return {
            path: metadataFilePath,
            payload: JSON.parse(await dntShim.Deno.readTextFile(metadataFilePath)),
            isJson: true,
        };
    }
    catch {
        try {
            metadataFilePath = scriptPath + ".script.yaml";
            await dntShim.Deno.stat(metadataFilePath);
            const payload = await yamlParseFile(metadataFilePath);
            replaceLock(payload);
            return {
                path: metadataFilePath,
                payload,
                isJson: false,
            };
        }
        catch {
            // no metadata file at all. Create it
            log.info((await blueColor())(`Creating script metadata file for ${metadataFilePath}`));
            metadataFilePath = scriptPath + ".script.yaml";
            let scriptInitialMetadata = defaultScriptMetadata();
            const lockPath = scriptPath + ".script.lock";
            scriptInitialMetadata.lock = "!inline " + lockPath;
            const scriptInitialMetadataYaml = yamlStringify(scriptInitialMetadata, yamlOptions);
            await dntShim.Deno.writeTextFile(metadataFilePath, scriptInitialMetadataYaml, {
                createNew: true,
            });
            await dntShim.Deno.writeTextFile(lockPath, "", {
                createNew: true,
            });
            if (generateMetadataIfMissing) {
                log.info((await blueColor())(`Generating lockfile and schema for ${metadataFilePath}`));
                try {
                    await generateScriptMetadataInternal(generateMetadataIfMissing.path, generateMetadataIfMissing.workspaceRemote, generateMetadataIfMissing, false, false, generateMetadataIfMissing.globalDeps, generateMetadataIfMissing.codebases, false);
                    scriptInitialMetadata = (await yamlParseFile(metadataFilePath));
                    if (!generateMetadataIfMissing.schemaOnly) {
                        replaceLock(scriptInitialMetadata);
                    }
                }
                catch (e) {
                    log.info(colors.yellow(`Failed to generate lockfile and schema for ${metadataFilePath}: ${e}`));
                }
            }
            return {
                path: metadataFilePath,
                payload: scriptInitialMetadata,
                isJson: false,
            };
        }
    }
}
const WMILL_LOCKFILE = "wmill-lock.yaml";
export async function readLockfile() {
    try {
        const read = await yamlParseFile(WMILL_LOCKFILE);
        if (typeof read == "object" && read != null) {
            return read;
        }
        else {
            throw new Error("Invalid lockfile");
        }
    }
    catch {
        const lock = { locks: {}, version: "v2" };
        await dntShim.Deno.writeTextFile(WMILL_LOCKFILE, yamlStringify(lock, yamlOptions));
        log.info(colors.green("wmill-lock.yaml created"));
        return lock;
    }
}
function v2LockPath(path, subpath) {
    if (subpath) {
        return `${path}+${subpath}`;
    }
    else {
        return path;
    }
}
export async function checkifMetadataUptodate(path, hash, conf, subpath) {
    if (!conf) {
        conf = await readLockfile();
    }
    if (!conf.locks) {
        return false;
    }
    const isV2 = conf?.version == "v2";
    if (isV2) {
        const current = conf.locks?.[v2LockPath(path, subpath)];
        return current == hash;
    }
    else {
        const obj = conf.locks?.[path];
        const current = subpath && typeof obj == "object" ? obj?.[subpath] : obj;
        return current == hash;
    }
}
export async function generateScriptHash(rawReqs, scriptContent, newMetadataContent) {
    return await generateHash((rawReqs ?? "") + scriptContent + newMetadataContent);
}
export async function clearGlobalLock(path) {
    const conf = await readLockfile();
    if (!conf?.locks) {
        conf.locks = {};
    }
    const isV2 = conf?.version == "v2";
    if (isV2) {
        // Remove the specific v2 lock entry
        const key = v2LockPath(path);
        if (conf.locks) {
            Object.keys(conf.locks).forEach((k) => {
                if (conf.locks) {
                    if (k.startsWith(key)) {
                        delete conf.locks[k];
                    }
                }
            });
        }
        await dntShim.Deno.writeTextFile(WMILL_LOCKFILE, yamlStringify(conf, yamlOptions));
    }
}
export async function updateMetadataGlobalLock(path, hash, subpath) {
    const conf = await readLockfile();
    if (!conf?.locks) {
        conf.locks = {};
    }
    const isV2 = conf?.version == "v2";
    if (isV2) {
        conf.locks[v2LockPath(path, subpath)] = hash;
    }
    else {
        if (subpath) {
            let prev = conf.locks[path];
            if (!prev || typeof prev != "object") {
                prev = {};
                conf.locks[path] = prev;
            }
            prev[subpath] = hash;
        }
        else {
            conf.locks[path] = hash;
        }
    }
    await dntShim.Deno.writeTextFile(WMILL_LOCKFILE, yamlStringify(conf, yamlOptions));
}
