UNPKG

@intlayer/chokidar

Version:

Scans and builds Intlayer declaration files into dictionaries based on Intlayer configuration.

1 lines 12.5 kB
{"version":3,"file":"writeContentDeclaration.cjs","names":["processContentDeclarationContent","getFormatFromExtension","COMPILER_NO_METADATA","readDictionariesFromDisk","writeMarkdownFile","writeYamlFile","transformJSONFile","detectFormatCommand","writeJSFile"],"sources":["../../../src/writeContentDeclaration/writeContentDeclaration.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport { basename, dirname, extname, join, resolve } from 'node:path';\nimport { isDeepStrictEqual } from 'node:util';\nimport { COMPILER_NO_METADATA } from '@intlayer/config/defaultValues';\nimport {\n getFilteredLocalesDictionary,\n getPerLocaleDictionary,\n} from '@intlayer/core/plugins';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { detectFormatCommand } from '../detectFormatCommand';\nimport {\n type Extension,\n getFormatFromExtension,\n} from '../utils/getFormatFromExtension';\nimport { readDictionariesFromDisk } from '../utils/readDictionariesFromDisk';\nimport type { DictionaryStatus } from './dictionaryStatus';\nimport { processContentDeclarationContent } from './processContentDeclarationContent';\nimport { transformJSONFile } from './transformJSONFile';\nimport { writeJSFile } from './writeJSFile';\nimport { writeMarkdownFile } from './writeMarkdownFile';\nimport { writeYamlFile } from './writeYamlFile';\n\nconst formatContentDeclaration = async (\n dictionary: Dictionary,\n configuration: IntlayerConfig,\n localeList?: LocalesValues[]\n) => {\n /**\n * Clean Markdown, Insertion, File, etc. node metadata\n */\n const processedDictionary =\n await processContentDeclarationContent(dictionary);\n\n let content = processedDictionary.content;\n\n /**\n * Filter locales content\n */\n\n if (dictionary.locale) {\n content = getPerLocaleDictionary(\n processedDictionary,\n dictionary.locale\n ).content;\n } else if (localeList) {\n content = getFilteredLocalesDictionary(\n processedDictionary,\n localeList\n ).content;\n }\n\n let pluginFormatResult: any = {\n ...dictionary,\n content,\n } satisfies Dictionary;\n\n /**\n * Format the dictionary with the plugins\n */\n\n for await (const plugin of configuration.plugins ?? []) {\n if (plugin.formatOutput) {\n const formattedResult = await plugin.formatOutput?.({\n dictionary: pluginFormatResult,\n configuration,\n });\n\n if (formattedResult) {\n pluginFormatResult = formattedResult;\n }\n }\n }\n\n const isDictionaryFormat =\n pluginFormatResult.content && pluginFormatResult.key;\n\n if (!isDictionaryFormat) return pluginFormatResult;\n\n // Build result from the original dictionary so that extra user-defined fields\n // (e.g. custom frontmatter in markdown files) are preserved.\n // Strip internal-only fields that must never appear in persisted output.\n const INTERNAL_FIELDS = new Set([\n '$schema',\n 'filePath',\n 'localId',\n 'localIds',\n 'projectIds',\n ]);\n\n const preservedFields = Object.fromEntries(\n Object.entries(dictionary as Record<string, unknown>).filter(\n ([k]) => !INTERNAL_FIELDS.has(k)\n )\n );\n\n let result: Dictionary = {\n ...preservedFields,\n content,\n } as Dictionary;\n\n /**\n * Add $schema to JSON dictionaries\n */\n const extension = (\n dictionary.filePath ? extname(dictionary.filePath) : '.json'\n ) as Extension;\n const format = getFormatFromExtension(extension);\n\n if (\n format === 'json' &&\n pluginFormatResult.content &&\n pluginFormatResult.key\n ) {\n result = {\n $schema: 'https://intlayer.org/schema.json',\n ...result,\n };\n }\n\n return result;\n};\n\ntype WriteContentDeclarationOptions = {\n newDictionariesPath?: string;\n localeList?: LocalesValues[];\n fallbackLocale?: Locale;\n};\n\nconst defaultOptions = {\n newDictionariesPath: 'intlayer-dictionaries',\n} satisfies WriteContentDeclarationOptions;\n\nexport const writeContentDeclaration = async (\n dictionary: Dictionary,\n configuration: IntlayerConfig,\n options?: WriteContentDeclarationOptions\n): Promise<{ status: DictionaryStatus; path: string }> => {\n const { system, compiler } = configuration;\n const { baseDir } = system;\n\n const noMetadata = compiler?.noMetadata ?? COMPILER_NO_METADATA;\n const { newDictionariesPath, localeList } = {\n ...defaultOptions,\n ...options,\n };\n\n const newDictionaryLocationPath = join(baseDir, newDictionariesPath);\n\n const unmergedDictionariesRecord = readDictionariesFromDisk<\n Record<string, Dictionary[]>\n >(configuration.system.unmergedDictionariesDir);\n const unmergedDictionaries = unmergedDictionariesRecord[\n dictionary.key\n ] as Dictionary[];\n\n const existingDictionary = unmergedDictionaries?.find(\n (el) => el.localId === dictionary.localId\n );\n\n const formattedContentDeclaration = await formatContentDeclaration(\n dictionary,\n configuration,\n localeList\n );\n\n if (existingDictionary?.filePath) {\n // Compare existing dictionary content with new dictionary content\n const isSameContent = isDeepStrictEqual(existingDictionary, dictionary);\n\n const filePath = resolve(\n configuration.system.baseDir,\n existingDictionary.filePath\n );\n\n // Up to date, nothing to do\n if (isSameContent) {\n return {\n status: 'up-to-date',\n path: filePath,\n };\n }\n\n await writeFileWithDirectories(\n filePath,\n formattedContentDeclaration,\n configuration,\n noMetadata\n );\n\n return { status: 'updated', path: filePath };\n }\n\n if (dictionary.filePath) {\n const filePath = resolve(configuration.system.baseDir, dictionary.filePath);\n\n await writeFileWithDirectories(\n filePath,\n formattedContentDeclaration,\n configuration,\n noMetadata\n );\n\n return { status: 'created', path: filePath };\n }\n\n // No existing dictionary, write to new location\n const contentDeclarationPath = join(\n newDictionaryLocationPath,\n `${dictionary.key}.content.json`\n );\n\n await writeFileWithDirectories(\n contentDeclarationPath,\n formattedContentDeclaration,\n configuration,\n noMetadata\n );\n\n return {\n status: 'imported',\n path: contentDeclarationPath,\n };\n};\n\nconst writeFileWithDirectories = async (\n absoluteFilePath: string,\n dictionary: Dictionary,\n configuration: IntlayerConfig,\n noMetadata?: boolean\n): Promise<void> => {\n // Extract the directory from the file path\n const dir = dirname(absoluteFilePath);\n\n // Create the directory recursively\n await mkdir(dir, { recursive: true });\n\n const extension = extname(absoluteFilePath);\n\n if (extension === '.md' || extension === '.mdx') {\n await writeMarkdownFile(absoluteFilePath, dictionary, configuration);\n return;\n }\n\n if (extension === '.yaml' || extension === '.yml') {\n await writeYamlFile(absoluteFilePath, dictionary, configuration);\n return;\n }\n\n // Handle JSON, JSONC, and JSON5 via the AST transformer\n if (['.json', '.jsonc', '.json5'].includes(extension)) {\n let fileContent = '{}';\n\n if (existsSync(absoluteFilePath)) {\n try {\n fileContent = await readFile(absoluteFilePath, 'utf-8');\n } catch {\n // ignore read errors, start with empty object\n }\n }\n\n const transformedContent = transformJSONFile(\n fileContent,\n dictionary,\n noMetadata\n );\n\n // We use standard writeFile because transformedContent is already a string\n const tempDir = configuration.system?.tempDir;\n if (tempDir) {\n await mkdir(tempDir, { recursive: true });\n }\n\n const tempFileName = `${basename(absoluteFilePath)}.${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`;\n const tempPath = tempDir\n ? join(tempDir, tempFileName)\n : `${absoluteFilePath}.${tempFileName}`;\n try {\n await writeFile(tempPath, transformedContent, 'utf-8');\n await rename(tempPath, absoluteFilePath);\n } catch (error) {\n try {\n await rm(tempPath, { force: true });\n } catch {\n // Ignore\n }\n throw error;\n }\n\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', absoluteFilePath), {\n stdio: 'inherit',\n cwd: configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n\n return;\n }\n\n const knownJsExtensions = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n ]);\n\n if (!knownJsExtensions.has(extension)) {\n // Unknown extension (e.g. .po managed by a plugin) — skip; the plugin\n // owns this file format and writeJSFile / prettier would corrupt it.\n return;\n }\n\n await writeJSFile(absoluteFilePath, dictionary, configuration, noMetadata);\n\n // remove the cache as content has changed\n // Will force a new preparation of the intlayer on next build\n try {\n const sentinelPath = join(\n configuration.system.cacheDir,\n 'intlayer-prepared.lock'\n );\n await rm(sentinelPath, { recursive: true });\n } catch {}\n};\n"],"mappings":"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"}