"use strict";
/* istanbul ignore file */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = require("fs-extra");
const shelljs_1 = __importDefault(require("shelljs"));
const path_1 = require("path");
const bundle_esbuild_1 = require("./bundle-esbuild");
const globby_1 = __importDefault(require("globby"));
const chalk_1 = __importDefault(require("chalk"));
const Router_1 = __importDefault(require("../router/Router"));
const paths_1 = require("./paths");
const init_1 = require("../init");
const constants_1 = require("../constants");
const listSources_1 = __importDefault(require("./listSources"));
const parseRouter_1 = __importDefault(require("../parser/parseRouter"));
const config_1 = require("../config");
const PropertyContext_1 = __importDefault(require("../runtime/PropertyContext"));
const getProjectType_1 = __importDefault(require("../utils/getProjectType"));
const createEdgeConfig_1 = __importDefault(require("../runtime/createEdgeConfig"));
const bundle_sw_1 = require("./bundle-sw");
const extractStaticAssets_1 = require("./converters/extractStaticAssets");
const workbox_build_1 = require("workbox-build");
const watch_1 = __importDefault(require("../utils/watch"));
const EdgeFunctionsNodeBuilder_1 = __importDefault(require("./edge-functions/EdgeFunctionsNodeBuilder"));
const edgeFunctionUtils_1 = require("../utils/edgeFunctionUtils");
/**
 * The class helps you bundle apps for deployment on Edgio.
 */
class DeploymentBuilder {
    constructor(appDir = process.cwd()) {
        // This structure has connasence of structure, names and types with Edgio subaccount deployment lambda.
        this.assetExpiration = {};
        this.appDir = appDir;
        this.buildDir = (0, path_1.join)(this.appDir, paths_1.EDGIO_DIR);
        this.lambdaDir = (0, path_1.join)(this.appDir, paths_1.LAMBDA_DIR);
        this.jsInternalDir = (0, path_1.join)(this.appDir, paths_1.JS_INTERNAL_DIR);
        this.jsAppDir = (0, path_1.join)(this.appDir, paths_1.JS_APP_DIR);
        this.staticAssetsDir = (0, path_1.join)(this.appDir, paths_1.ASSETS_DIR);
        this.permanentStaticAssetsDir = (0, path_1.join)(this.appDir, paths_1.PERMANENT_ASSETS_DIR);
        this.sourcesDir = (0, path_1.join)(this.appDir, paths_1.SOURCES_DIR);
        this.tempDir = (0, path_1.join)(this.appDir, paths_1.TMP_DIR);
    }
    /**
     * Logs a message to the console if process.env.debug is set to true
     * @param msg The message to log
     */
    log(...msg) {
        if (process.env.DEBUG === 'true') {
            console.log('[Edgio BUILD]:', ...msg);
        }
    }
    /**
     * Runs a command.
     * @param command The command to run
     * @param options Options for shelljs
     * @return {Promise} A promise that resolves when the command is finished.
     */
    exec(command, options = {}) {
        return new Promise((resolve, reject) => {
            console.log(chalk_1.default.grey(`[Edgio] Running command: ${command}`));
            shelljs_1.default.exec(command, options, (code, stdout, stderr) => {
                if (code != 0)
                    return reject(new Error(stderr));
                return resolve(stdout);
            });
        });
    }
    /**
     * Adds an asset to the lambda JS directory '.edgio/lambda/internal' with Edgio's code.
     * This function is used only by the DeploymentBuilder itself and not exposed to the connectors.
     * @param src The source path
     * @param dest An optional destination path within the '.edgio/lambda/internal' directory.
     * If not provided, the destination path will be the same as the source path.
     * @param options Options for copySync
     * @return a self reference, suitable for chaining
     * @private
     */
    addInternalJSAsset(src, dest, options) {
        dest = dest || (0, path_1.relative)(process.cwd(), src);
        this.copySync(src, (0, path_1.join)(this.jsInternalDir, dest), options);
        return this;
    }
    /**
     * Adds an asset to the lambda JS directory '.edgio/lambda/app' with user's code.
     * @param src The source path
     * @param dest An optional destination path within the '.edgio/lambda/app' directory.
     * If not provided, the destination path will be the same as the source path.
     * @param options Options for copySync
     * @return a self reference, suitable for chaining
     */
    addJSAsset(src, dest, options) {
        dest = dest || (0, path_1.relative)(process.cwd(), src);
        this.copySync(src, (0, path_1.join)(this.jsAppDir, dest), options);
        return this;
    }
    /**
     * Copies a file or directory into .edgio/src
     * @param src
     */
    addSource(src) {
        const dest = (0, path_1.relative)(this.appDir, src);
        this.copySync(src, (0, path_1.join)(this.sourcesDir, dest));
        return this;
    }
    /**
     * Adds source files to the bundle based on:
     *
     *  - The `sources` property of edgio, or, if not present
     *  - All files committed to git.
     *
     * If sources is not present in edgio, and the project does not use git, an error will be thrown.
     */
    async addSources(config) {
        if (config.sources) {
            process.stdout.write('> Copying sources into bundle... ');
            for (let file of await (0, listSources_1.default)(this.appDir, config.sources)) {
                this.addSource(file);
            }
            process.stdout.write('done.\n');
        }
    }
    /**
     * Adds the prod entry point from the connector attribute in edgio
     * @return a self reference, suitable for chaining
     */
    async addProdEntrypoint(connector) {
        /* set by @edgio/cli/build when falling back to framework detection */
        connector = connector || process.env.EDGIO_CONNECTOR;
        // Try to locate the prod entry file
        const prod = (0, paths_1.getEntryPoint)('prod', this.appDir, connector);
        if (prod !== undefined)
            this.addInternalJSAsset(prod, (0, paths_1.pathForBackend)(constants_1.BACKENDS.js));
        else {
            // If we didn't found connector's prod.js file, we fallback
            // to core's prod.js file, as we allow connectors to NOT
            // specify a prod.js file. We cannot do this in a runtime,
            // as AWS runtime doesn't contain "node_modules", so we cannot
            // fallback to @edgio/core, so we include it on deployment build
            const coreProd = (0, paths_1.getEntryPoint)('prod', this.appDir);
            if (coreProd !== undefined)
                this.addInternalJSAsset(coreProd, (0, paths_1.pathForBackend)(constants_1.BACKENDS.js));
            else
                throw new Error('No production entry point found');
        }
        return this;
    }
    /**
     * Adds a folder or file as static asset.
     * Also register directories as asset aliases when they contains an index.html file
     *
     * @param src The source path
     * @param dest An optional destination path in s3. If not provided, the destination path will be the same as the source path.
     * @return a self reference, suitable for chaining
     */
    addStaticAsset(src, dest, { permanent = false, exclude = [] } = {}) {
        dest = dest || (0, path_1.relative)(process.cwd(), src);
        permanent = permanent && process.env.EDGIO_DISABLE_PERMANENT_ASSETS !== 'true';
        const staticAssetDir = permanent ? this.permanentStaticAssetsDir : this.staticAssetsDir;
        const absoluteDest = (0, path_1.join)(staticAssetDir, dest);
        this.copySync(src, absoluteDest, {
            filter: file => !exclude.some(excludedFile => file.indexOf(excludedFile) >= 0),
        });
        return this;
    }
    /**
     * Ensures that all path/to/dir/index.html files in S3 have an alias for /path/to/dir. This
     * allows the edge to simply forward URLs like /foo to S3 and have S3 respond with /foo/index.html.
     * THis is critical to the functionality of Gatsby, Next.js, and other static site builders.
     */
    createStaticAssetAliases(router) {
        process.stdout.write('> Writing static asset aliases... ');
        const assetAliases = {};
        const dirs = [this.staticAssetsDir, this.permanentStaticAssetsDir];
        dirs.forEach(dir => {
            const files = globby_1.default.sync('**/index.html', {
                cwd: dir,
                onlyFiles: true,
                dot: true,
            });
            files.forEach(indexFile => {
                const indexDirectory = (0, path_1.dirname)(indexFile);
                assetAliases[indexDirectory] = indexFile;
                // Create an alias with a trailing slash for router.static/dir directories,
                // so the index file in directory can be correctly served at the root path /.
                if (indexDirectory in router.getStaticAssetManifest()) {
                    assetAliases[`${indexDirectory}/`] = indexFile;
                }
            });
        });
        this.writeFileSync((0, path_1.join)(this.buildDir, constants_1.EDGIO_ASSET_ALIASES_FILE), JSON.stringify(assetAliases, null, '  '));
        process.stdout.write('done.\n');
    }
    /**
     * Ensures all assets in the "all", "js", and "ts" subdirectories of `defaultAppPath` are either already
     * present in the user's app or are copied over from `defaultAppPath`.
     * @param defaultAppPath
     * @return A self reference, suitable for chaining
     */
    addDefaultAppResources(defaultAppPath, replacer) {
        const addResources = (fromPath) => {
            for (let file of globby_1.default.sync('**/*', {
                cwd: fromPath,
            })) {
                if (!(0, fs_extra_1.existsSync)((0, path_1.join)(this.appDir, file))) {
                    console.log(`> ${chalk_1.default.green(file)} not found, creating...`);
                    const source = (0, path_1.join)(fromPath, file);
                    const target = (0, path_1.join)(this.appDir, file);
                    replacer
                        ? (0, fs_extra_1.writeFileSync)(target, replacer((0, fs_extra_1.readFileSync)(source).toString()))
                        : this.copySync(source, target);
                }
            }
        };
        const renameResource = (from, to) => {
            if ((0, fs_extra_1.existsSync)(from) && !(0, fs_extra_1.existsSync)(to)) {
                (0, fs_extra_1.rename)(from, to);
            }
        };
        const hasMjsResources = (0, fs_extra_1.existsSync)((0, path_1.join)(defaultAppPath, 'mjs'));
        const hasTsResources = (0, fs_extra_1.existsSync)((0, path_1.join)(defaultAppPath, 'ts'));
        const isTypeModule = (0, getProjectType_1.default)() === 'module';
        const isTsProject = (0, fs_extra_1.existsSync)((0, path_1.join)(process.cwd(), 'tsconfig.json'));
        // Add resources for all project types
        addResources((0, path_1.join)(defaultAppPath, 'all'));
        // Add resources for specific project types
        if (isTsProject && hasTsResources) {
            addResources((0, path_1.join)(defaultAppPath, 'ts'));
        }
        else if (isTypeModule && hasMjsResources) {
            addResources((0, path_1.join)(defaultAppPath, 'mjs'));
        }
        else {
            addResources((0, path_1.join)(defaultAppPath, 'js'));
        }
        if (isTypeModule) {
            renameResource((0, path_1.join)(process.cwd(), constants_1.EDGIO_CONFIG_FILE), (0, path_1.join)(process.cwd(), constants_1.EDGIO_CONFIG_FILE.replace('.js', '.cjs')));
        }
        return this;
    }
    /**
     * Copies all the edgio scripts into the package.json file, if they don't already exist.
     * @param scriptsOverride Used to pass framework-specific script overrides
     * @param overrideExisting Set to true to override existing edgio script elements
     * @return A self reference, suitable for chaining
     */
    addDefaultEdgioScripts(scriptsOverride = {}, overrideExisting = false) {
        (0, init_1.addToPackageJson)({
            scripts: {
                'edgio:dev': 'edgio dev',
                'edgio:build': 'edgio build',
                'edgio:deploy': 'edgio deploy',
                ...scriptsOverride,
            },
        }, overrideExisting);
        console.log(chalk_1.default.bold('\nAdded the following scripts to package.json:\n'));
        console.log(`    ${chalk_1.default.cyan('edgio:dev')} - Simulate your app on Edgio locally.`);
        console.log(`    ${chalk_1.default.cyan('edgio:build')} - Build your app for deployment on Edgio.`);
        console.log(`    ${chalk_1.default.cyan('edgio:deploy')} - Build and deploy your app on Edgio.`);
        return this;
    }
    /**
     * Copies a file
     * @param from the source path
     * @param to the destination path
     */
    copySync(from, to, copySyncOptions = {}) {
        if ((0, fs_extra_1.existsSync)(from)) {
            this.log(`COPY: ${from.replace(this.appDir + '/', '')} -> ${to.replace(this.appDir + '/', '')}`);
            return (0, fs_extra_1.copySync)(from, to, copySyncOptions);
        }
        else {
            console.warn(`${chalk_1.default.yellow('Warning:')} The following file/directory path referenced in your Edgio router configuration does not exist: ${(0, path_1.relative)(process.cwd(), from)}. Requests matching this route will return a 404 status.`);
        }
    }
    /**
     * Writes a file
     * @param to the destination path
     * @param content the contents of the file
     * @param encoding the encoding
     */
    writeFileSync(to, content) {
        this.log(`WRITE: ${to.replace(this.appDir + '/', '')}`);
        (0, fs_extra_1.mkdirpSync)((0, path_1.dirname)(to));
        return (0, fs_extra_1.writeFileSync)(to, content, 'utf8');
    }
    /**
     * Deletes all files in a directory
     * @param dir The directory to empty
     */
    emptyDirSync(dir) {
        this.log(`EMPTY: ${dir.replace(this.appDir + '/', '')}`);
        return (0, fs_extra_1.emptyDirSync)(dir);
    }
    /**
     * Reads a file
     * @param path the file path
     * @param options Options for `fs.readFileSync`
     * @returns The file contents
     */
    readFileSync(path, { encoding = 'utf8' } = {}) {
        this.log(`READ: ${path.replace(this.appDir + '/', '')}`);
        return (0, fs_extra_1.readFileSync)(path, { encoding });
    }
    /**
     * Deletes a file
     * @param path The file to delete
     */
    removeSync(path) {
        this.log(`DELETE: ${path.replace(this.appDir + '/', '')}`);
        return (0, fs_extra_1.removeSync)(path);
    }
    /**
     * Deletes the output of the previous build.
     */
    clearPreviousBuildOutput() {
        // clear .edgio directory
        this.emptyDirSync(this.buildDir);
        return this;
    }
    /**
     * Edgio version of @edgio/core.
     * @type {String}
     */
    static get platformVersion() {
        return (process.env[constants_1.EDGIO_ENV_VARIABLES.versionOverride] ||
            require('../package.json').version.replace(/\+.*/, '')); // remove the suffix added by yalc so that the last deployed lamba is used when testing an unreleased version of Edgio core
    }
    /**
     * Copies all of the standard assets into the JS and static asset bundles.  These are the same for any framework.
     */
    async build(options) {
        const { router, config, srcRouterPath } = await this.buildSources();
        await this.addMetadataFiles(srcRouterPath, router);
        await this.createStaticAssetBundle(config, router);
        await this.createServerlessBundle(config, router, options);
        await this.createSourcesBundle(config, srcRouterPath);
        (0, fs_extra_1.emptyDirSync)(this.tempDir);
    }
    /**
     * Creates the .edgio/lambda directory which gets deployed to the Edgio's serverless cloud.
     * @param param0
     * @param router
     * @param options
     */
    async createServerlessBundle({ serverless, connector }, router, options) {
        (0, fs_extra_1.mkdirpSync)(this.lambdaDir);
        (0, fs_extra_1.mkdirpSync)(this.jsAppDir);
        (0, fs_extra_1.mkdirpSync)(this.jsInternalDir);
        // Adds the package.json file to the serverless bundle and adjusts deps
        this.preparePackageJson();
        // Add included files to the serverless bundle.
        if (serverless === null || serverless === void 0 ? void 0 : serverless.include) {
            this.addAssets(serverless.include, this.jsAppDir);
        }
        // Add production node_modules to the serverless bundle.
        if (serverless === null || serverless === void 0 ? void 0 : serverless.includeNodeModules) {
            await this.includeNodeModules();
        }
        // Add the prod entry point from the connector attribute in edgio.config.js
        if (!(options === null || options === void 0 ? void 0 : options.excludeProdEntryPoint)) {
            await this.addProdEntrypoint(connector);
        }
        // Add bundled lambda handler to internal js directory,
        // so __dirname in source code will still point to the correct directory with all other files
        // even after the esbuild bundling process
        this.copySync((0, path_1.join)(__dirname, '..', 'lambda', 'handler.dist.js'), (0, path_1.join)(this.jsInternalDir, 'handler.cjs'));
        this.copySync((0, path_1.join)(__dirname, '..', 'lambda', 'handler.dist.js.map'), (0, path_1.join)(this.jsInternalDir, 'handler.cjs.map'));
        // Add bundled lambda server to internal js directory
        this.copySync((0, path_1.join)(__dirname, '..', 'lambda', 'server.dist.js'), (0, path_1.join)(this.jsInternalDir, 'server.cjs'));
        this.copySync((0, path_1.join)(__dirname, '..', 'lambda', 'server.dist.js.map'), (0, path_1.join)(this.jsInternalDir, 'server.cjs.map'));
        // Re-export the internal handler from the root of the lambda directory
        this.writeFileSync((0, path_1.join)(this.lambdaDir, 'handler.js'), `module.exports = require('./internal/handler.cjs')`);
        // add default package.json for our lambda handler,
        // so it doesn't load the nearest package.json from the parent directory
        this.writeFileSync((0, path_1.join)(this.lambdaDir, 'package.json'), JSON.stringify({
            name: 'edgio-lambda',
            type: 'commonjs',
        }, null, '  '));
        this.writeFileSync((0, path_1.join)(this.jsInternalDir, paths_1.STATIC_ASSET_MANIFEST_FILE_NAME), JSON.stringify(router.getStaticAssetManifest(), null, '  '));
        // Check if there are any edge function features
        if ((0, edgeFunctionUtils_1.containsEdgeFunctionFeature)(router.rules)) {
            // This file is used by the EdgeFunctionsManager to create Workers
            const workerFilename = 'EdgeFunctionWorker.js';
            this.copySync((0, path_1.join)(__dirname, '..', 'runtime', 'edge-functions', workerFilename), (0, path_1.join)(this.jsInternalDir, workerFilename));
            // QuickJS runtime is required by our simulator
            const quickjsRuntimeFilename = 'quickjs-llrt-runtime.wasm';
            this.copySync((0, path_1.join)(__dirname, '..', 'runtime', 'edge-functions', quickjsRuntimeFilename), (0, path_1.join)(this.jsInternalDir, quickjsRuntimeFilename));
        }
    }
    /**
     * Creates the .edgio/s3 and .edgio/s3-permanent directories, which get deployed to S3.
     * @param config
     * @param router
     */
    async createStaticAssetBundle(config, router) {
        var _a;
        (0, fs_extra_1.mkdirpSync)(this.staticAssetsDir);
        (0, fs_extra_1.mkdirpSync)(this.permanentStaticAssetsDir);
        // This is adding static assets from the config, check whether to leave this in or not
        (_a = config.staticAssets) === null || _a === void 0 ? void 0 : _a.forEach(entry => this.addStaticAssets(entry));
        // This is adding static assets from the router, from all rewirite rules
        const rewritesStaticAssests = (0, extractStaticAssets_1.extractStaticAssets)(router.rules);
        rewritesStaticAssests.forEach(entry => this.addStaticAssets(entry));
        this.createStaticAssetAliases(router);
    }
    async createSourcesBundle(config, srcRouterPath) {
        // store the router source so we can show it in console and download for debugging purposes
        this.addSource(srcRouterPath);
        const configPath = [
            (0, path_1.join)(this.appDir, constants_1.EDGIO_CONFIG_FILE),
            (0, path_1.join)(this.appDir, constants_1.EDGIO_CONFIG_FILE.replace('.js', '.cjs')),
            (0, path_1.join)(this.appDir, constants_1.EDGIO_CONFIG_FILE),
            (0, path_1.join)(this.appDir, constants_1.EDGIO_CONFIG_FILE.replace('.js', '.cjs')),
        ].find(fs_extra_1.existsSync);
        // add edgio.config.js to src
        if (configPath)
            this.addSource(configPath);
        // When run with --sources, add the source code to the build for debugging purposes based on the sources property of edgio.config.js
        if (process.env.EDGIO_INCLUDE_SOURCES === 'true') {
            await this.addSources(config);
        }
    }
    /**
     * Copies files to .edgio/s3 or .edgio/s3-permanent based on an staticAssets entry from
     * edgio.config.js.
     * @param config
     */
    addStaticAssets(entry) {
        const destination = entry.permanent ? this.permanentStaticAssetsDir : this.staticAssetsDir;
        this.addAssets(entry.glob, destination);
    }
    /**
     * Copies matching files to the destination directory
     * @param globs
     * @param destinationDir
     */
    addAssets(globs, destinationDir) {
        globby_1.default.sync(globs).forEach(file => {
            const destination = (0, path_1.join)(destinationDir, file);
            this.copySync(file, destination);
        });
    }
    /**
     * Builds the router and config.
     * @returns
     */
    async buildSources() {
        await (0, bundle_esbuild_1.bundleRouterAndConfig)();
        const cwd = process.cwd();
        const config = (0, config_1.getConfig)();
        const srcRouterPath = (0, paths_1.getSrcRouterPath)(config);
        const destRouterPath = (0, path_1.join)(this.jsInternalDir, paths_1.ROUTES_FILE_NAME);
        try {
            return {
                router: Router_1.default.load(destRouterPath),
                srcRouterPath,
                destRouterPath,
                config,
            };
        }
        finally {
            process.chdir(cwd);
        }
    }
    async addMetadataFiles(srcRouterPath, router) {
        const routerInfo = (0, parseRouter_1.default)(srcRouterPath);
        // routerInfo.json
        this.writeFileSync((0, path_1.join)(this.buildDir, 'routerInfo.json'), JSON.stringify({ location: (0, path_1.relative)(this.appDir, srcRouterPath), info: routerInfo }, null, 2));
        // EDGIO_VERSION
        this.writeFileSync((0, path_1.join)(this.buildDir, 'EDGIO_VERSION'), DeploymentBuilder.platformVersion);
        // static-asset-expiration.json
        this.writeFileSync((0, path_1.join)(this.buildDir, paths_1.STATIC_ASSET_EXPIRATION_FILE_NAME), JSON.stringify(this.assetExpiration, null, 2));
        // Build edge functions
        const edgeFunctionsNodeBuilder = new EdgeFunctionsNodeBuilder_1.default(router, this.appDir);
        const edgeFunctionsByteCode = await edgeFunctionsNodeBuilder.build({
            failOnBuildError: true,
            gzipBytecode: true,
        });
        // Write edge function source code to the bundle so we can display it in the Edgio console.
        const edgeFunctionSources = {
            sources: edgeFunctionsNodeBuilder.sources,
            // Leave a hint on the edge function init script
            initScript: router.routerOptions.edge_function_init_script,
        };
        this.writeFileSync((0, path_1.join)((0, paths_1.getJsInternalPath)(), paths_1.EDGE_FUNCTION_SOURCES_FILENAME), JSON.stringify(edgeFunctionSources, null, 2));
        // Write the edge control json file for configuring the CDN last, because it includes EDGE_FUNCTION_SOURCES_FILENAME
        this.writeFileSync((0, path_1.join)(this.buildDir, 'edge-control.json'), this.createEdgeControlJson(router, edgeFunctionsByteCode));
    }
    /**
     * Returns true if the project uses yarn for bundling
     * @returns
     */
    isYarn() {
        return (0, fs_extra_1.existsSync)((0, path_1.join)(process.cwd(), 'yarn.lock'));
    }
    /**
     * Adds all dependencies from package.json to the JS bundle (excluding devDependencies)
     */
    async includeNodeModules() {
        const args = [];
        const envVars = { ...process.env };
        const yalc = process.env.YALC === 'true';
        let cmd = 'npm'; // default to npm
        let lockFile;
        // Set Env variables for the lambda functions
        envVars['npm_config_target_arch'] = 'x64';
        envVars['npm_config_target_platform'] = 'linux';
        // yarn
        if (this.isYarn()) {
            lockFile = 'yarn.lock';
            cmd = 'yarn';
            if (!yalc) {
                args.push('install', '--frozen-lockfile');
            }
        }
        else {
            args.push('--target_arch=x64');
            args.push('--target_platform=linux');
            if ((0, fs_extra_1.existsSync)((0, path_1.join)(process.cwd(), 'package-lock.json'))) {
                lockFile = 'package-lock.json';
                args.push(yalc ? 'install' : 'ci');
            }
            else {
                args.push('install');
            }
        }
        args.push('--production');
        process.stdout.write('> Adding production dependencies to the bundle... ');
        if (lockFile && !yalc) {
            this.copySync((0, path_1.join)(this.appDir, lockFile), (0, path_1.join)(this.jsAppDir, lockFile));
        }
        await this.exec(`${cmd} ${args.join(' ')}`, { cwd: this.jsAppDir, env: envVars });
        process.stdout.write('done.\n');
    }
    /**
     * Copies the package.json file into the lambda and adjust all dependencies that
     * use file paths
     */
    preparePackageJson() {
        var _a, _b;
        const src = (0, path_1.join)(this.appDir, 'package.json');
        const dest = (0, path_1.join)(this.jsAppDir, 'package.json');
        // Create a default package.json if there isn't any in the project root
        if (!(0, fs_extra_1.existsSync)(src)) {
            (0, fs_extra_1.writeFileSync)(dest, JSON.stringify({ name: 'app' }, null, '  '), 'utf8');
            return;
        }
        // Copy package.json from the project root to the lambda directory
        // if it's not already copied with the build folder of the app.
        if (!(0, fs_extra_1.existsSync)(dest)) {
            this.copySync(src, dest);
        }
        const srcPackageJson = JSON.parse((0, fs_extra_1.readFileSync)(src, 'utf8'));
        const destPackageJson = JSON.parse((0, fs_extra_1.readFileSync)(dest, 'utf8'));
        const { dependencies } = srcPackageJson;
        if (dependencies) {
            for (let name in dependencies) {
                const value = dependencies[name];
                if (value.startsWith('file:')) {
                    // convert file:./path/to/module to file:../../path/to/module
                    const newValue = value.replace(/file:/, `file:..${path_1.sep}..${path_1.sep}`);
                    this.log(`Rewriting ${name}: ${value} => ${newValue}`);
                    dependencies[name] = newValue;
                }
            }
        }
        // Add transformed production dependencies from the project root package.json
        // to package.json in the .edgio/lambda/app directory.
        destPackageJson.dependencies = dependencies;
        destPackageJson === null || destPackageJson === void 0 ? true : delete destPackageJson.devDependencies;
        // We don't want to run these project scripts after installation,
        // but still allow to run scripts of installed dependencies.
        // That's why we need to remove them instead of using --ignore-scripts npm/yarn flag.
        (_a = destPackageJson === null || destPackageJson === void 0 ? void 0 : destPackageJson.scripts) === null || _a === void 0 ? true : delete _a.prepare;
        (_b = destPackageJson === null || destPackageJson === void 0 ? void 0 : destPackageJson.scripts) === null || _b === void 0 ? true : delete _b.postinstall;
        (0, fs_extra_1.writeFileSync)(dest, JSON.stringify(destPackageJson, null, '  '), 'utf8');
    }
    /**
     * Configures the TTL for a static asset. Once the TTL has expired, it will be refreshed
     * by forwarding requests for that asset to serverless.
     * @param path The path to the asset within the app
     * @param maxAgeSeconds The TTL in seconds
     * @return A self-reference, suitable for chaining
     */
    setStaticAssetExpiration(path, maxAgeSeconds, staleWhileRevalidateSeconds) {
        this.assetExpiration[path] = {
            maxAgeSeconds,
            staleWhileRevalidateSeconds,
        };
        return this;
    }
    /**
     * Creates contents of the edge control json file for configuring the CDN
     * @param routerPath The path to the router file
     * @param router The router instance
     * @returns The edge control json
     */
    createEdgeControlJson(router, edgeFunctionsBundle) {
        process.stdout.write(`> Bundling edge functions... `);
        const config = (0, config_1.getConfig)();
        const propertyContext = new PropertyContext_1.default({
            ...config,
            edge_functions: {
                quickjs_bytecode_base64: edgeFunctionsBundle === null || edgeFunctionsBundle === void 0 ? void 0 : edgeFunctionsBundle.toString('base64'),
            },
        });
        process.stdout.write(`done.\n`);
        return (0, createEdgeConfig_1.default)(propertyContext, router);
    }
    /**
     * Deletes all .map files from specified folder
     */
    deleteMapFiles(dir) {
        const mapFiles = globby_1.default.sync('**/*.map', {
            onlyFiles: true,
            cwd: dir,
        });
        mapFiles.forEach(file => {
            this.removeSync((0, path_1.join)(dir, file));
        });
    }
    /**
     * Builds the service worker and injects the static assets to preload into it.
     */
    async buildServiceWorker(options) {
        if (!options.swSrc) {
            throw new Error('Missing required option swSrc');
        }
        if (!options.swDest) {
            throw new Error('Missing required option swDest');
        }
        let entryPoint = options.swSrc;
        if (!(0, fs_extra_1.existsSync)(entryPoint)) {
            console.warn(`> ${chalk_1.default.yellow('Warning')} Service worker source file not found: ${chalk_1.default.grey((0, path_1.relative)(process.cwd(), entryPoint))}, skipping...`);
            return;
        }
        process.stdout.write('> Building service worker... ');
        if (options.globPatterns) {
            // inject the static assets into the service worker source
            entryPoint = this.createTempfile('service-worker.js');
            await (0, workbox_build_1.injectManifest)({ ...options, swDest: entryPoint });
        }
        // bundle the service worker and its dependencies into the final output file
        await (0, bundle_sw_1.esBuildServiceWorker)({
            entryPoints: [entryPoint],
            outfile: options.swDest,
            // The @edgio/prefetch library checks for some environment variables being assigned.
            // Before switching to ESBuild, process.env was polyfilled by webpack.
            define: { 'process.env': JSON.stringify({}) },
        });
        process.stdout.write('done.\n');
    }
    async watchServiceWorker(srcPath = 'sw/service-worker.js', destPath = '.edgio/s3/service-worker.js') {
        let buildInProgress = false;
        const src = (0, path_1.resolve)(process.cwd(), srcPath);
        const dest = (0, path_1.resolve)(process.cwd(), destPath);
        const build = () => this.buildServiceWorker({ swSrc: src, swDest: dest });
        await build();
        (0, watch_1.default)(src).on('change', () => {
            if (!buildInProgress) {
                buildInProgress = true;
                build().finally(() => (buildInProgress = false));
            }
        });
    }
    /**
     * Prepends the path to the .edgio/temp dir to the specified path.
     * @param path
     * @returns
     */
    createTempfile(path) {
        return (0, path_1.resolve)(this.tempDir, path);
    }
}
exports.default = DeploymentBuilder;
