"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const chalk_1 = __importDefault(require("chalk"));
const EdgeFunctionsBuilder_1 = __importDefault(require("./EdgeFunctionsBuilder"));
const fs_1 = require("fs");
const path_1 = require("path");
const esbuild_1 = require("esbuild");
const environment_1 = require("../../environment");
const EdgeFunctionCompiler_1 = require("../../runtime/edge-functions/EdgeFunctionCompiler");
const EdgeFunctionsWorkerManager_1 = __importDefault(require("../../runtime/edge-functions/EdgeFunctionsWorkerManager"));
const paths_1 = require("./paths");
const edgeFunctionUtils_1 = require("../../utils/edgeFunctionUtils");
const paths_2 = require("../paths");
const acorn = __importStar(require("acorn"));
const acorn_walk_1 = require("acorn-walk");
const zlib_1 = __importDefault(require("zlib"));
/**
 * The edge functions builder for NodeJS environment.
 * This builder uses esbuild to bundle the index file, so it allows to use imports in edge functions.
 * This builder builds the index file 'edgeFunctionsIndexCodeTemplate' with sdk, edge functions and bundle it.
 * The index file is then compiled to QuickJS bytecode.
 */
class EdgeFunctionsNodeBuilder extends EdgeFunctionsBuilder_1.default {
    /**
     * @param router The Router
     * @param baseDir The base of the project directory
     * @param options Additional options
     */
    constructor(router, baseDir, options = {
        bundleFilePath: (0, paths_1.getBundlePath)(),
        skipAcornValidation: false,
    }) {
        super(router);
        /**
         * A map of edge function paths to source code to upload for display in console.
         */
        this.sources = {};
        this.baseDir = (0, path_1.isAbsolute)(baseDir) ? baseDir : (0, path_1.join)(process.cwd(), (0, path_1.dirname)(baseDir));
        this.options = options;
    }
    /**
     * Returns the code with edge function import as string
     * which can later be inserted into edge functions map.
     * @returns
     */
    getFunctionCode(srcPath, isInitScript, failOnBuildError) {
        const expectedExportName = isInitScript ? 'handleHttpInit' : 'handleHttpRequest';
        const expectedParameters = isInitScript ? 'context' : 'request, context';
        // Accumulate the source code for deploys so it can be displayed in the console.
        const funcPath = (0, path_1.join)(this.baseDir, srcPath);
        try {
            this.sources[srcPath] = (0, fs_1.readFileSync)(funcPath, 'utf8');
        }
        catch (e) {
            // When the edge function file is missing, return an edge function that says the file is missing.
            console.error(chalk_1.default.red(`\nError reading edge function file '${srcPath}:\n${e}`));
            if (failOnBuildError) {
                throw e;
            }
            this.sources[srcPath] = `async function ${expectedExportName}(${expectedParameters}) {
        return new Response('Missing file ${srcPath}', {status: 404})
      }`.replace(/\n/g, ''); // should be one line
            return `const ${this.functionDefaultExportName} = ${this.sources[srcPath]}`;
        }
        if (!this.options.skipAcornValidation) {
            // Verify the edge function file is valid JavaScript and exports the required function.
            const ast = acorn.Parser.parse(this.sources[srcPath], {
                sourceType: 'module',
                ecmaVersion: 2020,
            });
            let foundExport = false;
            (0, acorn_walk_1.simple)(ast, {
                ExportNamedDeclaration(node) {
                    var _a, _b;
                    if ((node === null || node === void 0 ? void 0 : node.type) === 'ExportNamedDeclaration' &&
                        ((_a = node.declaration) === null || _a === void 0 ? void 0 : _a.type) === 'FunctionDeclaration' &&
                        ((_b = node.declaration.id) === null || _b === void 0 ? void 0 : _b.name) === expectedExportName) {
                        foundExport = true;
                    }
                },
            });
            if (!foundExport) {
                let msg = `Missing '${expectedExportName}' export in edge function file '${srcPath}'\n`;
                msg += `The file must include an export: e.g. export async function ${expectedExportName}(${expectedParameters}) { ... }`;
                console.log(chalk_1.default.red('\n' + msg));
                if (failOnBuildError)
                    throw new Error(msg);
            }
        }
        // Here we use a require('module/path') call instead of import * from 'module/path' to ensure that side effects
        // are only executed if the function is used. This may help reduce unexpected behavior
        // from side-effects caused by poorly written code.
        return `const ${this.functionDefaultExportName} = require(${JSON.stringify((0, path_1.join)(this.baseDir, srcPath))}).${expectedExportName};`;
    }
    /**
     * Returns the code of prebuilt the index code template from the file system.
     * @returns
     */
    async getIndexCodeTemplate() {
        return (0, fs_1.readFileSync)((0, path_1.join)(__dirname, this.indexCodeTemplateFilename), 'utf8');
    }
    /**
     * Bundles the generated index code into a single file bundle
     * and returns the bundled code
     * @returns
     */
    async bundleIndexCode(indexFilePath = (0, paths_1.getIndexPath)(), failOnBuildError, esbuildLogLevel = 'warning') {
        const indexCode = await this.getIndexCode(failOnBuildError);
        (0, fs_1.writeFileSync)(indexFilePath, indexCode);
        // We bundle the index file to a single file bundle
        // so the all imports in edge functions are resolved and added to bundle.
        const { code: bundledIndexCode } = await this.bundle(indexFilePath, esbuildLogLevel);
        return bundledIndexCode;
    }
    /**
     * Builds all edge functions referenced by the specified rules and compiles them.
     * Returns a Buffer with the compiled bytecode for the edge functions index file
     * or undefined if there are no edge functions.
     * @param options Additional options with custom paths
     * @returns
     */
    async build(options = {}) {
        // Check if there are any edge function features at all.
        if (!(0, edgeFunctionUtils_1.containsEdgeFunctionFeature)(this.router.rules))
            return undefined;
        options.indexFilePath = options.indexFilePath || (0, paths_1.getIndexPath)();
        options.bytecodePath = options.bytecodePath || (0, paths_1.getQuickJSBytecodePath)();
        let bundledIndexCode;
        try {
            bundledIndexCode = await this.bundleIndexCode(options.indexFilePath, options.failOnBuildError || false, options.supressEsbuildOutput ? 'silent' : 'warning');
        }
        catch (e) {
            if (options.failOnBuildError) {
                // When deploying, we want to fail the deploy if there is a bundling error.
                throw e;
            }
            // If there is a bundling error, the bundler will display the error for us. So just return
            // undefined here. This prevents the CLI from exiting when edge functions have a bundling error.
            return undefined;
        }
        // Use relative path when compiling so stack traces do not have absolute paths which
        // may otherwise leak information about the developer's machine.
        const relativePath = (0, path_1.relative)(process.cwd(), (0, paths_1.getBundlePath)());
        let bytecode = await (0, EdgeFunctionCompiler_1.compileEdgeFunction)(bundledIndexCode, relativePath);
        // We do not gzip when running the local dev server because we would have to immediately
        // unzip it to load it into WebAssembly. This would be a waste of CPU cycles.
        if (options.gzipBytecode) {
            // When gzipping the edge functions bundle, we prepend the string 'gzip' before base64 encoding
            // to indicate that the bundle is gzipped. Without the prefix, it is just wasm byte code.
            bytecode = Buffer.concat([Buffer.from('gzip'), zlib_1.default.gzipSync(bytecode)]);
        }
        (0, fs_1.writeFileSync)(options.bytecodePath, bytecode);
        // On successful build, reset the edge function memory so it will use cold start.
        EdgeFunctionsWorkerManager_1.default.clearWasmInitialMemory();
        return bytecode;
    }
    /**
     * Bundles the edge functions index at the specified path into a single file bundle.
     * @param indexPath
     * @returns
     */
    async bundle(indexPath, esbuildLogLevel) {
        const esBuildOptions = {
            entryPoints: [indexPath],
            bundle: true,
            minify: (0, environment_1.isProductionBuild)(),
            // format: esm, with mainFields: [ module, main] gives the same output as
            // platform: node, but without allowing NodeJs builtins like `url` to be imported.
            // these values must be in sync with package.json "config.esbuild_opts_edgefunction_esm" section.
            format: 'esm',
            platform: 'neutral',
            target: 'es2020',
            mainFields: ['module', 'main'],
            sourcemap: false,
            sourceRoot: (0, environment_1.isCloud)() ? (0, paths_2.getJsInternalPath)() : '',
            write: false,
            logLevel: esbuildLogLevel,
        };
        const result = await (0, esbuild_1.build)(esBuildOptions);
        const { TextDecoder } = require('util');
        const textDecoder = new TextDecoder();
        let code = '';
        for (let out of result.outputFiles || []) {
            code += textDecoder.decode(out.contents);
        }
        // Always persist the bundle for debugging purposes, unless explicitly disabled.
        if (this.options.bundleFilePath) {
            (0, fs_1.mkdirSync)((0, path_1.parse)(this.options.bundleFilePath).dir, { recursive: true });
            (0, fs_1.writeFileSync)(this.options.bundleFilePath, code);
        }
        return { code };
    }
    /**
     * Converts the edge function into cjs
     * @param code
     * @returns cjs
     */
    async convertToCjs(code) {
        const esTransformOptions = {
            minify: false,
            target: 'node16.0',
            format: 'esm',
        };
        const result = await (0, esbuild_1.transform)(code, esTransformOptions);
        return result.code;
    }
}
exports.default = EdgeFunctionsNodeBuilder;
