{"version":3,"file":"emitRewrittenPages.mjs","names":[],"sources":["../../src/emitRewrittenPages.ts"],"sourcesContent":["import { copyFile, mkdir, readdir } from 'node:fs/promises';\nimport { dirname, join, relative, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n  getCanonicalPath,\n  getRewriteRules,\n  resolveLocalizedPath,\n} from '@intlayer/core/localization';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * Description of a built HTML page, expressed both as the URL path it is served\n * at and as the on-disk layout Astro used to emit it.\n */\ntype BuiltPage = {\n  /** Absolute path of the emitted HTML file. */\n  filePath: string;\n  /** URL path the file is served at, without trailing slash (e.g. `/en/about`). */\n  urlPath: string;\n  /** Whether the file is a directory index (`about/index.html`) or flat (`about.html`). */\n  isDirectoryIndex: boolean;\n};\n\n/**\n * Recursively lists every `.html` file contained in a directory.\n */\nconst listHtmlFiles = async (directory: string): Promise<string[]> => {\n  const entries = await readdir(directory, { withFileTypes: true });\n\n  const nestedFiles = await Promise.all(\n    entries.map(async (entry) => {\n      const entryPath = join(directory, entry.name);\n\n      if (entry.isDirectory()) return listHtmlFiles(entryPath);\n\n      return entry.isFile() && entry.name.endsWith('.html') ? [entryPath] : [];\n    })\n  );\n\n  return nestedFiles.flat();\n};\n\n/**\n * Converts an emitted HTML file path into the URL path it is served at.\n *\n * - `about/index.html` → `/about`\n * - `about.html`       → `/about`\n * - `index.html`       → `/`\n */\nconst toBuiltPage = (outputDirectory: string, filePath: string): BuiltPage => {\n  const relativePath = relative(outputDirectory, filePath).split(sep).join('/');\n\n  const isDirectoryIndex = relativePath.endsWith('index.html');\n\n  const pathWithoutExtension = isDirectoryIndex\n    ? relativePath.slice(0, -'index.html'.length).replace(/\\/$/, '')\n    : relativePath.slice(0, -'.html'.length);\n\n  return {\n    filePath,\n    urlPath: `/${pathWithoutExtension}`.replace(/\\/{2,}/g, '/'),\n    isDirectoryIndex,\n  };\n};\n\n/**\n * Splits a URL path into its locale prefix (when present) and the remainder.\n */\nconst splitLocalePrefix = (\n  urlPath: string,\n  locales: Locale[]\n): { localePrefix: string; pathWithoutLocale: string } => {\n  const firstSegment = urlPath.split('/')[1];\n\n  if (firstSegment && locales.includes(firstSegment as Locale)) {\n    return {\n      localePrefix: `/${firstSegment}`,\n      pathWithoutLocale: urlPath.slice(firstSegment.length + 1) || '/',\n    };\n  }\n\n  return { localePrefix: '', pathWithoutLocale: urlPath || '/' };\n};\n\n/**\n * Maps a URL path back onto the on-disk layout Astro used for the source page,\n * so the emitted twin keeps the same `directory` / `file` build format.\n */\nconst toFilePath = (\n  outputDirectory: string,\n  urlPath: string,\n  isDirectoryIndex: boolean\n): string => {\n  const trimmedPath = urlPath.replace(/^\\//, '');\n\n  return join(\n    outputDirectory,\n    isDirectoryIndex ? join(trimmedPath, 'index.html') : `${trimmedPath}.html`\n  );\n};\n\n/**\n * Emits a copy of every prerendered page at its rewritten (\"pretty\") URL.\n *\n * Astro renders pages from their canonical file-system route (`/about`,\n * `/en/about`), so a static build contains no file for the localized paths\n * declared in `routing.rewrite` (`/nosotros`). The dev and SSR proxies resolve\n * those paths at request time, but a static host has nothing to serve and\n * answers 404 — even though `getLocalizedUrl` (links, hreflang, sitemap)\n * already points at them.\n *\n * This mirrors each canonical page onto its localized path at the end of the\n * build. The canonical path is kept reachable, matching the proxy behaviour.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param outputDirectoryUrl - The build output directory, as given by `astro:build:done`.\n * @returns The list of `[from, to]` URL paths that were emitted.\n */\nexport const emitRewrittenPages = async (\n  configuration: IntlayerConfig,\n  outputDirectoryUrl: URL\n): Promise<[from: string, to: string][]> => {\n  const { routing, internationalization } = configuration;\n\n  const rewriteRules = getRewriteRules(routing.rewrite, 'url');\n\n  // Without prefixes a single file serves every locale, so a per-locale\n  // rewrite cannot be resolved from the file path alone.\n  const isPrefixMode =\n    routing.mode === 'prefix-all' || routing.mode === 'prefix-no-default';\n\n  if (!rewriteRules || !isPrefixMode) return [];\n\n  const locales = internationalization.locales as Locale[];\n  const defaultLocale = internationalization.defaultLocale as Locale;\n\n  const outputDirectory = fileURLToPath(outputDirectoryUrl);\n  const htmlFiles = await listHtmlFiles(outputDirectory);\n\n  const emittedPages: [from: string, to: string][] = [];\n\n  for (const htmlFile of htmlFiles) {\n    const { filePath, urlPath, isDirectoryIndex } = toBuiltPage(\n      outputDirectory,\n      htmlFile\n    );\n\n    const { localePrefix, pathWithoutLocale } = splitLocalePrefix(\n      urlPath,\n      locales\n    );\n\n    // An unprefixed path is only reachable when the default locale is not\n    // prefixed, in which case it belongs to the default locale.\n    const locale = (localePrefix.slice(1) || defaultLocale) as Locale;\n\n    const canonicalPath = getCanonicalPath(\n      pathWithoutLocale,\n      locale,\n      rewriteRules\n    );\n\n    const { path: localizedPath, isRewritten } = resolveLocalizedPath(\n      canonicalPath,\n      locale,\n      rewriteRules\n    );\n\n    // Either no rule matches, or the page is already emitted at its pretty URL.\n    if (!isRewritten || localizedPath === pathWithoutLocale) continue;\n\n    const targetUrlPath = `${localePrefix}${localizedPath}`.replace(\n      /\\/{2,}/g,\n      '/'\n    );\n    const targetFilePath = toFilePath(\n      outputDirectory,\n      targetUrlPath,\n      isDirectoryIndex\n    );\n\n    await mkdir(dirname(targetFilePath), { recursive: true });\n    await copyFile(filePath, targetFilePath);\n\n    emittedPages.push([urlPath, targetUrlPath]);\n  }\n\n  return emittedPages;\n};\n"],"mappings":";;;;;;;;;AA2BA,MAAM,gBAAgB,OAAO,cAAyC;CACpE,MAAM,UAAU,MAAM,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;CAYhE,QAAO,MAVmB,QAAQ,IAChC,QAAQ,IAAI,OAAO,UAAU;EAC3B,MAAM,YAAY,KAAK,WAAW,MAAM,IAAI;EAE5C,IAAI,MAAM,YAAY,GAAG,OAAO,cAAc,SAAS;EAEvD,OAAO,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,OAAO,IAAI,CAAC,SAAS,IAAI,CAAC;CACzE,CAAC,CACH,EAEkB,CAAC,KAAK;AAC1B;;;;;;;;AASA,MAAM,eAAe,iBAAyB,aAAgC;CAC5E,MAAM,eAAe,SAAS,iBAAiB,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG;CAE5E,MAAM,mBAAmB,aAAa,SAAS,YAAY;CAM3D,OAAO;EACL;EACA,SAAS,IANkB,mBACzB,aAAa,MAAM,GAAG,GAAoB,CAAC,CAAC,QAAQ,OAAO,EAAE,IAC7D,aAAa,MAAM,GAAG,EAAe,IAIH,QAAQ,WAAW,GAAG;EAC1D;CACF;AACF;;;;AAKA,MAAM,qBACJ,SACA,YACwD;CACxD,MAAM,eAAe,QAAQ,MAAM,GAAG,CAAC,CAAC;CAExC,IAAI,gBAAgB,QAAQ,SAAS,YAAsB,GACzD,OAAO;EACL,cAAc,IAAI;EAClB,mBAAmB,QAAQ,MAAM,aAAa,SAAS,CAAC,KAAK;CAC/D;CAGF,OAAO;EAAE,cAAc;EAAI,mBAAmB,WAAW;CAAI;AAC/D;;;;;AAMA,MAAM,cACJ,iBACA,SACA,qBACW;CACX,MAAM,cAAc,QAAQ,QAAQ,OAAO,EAAE;CAE7C,OAAO,KACL,iBACA,mBAAmB,KAAK,aAAa,YAAY,IAAI,GAAG,YAAY,MACtE;AACF;;;;;;;;;;;;;;;;;;AAmBA,MAAa,qBAAqB,OAChC,eACA,uBAC0C;CAC1C,MAAM,EAAE,SAAS,yBAAyB;CAE1C,MAAM,eAAe,gBAAgB,QAAQ,SAAS,KAAK;CAI3D,MAAM,eACJ,QAAQ,SAAS,gBAAgB,QAAQ,SAAS;CAEpD,IAAI,CAAC,gBAAgB,CAAC,cAAc,OAAO,CAAC;CAE5C,MAAM,UAAU,qBAAqB;CACrC,MAAM,gBAAgB,qBAAqB;CAE3C,MAAM,kBAAkB,cAAc,kBAAkB;CACxD,MAAM,YAAY,MAAM,cAAc,eAAe;CAErD,MAAM,eAA6C,CAAC;CAEpD,KAAK,MAAM,YAAY,WAAW;EAChC,MAAM,EAAE,UAAU,SAAS,qBAAqB,YAC9C,iBACA,QACF;EAEA,MAAM,EAAE,cAAc,sBAAsB,kBAC1C,SACA,OACF;EAIA,MAAM,SAAU,aAAa,MAAM,CAAC,KAAK;EAEzC,MAAM,gBAAgB,iBACpB,mBACA,QACA,YACF;EAEA,MAAM,EAAE,MAAM,eAAe,gBAAgB,qBAC3C,eACA,QACA,YACF;EAGA,IAAI,CAAC,eAAe,kBAAkB,mBAAmB;EAEzD,MAAM,gBAAgB,GAAG,eAAe,gBAAgB,QACtD,WACA,GACF;EACA,MAAM,iBAAiB,WACrB,iBACA,eACA,gBACF;EAEA,MAAM,MAAM,QAAQ,cAAc,GAAG,EAAE,WAAW,KAAK,CAAC;EACxD,MAAM,SAAS,UAAU,cAAc;EAEvC,aAAa,KAAK,CAAC,SAAS,aAAa,CAAC;CAC5C;CAEA,OAAO;AACT"}