import '../server/node-environment';
process.env.NEXT_IS_EXPORT_WORKER = 'true';
import { extname, join, dirname, sep } from 'path';
import fs from 'fs/promises';
import { loadComponents } from '../server/load-components';
import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic';
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path';
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path';
import { trace } from '../trace';
import { setHttpClientAndAgentOptions } from '../server/setup-http-agent-env';
import isError from '../lib/is-error';
import { addRequestMeta } from '../server/request-meta';
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths';
import { createRequestResponseMocks } from '../server/lib/mock-request';
import { isAppRouteRoute } from '../lib/is-app-route-route';
import { hasNextSupport } from '../telemetry/ci-info';
import { exportAppRoute } from './routes/app-route';
import { exportAppPage } from './routes/app-page';
import { exportPagesPage } from './routes/pages';
import { getParams } from './helpers/get-params';
import { createIncrementalCache } from './helpers/create-incremental-cache';
import { isPostpone } from '../server/lib/router-utils/is-postpone';
import { isDynamicUsageError } from './helpers/is-dynamic-usage-error';
import { isBailoutToCSRError } from '../shared/lib/lazy-dynamic/bailout-to-csr';
import { turborepoTraceAccess, TurborepoAccessTraceResult } from '../build/turborepo-access-trace';
import { needsExperimentalReact } from '../lib/needs-experimental-react';
const envConfig = require('../shared/lib/runtime-config.external');
globalThis.__NEXT_DATA__ = {
    nextExport: true
};
class TimeoutError extends Error {
    constructor(...args){
        super(...args);
        this.code = 'NEXT_EXPORT_TIMEOUT_ERROR';
    }
}
class ExportPageError extends Error {
    constructor(...args){
        super(...args);
        this.code = 'NEXT_EXPORT_PAGE_ERROR';
    }
}
async function exportPageImpl(input, fileWriter) {
    const { path, pathMap, distDir, pagesDataDir, buildExport = false, serverRuntimeConfig, subFolders = false, optimizeCss, disableOptimizedLoading, debugOutput = false, enableExperimentalReact, ampValidatorPath, trailingSlash } = input;
    if (enableExperimentalReact) {
        process.env.__NEXT_EXPERIMENTAL_REACT = 'true';
    }
    const { page, // Check if this is an `app/` page.
    _isAppDir: isAppDir = false, // Check if this should error when dynamic usage is detected.
    _isDynamicError: isDynamicError = false, // If this page supports partial prerendering, then we need to pass that to
    // the renderOpts.
    _isRoutePPREnabled: isRoutePPREnabled, // Pull the original query out.
    query: originalQuery = {} } = pathMap;
    try {
        var _req_url;
        let query = {
            ...originalQuery
        };
        const pathname = normalizeAppPath(page);
        const isDynamic = isDynamicRoute(page);
        const outDir = isAppDir ? join(distDir, 'server/app') : input.outDir;
        const filePath = normalizePagePath(path);
        const ampPath = `${filePath}.amp`;
        let renderAmpPath = ampPath;
        let updatedPath = query.__nextSsgPath || path;
        delete query.__nextSsgPath;
        let locale = query.__nextLocale || input.renderOpts.locale;
        delete query.__nextLocale;
        if (input.renderOpts.locale) {
            const localePathResult = normalizeLocalePath(path, input.renderOpts.locales);
            if (localePathResult.detectedLocale) {
                updatedPath = localePathResult.pathname;
                locale = localePathResult.detectedLocale;
                if (locale === input.renderOpts.defaultLocale) {
                    renderAmpPath = `${normalizePagePath(updatedPath)}.amp`;
                }
            }
        }
        // We need to show a warning if they try to provide query values
        // for an auto-exported page since they won't be available
        const hasOrigQueryValues = Object.keys(originalQuery).length > 0;
        // Check if the page is a specified dynamic route
        const { pathname: nonLocalizedPath } = normalizeLocalePath(path, input.renderOpts.locales);
        let params;
        if (isDynamic && page !== nonLocalizedPath) {
            const normalizedPage = isAppDir ? normalizeAppPath(page) : page;
            params = getParams(normalizedPage, updatedPath);
            if (params) {
                query = {
                    ...query,
                    ...params
                };
            }
        }
        const { req, res } = createRequestResponseMocks({
            url: updatedPath
        });
        // If this is a status code page, then set the response code.
        for (const statusCode of [
            404,
            500
        ]){
            if ([
                `/${statusCode}`,
                `/${statusCode}.html`,
                `/${statusCode}/index.html`
            ].some((p)=>p === updatedPath || `/${locale}${p}` === updatedPath)) {
                res.statusCode = statusCode;
            }
        }
        // Ensure that the URL has a trailing slash if it's configured.
        if (trailingSlash && !((_req_url = req.url) == null ? void 0 : _req_url.endsWith('/'))) {
            req.url += '/';
        }
        if (locale && buildExport && input.renderOpts.domainLocales && input.renderOpts.domainLocales.some((dl)=>{
            var _dl_locales;
            return dl.defaultLocale === locale || ((_dl_locales = dl.locales) == null ? void 0 : _dl_locales.includes(locale || ''));
        })) {
            addRequestMeta(req, 'isLocaleDomain', true);
        }
        envConfig.setConfig({
            serverRuntimeConfig,
            publicRuntimeConfig: input.renderOpts.runtimeConfig
        });
        const getHtmlFilename = (p)=>subFolders ? `${p}${sep}index.html` : `${p}.html`;
        let htmlFilename = getHtmlFilename(filePath);
        // dynamic routes can provide invalid extensions e.g. /blog/[...slug] returns an
        // extension of `.slug]`
        const pageExt = isDynamic || isAppDir ? '' : extname(page);
        const pathExt = isDynamic || isAppDir ? '' : extname(path);
        // force output 404.html for backwards compat
        if (path === '/404.html') {
            htmlFilename = path;
        } else if (pageExt !== pathExt && pathExt !== '') {
            const isBuiltinPaths = [
                '/500',
                '/404'
            ].some((p)=>p === path || p === path + '.html');
            // If the ssg path has .html extension, and it's not builtin paths, use it directly
            // Otherwise, use that as the filename instead
            const isHtmlExtPath = !isBuiltinPaths && path.endsWith('.html');
            htmlFilename = isHtmlExtPath ? getHtmlFilename(path) : path;
        } else if (path === '/') {
            // If the path is the root, just use index.html
            htmlFilename = 'index.html';
        }
        const baseDir = join(outDir, dirname(htmlFilename));
        let htmlFilepath = join(outDir, htmlFilename);
        await fs.mkdir(baseDir, {
            recursive: true
        });
        // Handle App Routes.
        if (isAppDir && isAppRouteRoute(page)) {
            return await exportAppRoute(req, res, params, page, input.renderOpts.incrementalCache, distDir, htmlFilepath, fileWriter, input.renderOpts.experimental);
        }
        const components = await loadComponents({
            distDir,
            page,
            isAppPath: isAppDir
        });
        const renderOpts = {
            ...components,
            ...input.renderOpts,
            ampPath: renderAmpPath,
            params,
            optimizeCss,
            disableOptimizedLoading,
            locale,
            supportsDynamicResponse: false,
            experimental: {
                ...input.renderOpts.experimental,
                isRoutePPREnabled
            },
            waitUntil: undefined,
            onClose: undefined
        };
        if (hasNextSupport) {
            renderOpts.isRevalidate = true;
        }
        // Handle App Pages
        if (isAppDir) {
            return await exportAppPage(req, res, page, path, pathname, query, renderOpts, htmlFilepath, debugOutput, isDynamicError, fileWriter);
        }
        return await exportPagesPage(req, res, path, page, query, htmlFilepath, htmlFilename, ampPath, subFolders, outDir, ampValidatorPath, pagesDataDir, buildExport, isDynamic, hasOrigQueryValues, renderOpts, components, fileWriter);
    } catch (err) {
        console.error(`\nError occurred prerendering page "${path}". Read more: https://nextjs.org/docs/messages/prerender-error\n`);
        if (!isBailoutToCSRError(err)) {
            console.error(isError(err) && err.stack ? err.stack : err);
        }
        return {
            error: true
        };
    }
}
export async function exportPages(input) {
    const { exportPathMap, paths, dir, distDir, outDir, cacheHandler, cacheMaxMemorySize, fetchCacheKeyPrefix, pagesDataDir, renderOpts, nextConfig, options } = input;
    // If the fetch cache was enabled, we need to create an incremental
    // cache instance for this page.
    const incrementalCache = await createIncrementalCache({
        cacheHandler,
        cacheMaxMemorySize,
        fetchCacheKeyPrefix,
        distDir,
        dir,
        // skip writing to disk in minimal mode for now, pending some
        // changes to better support it
        flushToDisk: !hasNextSupport
    });
    renderOpts.incrementalCache = incrementalCache;
    const maxConcurrency = nextConfig.experimental.staticGenerationMaxConcurrency ?? 8;
    const results = [];
    const exportPageWithRetry = async (path, maxAttempts)=>{
        const pathMap = exportPathMap[path];
        const { page } = exportPathMap[path];
        const pageKey = page !== path ? `${page}: ${path}` : path;
        let attempt = 0;
        let result;
        while(attempt < maxAttempts){
            try {
                var _nextConfig_experimental_amp;
                result = await Promise.race([
                    exportPage({
                        path,
                        pathMap,
                        distDir,
                        outDir,
                        pagesDataDir,
                        renderOpts,
                        ampValidatorPath: ((_nextConfig_experimental_amp = nextConfig.experimental.amp) == null ? void 0 : _nextConfig_experimental_amp.validator) || undefined,
                        trailingSlash: nextConfig.trailingSlash,
                        serverRuntimeConfig: nextConfig.serverRuntimeConfig,
                        subFolders: nextConfig.trailingSlash && !options.buildExport,
                        buildExport: options.buildExport,
                        optimizeCss: nextConfig.experimental.optimizeCss,
                        disableOptimizedLoading: nextConfig.experimental.disableOptimizedLoading,
                        parentSpanId: input.parentSpanId,
                        httpAgentOptions: nextConfig.httpAgentOptions,
                        debugOutput: options.debugOutput,
                        enableExperimentalReact: needsExperimentalReact(nextConfig)
                    }),
                    // If exporting the page takes longer than the timeout, reject the promise.
                    new Promise((_, reject)=>{
                        setTimeout(()=>{
                            reject(new TimeoutError());
                        }, nextConfig.staticPageGenerationTimeout * 1000);
                    })
                ]);
                // If there was an error in the export, throw it immediately. In the catch block, we might retry the export,
                // or immediately fail the build, depending on user configuration. We might also continue on and attempt other pages.
                if (result && 'error' in result) {
                    throw new ExportPageError();
                }
                break;
            } catch (err) {
                // The only error that should be caught here is an ExportError, as `exportPage` doesn't throw and instead returns an object with an `error` property.
                // This is an overly cautious check to ensure that we don't accidentally catch an unexpected error.
                if (!(err instanceof ExportPageError || err instanceof TimeoutError)) {
                    throw err;
                }
                if (err instanceof TimeoutError) {
                    // If the export times out, we will restart the worker up to 3 times.
                    maxAttempts = 3;
                }
                // We've reached the maximum number of attempts
                if (attempt >= maxAttempts - 1) {
                    // Log a message if we've reached the maximum number of attempts.
                    // We only care to do this if maxAttempts was configured.
                    if (maxAttempts > 0) {
                        console.info(`Failed to build ${pageKey} after ${maxAttempts} attempts.`);
                    }
                    // If prerenderEarlyExit is enabled, we'll exit the build immediately.
                    if (nextConfig.experimental.prerenderEarlyExit) {
                        throw new ExportPageError(`Export encountered an error on ${pageKey}, exiting the build.`);
                    } else {
                    // Otherwise, this is a no-op. The build will continue, and a summary of failed pages will be displayed at the end.
                    }
                } else {
                    // Otherwise, we have more attempts to make. Wait before retrying
                    if (err instanceof TimeoutError) {
                        console.info(`Failed to build ${pageKey} (attempt ${attempt + 1} of ${maxAttempts}) because it took more than ${nextConfig.staticPageGenerationTimeout} seconds. Retrying again shortly.`);
                    } else {
                        console.info(`Failed to build ${pageKey} (attempt ${attempt + 1} of ${maxAttempts}). Retrying again shortly.`);
                    }
                    await new Promise((r)=>setTimeout(r, Math.random() * 500));
                }
            }
            attempt++;
        }
        return {
            result,
            path,
            pageKey
        };
    };
    for(let i = 0; i < paths.length; i += maxConcurrency){
        const subset = paths.slice(i, i + maxConcurrency);
        const subsetResults = await Promise.all(subset.map((path)=>exportPageWithRetry(path, nextConfig.experimental.staticGenerationRetryCount ?? 1)));
        results.push(...subsetResults);
    }
    return results;
}
async function exportPage(input) {
    trace('export-page', input.parentSpanId).setAttribute('path', input.path);
    // Configure the http agent.
    setHttpClientAndAgentOptions({
        httpAgentOptions: input.httpAgentOptions
    });
    const files = [];
    const baseFileWriter = async (type, path, content, encodingOptions = 'utf-8')=>{
        await fs.mkdir(dirname(path), {
            recursive: true
        });
        await fs.writeFile(path, content, encodingOptions);
        files.push({
            type,
            path
        });
    };
    const exportPageSpan = trace('export-page-worker', input.parentSpanId);
    const start = Date.now();
    const turborepoAccessTraceResult = new TurborepoAccessTraceResult();
    // Export the page.
    const result = await exportPageSpan.traceAsyncFn(()=>turborepoTraceAccess(()=>exportPageImpl(input, baseFileWriter), turborepoAccessTraceResult));
    // If there was no result, then we can exit early.
    if (!result) return;
    // If there was an error, then we can exit early.
    if ('error' in result) {
        return {
            error: result.error,
            duration: Date.now() - start,
            files: []
        };
    }
    // Notify the parent process that we processed a page (used by the progress activity indicator)
    process.send == null ? void 0 : process.send.call(process, [
        3,
        {
            type: 'activity'
        }
    ]);
    // Otherwise we can return the result.
    return {
        duration: Date.now() - start,
        files,
        ampValidations: result.ampValidations,
        revalidate: result.revalidate,
        metadata: result.metadata,
        ssgNotFound: result.ssgNotFound,
        hasEmptyPrelude: result.hasEmptyPrelude,
        hasPostponed: result.hasPostponed,
        turborepoAccessTraceResult: turborepoAccessTraceResult.serialize(),
        fetchMetrics: result.fetchMetrics
    };
}
process.on('unhandledRejection', (err)=>{
    // if it's a postpone error, it'll be handled later
    // when the postponed promise is actually awaited.
    if (isPostpone(err)) {
        return;
    }
    // we don't want to log these errors
    if (isDynamicUsageError(err)) {
        return;
    }
    console.error(err);
});
process.on('rejectionHandled', ()=>{
// It is ok to await a Promise late in Next.js as it allows for better
// prefetching patterns to avoid waterfalls. We ignore logging these.
// We should've already errored in anyway unhandledRejection.
});

//# sourceMappingURL=worker.js.map