{"version":3,"file":"cleanRemovedContentDeclaration.cjs","names":["relative","rm","colorizePath","getAppLogger","readDictionariesFromDisk","normalize","join","readFile","colorizeKey","writeJsonIfChanged","fg","normalizePath","createDictionaryEntryPoint"],"sources":["../../src/cleanRemovedContentDeclaration.ts"],"sourcesContent":["import { readFile, rm } from 'node:fs/promises';\nimport { join, normalize, relative } from 'node:path';\nimport { normalizePath } from '@intlayer/config/client';\nimport {\n  colorizeKey,\n  colorizePath,\n  getAppLogger,\n} from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport fg from 'fast-glob';\nimport { createDictionaryEntryPoint } from './createDictionaryEntryPoint';\nimport { readDictionariesFromDisk } from './utils/readDictionariesFromDisk';\nimport { writeJsonIfChanged } from './writeJsonIfChanged';\n\n/**\n * Grace period before bundler-graph artifacts (JSON, dynamic chunks) are\n * deleted, so a bundler rebuild started from the previous entry point can\n * still resolve them.\n */\nconst ARTIFACT_REMOVAL_DELAY_MS = 3000;\n\n/**\n * Source ids of a merged dictionary. A dictionary built from a single source\n * is written as-is, so it carries `localId` rather than `localIds`.\n */\nconst getMergedLocalIds = (dictionary: Dictionary): LocalDictionaryId[] =>\n  dictionary.localIds ?? (dictionary.localId ? [dictionary.localId] : []);\n\nconst removeArtifacts = async (\n  paths: string[],\n  baseDir: string,\n  appLogger: ReturnType<typeof getAppLogger>\n) =>\n  await Promise.all(\n    paths.map(async (path) => {\n      const relativePath = relative(baseDir, path);\n      try {\n        await rm(path, { force: true });\n\n        appLogger(`Deleted artifact: ${colorizePath(relativePath)}`, {\n          isVerbose: true,\n        });\n      } catch {\n        appLogger(`Error while removing file ${colorizePath(relativePath)}`, {\n          isVerbose: true,\n        });\n      }\n    })\n  );\n\nexport const cleanRemovedContentDeclaration = async (\n  filePath: string,\n  keysToKeep: string[],\n  configuration: IntlayerConfig\n): Promise<{\n  changedDictionariesLocalIds: string[];\n  excludeKeys: string[];\n  hasRebuilt: boolean;\n}> => {\n  const appLogger = getAppLogger(configuration);\n\n  const unmergedDictionaries = readDictionariesFromDisk<\n    Record<string, Dictionary[]>\n  >(configuration.system.unmergedDictionariesDir);\n\n  const baseDir = configuration.system.baseDir;\n\n  const relativeFilePath = relative(baseDir, filePath);\n  const flatUnmergedDictionaries = Object.values(unmergedDictionaries).flat();\n\n  const filteredUnmergedDictionaries = flatUnmergedDictionaries.filter(\n    (dictionary) =>\n      dictionary.filePath === relativeFilePath &&\n      !keysToKeep.includes(dictionary.key)\n  );\n\n  // Deduplicate dictionaries by key\n  const uniqueUnmergedDictionaries = filteredUnmergedDictionaries.filter(\n    (dictionary, index, self) =>\n      index === self.findIndex((t) => t.key === dictionary.key)\n  );\n\n  const changedDictionariesLocalIds: string[] = [];\n  // Bundler-graph artifacts, deleted after the entry points stop importing them\n  const filesToRemove: string[] = [];\n  // Type declarations are not in the bundler graph and must be gone before the\n  // module augmentation is regenerated, so they are deleted right away\n  const typeFilesToRemove: string[] = [];\n  const excludeKeys: string[] = [];\n\n  // Identify Unmerged Dictionaries to remove or clean\n  await Promise.all(\n    uniqueUnmergedDictionaries.map(async (dictionary) => {\n      const unmergedFilePath = normalize(\n        join(\n          configuration.system.unmergedDictionariesDir,\n          `${dictionary.key}.json`\n        )\n      );\n\n      try {\n        const jsonContent = await readFile(unmergedFilePath, 'utf8');\n        const parsedContent = JSON.parse(jsonContent);\n\n        if (parsedContent.length === 1) {\n          if (parsedContent[0].filePath === relativeFilePath) {\n            appLogger(\n              `Removing outdated dictionary ${colorizeKey(dictionary.key)}`,\n              { isVerbose: true }\n            );\n            filesToRemove.push(unmergedFilePath);\n            excludeKeys.push(dictionary.key);\n          }\n        } else {\n          const filteredContent = parsedContent.filter(\n            (content: any) => content.filePath !== relativeFilePath\n          );\n          await writeJsonIfChanged(unmergedFilePath, filteredContent);\n          changedDictionariesLocalIds.push(dictionary.localId!);\n        }\n      } catch (error: any) {\n        if (error.code === 'ENOENT') {\n          if (!excludeKeys.includes(dictionary.key)) {\n            excludeKeys.push(dictionary.key);\n          }\n        }\n      }\n    })\n  );\n\n  const dictionaries = readDictionariesFromDisk<Record<string, Dictionary>>(\n    configuration.system.dictionariesDir\n  );\n  const flatDictionaries = Object.values(dictionaries) as Dictionary[];\n\n  const isFromChangedFile = (localId: LocalDictionaryId) =>\n    localId.endsWith(`::local::${relativeFilePath}`);\n\n  const filteredMergedDictionaries = flatDictionaries?.filter(\n    (dictionary) =>\n      !keysToKeep.includes(dictionary.key) &&\n      getMergedLocalIds(dictionary).some(isFromChangedFile)\n  );\n\n  const uniqueMergedDictionaries = filteredMergedDictionaries.filter(\n    (dictionary, index, self) =>\n      index === self.findIndex((t) => t.key === dictionary.key)\n  );\n\n  // Identify Merged Dictionaries, Types, and Dynamic Dictionaries to remove\n  await Promise.all(\n    uniqueMergedDictionaries.map(async (dictionary) => {\n      const mergedFilePath = normalize(\n        join(configuration.system.dictionariesDir, `${dictionary.key}.json`)\n      );\n\n      try {\n        const fileContent = await readFile(mergedFilePath, 'utf8');\n        const parsedContent = JSON.parse(fileContent) as Dictionary;\n        const localIds = getMergedLocalIds(parsedContent);\n\n        if (localIds.length === 1) {\n          if (isFromChangedFile(localIds[0]!)) {\n            appLogger(\n              `Removing outdated unmerged dictionary ${colorizeKey(dictionary.key)}`,\n              { isVerbose: true }\n            );\n\n            // Mark JSON for removal\n            filesToRemove.push(mergedFilePath);\n\n            // Mark TS Types for removal\n            const typesFilePath = normalize(\n              join(configuration.system.typesDir, `${dictionary.key}.ts`)\n            );\n            typeFilesToRemove.push(typesFilePath);\n\n            // Mark Dynamic Dictionaries for removal\n            // We use glob to catch the loader files (.cjs, .mjs) AND the split locale files (.en.json, etc.)\n            const dynamicFilesGlob = join(\n              configuration.system.dynamicDictionariesDir,\n              `${dictionary.key}.*`\n            );\n            const dynamicFiles = await fg(normalizePath(dynamicFilesGlob), {\n              absolute: true,\n            });\n            filesToRemove.push(...dynamicFiles);\n\n            if (!excludeKeys.includes(dictionary.key)) {\n              excludeKeys.push(dictionary.key);\n            }\n          }\n        } else {\n          const newContent = {\n            ...parsedContent,\n            localIds: localIds.filter((localId) => !isFromChangedFile(localId)),\n          };\n          await writeJsonIfChanged(mergedFilePath, newContent);\n        }\n      } catch (error: any) {\n        if (error.code === 'ENOENT') {\n          if (!excludeKeys.includes(dictionary.key)) {\n            excludeKeys.push(dictionary.key);\n          }\n          const typesFilePath = normalize(\n            join(configuration.system.typesDir, `${dictionary.key}.ts`)\n          );\n          typeFilesToRemove.push(typesFilePath);\n        }\n      }\n    })\n  );\n\n  const hasRebuilt =\n    filesToRemove.length > 0 ||\n    typeFilesToRemove.length > 0 ||\n    excludeKeys.length > 0;\n\n  // Execute Cleanup\n  if (hasRebuilt) {\n    // Update entry points (indexes) first so the app doesn't import dead files\n    await createDictionaryEntryPoint(configuration, { excludeKeys });\n\n    await removeArtifacts(typeFilesToRemove, baseDir, appLogger);\n\n    if (filesToRemove.length > 0) {\n      setTimeout(\n        () => removeArtifacts(filesToRemove, baseDir, appLogger),\n        ARTIFACT_REMOVAL_DELAY_MS\n      );\n    }\n  }\n\n  return {\n    changedDictionariesLocalIds,\n    excludeKeys,\n    hasRebuilt,\n  };\n};\n"],"mappings":"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"}