Skip to main content
Glama
cleanRemovedContentDeclaration.ts6.96 kB
import { readFile, rm } from 'node:fs/promises'; import { join, normalize, relative } from 'node:path'; import { colorizeKey, colorizePath, getAppLogger, normalizePath, } from '@intlayer/config/client'; import { getDictionaries } from '@intlayer/dictionaries-entry'; import type { Dictionary, IntlayerConfig } from '@intlayer/types'; import { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry'; import fg from 'fast-glob'; import { createDictionaryEntryPoint } from './createDictionaryEntryPoint'; import { writeJsonIfChanged } from './writeJsonIfChanged'; export const cleanRemovedContentDeclaration = async ( filePath: string, keysToKeep: string[], configuration: IntlayerConfig ): Promise<{ changedDictionariesLocalIds: string[]; excludeKeys: string[]; hasRebuilt: boolean; }> => { const appLogger = getAppLogger(configuration); const unmergedDictionaries = getUnmergedDictionaries(configuration); const baseDir = configuration.content.baseDir; const relativeFilePath = relative(baseDir, filePath); const flatUnmergedDictionaries = Object.values(unmergedDictionaries).flat(); const filteredUnmergedDictionaries = flatUnmergedDictionaries.filter( (dictionary) => dictionary.filePath === relativeFilePath && !keysToKeep.includes(dictionary.key) ); // Deduplicate dictionaries by key const uniqueUnmergedDictionaries = filteredUnmergedDictionaries.filter( (dictionary, index, self) => index === self.findIndex((t) => t.key === dictionary.key) ); const changedDictionariesLocalIds: string[] = []; const filesToRemove: string[] = []; const excludeKeys: string[] = []; // Identify Unmerged Dictionaries to remove or clean await Promise.all( uniqueUnmergedDictionaries.map(async (dictionary) => { const unmergedFilePath = normalize( join( configuration.content.unmergedDictionariesDir, `${dictionary.key}.json` ) ); try { const jsonContent = await readFile(unmergedFilePath, 'utf8'); const parsedContent = JSON.parse(jsonContent); if (parsedContent.length === 1) { if (parsedContent[0].filePath === relativeFilePath) { appLogger( `Removing outdated dictionary ${colorizeKey(dictionary.key)}`, { isVerbose: true } ); filesToRemove.push(unmergedFilePath); excludeKeys.push(dictionary.key); } } else { const filteredContent = parsedContent.filter( (content: any) => content.filePath !== relativeFilePath ); await writeJsonIfChanged(unmergedFilePath, filteredContent); changedDictionariesLocalIds.push(dictionary.localId!); } } catch (error: any) { if (error.code === 'ENOENT') { if (!excludeKeys.includes(dictionary.key)) { excludeKeys.push(dictionary.key); } } } }) ); const dictionaries = getDictionaries(configuration); const flatDictionaries = Object.values(dictionaries) as Dictionary[]; const filteredMergedDictionaries = flatDictionaries?.filter( (dictionary) => !keysToKeep.includes(dictionary.key) && dictionary.localIds?.length === 1 && (dictionary.localIds[0] as string).endsWith( `::local::${relativeFilePath}` ) ); const uniqueMergedDictionaries = filteredMergedDictionaries.filter( (dictionary, index, self) => index === self.findIndex((t) => t.key === dictionary.key) ); // Identify Merged Dictionaries, Types, and Dynamic Dictionaries to remove await Promise.all( uniqueMergedDictionaries.map(async (dictionary) => { const mergedFilePath = normalize( join(configuration.content.dictionariesDir, `${dictionary.key}.json`) ); try { const fileContent = await readFile(mergedFilePath, 'utf8'); const parsedContent = JSON.parse(fileContent) as Dictionary; if (parsedContent.localIds?.length === 1) { if ( parsedContent.localIds[0].endsWith(`::local::${relativeFilePath}`) ) { appLogger( `Removing outdated unmerged dictionary ${colorizeKey(dictionary.key)}`, { isVerbose: true } ); // Mark JSON for removal filesToRemove.push(mergedFilePath); // Mark TS Types for removal const typesFilePath = normalize( join(configuration.content.typesDir, `${dictionary.key}.ts`) ); filesToRemove.push(typesFilePath); // Mark Dynamic Dictionaries for removal // We use glob to catch the loader files (.cjs, .mjs) AND the split locale files (.en.json, etc.) const dynamicFilesGlob = join( configuration.content.dynamicDictionariesDir, `${dictionary.key}.*` ); const dynamicFiles = await fg(normalizePath(dynamicFilesGlob), { absolute: true, }); filesToRemove.push(...dynamicFiles); if (!excludeKeys.includes(dictionary.key)) { excludeKeys.push(dictionary.key); } } } else { const localIds = parsedContent.localIds?.filter( (localeId) => !localeId.endsWith(`::local::${relativeFilePath}`) ) as string[]; const newContent = { ...parsedContent, localIds }; await writeJsonIfChanged(mergedFilePath, newContent); } } catch (error: any) { if (error.code === 'ENOENT') { if (!excludeKeys.includes(dictionary.key)) { excludeKeys.push(dictionary.key); } const typesFilePath = normalize( join(configuration.content.typesDir, `${dictionary.key}.ts`) ); filesToRemove.push(typesFilePath); } } }) ); // Execute Cleanup if (filesToRemove.length > 0 || excludeKeys.length > 0) { // Update entry points (indexes) first so the app doesn't import dead files await createDictionaryEntryPoint(configuration, { excludeKeys }); // Remove the files synchronously (awaited) immediately after. if (filesToRemove.length > 0) { await Promise.all( filesToRemove.map(async (path) => { const relativePath = relative(baseDir, path); try { await rm(path, { force: true }); appLogger(`Deleted artifact: ${colorizePath(relativePath)}`, { isVerbose: true, }); } catch { appLogger( `Error while removing file ${colorizePath(relativePath)}`, { isVerbose: true, } ); } }) ); } } return { changedDictionariesLocalIds, excludeKeys, hasRebuilt: filesToRemove.length > 0 || excludeKeys.length > 0, }; };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server