"use strict";
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = exports.SERVER_APP_MANIFEST_FILENAME = void 0;
exports.generateAngularServerAppEngineManifest = generateAngularServerAppEngineManifest;
exports.generateAngularServerAppManifest = generateAngularServerAppManifest;
const node_path_1 = require("node:path");
const node_vm_1 = require("node:vm");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const utils_1 = require("../../tools/esbuild/utils");
const environment_options_1 = require("../environment-options");
exports.SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs';
exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = 'angular-app-engine-manifest.mjs';
const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
/**
 * A mapping of unsafe characters to their escaped Unicode equivalents.
 */
const UNSAFE_CHAR_MAP = {
    '`': '\\`',
    '$': '\\$',
    '\\': '\\\\',
};
/**
 * Escapes unsafe characters in a given string by replacing them with
 * their Unicode escape sequences.
 *
 * @param str - The string to be escaped.
 * @returns The escaped string where unsafe characters are replaced.
 */
function escapeUnsafeChars(str) {
    return str.replace(/[$`\\]/g, (c) => UNSAFE_CHAR_MAP[c]);
}
/**
 * Generates the server manifest for the App Engine environment.
 *
 * This manifest is used to configure the server-side rendering (SSR) setup for the
 * Angular application when deployed to Google App Engine. It includes the entry points
 * for different locales and the base HREF for the application.
 *
 * @param i18nOptions - The internationalization options for the application build. This
 * includes settings for inlining locales and determining the output structure.
 * @param allowedHosts - A list of hosts that are allowed to access the server-side application.
 * @param baseHref - The base HREF for the application. This is used to set the base URL
 * for all relative URLs in the application.
 */
function generateAngularServerAppEngineManifest(i18nOptions, allowedHosts, baseHref) {
    const entryPoints = {};
    const supportedLocales = {};
    if (i18nOptions.shouldInline && !i18nOptions.flatOutput) {
        for (const locale of i18nOptions.inlineLocales) {
            const { subPath } = i18nOptions.locales[locale];
            const importPath = `${subPath ? `${subPath}/` : ''}${MAIN_SERVER_OUTPUT_FILENAME}`;
            entryPoints[subPath] = `() => import('./${importPath}')`;
            supportedLocales[locale] = subPath;
        }
    }
    else {
        entryPoints[''] = `() => import('./${MAIN_SERVER_OUTPUT_FILENAME}')`;
        supportedLocales[i18nOptions.sourceLocale] = '';
    }
    // Remove trailing slash but retain leading slash.
    let basePath = baseHref || '/';
    if (basePath.length > 1 && basePath.at(-1) === '/') {
        basePath = basePath.slice(0, -1);
    }
    const manifestContent = `
export default {
  basePath: '${basePath}',
  allowedHosts: ${JSON.stringify(allowedHosts, undefined, 2)},
  supportedLocales: ${JSON.stringify(supportedLocales, undefined, 2)},
  entryPoints: {
    ${Object.entries(entryPoints)
        .map(([key, value]) => `'${key}': ${value}`)
        .join(',\n    ')}
  },
};
`;
    return manifestContent;
}
/**
 * Generates the server manifest for the standard Node.js environment.
 *
 * This manifest is used to configure the server-side rendering (SSR) setup for the
 * Angular application when running in a standard Node.js environment. It includes
 * information about the bootstrap module, whether to inline critical CSS, and any
 * additional HTML and CSS output files.
 *
 * @param additionalHtmlOutputFiles - A map of additional HTML output files generated
 * during the build process, keyed by their file paths.
 * @param outputFiles - An array of all output files from the build process, including
 * JavaScript and CSS files.
 * @param inlineCriticalCss - A boolean indicating whether critical CSS should be inlined
 * in the server-side rendered pages.
 * @param routes - An optional array of route definitions for the application, used for
 * server-side rendering and routing.
 * @param locale - An optional string representing the locale or language code to be used for
 * the application, helping with localization and rendering content specific to the locale.
 * @param baseHref - The base HREF for the application. This is used to set the base URL
 * for all relative URLs in the application.
 * @param initialFiles - A list of initial files that preload tags have already been added for.
 * @param metafile - An esbuild metafile object.
 * @param publicPath - The configured public path.
 *
 * @returns An object containing:
 * - `manifestContent`: A string of the SSR manifest content.
 * - `serverAssetsChunks`: An array of build output files containing the generated assets for the server.
 */
function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale, baseHref, initialFiles, metafile, publicPath) {
    const serverAssetsChunks = [];
    const serverAssets = {};
    for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
        const extension = (0, node_path_1.extname)(file.path);
        if (extension === '.html' || (inlineCriticalCss && extension === '.css')) {
            const jsChunkFilePath = `assets-chunks/${file.path.replace(/[./]/g, '_')}.mjs`;
            const escapedContent = escapeUnsafeChars(file.text);
            serverAssetsChunks.push((0, utils_1.createOutputFile)(jsChunkFilePath, `export default \`${escapedContent}\`;`, bundler_context_1.BuildOutputFileType.ServerApplication));
            // This is needed because JavaScript engines script parser convert `\r\n` to `\n` in template literals,
            // which can result in an incorrect byte length.
            const size = (0, node_vm_1.runInThisContext)(`new TextEncoder().encode(\`${escapedContent}\`).byteLength`);
            serverAssets[file.path] =
                `{size: ${size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`;
        }
    }
    // When routes have been extracted, mappings are no longer needed, as preloads will be included in the metadata.
    // When shouldOptimizeChunks is enabled the metadata is no longer correct and thus we cannot generate the mappings.
    const entryPointToBrowserMapping = routes?.length || environment_options_1.shouldOptimizeChunks
        ? undefined
        : generateLazyLoadedFilesMappings(metafile, initialFiles, publicPath);
    const manifestContent = `
export default {
  bootstrap: () => import('./main.server.mjs').then(m => m.default),
  inlineCriticalCss: ${inlineCriticalCss},
  baseHref: '${baseHref}',
  locale: ${JSON.stringify(locale)},
  routes: ${JSON.stringify(routes, undefined, 2)},
  entryPointToBrowserMapping: ${JSON.stringify(entryPointToBrowserMapping, undefined, 2)},
  assets: {
    ${Object.entries(serverAssets)
        .map(([key, value]) => `'${key}': ${value}`)
        .join(',\n    ')}
  },
};
`;
    return { manifestContent, serverAssetsChunks };
}
/**
 * Maps entry points to their corresponding browser bundles for lazy loading.
 *
 * This function processes a metafile's outputs to generate a mapping between browser-side entry points
 * and the associated JavaScript files that should be loaded in the browser. It includes the entry-point's
 * own path and any valid imports while excluding initial files or external resources.
 */
function generateLazyLoadedFilesMappings(metafile, initialFiles, publicPath = '') {
    const entryPointToBundles = {};
    for (const [fileName, { entryPoint, exports, imports }] of Object.entries(metafile.outputs)) {
        // Skip files that don't have an entryPoint, no exports, or are not .js
        if (!entryPoint || exports?.length < 1 || !fileName.endsWith('.js')) {
            continue;
        }
        const importedPaths = [`${publicPath}${fileName}`];
        for (const { kind, external, path } of imports) {
            if (external || initialFiles.has(path) || kind !== 'import-statement') {
                continue;
            }
            importedPaths.push(`${publicPath}${path}`);
        }
        entryPointToBundles[entryPoint] = importedPaths;
    }
    return entryPointToBundles;
}
//# sourceMappingURL=manifest.js.map