gt 2.14.66 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/api/downloadFileBatch.js +28 -1
- package/dist/api/downloadFileBatch.js.map +1 -1
- package/dist/api/saveLocalEdits.js +1 -3
- package/dist/api/saveLocalEdits.js.map +1 -1
- package/dist/cli/base.js +6 -0
- package/dist/cli/base.js.map +1 -1
- package/dist/cli/commands/download.js +2 -1
- package/dist/cli/commands/download.js.map +1 -1
- package/dist/cli/commands/upload.js +5 -3
- package/dist/cli/commands/upload.js.map +1 -1
- package/dist/cli/inline.js +1 -3
- package/dist/cli/inline.js.map +1 -1
- package/dist/console/index.d.ts +3 -0
- package/dist/console/index.js +4 -1
- package/dist/console/index.js.map +1 -1
- package/dist/console/logger.d.ts +13 -0
- package/dist/console/logger.js +35 -8
- package/dist/console/logger.js.map +1 -1
- package/dist/console/logging.js +4 -2
- package/dist/console/logging.js.map +1 -1
- package/dist/formats/files/aggregateFiles.js +32 -2
- package/dist/formats/files/aggregateFiles.js.map +1 -1
- package/dist/formats/files/collectFonts.d.ts +7 -0
- package/dist/formats/files/collectFonts.js +30 -0
- package/dist/formats/files/collectFonts.js.map +1 -0
- package/dist/formats/files/detectLottieExpressions.d.ts +8 -0
- package/dist/formats/files/detectLottieExpressions.js +42 -0
- package/dist/formats/files/detectLottieExpressions.js.map +1 -0
- package/dist/formats/files/supportedFiles.d.ts +2 -1
- package/dist/formats/files/supportedFiles.js +4 -2
- package/dist/formats/files/supportedFiles.js.map +1 -1
- package/dist/formats/files/transformFormat.d.ts +2 -0
- package/dist/formats/files/transformFormat.js +6 -3
- package/dist/formats/files/transformFormat.js.map +1 -1
- package/dist/fs/findFilepath.d.ts +7 -0
- package/dist/fs/findFilepath.js +11 -1
- package/dist/fs/findFilepath.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/workflows/download.js +3 -9
- package/dist/workflows/download.js.map +1 -1
- package/dist/workflows/enqueue.js +2 -2
- package/dist/workflows/enqueue.js.map +1 -1
- package/dist/workflows/publish.js +1 -3
- package/dist/workflows/publish.js.map +1 -1
- package/dist/workflows/setupProject.js +0 -3
- package/dist/workflows/setupProject.js.map +1 -1
- package/dist/workflows/stage.js +5 -13
- package/dist/workflows/stage.js.map +1 -1
- package/dist/workflows/steps/BranchStep.d.ts +1 -3
- package/dist/workflows/steps/BranchStep.js +1 -4
- package/dist/workflows/steps/BranchStep.js.map +1 -1
- package/dist/workflows/steps/DownloadStep.d.ts +1 -3
- package/dist/workflows/steps/DownloadStep.js +1 -4
- package/dist/workflows/steps/DownloadStep.js.map +1 -1
- package/dist/workflows/steps/EnqueueStep.d.ts +1 -5
- package/dist/workflows/steps/EnqueueStep.js +9 -19
- package/dist/workflows/steps/EnqueueStep.js.map +1 -1
- package/dist/workflows/steps/PollJobsStep.d.ts +1 -3
- package/dist/workflows/steps/PollJobsStep.js +1 -4
- package/dist/workflows/steps/PollJobsStep.js.map +1 -1
- package/dist/workflows/steps/PublishStep.d.ts +1 -3
- package/dist/workflows/steps/PublishStep.js +1 -4
- package/dist/workflows/steps/PublishStep.js.map +1 -1
- package/dist/workflows/steps/SetupStep.d.ts +1 -6
- package/dist/workflows/steps/SetupStep.js +14 -41
- package/dist/workflows/steps/SetupStep.js.map +1 -1
- package/dist/workflows/steps/TagStep.d.ts +1 -4
- package/dist/workflows/steps/TagStep.js +4 -9
- package/dist/workflows/steps/TagStep.js.map +1 -1
- package/dist/workflows/steps/UploadSourcesStep.d.ts +1 -7
- package/dist/workflows/steps/UploadSourcesStep.js +4 -8
- package/dist/workflows/steps/UploadSourcesStep.js.map +1 -1
- package/dist/workflows/steps/UploadTranslationsStep.d.ts +1 -4
- package/dist/workflows/steps/UploadTranslationsStep.js +6 -11
- package/dist/workflows/steps/UploadTranslationsStep.js.map +1 -1
- package/dist/workflows/steps/UserEditDiffsStep.d.ts +1 -5
- package/dist/workflows/steps/UserEditDiffsStep.js +3 -11
- package/dist/workflows/steps/UserEditDiffsStep.js.map +1 -1
- package/dist/workflows/upload.js +3 -6
- package/dist/workflows/upload.js.map +1 -1
- package/dist/workflows/utils/syncFonts.d.ts +9 -0
- package/dist/workflows/utils/syncFonts.js +25 -0
- package/dist/workflows/utils/syncFonts.js.map +1 -0
- package/package.json +7 -6
- package/dist/workflows/steps/WorkflowStep.d.ts +0 -4
- package/dist/workflows/steps/WorkflowStep.js +0 -6
- package/dist/workflows/steps/WorkflowStep.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregateFiles.js","names":[],"sources":["../../../src/formats/files/aggregateFiles.ts"],"sourcesContent":["import { logger } from '../../console/logger.js';\nimport { recordWarning } from '../../state/translateWarnings.js';\nimport { getRelative, readFile } from '../../fs/findFilepath.js';\nimport { Settings } from '../../types/index.js';\nimport type { FileFormat, DataFormat, FileToUpload } from '../../types/data.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';\nimport { parseJson } from '../json/parseJson.js';\nimport {\n resolveMintlifyRefs,\n shouldResolveRefs,\n} from '../../utils/resolveMintlifyRefs.js';\nimport { storeRefMap } from '../../state/mintlifyRefMap.js';\nimport parseYaml from '../yaml/parseYaml.js';\nimport { validateYamlSchema } from '../yaml/utils.js';\nimport { flattenJson } from '../json/flattenJson.js';\nimport type { JSONObject } from '../../types/data/json.js';\nimport YAML from 'yaml';\nimport { determineLibrary } from '../../fs/determineFramework/index.js';\nimport { hashStringSync, hashVersionId } from '../../utils/hash.js';\nimport { preprocessContent } from './preprocessContent.js';\nimport {\n parseKeyedMetadata,\n type KeyedMetadata,\n} from '../parseKeyedMetadata.js';\nimport { buildPublishMap } from '../../utils/resolvePublish.js';\nimport { getTransformFormatProperty } from './transformFormat.js';\n\n/**\n * Checks if a file path is a metadata companion file (e.g. foo.metadata.json)\n * AND its corresponding source file (e.g. foo.json) exists in the file list.\n * If both conditions are true, the metadata file should be skipped as a translation source.\n */\nfunction isCompanionMetadataFile(\n filePath: string,\n allFilePaths: string[]\n): boolean {\n const metadataPattern = /\\.metadata\\.(json|yaml|yml)$/;\n if (!metadataPattern.test(filePath)) return false;\n\n // Derive the source file path: foo.metadata.json -> foo.json\n const sourceFilePath = filePath.replace(\n /\\.metadata\\.(json|yaml|yml)$/,\n '.$1'\n );\n return allFilePaths.includes(sourceFilePath);\n}\n\nexport async function aggregateFiles(\n settings: Settings\n): Promise<{ files: FileToUpload[]; publishMap: Map<string, boolean> }> {\n // Aggregate all files to translate\n const files: FileToUpload[] = [];\n if (\n !settings.files ||\n (Object.keys(settings.files.placeholderPaths).length === 1 &&\n settings.files.placeholderPaths.gt)\n ) {\n return { files, publishMap: new Map<string, boolean>() };\n }\n\n const { resolvedPaths: filePaths } = settings.files;\n // Tolerate partially-constructed settings from programmatic callers\n const requiresReviewPaths =\n settings.files.requiresReviewPaths ?? new Set<string>();\n const skipValidation = settings.options?.skipFileValidation;\n\n // Build publish map upfront from resolved paths.\n const publishMap = buildPublishMap(filePaths, settings);\n\n // Process JSON files\n if (filePaths.json) {\n const { library, additionalModules } = determineLibrary();\n\n // Determine dataFormat for JSONs\n let dataFormat: DataFormat;\n if (library === 'next-intl') {\n dataFormat = 'ICU';\n } else if (library === 'i18next') {\n if (additionalModules.includes('i18next-icu')) {\n dataFormat = 'ICU';\n } else {\n dataFormat = 'I18NEXT';\n }\n } else {\n dataFormat = 'STRING';\n }\n\n const jsonFiles = filePaths.json\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.json!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n // Resolve $ref before parsing if configured\n let contentForParsing = content;\n if (shouldResolveRefs(filePath, settings.options)) {\n try {\n const json = JSON.parse(content);\n const { resolved, refMap } = resolveMintlifyRefs(json, filePath);\n storeRefMap(refMap);\n contentForParsing = JSON.stringify(resolved, null, 2);\n } catch {\n // JSON parse errors are handled below by parseJson\n }\n }\n\n const parsedJson = parseJson(\n contentForParsing,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n let parsedContent: JSONObject | undefined;\n try {\n parsedContent = JSON.parse(content) as JSONObject;\n } catch {\n // Content not parsable — skip metadata detection\n }\n if (parsedContent) {\n const rawMetadata = parseKeyedMetadata(filePath, parsedContent);\n if (rawMetadata) {\n // Run metadata through the same include/composite schema as the source\n // so key paths align at translation time\n const transformed = parseJson(\n JSON.stringify(rawMetadata),\n filePath,\n settings.options || {},\n settings.defaultLocale,\n false\n );\n const transformedMetadata = JSON.parse(transformed);\n\n // Filter metadata to only keep keys that exist in the transformed source\n // This prevents misaligned entries from wide JSONPath patterns\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedJson)));\n const filtered = Object.fromEntries(\n Object.entries(transformedMetadata).filter(([k]) =>\n sourceKeys.has(k)\n )\n ) as KeyedMetadata;\n\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the JSON schema — metadata was not attached`\n );\n }\n }\n }\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'JSON' as const,\n ...getTransformFormatProperty(settings, 'json'),\n dataFormat,\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...jsonFiles.filter((file) => file !== null));\n }\n\n // Process YAML files\n if (filePaths.yaml) {\n const yamlFiles = filePaths.yaml\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.yaml!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate YAML parseability\n if (!skipValidation?.yaml) {\n try {\n YAML.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: YAML file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'YAML file is not parsable'\n );\n return null;\n }\n }\n\n const { content: parsedYaml, fileFormat } = parseYaml(\n content,\n filePath,\n settings.options || {}\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n try {\n const parsedYamlContent = YAML.parse(content);\n const rawMetadata = parseKeyedMetadata(filePath, parsedYamlContent);\n if (rawMetadata) {\n const yamlSchema = validateYamlSchema(\n settings.options || {},\n filePath\n );\n if (yamlSchema?.include) {\n // Flatten metadata through the same include schema as the source\n const flattened = flattenJson(rawMetadata, yamlSchema.include);\n // Filter to only keep keys that exist in the transformed source\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedYaml)));\n const filtered = Object.fromEntries(\n Object.entries(flattened).filter(([k]) => sourceKeys.has(k))\n ) as KeyedMetadata;\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the YAML schema — metadata was not attached`\n );\n }\n } else {\n keyedMetadata = rawMetadata;\n }\n }\n } catch {\n // Content not parsable as YAML — skip metadata detection\n }\n\n return {\n content: parsedYaml,\n fileName: relativePath,\n fileFormat,\n ...getTransformFormatProperty(settings, 'yaml'),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedYaml,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file || typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: YAML file is empty`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'YAML file is empty'\n );\n return false;\n }\n return true;\n });\n files.push(...yamlFiles.filter((file) => file !== null));\n }\n\n // Process Twilio Content JSON files\n if (filePaths.twilioContentJson) {\n const twilioContentJsonFiles = filePaths.twilioContentJson\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n const parsedJson = parseJson(\n content,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'TWILIO_CONTENT_JSON' as const,\n ...getTransformFormatProperty(settings, 'twilioContentJson'),\n dataFormat: 'STRING' as const,\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...twilioContentJsonFiles.filter((file) => file !== null));\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (\n fileType === 'json' ||\n fileType === 'yaml' ||\n fileType === 'twilioContentJson'\n )\n continue;\n if (filePaths[fileType]) {\n const parsed = filePaths[fileType]\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n const processed = preprocessContent(\n content,\n relativePath,\n fileType,\n settings\n );\n\n if (typeof processed !== 'string') {\n logger.warn(`Skipping ${relativePath}: ${processed.skip}`);\n recordWarning('skipped_file', relativePath, processed.skip);\n return null;\n }\n\n return {\n content: processed,\n fileName: relativePath,\n fileFormat: fileType.toUpperCase() as FileFormat,\n ...getTransformFormatProperty(settings, fileType),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n processed,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (\n !file ||\n typeof file.content !== 'string' ||\n !file.content.trim()\n ) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: File is empty after sanitization`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'File is empty after sanitization'\n );\n return false;\n }\n return true;\n });\n files.push(...parsed.filter((file) => file !== null));\n }\n }\n\n // Remove stale entries for files that were skipped during validation\n const validFileIds = new Set(files.map((f) => f.fileId));\n for (const fileId of publishMap.keys()) {\n if (!validFileIds.has(fileId)) {\n publishMap.delete(fileId);\n }\n }\n\n return { files, publishMap };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aggregateFiles.js","names":[],"sources":["../../../src/formats/files/aggregateFiles.ts"],"sourcesContent":["import { logger } from '../../console/logger.js';\nimport { logErrorAndExit } from '../../console/logging.js';\nimport { lottieExpressionsError } from '../../console/index.js';\nimport { recordWarning } from '../../state/translateWarnings.js';\nimport { lottieHasExpressions } from './detectLottieExpressions.js';\nimport {\n getRelative,\n readFile,\n readBinaryFileBase64,\n} from '../../fs/findFilepath.js';\nimport { Settings } from '../../types/index.js';\nimport type { FileFormat, DataFormat, FileToUpload } from '../../types/data.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';\nimport { parseJson } from '../json/parseJson.js';\nimport {\n resolveMintlifyRefs,\n shouldResolveRefs,\n} from '../../utils/resolveMintlifyRefs.js';\nimport { storeRefMap } from '../../state/mintlifyRefMap.js';\nimport parseYaml from '../yaml/parseYaml.js';\nimport { validateYamlSchema } from '../yaml/utils.js';\nimport { flattenJson } from '../json/flattenJson.js';\nimport type { JSONObject } from '../../types/data/json.js';\nimport YAML from 'yaml';\nimport { determineLibrary } from '../../fs/determineFramework/index.js';\nimport { hashStringSync, hashVersionId } from '../../utils/hash.js';\nimport { preprocessContent } from './preprocessContent.js';\nimport {\n parseKeyedMetadata,\n type KeyedMetadata,\n} from '../parseKeyedMetadata.js';\nimport { buildPublishMap } from '../../utils/resolvePublish.js';\nimport { getTransformFormatProperty } from './transformFormat.js';\n\n/**\n * Checks if a file path is a metadata companion file (e.g. foo.metadata.json)\n * AND its corresponding source file (e.g. foo.json) exists in the file list.\n * If both conditions are true, the metadata file should be skipped as a translation source.\n */\nfunction isCompanionMetadataFile(\n filePath: string,\n allFilePaths: string[]\n): boolean {\n const metadataPattern = /\\.metadata\\.(json|yaml|yml)$/;\n if (!metadataPattern.test(filePath)) return false;\n\n // Derive the source file path: foo.metadata.json -> foo.json\n const sourceFilePath = filePath.replace(\n /\\.metadata\\.(json|yaml|yml)$/,\n '.$1'\n );\n return allFilePaths.includes(sourceFilePath);\n}\n\nexport async function aggregateFiles(\n settings: Settings\n): Promise<{ files: FileToUpload[]; publishMap: Map<string, boolean> }> {\n // Aggregate all files to translate\n const files: FileToUpload[] = [];\n if (\n !settings.files ||\n (Object.keys(settings.files.placeholderPaths).length === 1 &&\n settings.files.placeholderPaths.gt)\n ) {\n return { files, publishMap: new Map<string, boolean>() };\n }\n\n const { resolvedPaths: filePaths } = settings.files;\n // Tolerate partially-constructed settings from programmatic callers\n const requiresReviewPaths =\n settings.files.requiresReviewPaths ?? new Set<string>();\n const skipValidation = settings.options?.skipFileValidation;\n\n // Build publish map upfront from resolved paths.\n const publishMap = buildPublishMap(filePaths, settings);\n\n // Process JSON files\n if (filePaths.json) {\n const { library, additionalModules } = determineLibrary();\n\n // Determine dataFormat for JSONs\n let dataFormat: DataFormat;\n if (library === 'next-intl') {\n dataFormat = 'ICU';\n } else if (library === 'i18next') {\n if (additionalModules.includes('i18next-icu')) {\n dataFormat = 'ICU';\n } else {\n dataFormat = 'I18NEXT';\n }\n } else {\n dataFormat = 'STRING';\n }\n\n const jsonFiles = filePaths.json\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.json!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n // Resolve $ref before parsing if configured\n let contentForParsing = content;\n if (shouldResolveRefs(filePath, settings.options)) {\n try {\n const json = JSON.parse(content);\n const { resolved, refMap } = resolveMintlifyRefs(json, filePath);\n storeRefMap(refMap);\n contentForParsing = JSON.stringify(resolved, null, 2);\n } catch {\n // JSON parse errors are handled below by parseJson\n }\n }\n\n const parsedJson = parseJson(\n contentForParsing,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n let parsedContent: JSONObject | undefined;\n try {\n parsedContent = JSON.parse(content) as JSONObject;\n } catch {\n // Content not parsable — skip metadata detection\n }\n if (parsedContent) {\n const rawMetadata = parseKeyedMetadata(filePath, parsedContent);\n if (rawMetadata) {\n // Run metadata through the same include/composite schema as the source\n // so key paths align at translation time\n const transformed = parseJson(\n JSON.stringify(rawMetadata),\n filePath,\n settings.options || {},\n settings.defaultLocale,\n false\n );\n const transformedMetadata = JSON.parse(transformed);\n\n // Filter metadata to only keep keys that exist in the transformed source\n // This prevents misaligned entries from wide JSONPath patterns\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedJson)));\n const filtered = Object.fromEntries(\n Object.entries(transformedMetadata).filter(([k]) =>\n sourceKeys.has(k)\n )\n ) as KeyedMetadata;\n\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the JSON schema — metadata was not attached`\n );\n }\n }\n }\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'JSON' as const,\n ...getTransformFormatProperty(settings, 'json'),\n dataFormat,\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...jsonFiles.filter((file) => file !== null));\n }\n\n // Process YAML files\n if (filePaths.yaml) {\n const yamlFiles = filePaths.yaml\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.yaml!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate YAML parseability\n if (!skipValidation?.yaml) {\n try {\n YAML.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: YAML file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'YAML file is not parsable'\n );\n return null;\n }\n }\n\n const { content: parsedYaml, fileFormat } = parseYaml(\n content,\n filePath,\n settings.options || {}\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n try {\n const parsedYamlContent = YAML.parse(content);\n const rawMetadata = parseKeyedMetadata(filePath, parsedYamlContent);\n if (rawMetadata) {\n const yamlSchema = validateYamlSchema(\n settings.options || {},\n filePath\n );\n if (yamlSchema?.include) {\n // Flatten metadata through the same include schema as the source\n const flattened = flattenJson(rawMetadata, yamlSchema.include);\n // Filter to only keep keys that exist in the transformed source\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedYaml)));\n const filtered = Object.fromEntries(\n Object.entries(flattened).filter(([k]) => sourceKeys.has(k))\n ) as KeyedMetadata;\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the YAML schema — metadata was not attached`\n );\n }\n } else {\n keyedMetadata = rawMetadata;\n }\n }\n } catch {\n // Content not parsable as YAML — skip metadata detection\n }\n\n return {\n content: parsedYaml,\n fileName: relativePath,\n fileFormat,\n ...getTransformFormatProperty(settings, 'yaml'),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedYaml,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file || typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: YAML file is empty`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'YAML file is empty'\n );\n return false;\n }\n return true;\n });\n files.push(...yamlFiles.filter((file) => file !== null));\n }\n\n // Process Twilio Content JSON files\n if (filePaths.twilioContentJson) {\n const twilioContentJsonFiles = filePaths.twilioContentJson\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n const parsedJson = parseJson(\n content,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'TWILIO_CONTENT_JSON' as const,\n ...getTransformFormatProperty(settings, 'twilioContentJson'),\n dataFormat: 'STRING' as const,\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...twilioContentJsonFiles.filter((file) => file !== null));\n }\n\n // Process Lottie files (binary zip bundles). Content is read as raw bytes and\n // carried base64-encoded end-to-end; the downloaded translation is written\n // back as bytes. No text parsing/merge.\n if (filePaths.lottie) {\n // Lottie files carrying After Effects expressions (executable JS) are\n // rejected outright — collect every offender, then fail once with all of\n // them named rather than aborting on the first.\n const expressionOffenders: string[] = [];\n const lottieFiles = filePaths.lottie\n .map((filePath) => {\n const content = readBinaryFileBase64(filePath);\n const relativePath = getRelative(filePath);\n if (!content) {\n logger.warn(`Skipping ${relativePath}: file is empty or unreadable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'File is empty or unreadable'\n );\n return null;\n }\n if (lottieHasExpressions(content)) {\n expressionOffenders.push(relativePath);\n return null;\n }\n return {\n content,\n fileName: relativePath,\n fileFormat: 'LOTTIE' as const,\n ...getTransformFormatProperty(settings, 'lottie'),\n fileId: hashStringSync(relativePath),\n versionId: hashStringSync(content),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => file !== null);\n if (expressionOffenders.length > 0) {\n logErrorAndExit(lottieExpressionsError(expressionOffenders));\n }\n files.push(...lottieFiles);\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (\n fileType === 'json' ||\n fileType === 'yaml' ||\n fileType === 'twilioContentJson' ||\n fileType === 'lottie'\n )\n continue;\n if (filePaths[fileType]) {\n const parsed = filePaths[fileType]\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n const processed = preprocessContent(\n content,\n relativePath,\n fileType,\n settings\n );\n\n if (typeof processed !== 'string') {\n logger.warn(`Skipping ${relativePath}: ${processed.skip}`);\n recordWarning('skipped_file', relativePath, processed.skip);\n return null;\n }\n\n return {\n content: processed,\n fileName: relativePath,\n fileFormat: fileType.toUpperCase() as FileFormat,\n ...getTransformFormatProperty(settings, fileType),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n processed,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (\n !file ||\n typeof file.content !== 'string' ||\n !file.content.trim()\n ) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: File is empty after sanitization`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'File is empty after sanitization'\n );\n return false;\n }\n return true;\n });\n files.push(...parsed.filter((file) => file !== null));\n }\n }\n\n // Remove stale entries for files that were skipped during validation\n const validFileIds = new Set(files.map((f) => f.fileId));\n for (const fileId of publishMap.keys()) {\n if (!validFileIds.has(fileId)) {\n publishMap.delete(fileId);\n }\n }\n\n return { files, publishMap };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fg from "fast-glob";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
//#region src/formats/files/collectFonts.ts
|
|
5
|
+
async function collectFonts(settings) {
|
|
6
|
+
const fonts = settings.fonts;
|
|
7
|
+
if (!fonts?.include?.length) return [];
|
|
8
|
+
const cwd = settings.configDirectory ? path.dirname(settings.configDirectory) : process.cwd();
|
|
9
|
+
const matches = await fg(fonts.include, {
|
|
10
|
+
cwd,
|
|
11
|
+
ignore: fonts.exclude ?? [],
|
|
12
|
+
absolute: true,
|
|
13
|
+
onlyFiles: true,
|
|
14
|
+
unique: true
|
|
15
|
+
});
|
|
16
|
+
const uploads = [];
|
|
17
|
+
for (const absolutePath of matches) {
|
|
18
|
+
const bytes = await readFile(absolutePath);
|
|
19
|
+
uploads.push({
|
|
20
|
+
assetType: "FONT",
|
|
21
|
+
fileName: path.basename(absolutePath),
|
|
22
|
+
content: bytes.toString("base64")
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return uploads;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { collectFonts };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=collectFonts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collectFonts.js","names":[],"sources":["../../../src/formats/files/collectFonts.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport fg from 'fast-glob';\n\nimport type { Settings } from '../../types/index.js';\n\n// One font to upload to /v2/project/assets. Structurally matches the SDK's\n// AssetUpload; content is base64 (fonts are binary).\nexport type FontUpload = {\n assetType: 'FONT';\n fileName: string;\n content: string;\n};\n\n// Resolve the config's `fonts` globs to base64-encoded font uploads. Fonts are\n// locale-invariant, so there's no [locale] placeholder — just match the files\n// and read them as binary.\nexport async function collectFonts(settings: Settings): Promise<FontUpload[]> {\n const fonts = settings.fonts;\n if (!fonts?.include?.length) return [];\n\n // `files.include` globs resolve from the cwd generateSettings ran in, and\n // configDirectory is always `<cwd>/.gt` — so its parent is that same project\n // root. Globbing from configDirectory itself would search under `.gt/`.\n const cwd = settings.configDirectory\n ? path.dirname(settings.configDirectory)\n : process.cwd();\n const matches = await fg(fonts.include, {\n cwd,\n ignore: fonts.exclude ?? [],\n absolute: true,\n onlyFiles: true,\n unique: true,\n });\n\n const uploads: FontUpload[] = [];\n for (const absolutePath of matches) {\n const bytes = await readFile(absolutePath);\n uploads.push({\n assetType: 'FONT',\n fileName: path.basename(absolutePath),\n content: bytes.toString('base64'),\n });\n }\n return uploads;\n}\n"],"mappings":";;;;AAkBA,eAAsB,aAAa,UAA2C;CAC5E,MAAM,QAAQ,SAAS;AACvB,KAAI,CAAC,OAAO,SAAS,OAAQ,QAAO,EAAE;CAKtC,MAAM,MAAM,SAAS,kBACjB,KAAK,QAAQ,SAAS,gBAAgB,GACtC,QAAQ,KAAK;CACjB,MAAM,UAAU,MAAM,GAAG,MAAM,SAAS;EACtC;EACA,QAAQ,MAAM,WAAW,EAAE;EAC3B,UAAU;EACV,WAAW;EACX,QAAQ;EACT,CAAC;CAEF,MAAM,UAAwB,EAAE;AAChC,MAAK,MAAM,gBAAgB,SAAS;EAClC,MAAM,QAAQ,MAAM,SAAS,aAAa;AAC1C,UAAQ,KAAK;GACX,WAAW;GACX,UAAU,KAAK,SAAS,aAAa;GACrC,SAAS,MAAM,SAAS,SAAS;GAClC,CAAC;;AAEJ,QAAO"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true if a base64-encoded Lottie carries any After Effects expression
|
|
3
|
+
* (executable JavaScript). Handles both the dotLottie ZIP bundle (scans each
|
|
4
|
+
* `animations/*.json`) and a bare bodymovin JSON that uses the `.lottie`
|
|
5
|
+
* extension. Best-effort: an unreadable/non-Lottie payload returns false and is
|
|
6
|
+
* left to the existing upload validation.
|
|
7
|
+
*/
|
|
8
|
+
export declare function lottieHasExpressions(base64Content: string): boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { unzipSync } from "fflate";
|
|
2
|
+
//#region src/formats/files/detectLottieExpressions.ts
|
|
3
|
+
function hasExpressionNode(node) {
|
|
4
|
+
if (Array.isArray(node)) {
|
|
5
|
+
for (const v of node) if (hasExpressionNode(v)) return true;
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (node !== null && typeof node === "object") {
|
|
9
|
+
const x = node.x;
|
|
10
|
+
if (typeof x === "string" && x.trim() !== "") return true;
|
|
11
|
+
for (const key in node) if (hasExpressionNode(node[key])) return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function jsonHasExpression(text) {
|
|
16
|
+
try {
|
|
17
|
+
return hasExpressionNode(JSON.parse(text));
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns true if a base64-encoded Lottie carries any After Effects expression
|
|
24
|
+
* (executable JavaScript). Handles both the dotLottie ZIP bundle (scans each
|
|
25
|
+
* `animations/*.json`) and a bare bodymovin JSON that uses the `.lottie`
|
|
26
|
+
* extension. Best-effort: an unreadable/non-Lottie payload returns false and is
|
|
27
|
+
* left to the existing upload validation.
|
|
28
|
+
*/
|
|
29
|
+
function lottieHasExpressions(base64Content) {
|
|
30
|
+
const bytes = Buffer.from(base64Content, "base64");
|
|
31
|
+
try {
|
|
32
|
+
const entries = unzipSync(bytes, { filter: (file) => file.name.toLowerCase().endsWith(".json") });
|
|
33
|
+
for (const data of Object.values(entries)) if (jsonHasExpression(Buffer.from(data).toString("utf8"))) return true;
|
|
34
|
+
return false;
|
|
35
|
+
} catch {
|
|
36
|
+
return jsonHasExpression(bytes.toString("utf8"));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { lottieHasExpressions };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=detectLottieExpressions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detectLottieExpressions.js","names":[],"sources":["../../../src/formats/files/detectLottieExpressions.ts"],"sourcesContent":["import { unzipSync } from 'fflate';\n\nimport type { JSONValue } from '../../types/data/json.js';\n\n// After Effects \"expressions\" are JavaScript strings attached to an animated\n// property at key `x`; the full lottie-web build evaluates them with eval() at\n// render time, so a crafted .lottie is an arbitrary-code-execution vector. Until\n// the renderer strips/sandboxes them, we reject any Lottie that carries one.\n//\n// A split-dimension position ALSO uses `x`, but as an object/array (a nested\n// property) — never a string. So a non-empty string value at key `x` is the\n// unambiguous marker of an expression.\nfunction hasExpressionNode(node: JSONValue): boolean {\n if (Array.isArray(node)) {\n for (const v of node) if (hasExpressionNode(v)) return true;\n return false;\n }\n if (node !== null && typeof node === 'object') {\n const x = (node as { [key: string]: JSONValue }).x;\n if (typeof x === 'string' && x.trim() !== '') return true;\n for (const key in node) {\n if (hasExpressionNode((node as { [key: string]: JSONValue })[key])) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction jsonHasExpression(text: string): boolean {\n try {\n return hasExpressionNode(JSON.parse(text) as JSONValue);\n } catch {\n return false; // not JSON — nothing to scan\n }\n}\n\n/**\n * Returns true if a base64-encoded Lottie carries any After Effects expression\n * (executable JavaScript). Handles both the dotLottie ZIP bundle (scans each\n * `animations/*.json`) and a bare bodymovin JSON that uses the `.lottie`\n * extension. Best-effort: an unreadable/non-Lottie payload returns false and is\n * left to the existing upload validation.\n */\nexport function lottieHasExpressions(base64Content: string): boolean {\n const bytes = Buffer.from(base64Content, 'base64');\n try {\n // Only decompress JSON entries — skip embedded images/fonts/audio.\n const entries = unzipSync(bytes, {\n filter: (file) => file.name.toLowerCase().endsWith('.json'),\n });\n for (const data of Object.values(entries)) {\n if (jsonHasExpression(Buffer.from(data).toString('utf8'))) return true;\n }\n return false;\n } catch {\n // Not a valid ZIP — maybe a bare bodymovin JSON with a .lottie extension.\n return jsonHasExpression(bytes.toString('utf8'));\n }\n}\n"],"mappings":";;AAYA,SAAS,kBAAkB,MAA0B;AACnD,KAAI,MAAM,QAAQ,KAAK,EAAE;AACvB,OAAK,MAAM,KAAK,KAAM,KAAI,kBAAkB,EAAE,CAAE,QAAO;AACvD,SAAO;;AAET,KAAI,SAAS,QAAQ,OAAO,SAAS,UAAU;EAC7C,MAAM,IAAK,KAAsC;AACjD,MAAI,OAAO,MAAM,YAAY,EAAE,MAAM,KAAK,GAAI,QAAO;AACrD,OAAK,MAAM,OAAO,KAChB,KAAI,kBAAmB,KAAsC,KAAK,CAChE,QAAO;;AAIb,QAAO;;AAGT,SAAS,kBAAkB,MAAuB;AAChD,KAAI;AACF,SAAO,kBAAkB,KAAK,MAAM,KAAK,CAAc;SACjD;AACN,SAAO;;;;;;;;;;AAWX,SAAgB,qBAAqB,eAAgC;CACnE,MAAM,QAAQ,OAAO,KAAK,eAAe,SAAS;AAClD,KAAI;EAEF,MAAM,UAAU,UAAU,OAAO,EAC/B,SAAS,SAAS,KAAK,KAAK,aAAa,CAAC,SAAS,QAAQ,EAC5D,CAAC;AACF,OAAK,MAAM,QAAQ,OAAO,OAAO,QAAQ,CACvC,KAAI,kBAAkB,OAAO,KAAK,KAAK,CAAC,SAAS,OAAO,CAAC,CAAE,QAAO;AAEpE,SAAO;SACD;AAEN,SAAO,kBAAkB,MAAM,SAAS,OAAO,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SUPPORTED_FILE_EXTENSIONS: readonly ["json", "pot", "mdx", "md", "ts", "js", "yaml", "html", "txt", "twilioContentJson"];
|
|
1
|
+
export declare const SUPPORTED_FILE_EXTENSIONS: readonly ["json", "pot", "mdx", "md", "ts", "js", "yaml", "html", "txt", "twilioContentJson", "lottie"];
|
|
2
2
|
export declare const FILE_EXT_TO_EXT_LABEL: {
|
|
3
3
|
json: string;
|
|
4
4
|
pot: string;
|
|
@@ -10,4 +10,5 @@ export declare const FILE_EXT_TO_EXT_LABEL: {
|
|
|
10
10
|
html: string;
|
|
11
11
|
txt: string;
|
|
12
12
|
twilioContentJson: string;
|
|
13
|
+
lottie: string;
|
|
13
14
|
};
|
|
@@ -9,7 +9,8 @@ const SUPPORTED_FILE_EXTENSIONS = [
|
|
|
9
9
|
"yaml",
|
|
10
10
|
"html",
|
|
11
11
|
"txt",
|
|
12
|
-
"twilioContentJson"
|
|
12
|
+
"twilioContentJson",
|
|
13
|
+
"lottie"
|
|
13
14
|
];
|
|
14
15
|
const FILE_EXT_TO_EXT_LABEL = {
|
|
15
16
|
json: "JSON",
|
|
@@ -21,7 +22,8 @@ const FILE_EXT_TO_EXT_LABEL = {
|
|
|
21
22
|
yaml: "YAML",
|
|
22
23
|
html: "HTML",
|
|
23
24
|
txt: "Text",
|
|
24
|
-
twilioContentJson: "Twilio Content JSON"
|
|
25
|
+
twilioContentJson: "Twilio Content JSON",
|
|
26
|
+
lottie: "Lottie"
|
|
25
27
|
};
|
|
26
28
|
//#endregion
|
|
27
29
|
export { FILE_EXT_TO_EXT_LABEL, SUPPORTED_FILE_EXTENSIONS };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supportedFiles.js","names":[],"sources":["../../../src/formats/files/supportedFiles.ts"],"sourcesContent":["export const SUPPORTED_FILE_EXTENSIONS = [\n 'json',\n 'pot',\n 'mdx',\n 'md',\n 'ts',\n 'js',\n 'yaml',\n 'html',\n 'txt',\n 'twilioContentJson',\n] as const;\n\nexport const FILE_EXT_TO_EXT_LABEL = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'Markdown',\n ts: 'TypeScript',\n js: 'JavaScript',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'Text',\n twilioContentJson: 'Twilio Content JSON',\n};\n"],"mappings":";AAAA,MAAa,4BAA4B;CACvC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAa,wBAAwB;CACnC,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;
|
|
1
|
+
{"version":3,"file":"supportedFiles.js","names":[],"sources":["../../../src/formats/files/supportedFiles.ts"],"sourcesContent":["export const SUPPORTED_FILE_EXTENSIONS = [\n 'json',\n 'pot',\n 'mdx',\n 'md',\n 'ts',\n 'js',\n 'yaml',\n 'html',\n 'txt',\n 'twilioContentJson',\n 'lottie',\n] as const;\n\nexport const FILE_EXT_TO_EXT_LABEL = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'Markdown',\n ts: 'TypeScript',\n js: 'JavaScript',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'Text',\n twilioContentJson: 'Twilio Content JSON',\n lottie: 'Lottie',\n};\n"],"mappings":";AAAA,MAAa,4BAA4B;CACvC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAa,wBAAwB;CACnC,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;CACnB,QAAQ;CACT"}
|
|
@@ -14,6 +14,7 @@ export declare const CONFIG_FILE_TYPE_TO_FILE_FORMAT: {
|
|
|
14
14
|
readonly html: "HTML";
|
|
15
15
|
readonly txt: "TXT";
|
|
16
16
|
readonly twilioContentJson: "TWILIO_CONTENT_JSON";
|
|
17
|
+
readonly lottie: "LOTTIE";
|
|
17
18
|
};
|
|
18
19
|
/**
|
|
19
20
|
* Maps uppercase config aliases to the CLI's canonical lowercase file keys.
|
|
@@ -29,6 +30,7 @@ export declare const FILE_FORMAT_TO_CONFIG_FILE_TYPE: {
|
|
|
29
30
|
readonly HTML: "html";
|
|
30
31
|
readonly TXT: "txt";
|
|
31
32
|
readonly TWILIO_CONTENT_JSON: "twilioContentJson";
|
|
33
|
+
readonly LOTTIE: "lottie";
|
|
32
34
|
};
|
|
33
35
|
/**
|
|
34
36
|
* Converts uppercase file format config keys into the CLI's canonical lowercase keys.
|
|
@@ -13,7 +13,8 @@ const CONFIG_FILE_TYPE_TO_FILE_FORMAT = {
|
|
|
13
13
|
yaml: "YAML",
|
|
14
14
|
html: "HTML",
|
|
15
15
|
txt: "TXT",
|
|
16
|
-
twilioContentJson: "TWILIO_CONTENT_JSON"
|
|
16
|
+
twilioContentJson: "TWILIO_CONTENT_JSON",
|
|
17
|
+
lottie: "LOTTIE"
|
|
17
18
|
};
|
|
18
19
|
/**
|
|
19
20
|
* Maps uppercase config aliases to the CLI's canonical lowercase file keys.
|
|
@@ -28,7 +29,8 @@ const FILE_FORMAT_TO_CONFIG_FILE_TYPE = {
|
|
|
28
29
|
YAML: "yaml",
|
|
29
30
|
HTML: "html",
|
|
30
31
|
TXT: "txt",
|
|
31
|
-
TWILIO_CONTENT_JSON: "twilioContentJson"
|
|
32
|
+
TWILIO_CONTENT_JSON: "twilioContentJson",
|
|
33
|
+
LOTTIE: "lottie"
|
|
32
34
|
};
|
|
33
35
|
/**
|
|
34
36
|
* Maps API file format enum values to the extension the CLI should write.
|
|
@@ -45,7 +47,8 @@ const FILE_FORMAT_EXTENSIONS = {
|
|
|
45
47
|
JS: "js",
|
|
46
48
|
HTML: "html",
|
|
47
49
|
TXT: "txt",
|
|
48
|
-
TWILIO_CONTENT_JSON: "json"
|
|
50
|
+
TWILIO_CONTENT_JSON: "json",
|
|
51
|
+
LOTTIE: "lottie"
|
|
49
52
|
};
|
|
50
53
|
/**
|
|
51
54
|
* Converts uppercase file format config keys into the CLI's canonical lowercase keys.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformFormat.js","names":[],"sources":["../../../src/formats/files/transformFormat.ts"],"sourcesContent":["import type { FileFormat } from '../../types/data.js';\nimport type {\n FilesOptions,\n Settings,\n SupportedFileExtension,\n} from '../../types/index.js';\nimport { isSupportedFileFormatTransform } from 'generaltranslation/internal';\n\n/**\n * Maps CLI config file keys to API file format enum values.\n */\nexport const CONFIG_FILE_TYPE_TO_FILE_FORMAT = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'MD',\n ts: 'TS',\n js: 'JS',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'TXT',\n twilioContentJson: 'TWILIO_CONTENT_JSON',\n} as const satisfies Record<SupportedFileExtension, FileFormat>;\n\n/**\n * Maps uppercase config aliases to the CLI's canonical lowercase file keys.\n */\nexport const FILE_FORMAT_TO_CONFIG_FILE_TYPE = {\n JSON: 'json',\n POT: 'pot',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n YAML: 'yaml',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'twilioContentJson',\n} as const satisfies Partial<Record<FileFormat, SupportedFileExtension>>;\n\n/**\n * Maps API file format enum values to the extension the CLI should write.\n */\nconst FILE_FORMAT_EXTENSIONS = {\n GTJSON: 'json',\n JSON: 'json',\n PO: 'po',\n POT: 'pot',\n YAML: 'yaml',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'json',\n} as const satisfies Record<FileFormat, string>;\n\n/**\n * Converts uppercase file format config keys into the CLI's canonical lowercase keys.\n *\n * This lets users write either `files.POT` or `files.pot` while keeping the\n * rest of the CLI on its existing lowercase file-type convention.\n */\nexport function normalizeFilesOptions(files: FilesOptions): FilesOptions {\n const normalized = { ...files } as FilesOptions & Record<string, unknown>;\n\n for (const [fileFormat, fileType] of Object.entries(\n FILE_FORMAT_TO_CONFIG_FILE_TYPE\n ) as [string, SupportedFileExtension][]) {\n const uppercaseConfig = normalized[fileFormat] as\n | FilesOptions[SupportedFileExtension]\n | undefined;\n if (!normalized[fileType] && uppercaseConfig) {\n normalized[fileType] = uppercaseConfig;\n }\n delete normalized[fileFormat];\n }\n\n return normalized;\n}\n\n/**\n * Validates and resolves a configured output format for a source file type.\n *\n * Throws when the requested source -> output format is not supported by\n * `generaltranslation/internal`.\n */\nexport function resolveTransformationFormat(\n fileType: SupportedFileExtension,\n transformationFormat: string | undefined\n): FileFormat | undefined {\n if (!transformationFormat) return undefined;\n\n // Normalize to uppercase to match the FileFormat enum (e.g. \"po\" -> \"PO\")\n const normalized = transformationFormat.toUpperCase() as FileFormat;\n const fileFormat = CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType];\n\n if (!isSupportedFileFormatTransform(fileFormat, normalized)) {\n throw new Error(\n `Unsupported file format transform: ${fileFormat} -> ${normalized} in files.${fileType}. ` +\n `\"${normalized}\" is not a valid transformationFormat for ${fileFormat} source files.`\n );\n }\n\n return normalized;\n}\n\n/**\n * Returns the API upload/enqueue property for a file type when one is configured.\n */\nexport function getTransformFormatProperty(\n settings: Settings,\n fileType: SupportedFileExtension\n): { transformFormat?: FileFormat } {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return transformFormat ? { transformFormat } : {};\n}\n\n/**\n * Returns the preferred file extension for a translated file format.\n */\nexport function getFileExtensionForFormat(format: FileFormat): string {\n return FILE_FORMAT_EXTENSIONS[format];\n}\n\n/**\n * Rewrites a path's extension to match the translated file format.\n */\nexport function replaceFileExtensionForFormat(\n filePath: string,\n format: FileFormat\n): string {\n const extension = getFileExtensionForFormat(format);\n return /\\.[^/.]+$/.test(filePath)\n ? filePath.replace(/\\.[^/.]+$/, `.${extension}`)\n : `${filePath}.${extension}`;\n}\n\n/**\n * Detects whether any configured format transform changes the output file type.\n */\nexport function hasNonIdentityFileFormatTransform(settings: Settings): boolean {\n return Object.entries(settings.files?.transformFormats || {}).some(\n ([fileType, transformFormat]) =>\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType as SupportedFileExtension] !==\n transformFormat\n );\n}\n\n/**\n * Returns true when the configured transform for a file type changes its format.\n */\nexport function hasNonIdentityFileFormatTransformForType(\n settings: Settings,\n fileType: SupportedFileExtension\n): boolean {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return !!(\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType] !== transformFormat\n );\n}\n"],"mappings":";;;;;AAWA,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;
|
|
1
|
+
{"version":3,"file":"transformFormat.js","names":[],"sources":["../../../src/formats/files/transformFormat.ts"],"sourcesContent":["import type { FileFormat } from '../../types/data.js';\nimport type {\n FilesOptions,\n Settings,\n SupportedFileExtension,\n} from '../../types/index.js';\nimport { isSupportedFileFormatTransform } from 'generaltranslation/internal';\n\n/**\n * Maps CLI config file keys to API file format enum values.\n */\nexport const CONFIG_FILE_TYPE_TO_FILE_FORMAT = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'MD',\n ts: 'TS',\n js: 'JS',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'TXT',\n twilioContentJson: 'TWILIO_CONTENT_JSON',\n lottie: 'LOTTIE',\n} as const satisfies Record<SupportedFileExtension, FileFormat>;\n\n/**\n * Maps uppercase config aliases to the CLI's canonical lowercase file keys.\n */\nexport const FILE_FORMAT_TO_CONFIG_FILE_TYPE = {\n JSON: 'json',\n POT: 'pot',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n YAML: 'yaml',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'twilioContentJson',\n LOTTIE: 'lottie',\n} as const satisfies Partial<Record<FileFormat, SupportedFileExtension>>;\n\n/**\n * Maps API file format enum values to the extension the CLI should write.\n */\nconst FILE_FORMAT_EXTENSIONS = {\n GTJSON: 'json',\n JSON: 'json',\n PO: 'po',\n POT: 'pot',\n YAML: 'yaml',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'json',\n LOTTIE: 'lottie',\n} as const satisfies Record<FileFormat, string>;\n\n/**\n * Converts uppercase file format config keys into the CLI's canonical lowercase keys.\n *\n * This lets users write either `files.POT` or `files.pot` while keeping the\n * rest of the CLI on its existing lowercase file-type convention.\n */\nexport function normalizeFilesOptions(files: FilesOptions): FilesOptions {\n const normalized = { ...files } as FilesOptions & Record<string, unknown>;\n\n for (const [fileFormat, fileType] of Object.entries(\n FILE_FORMAT_TO_CONFIG_FILE_TYPE\n ) as [string, SupportedFileExtension][]) {\n const uppercaseConfig = normalized[fileFormat] as\n | FilesOptions[SupportedFileExtension]\n | undefined;\n if (!normalized[fileType] && uppercaseConfig) {\n normalized[fileType] = uppercaseConfig;\n }\n delete normalized[fileFormat];\n }\n\n return normalized;\n}\n\n/**\n * Validates and resolves a configured output format for a source file type.\n *\n * Throws when the requested source -> output format is not supported by\n * `generaltranslation/internal`.\n */\nexport function resolveTransformationFormat(\n fileType: SupportedFileExtension,\n transformationFormat: string | undefined\n): FileFormat | undefined {\n if (!transformationFormat) return undefined;\n\n // Normalize to uppercase to match the FileFormat enum (e.g. \"po\" -> \"PO\")\n const normalized = transformationFormat.toUpperCase() as FileFormat;\n const fileFormat = CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType];\n\n if (!isSupportedFileFormatTransform(fileFormat, normalized)) {\n throw new Error(\n `Unsupported file format transform: ${fileFormat} -> ${normalized} in files.${fileType}. ` +\n `\"${normalized}\" is not a valid transformationFormat for ${fileFormat} source files.`\n );\n }\n\n return normalized;\n}\n\n/**\n * Returns the API upload/enqueue property for a file type when one is configured.\n */\nexport function getTransformFormatProperty(\n settings: Settings,\n fileType: SupportedFileExtension\n): { transformFormat?: FileFormat } {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return transformFormat ? { transformFormat } : {};\n}\n\n/**\n * Returns the preferred file extension for a translated file format.\n */\nexport function getFileExtensionForFormat(format: FileFormat): string {\n return FILE_FORMAT_EXTENSIONS[format];\n}\n\n/**\n * Rewrites a path's extension to match the translated file format.\n */\nexport function replaceFileExtensionForFormat(\n filePath: string,\n format: FileFormat\n): string {\n const extension = getFileExtensionForFormat(format);\n return /\\.[^/.]+$/.test(filePath)\n ? filePath.replace(/\\.[^/.]+$/, `.${extension}`)\n : `${filePath}.${extension}`;\n}\n\n/**\n * Detects whether any configured format transform changes the output file type.\n */\nexport function hasNonIdentityFileFormatTransform(settings: Settings): boolean {\n return Object.entries(settings.files?.transformFormats || {}).some(\n ([fileType, transformFormat]) =>\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType as SupportedFileExtension] !==\n transformFormat\n );\n}\n\n/**\n * Returns true when the configured transform for a file type changes its format.\n */\nexport function hasNonIdentityFileFormatTransformForType(\n settings: Settings,\n fileType: SupportedFileExtension\n): boolean {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return !!(\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType] !== transformFormat\n );\n}\n"],"mappings":";;;;;AAWA,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;CACnB,QAAQ;CACT;;;;AAKD,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,qBAAqB;CACrB,QAAQ;CACT;;;;AAKD,MAAM,yBAAyB;CAC7B,QAAQ;CACR,MAAM;CACN,IAAI;CACJ,KAAK;CACL,MAAM;CACN,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,KAAK;CACL,qBAAqB;CACrB,QAAQ;CACT;;;;;;;AAQD,SAAgB,sBAAsB,OAAmC;CACvE,MAAM,aAAa,EAAE,GAAG,OAAO;AAE/B,MAAK,MAAM,CAAC,YAAY,aAAa,OAAO,QAC1C,gCACD,EAAwC;EACvC,MAAM,kBAAkB,WAAW;AAGnC,MAAI,CAAC,WAAW,aAAa,gBAC3B,YAAW,YAAY;AAEzB,SAAO,WAAW;;AAGpB,QAAO;;;;;;;;AAST,SAAgB,4BACd,UACA,sBACwB;AACxB,KAAI,CAAC,qBAAsB,QAAO,KAAA;CAGlC,MAAM,aAAa,qBAAqB,aAAa;CACrD,MAAM,aAAa,gCAAgC;AAEnD,KAAI,CAAC,+BAA+B,YAAY,WAAW,CACzD,OAAM,IAAI,MACR,sCAAsC,WAAW,MAAM,WAAW,YAAY,SAAS,KACjF,WAAW,4CAA4C,WAAW,gBACzE;AAGH,QAAO;;;;;AAMT,SAAgB,2BACd,UACA,UACkC;CAClC,MAAM,kBAAkB,SAAS,OAAO,mBAAmB;AAC3D,QAAO,kBAAkB,EAAE,iBAAiB,GAAG,EAAE;;;;;AAMnD,SAAgB,0BAA0B,QAA4B;AACpE,QAAO,uBAAuB;;;;;AAMhC,SAAgB,8BACd,UACA,QACQ;CACR,MAAM,YAAY,0BAA0B,OAAO;AACnD,QAAO,YAAY,KAAK,SAAS,GAC7B,SAAS,QAAQ,aAAa,IAAI,YAAY,GAC9C,GAAG,SAAS,GAAG;;;;;AAMrB,SAAgB,kCAAkC,UAA6B;AAC7E,QAAO,OAAO,QAAQ,SAAS,OAAO,oBAAoB,EAAE,CAAC,CAAC,MAC3D,CAAC,UAAU,qBACV,mBACA,gCAAgC,cAC9B,gBACL;;;;;AAMH,SAAgB,yCACd,UACA,UACS;CACT,MAAM,kBAAkB,SAAS,OAAO,mBAAmB;AAC3D,QAAO,CAAC,EACN,mBACA,gCAAgC,cAAc"}
|
|
@@ -26,6 +26,13 @@ export declare function findFile(filePattern: string, file: string): string;
|
|
|
26
26
|
* @returns {string} - The contents of the file.
|
|
27
27
|
*/
|
|
28
28
|
export declare function readFile(filePath: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Read a file as raw bytes and return it base64-encoded. Used for binary
|
|
31
|
+
* formats (e.g. Lottie zip bundles) whose content must not be decoded as UTF-8.
|
|
32
|
+
* @param {string} filePath - The path to the file to read.
|
|
33
|
+
* @returns {string} - The base64-encoded contents, or '' if the file is absent.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readBinaryFileBase64(filePath: string): string;
|
|
29
36
|
/**
|
|
30
37
|
* Find a file in a directory.
|
|
31
38
|
* @param {string} dir - The directory to search in.
|
package/dist/fs/findFilepath.js
CHANGED
|
@@ -51,6 +51,16 @@ function readFile(filePath) {
|
|
|
51
51
|
return "";
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
+
* Read a file as raw bytes and return it base64-encoded. Used for binary
|
|
55
|
+
* formats (e.g. Lottie zip bundles) whose content must not be decoded as UTF-8.
|
|
56
|
+
* @param {string} filePath - The path to the file to read.
|
|
57
|
+
* @returns {string} - The base64-encoded contents, or '' if the file is absent.
|
|
58
|
+
*/
|
|
59
|
+
function readBinaryFileBase64(filePath) {
|
|
60
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) return fs.readFileSync(filePath).toString("base64");
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
54
64
|
* Find a file in a directory.
|
|
55
65
|
* @param {string} dir - The directory to search in.
|
|
56
66
|
* @param {string} file - The file to search for.
|
|
@@ -70,6 +80,6 @@ function getRelative(absolutePath) {
|
|
|
70
80
|
return path.relative(process.cwd(), path2);
|
|
71
81
|
}
|
|
72
82
|
//#endregion
|
|
73
|
-
export { findFilepath as default, findFile, findFileInDir, findFilepaths, getRelative, getRelativePath, readFile };
|
|
83
|
+
export { findFilepath as default, findFile, findFileInDir, findFilepaths, getRelative, getRelativePath, readBinaryFileBase64, readFile };
|
|
74
84
|
|
|
75
85
|
//# sourceMappingURL=findFilepath.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"findFilepath.js","names":[],"sources":["../../src/fs/findFilepath.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { logger } from '../console/logger.js';\nimport { exitSync } from '../console/logging.js';\n\n/**\n * Resolve the file path from the given file path or default paths.\n * @param {string} filePath - The file path to resolve.\n * @param {string[]} defaultPaths - The default paths to check.\n * @returns {string} - The resolved file path.\n */\nexport default function findFilepath(\n paths: string[],\n errorMessage: string = ''\n): string {\n return findFilepaths(paths, errorMessage)?.[0] || '';\n}\n\n/**\n * Resolve the file paths from the given file paths or default paths.\n * @param {string[]} paths - The file paths to resolve.\n * @param {string} errorMessage - The error message to throw if no paths are found.\n * @returns {string[]} - The resolved file paths.\n */\nexport function findFilepaths(\n paths: string[],\n errorMessage: string = ''\n): string[] {\n const resolvedPaths: string[] = [];\n for (const possiblePath of paths) {\n if (fs.existsSync(possiblePath)) {\n resolvedPaths.push(possiblePath);\n }\n }\n if (errorMessage) {\n logger.error(errorMessage);\n exitSync(1);\n }\n return resolvedPaths;\n}\n\nexport function getRelativePath(file: string, srcDirectory: string): string {\n // Create relative path from src directory and remove extension\n return path\n .relative(\n srcDirectory,\n file.replace(/\\.[^/.]+$/, '') // Remove file extension\n )\n .replace(/\\\\/g, '.') // Replace Windows backslashes with dots\n .split(/[./]/) // Split on dots or forward slashes\n .filter(Boolean) // Remove empty segments that might cause extra dots\n .map((segment) => segment.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()) // Convert each segment to snake case\n .join('.'); // Rejoin with dots\n}\n\n/**\n * Find a file in a directory based on a wildcard pattern.\n * @param {string} filePattern - The wildcard pattern to search for.\n * @param {string} file - The file to search for.\n * @returns {string} - The path to the file.\n */\nexport function findFile(filePattern: string, file: string): string {\n // Handle wildcard pattern by replacing the wildcard with the file parameter\n const resolvedPath = filePattern.replace(/\\*/, file);\n\n if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()) {\n return fs.readFileSync(resolvedPath, 'utf8');\n }\n return '';\n}\n\n/**\n * Read a file and return the contents.\n * @param {string} filePath - The path to the file to read.\n * @returns {string} - The contents of the file.\n */\nexport function readFile(filePath: string): string {\n if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {\n return fs.readFileSync(filePath, 'utf8');\n }\n return '';\n}\n\n/**\n * Find a file in a directory.\n * @param {string} dir - The directory to search in.\n * @param {string} file - The file to search for.\n * @returns {string} - The path to the file.\n */\nexport function findFileInDir(dir: string, file: string): string {\n const resolvedPath = path.join(dir, file);\n try {\n if (fs.existsSync(resolvedPath)) {\n return fs.readFileSync(resolvedPath, 'utf8');\n }\n } catch (error) {\n logger.error('Error finding file in directory: ' + String(error));\n }\n return '';\n}\n\nexport function getRelative(absolutePath: string): string {\n const path2 = path.resolve(absolutePath);\n return path.relative(process.cwd(), path2);\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAwB,aACtB,OACA,eAAuB,IACf;AACR,QAAO,cAAc,OAAO,aAAa,GAAG,MAAM;;;;;;;;AASpD,SAAgB,cACd,OACA,eAAuB,IACb;CACV,MAAM,gBAA0B,EAAE;AAClC,MAAK,MAAM,gBAAgB,MACzB,KAAI,GAAG,WAAW,aAAa,CAC7B,eAAc,KAAK,aAAa;AAGpC,KAAI,cAAc;AAChB,SAAO,MAAM,aAAa;AAC1B,WAAS,EAAE;;AAEb,QAAO;;AAGT,SAAgB,gBAAgB,MAAc,cAA8B;AAE1E,QAAO,KACJ,SACC,cACA,KAAK,QAAQ,aAAa,GAAG,CAC9B,CACA,QAAQ,OAAO,IAAI,CACnB,MAAM,OAAO,CACb,OAAO,QAAQ,CACf,KAAK,YAAY,QAAQ,QAAQ,iBAAiB,IAAI,CAAC,aAAa,CAAC,CACrE,KAAK,IAAI;;;;;;;;AASd,SAAgB,SAAS,aAAqB,MAAsB;CAElE,MAAM,eAAe,YAAY,QAAQ,MAAM,KAAK;AAEpD,KAAI,GAAG,WAAW,aAAa,IAAI,GAAG,SAAS,aAAa,CAAC,QAAQ,CACnE,QAAO,GAAG,aAAa,cAAc,OAAO;AAE9C,QAAO;;;;;;;AAQT,SAAgB,SAAS,UAA0B;AACjD,KAAI,GAAG,WAAW,SAAS,IAAI,GAAG,SAAS,SAAS,CAAC,QAAQ,CAC3D,QAAO,GAAG,aAAa,UAAU,OAAO;AAE1C,QAAO;;;;;;;;AAST,SAAgB,cAAc,KAAa,MAAsB;CAC/D,MAAM,eAAe,KAAK,KAAK,KAAK,KAAK;AACzC,KAAI;AACF,MAAI,GAAG,WAAW,aAAa,CAC7B,QAAO,GAAG,aAAa,cAAc,OAAO;UAEvC,OAAO;AACd,SAAO,MAAM,sCAAsC,OAAO,MAAM,CAAC;;AAEnE,QAAO;;AAGT,SAAgB,YAAY,cAA8B;CACxD,MAAM,QAAQ,KAAK,QAAQ,aAAa;AACxC,QAAO,KAAK,SAAS,QAAQ,KAAK,EAAE,MAAM"}
|
|
1
|
+
{"version":3,"file":"findFilepath.js","names":[],"sources":["../../src/fs/findFilepath.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { logger } from '../console/logger.js';\nimport { exitSync } from '../console/logging.js';\n\n/**\n * Resolve the file path from the given file path or default paths.\n * @param {string} filePath - The file path to resolve.\n * @param {string[]} defaultPaths - The default paths to check.\n * @returns {string} - The resolved file path.\n */\nexport default function findFilepath(\n paths: string[],\n errorMessage: string = ''\n): string {\n return findFilepaths(paths, errorMessage)?.[0] || '';\n}\n\n/**\n * Resolve the file paths from the given file paths or default paths.\n * @param {string[]} paths - The file paths to resolve.\n * @param {string} errorMessage - The error message to throw if no paths are found.\n * @returns {string[]} - The resolved file paths.\n */\nexport function findFilepaths(\n paths: string[],\n errorMessage: string = ''\n): string[] {\n const resolvedPaths: string[] = [];\n for (const possiblePath of paths) {\n if (fs.existsSync(possiblePath)) {\n resolvedPaths.push(possiblePath);\n }\n }\n if (errorMessage) {\n logger.error(errorMessage);\n exitSync(1);\n }\n return resolvedPaths;\n}\n\nexport function getRelativePath(file: string, srcDirectory: string): string {\n // Create relative path from src directory and remove extension\n return path\n .relative(\n srcDirectory,\n file.replace(/\\.[^/.]+$/, '') // Remove file extension\n )\n .replace(/\\\\/g, '.') // Replace Windows backslashes with dots\n .split(/[./]/) // Split on dots or forward slashes\n .filter(Boolean) // Remove empty segments that might cause extra dots\n .map((segment) => segment.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()) // Convert each segment to snake case\n .join('.'); // Rejoin with dots\n}\n\n/**\n * Find a file in a directory based on a wildcard pattern.\n * @param {string} filePattern - The wildcard pattern to search for.\n * @param {string} file - The file to search for.\n * @returns {string} - The path to the file.\n */\nexport function findFile(filePattern: string, file: string): string {\n // Handle wildcard pattern by replacing the wildcard with the file parameter\n const resolvedPath = filePattern.replace(/\\*/, file);\n\n if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()) {\n return fs.readFileSync(resolvedPath, 'utf8');\n }\n return '';\n}\n\n/**\n * Read a file and return the contents.\n * @param {string} filePath - The path to the file to read.\n * @returns {string} - The contents of the file.\n */\nexport function readFile(filePath: string): string {\n if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {\n return fs.readFileSync(filePath, 'utf8');\n }\n return '';\n}\n\n/**\n * Read a file as raw bytes and return it base64-encoded. Used for binary\n * formats (e.g. Lottie zip bundles) whose content must not be decoded as UTF-8.\n * @param {string} filePath - The path to the file to read.\n * @returns {string} - The base64-encoded contents, or '' if the file is absent.\n */\nexport function readBinaryFileBase64(filePath: string): string {\n if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {\n return fs.readFileSync(filePath).toString('base64');\n }\n return '';\n}\n\n/**\n * Find a file in a directory.\n * @param {string} dir - The directory to search in.\n * @param {string} file - The file to search for.\n * @returns {string} - The path to the file.\n */\nexport function findFileInDir(dir: string, file: string): string {\n const resolvedPath = path.join(dir, file);\n try {\n if (fs.existsSync(resolvedPath)) {\n return fs.readFileSync(resolvedPath, 'utf8');\n }\n } catch (error) {\n logger.error('Error finding file in directory: ' + String(error));\n }\n return '';\n}\n\nexport function getRelative(absolutePath: string): string {\n const path2 = path.resolve(absolutePath);\n return path.relative(process.cwd(), path2);\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAwB,aACtB,OACA,eAAuB,IACf;AACR,QAAO,cAAc,OAAO,aAAa,GAAG,MAAM;;;;;;;;AASpD,SAAgB,cACd,OACA,eAAuB,IACb;CACV,MAAM,gBAA0B,EAAE;AAClC,MAAK,MAAM,gBAAgB,MACzB,KAAI,GAAG,WAAW,aAAa,CAC7B,eAAc,KAAK,aAAa;AAGpC,KAAI,cAAc;AAChB,SAAO,MAAM,aAAa;AAC1B,WAAS,EAAE;;AAEb,QAAO;;AAGT,SAAgB,gBAAgB,MAAc,cAA8B;AAE1E,QAAO,KACJ,SACC,cACA,KAAK,QAAQ,aAAa,GAAG,CAC9B,CACA,QAAQ,OAAO,IAAI,CACnB,MAAM,OAAO,CACb,OAAO,QAAQ,CACf,KAAK,YAAY,QAAQ,QAAQ,iBAAiB,IAAI,CAAC,aAAa,CAAC,CACrE,KAAK,IAAI;;;;;;;;AASd,SAAgB,SAAS,aAAqB,MAAsB;CAElE,MAAM,eAAe,YAAY,QAAQ,MAAM,KAAK;AAEpD,KAAI,GAAG,WAAW,aAAa,IAAI,GAAG,SAAS,aAAa,CAAC,QAAQ,CACnE,QAAO,GAAG,aAAa,cAAc,OAAO;AAE9C,QAAO;;;;;;;AAQT,SAAgB,SAAS,UAA0B;AACjD,KAAI,GAAG,WAAW,SAAS,IAAI,GAAG,SAAS,SAAS,CAAC,QAAQ,CAC3D,QAAO,GAAG,aAAa,UAAU,OAAO;AAE1C,QAAO;;;;;;;;AAST,SAAgB,qBAAqB,UAA0B;AAC7D,KAAI,GAAG,WAAW,SAAS,IAAI,GAAG,SAAS,SAAS,CAAC,QAAQ,CAC3D,QAAO,GAAG,aAAa,SAAS,CAAC,SAAS,SAAS;AAErD,QAAO;;;;;;;;AAST,SAAgB,cAAc,KAAa,MAAsB;CAC/D,MAAM,eAAe,KAAK,KAAK,KAAK,KAAK;AACzC,KAAI;AACF,MAAI,GAAG,WAAW,aAAa,CAC7B,QAAO,GAAG,aAAa,cAAc,OAAO;UAEvC,OAAO;AACd,SAAO,MAAM,sCAAsC,OAAO,MAAM,CAAC;;AAEnE,QAAO;;AAGT,SAAgB,YAAY,cAA8B;CACxD,MAAM,QAAQ,KAAK,QAAQ,aAAa;AACxC,QAAO,KAAK,SAAS,QAAQ,KAAK,EAAE,MAAM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "2.
|
|
1
|
+
export declare const PACKAGE_VERSION = "2.16.0";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","names":[],"sources":["../../src/generated/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const PACKAGE_VERSION = '2.
|
|
1
|
+
{"version":3,"file":"version.js","names":[],"sources":["../../src/generated/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const PACKAGE_VERSION = '2.16.0';\n"],"mappings":";AACA,MAAa,kBAAkB"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -169,6 +169,10 @@ export type FilesOptions = {
|
|
|
169
169
|
includeSourceCodeContext?: boolean;
|
|
170
170
|
};
|
|
171
171
|
};
|
|
172
|
+
export type FontsConfig = {
|
|
173
|
+
include: string[];
|
|
174
|
+
exclude?: string[];
|
|
175
|
+
};
|
|
172
176
|
export type Settings = {
|
|
173
177
|
config: string;
|
|
174
178
|
configDirectory: string;
|
|
@@ -207,6 +211,7 @@ export type Settings = {
|
|
|
207
211
|
version?: string;
|
|
208
212
|
description?: string;
|
|
209
213
|
src?: string[];
|
|
214
|
+
fonts?: FontsConfig;
|
|
210
215
|
framework?: SupportedFrameworks;
|
|
211
216
|
options?: AdditionalOptions;
|
|
212
217
|
modelProvider?: string;
|
|
@@ -24,9 +24,7 @@ import path from "node:path";
|
|
|
24
24
|
*/
|
|
25
25
|
async function runDownloadWorkflow({ fileVersionData, jobData, branchData, locales, timeoutDuration, resolveOutputPath, options, forceRetranslation, forceDownload }) {
|
|
26
26
|
if (!branchData) {
|
|
27
|
-
const
|
|
28
|
-
const branchResult = await branchStep.run();
|
|
29
|
-
await branchStep.wait();
|
|
27
|
+
const branchResult = await new BranchStep(gt, options).run();
|
|
30
28
|
if (!branchResult) return logErrorAndExit(branchResolutionError);
|
|
31
29
|
branchData = branchResult;
|
|
32
30
|
}
|
|
@@ -43,15 +41,13 @@ async function runDownloadWorkflow({ fileVersionData, jobData, branchData, local
|
|
|
43
41
|
};
|
|
44
42
|
let pollTimedOut = false;
|
|
45
43
|
if (jobData) {
|
|
46
|
-
const
|
|
47
|
-
const pollResult = await pollStep.run({
|
|
44
|
+
const pollResult = await new PollTranslationJobsStep(gt).run({
|
|
48
45
|
fileTracker,
|
|
49
46
|
fileQueryData,
|
|
50
47
|
jobData,
|
|
51
48
|
timeoutDuration,
|
|
52
49
|
forceRetranslation
|
|
53
50
|
});
|
|
54
|
-
await pollStep.wait();
|
|
55
51
|
if (pollResult.fileTracker.failed.size > 0) {
|
|
56
52
|
logger.error(`${chalk.red(`${pollResult.fileTracker.failed.size} file(s) failed to translate:`)}\n${Array.from(pollResult.fileTracker.failed.entries()).map(([, value]) => `- ${value.fileName}`).join("\n")}`);
|
|
57
53
|
for (const [, value] of pollResult.fileTracker.failed) recordWarning("failed_translation", value.fileName, `Failed to translate for locale ${value.locale}`);
|
|
@@ -69,13 +65,11 @@ async function runDownloadWorkflow({ fileVersionData, jobData, branchData, local
|
|
|
69
65
|
resolveOutputPath,
|
|
70
66
|
options
|
|
71
67
|
});
|
|
72
|
-
const
|
|
73
|
-
const downloadResult = await downloadStep.run({
|
|
68
|
+
const downloadResult = await new DownloadTranslationsStep(gt, settingsForBranch).run({
|
|
74
69
|
fileTracker,
|
|
75
70
|
resolveOutputPath,
|
|
76
71
|
forceDownload
|
|
77
72
|
});
|
|
78
|
-
await downloadStep.wait();
|
|
79
73
|
if (pollTimedOut) return false;
|
|
80
74
|
return downloadResult;
|
|
81
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.js","names":[],"sources":["../../src/workflows/download.ts"],"sourcesContent":["import path from 'node:path';\nimport { Settings } from '../types/index.js';\nimport { gt } from '../utils/gt.js';\nimport { EnqueueFilesResult } from 'generaltranslation/types';\nimport { clearLocaleDirs } from '../fs/clearLocaleDirs.js';\nimport {\n FileStatusTracker,\n PollTranslationJobsStep,\n} from './steps/PollJobsStep.js';\nimport { DownloadTranslationsStep } from './steps/DownloadStep.js';\nimport { BranchData } from '../types/branch.js';\nimport { branchResolutionError } from '../console/index.js';\nimport { logErrorAndExit } from '../console/logging.js';\nimport { logger } from '../console/logger.js';\nimport { recordWarning } from '../state/translateWarnings.js';\nimport { BranchStep } from './steps/BranchStep.js';\nimport { FileProperties } from '../types/files.js';\nimport chalk from 'chalk';\n\nexport type FileTranslationData = {\n [fileId: string]: {\n versionId: string;\n fileName: string;\n /** Source component count for GTJSON files */\n componentCount?: number;\n };\n};\n\n/**\n * Checks the status of translations and downloads them using a workflow pattern\n * @param fileVersionData - Mapping of file IDs to their version and name information\n * @param jobData - Optional job data from enqueue operation\n * @param locales - The locales to wait for\n * @param timeoutDuration - The timeout duration for the wait in seconds\n * @param resolveOutputPath - Function to resolve the output path for a given source path and locale\n * @param options - Settings configuration\n * @param forceRetranslation - Whether to force retranslation\n * @param forceDownload - Whether to force download even if file exists\n * @returns True if all translations are downloaded successfully, false otherwise\n */\nexport async function runDownloadWorkflow({\n fileVersionData,\n jobData,\n branchData,\n locales,\n timeoutDuration,\n resolveOutputPath,\n options,\n forceRetranslation,\n forceDownload,\n}: {\n fileVersionData: FileTranslationData;\n jobData: EnqueueFilesResult | undefined;\n branchData: BranchData | undefined;\n locales: string[];\n timeoutDuration: number;\n resolveOutputPath: (sourcePath: string, locale: string) => string | null;\n options: Settings;\n forceRetranslation?: boolean;\n forceDownload?: boolean;\n}): Promise<boolean> {\n if (!branchData) {\n // Run the branch step\n const branchStep = new BranchStep(gt, options);\n const branchResult = await branchStep.run();\n
|
|
1
|
+
{"version":3,"file":"download.js","names":[],"sources":["../../src/workflows/download.ts"],"sourcesContent":["import path from 'node:path';\nimport { Settings } from '../types/index.js';\nimport { gt } from '../utils/gt.js';\nimport { EnqueueFilesResult } from 'generaltranslation/types';\nimport { clearLocaleDirs } from '../fs/clearLocaleDirs.js';\nimport {\n FileStatusTracker,\n PollTranslationJobsStep,\n} from './steps/PollJobsStep.js';\nimport { DownloadTranslationsStep } from './steps/DownloadStep.js';\nimport { BranchData } from '../types/branch.js';\nimport { branchResolutionError } from '../console/index.js';\nimport { logErrorAndExit } from '../console/logging.js';\nimport { logger } from '../console/logger.js';\nimport { recordWarning } from '../state/translateWarnings.js';\nimport { BranchStep } from './steps/BranchStep.js';\nimport { FileProperties } from '../types/files.js';\nimport chalk from 'chalk';\n\nexport type FileTranslationData = {\n [fileId: string]: {\n versionId: string;\n fileName: string;\n /** Source component count for GTJSON files */\n componentCount?: number;\n };\n};\n\n/**\n * Checks the status of translations and downloads them using a workflow pattern\n * @param fileVersionData - Mapping of file IDs to their version and name information\n * @param jobData - Optional job data from enqueue operation\n * @param locales - The locales to wait for\n * @param timeoutDuration - The timeout duration for the wait in seconds\n * @param resolveOutputPath - Function to resolve the output path for a given source path and locale\n * @param options - Settings configuration\n * @param forceRetranslation - Whether to force retranslation\n * @param forceDownload - Whether to force download even if file exists\n * @returns True if all translations are downloaded successfully, false otherwise\n */\nexport async function runDownloadWorkflow({\n fileVersionData,\n jobData,\n branchData,\n locales,\n timeoutDuration,\n resolveOutputPath,\n options,\n forceRetranslation,\n forceDownload,\n}: {\n fileVersionData: FileTranslationData;\n jobData: EnqueueFilesResult | undefined;\n branchData: BranchData | undefined;\n locales: string[];\n timeoutDuration: number;\n resolveOutputPath: (sourcePath: string, locale: string) => string | null;\n options: Settings;\n forceRetranslation?: boolean;\n forceDownload?: boolean;\n}): Promise<boolean> {\n if (!branchData) {\n // Run the branch step\n const branchStep = new BranchStep(gt, options);\n const branchResult = await branchStep.run();\n if (!branchResult) {\n return logErrorAndExit(branchResolutionError);\n }\n branchData = branchResult;\n }\n // readLockfile uses _branchId to select branch-specific lockfile entries.\n // Keep it on a scoped copy so the caller's settings object is not mutated.\n const settingsForBranch: Settings = {\n ...options,\n _branchId: branchData.currentBranch.id,\n };\n // Prepare the query data\n const fileQueryData = prepareFileQueryData(\n fileVersionData,\n locales,\n branchData\n );\n\n // Initialize download status\n const fileTracker: FileStatusTracker = {\n completed: new Map<string, FileProperties>(),\n inProgress: new Map<string, FileProperties>(),\n failed: new Map<string, FileProperties>(),\n skipped: new Map<string, FileProperties>(),\n };\n\n // Step 1: Poll translation jobs if jobData exists\n let pollTimedOut = false;\n if (jobData) {\n const pollStep = new PollTranslationJobsStep(gt);\n const pollResult = await pollStep.run({\n fileTracker,\n fileQueryData,\n jobData,\n timeoutDuration,\n forceRetranslation,\n });\n\n if (pollResult.fileTracker.failed.size > 0) {\n logger.error(\n `${chalk.red(`${pollResult.fileTracker.failed.size} file(s) failed to translate:`)}\\n${Array.from(\n pollResult.fileTracker.failed.entries()\n )\n .map(([, value]) => `- ${value.fileName}`)\n .join('\\n')}`\n );\n for (const [, value] of pollResult.fileTracker.failed) {\n recordWarning(\n 'failed_translation',\n value.fileName,\n `Failed to translate for locale ${value.locale}`\n );\n }\n\n // If all files failed translation, exit early\n if (pollResult.fileTracker.completed.size === 0) {\n return false;\n }\n }\n\n // Even if polling timed out, still download whatever completed successfully\n if (!pollResult.success) {\n pollTimedOut = true;\n if (pollResult.fileTracker.completed.size > 0) {\n logger.warn(\n chalk.yellow(\n `Timed out, but ${pollResult.fileTracker.completed.size} translation(s) completed successfully. Downloading completed files...`\n )\n );\n } else {\n return false;\n }\n }\n } else {\n for (const file of fileQueryData) {\n // Staging - assume all files are completed\n fileTracker.completed.set(\n `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`,\n file\n );\n }\n }\n\n await clearCompletedLocaleDirs({\n fileTracker,\n locales,\n resolveOutputPath,\n options,\n });\n\n // Step 2: Download translations\n const downloadStep = new DownloadTranslationsStep(gt, settingsForBranch);\n const downloadResult = await downloadStep.run({\n fileTracker,\n resolveOutputPath,\n forceDownload,\n });\n\n // If polling timed out, report failure even though we downloaded what we could\n if (pollTimedOut) {\n return false;\n }\n\n return downloadResult;\n}\n\nasync function clearCompletedLocaleDirs({\n fileTracker,\n locales,\n resolveOutputPath,\n options,\n}: {\n fileTracker: FileStatusTracker;\n locales: string[];\n resolveOutputPath: (sourcePath: string, locale: string) => string | null;\n options: Settings;\n}): Promise<void> {\n if (\n options.options?.experimentalClearLocaleDirs !== true ||\n fileTracker.completed.size === 0\n ) {\n return;\n }\n\n const translatedFiles = new Set(\n Array.from(fileTracker.completed.values())\n .map((file) => {\n const outputPath = resolveOutputPath(file.fileName, file.locale);\n // Only clear if the output path is different from the source.\n return outputPath !== null && outputPath !== file.fileName\n ? outputPath\n : null;\n })\n .filter((filePath): filePath is string => filePath !== null)\n );\n\n if (translatedFiles.size === 0) return;\n\n const cwd = path.dirname(options.config);\n\n await clearLocaleDirs(\n translatedFiles,\n locales,\n options.options?.clearLocaleDirsExclude,\n cwd\n );\n}\n\n/**\n * Prepares the file query data from input data and locales\n */\nfunction prepareFileQueryData(\n fileVersionData: FileTranslationData,\n locales: string[],\n branchData: BranchData\n): FileProperties[] {\n const fileQueryData: FileProperties[] = [];\n\n for (const fileId in fileVersionData) {\n for (const locale of locales) {\n fileQueryData.push({\n versionId: fileVersionData[fileId].versionId,\n fileName: fileVersionData[fileId].fileName,\n fileId,\n locale,\n branchId: branchData.currentBranch.id,\n componentCount: fileVersionData[fileId].componentCount,\n });\n }\n }\n\n return fileQueryData;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwCA,eAAsB,oBAAoB,EACxC,iBACA,SACA,YACA,SACA,iBACA,mBACA,SACA,oBACA,iBAWmB;AACnB,KAAI,CAAC,YAAY;EAGf,MAAM,eAAe,MAAM,IADJ,WAAW,IAAI,QACD,CAAC,KAAK;AAC3C,MAAI,CAAC,aACH,QAAO,gBAAgB,sBAAsB;AAE/C,eAAa;;CAIf,MAAM,oBAA8B;EAClC,GAAG;EACH,WAAW,WAAW,cAAc;EACrC;CAED,MAAM,gBAAgB,qBACpB,iBACA,SACA,WACD;CAGD,MAAM,cAAiC;EACrC,2BAAW,IAAI,KAA6B;EAC5C,4BAAY,IAAI,KAA6B;EAC7C,wBAAQ,IAAI,KAA6B;EACzC,yBAAS,IAAI,KAA6B;EAC3C;CAGD,IAAI,eAAe;AACnB,KAAI,SAAS;EAEX,MAAM,aAAa,MAAM,IADJ,wBAAwB,GACZ,CAAC,IAAI;GACpC;GACA;GACA;GACA;GACA;GACD,CAAC;AAEF,MAAI,WAAW,YAAY,OAAO,OAAO,GAAG;AAC1C,UAAO,MACL,GAAG,MAAM,IAAI,GAAG,WAAW,YAAY,OAAO,KAAK,+BAA+B,CAAC,IAAI,MAAM,KAC3F,WAAW,YAAY,OAAO,SAAS,CACxC,CACE,KAAK,GAAG,WAAW,KAAK,MAAM,WAAW,CACzC,KAAK,KAAK,GACd;AACD,QAAK,MAAM,GAAG,UAAU,WAAW,YAAY,OAC7C,eACE,sBACA,MAAM,UACN,kCAAkC,MAAM,SACzC;AAIH,OAAI,WAAW,YAAY,UAAU,SAAS,EAC5C,QAAO;;AAKX,MAAI,CAAC,WAAW,SAAS;AACvB,kBAAe;AACf,OAAI,WAAW,YAAY,UAAU,OAAO,EAC1C,QAAO,KACL,MAAM,OACJ,kBAAkB,WAAW,YAAY,UAAU,KAAK,wEACzD,CACF;OAED,QAAO;;OAIX,MAAK,MAAM,QAAQ,cAEjB,aAAY,UAAU,IACpB,GAAG,KAAK,SAAS,GAAG,KAAK,OAAO,GAAG,KAAK,UAAU,GAAG,KAAK,UAC1D,KACD;AAIL,OAAM,yBAAyB;EAC7B;EACA;EACA;EACA;EACD,CAAC;CAIF,MAAM,iBAAiB,MAAM,IADJ,yBAAyB,IAAI,kBACb,CAAC,IAAI;EAC5C;EACA;EACA;EACD,CAAC;AAGF,KAAI,aACF,QAAO;AAGT,QAAO;;AAGT,eAAe,yBAAyB,EACtC,aACA,SACA,mBACA,WAMgB;AAChB,KACE,QAAQ,SAAS,gCAAgC,QACjD,YAAY,UAAU,SAAS,EAE/B;CAGF,MAAM,kBAAkB,IAAI,IAC1B,MAAM,KAAK,YAAY,UAAU,QAAQ,CAAC,CACvC,KAAK,SAAS;EACb,MAAM,aAAa,kBAAkB,KAAK,UAAU,KAAK,OAAO;AAEhE,SAAO,eAAe,QAAQ,eAAe,KAAK,WAC9C,aACA;GACJ,CACD,QAAQ,aAAiC,aAAa,KAAK,CAC/D;AAED,KAAI,gBAAgB,SAAS,EAAG;CAEhC,MAAM,MAAM,KAAK,QAAQ,QAAQ,OAAO;AAExC,OAAM,gBACJ,iBACA,SACA,QAAQ,SAAS,wBACjB,IACD;;;;;AAMH,SAAS,qBACP,iBACA,SACA,YACkB;CAClB,MAAM,gBAAkC,EAAE;AAE1C,MAAK,MAAM,UAAU,gBACnB,MAAK,MAAM,UAAU,QACnB,eAAc,KAAK;EACjB,WAAW,gBAAgB,QAAQ;EACnC,UAAU,gBAAgB,QAAQ;EAClC;EACA;EACA,UAAU,WAAW,cAAc;EACnC,gBAAgB,gBAAgB,QAAQ;EACzC,CAAC;AAIN,QAAO"}
|
|
@@ -2,6 +2,7 @@ import { logger } from "../console/logger.js";
|
|
|
2
2
|
import { gt } from "../utils/gt.js";
|
|
3
3
|
import { logCollectedFiles, logErrorAndExit } from "../console/logging.js";
|
|
4
4
|
import { branchResolutionError, withOriginalError } from "../console/index.js";
|
|
5
|
+
import { syncFonts } from "./utils/syncFonts.js";
|
|
5
6
|
import { BranchStep } from "./steps/BranchStep.js";
|
|
6
7
|
import { EnqueueStep } from "./steps/EnqueueStep.js";
|
|
7
8
|
import { filterFilesForEnqueue } from "./utils/filterFilesForEnqueue.js";
|
|
@@ -20,10 +21,10 @@ async function runEnqueueWorkflow({ files, options, settings }) {
|
|
|
20
21
|
try {
|
|
21
22
|
logCollectedFiles(files);
|
|
22
23
|
logger.debug("Files: " + JSON.stringify(files, null, 2));
|
|
24
|
+
await syncFonts(settings);
|
|
23
25
|
const branchStep = new BranchStep(gt, settings);
|
|
24
26
|
const enqueueStep = new EnqueueStep(gt, settings, options.force);
|
|
25
27
|
const branchData = await branchStep.run();
|
|
26
|
-
await branchStep.wait();
|
|
27
28
|
if (!branchData) return logErrorAndExit(branchResolutionError);
|
|
28
29
|
logger.debug("Branch data: " + JSON.stringify(branchData, null, 2));
|
|
29
30
|
const { filesToEnqueue, skippedFiles } = await filterFilesForEnqueue({
|
|
@@ -37,7 +38,6 @@ async function runEnqueueWorkflow({ files, options, settings }) {
|
|
|
37
38
|
});
|
|
38
39
|
if (skippedFiles.length > 0) logger.info(`Skipped enqueue for ${skippedFiles.length} already translated file${skippedFiles.length === 1 ? "" : "s"}`);
|
|
39
40
|
const enqueueResult = await enqueueStep.run(filesToEnqueue);
|
|
40
|
-
await enqueueStep.wait();
|
|
41
41
|
logger.debug("Enqueue result: " + JSON.stringify(enqueueResult, null, 2));
|
|
42
42
|
logEnqueueResult(enqueueResult, filesToEnqueue.length === 0 ? files.length : filesToEnqueue.length);
|
|
43
43
|
return enqueueResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enqueue.js","names":[],"sources":["../../src/workflows/enqueue.ts"],"sourcesContent":["import { logCollectedFiles, logErrorAndExit } from '../console/logging.js';\nimport { branchResolutionError, withOriginalError } from '../console/index.js';\nimport { Settings, TranslateFlags } from '../types/index.js';\nimport { gt } from '../utils/gt.js';\nimport { EnqueueFilesResult, FileToUpload } from 'generaltranslation/types';\nimport { EnqueueStep } from './steps/EnqueueStep.js';\nimport { BranchStep } from './steps/BranchStep.js';\nimport { logger } from '../console/logger.js';\nimport { filterFilesForEnqueue } from './utils/filterFilesForEnqueue.js';\n\n/**\n * Enqueues translations for a given set of files\n * - Only enqueues uploaded files\n * - Don't have to worry about double enqueuing files because dedupe on API side\n *\n * @param {FileTranslationData} fileVersionData - The file version data\n * @param {TranslateFlags} options - The options for the enqueue operation\n * @param {Settings} settings - The settings for the enqueue operation\n * @returns {Promise<EnqueueFilesResult>} The enqueue result\n */\nexport async function runEnqueueWorkflow({\n files,\n options,\n settings,\n}: {\n files: FileToUpload[];\n options: TranslateFlags;\n settings: Settings;\n}): Promise<EnqueueFilesResult> {\n try {\n // Log files to be enqueued\n logCollectedFiles(files);\n\n logger.debug('Files: ' + JSON.stringify(files, null, 2));\n\n // Create workflow with steps\n const branchStep = new BranchStep(gt, settings);\n // const queryFileDataStep = new QueryFileDataStep(gt);\n const enqueueStep = new EnqueueStep(gt, settings, options.force);\n\n // (1) run the branch step\n const branchData = await branchStep.run();\n
|
|
1
|
+
{"version":3,"file":"enqueue.js","names":[],"sources":["../../src/workflows/enqueue.ts"],"sourcesContent":["import { logCollectedFiles, logErrorAndExit } from '../console/logging.js';\nimport { branchResolutionError, withOriginalError } from '../console/index.js';\nimport { Settings, TranslateFlags } from '../types/index.js';\nimport { gt } from '../utils/gt.js';\nimport { EnqueueFilesResult, FileToUpload } from 'generaltranslation/types';\nimport { EnqueueStep } from './steps/EnqueueStep.js';\nimport { BranchStep } from './steps/BranchStep.js';\nimport { logger } from '../console/logger.js';\nimport { filterFilesForEnqueue } from './utils/filterFilesForEnqueue.js';\nimport { syncFonts } from './utils/syncFonts.js';\n\n/**\n * Enqueues translations for a given set of files\n * - Only enqueues uploaded files\n * - Don't have to worry about double enqueuing files because dedupe on API side\n *\n * @param {FileTranslationData} fileVersionData - The file version data\n * @param {TranslateFlags} options - The options for the enqueue operation\n * @param {Settings} settings - The settings for the enqueue operation\n * @returns {Promise<EnqueueFilesResult>} The enqueue result\n */\nexport async function runEnqueueWorkflow({\n files,\n options,\n settings,\n}: {\n files: FileToUpload[];\n options: TranslateFlags;\n settings: Settings;\n}): Promise<EnqueueFilesResult> {\n try {\n // Log files to be enqueued\n logCollectedFiles(files);\n\n logger.debug('Files: ' + JSON.stringify(files, null, 2));\n\n // Sync fonts before enqueueing so the translation jobs (e.g. Lottie\n // layout refinement) can use them instead of fallback fonts.\n await syncFonts(settings);\n\n // Create workflow with steps\n const branchStep = new BranchStep(gt, settings);\n // const queryFileDataStep = new QueryFileDataStep(gt);\n const enqueueStep = new EnqueueStep(gt, settings, options.force);\n\n // (1) run the branch step\n const branchData = await branchStep.run();\n if (!branchData) {\n return logErrorAndExit(branchResolutionError);\n }\n logger.debug('Branch data: ' + JSON.stringify(branchData, null, 2));\n\n // (2) Enqueue the files\n const filesWithBranch = files.map((files) => ({\n branchId: branchData.currentBranch.id,\n ...files,\n }));\n const { filesToEnqueue, skippedFiles } = await filterFilesForEnqueue({\n gt,\n files: filesWithBranch,\n locales: settings.locales,\n force: options.force,\n });\n if (skippedFiles.length > 0) {\n logger.info(\n `Skipped enqueue for ${skippedFiles.length} already translated file${skippedFiles.length === 1 ? '' : 's'}`\n );\n }\n\n const enqueueResult = await enqueueStep.run(filesToEnqueue);\n\n logger.debug('Enqueue result: ' + JSON.stringify(enqueueResult, null, 2));\n\n logEnqueueResult(\n enqueueResult,\n filesToEnqueue.length === 0 ? files.length : filesToEnqueue.length\n );\n return enqueueResult;\n } catch (error) {\n return logErrorAndExit(\n withOriginalError(\n 'Translations could not be enqueued. Check the files, branch configuration, and API credentials, then try again.',\n error\n )\n );\n }\n}\n\n// ----- Helper functions ----- //\n\n/**\n * Logs the enqueue result\n * @param enqueueResult - The enqueue result\n * @returns void\n */\nfunction logEnqueueResult(\n enqueueResult: EnqueueFilesResult,\n fileCount: number\n): void {\n if (Object.keys(enqueueResult.jobData).length === 0) {\n logger.success(\n `All ${fileCount} ${fileCount === 1 ? 'file' : 'files'} already translated. 0 files enqueued.`\n );\n } else {\n logger.success(enqueueResult.message);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqBA,eAAsB,mBAAmB,EACvC,OACA,SACA,YAK8B;AAC9B,KAAI;AAEF,oBAAkB,MAAM;AAExB,SAAO,MAAM,YAAY,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC;AAIxD,QAAM,UAAU,SAAS;EAGzB,MAAM,aAAa,IAAI,WAAW,IAAI,SAAS;EAE/C,MAAM,cAAc,IAAI,YAAY,IAAI,UAAU,QAAQ,MAAM;EAGhE,MAAM,aAAa,MAAM,WAAW,KAAK;AACzC,MAAI,CAAC,WACH,QAAO,gBAAgB,sBAAsB;AAE/C,SAAO,MAAM,kBAAkB,KAAK,UAAU,YAAY,MAAM,EAAE,CAAC;EAOnE,MAAM,EAAE,gBAAgB,iBAAiB,MAAM,sBAAsB;GACnE;GACA,OANsB,MAAM,KAAK,WAAW;IAC5C,UAAU,WAAW,cAAc;IACnC,GAAG;IACJ,EAGuB;GACtB,SAAS,SAAS;GAClB,OAAO,QAAQ;GAChB,CAAC;AACF,MAAI,aAAa,SAAS,EACxB,QAAO,KACL,uBAAuB,aAAa,OAAO,0BAA0B,aAAa,WAAW,IAAI,KAAK,MACvG;EAGH,MAAM,gBAAgB,MAAM,YAAY,IAAI,eAAe;AAE3D,SAAO,MAAM,qBAAqB,KAAK,UAAU,eAAe,MAAM,EAAE,CAAC;AAEzE,mBACE,eACA,eAAe,WAAW,IAAI,MAAM,SAAS,eAAe,OAC7D;AACD,SAAO;UACA,OAAO;AACd,SAAO,gBACL,kBACE,mHACA,MACD,CACF;;;;;;;;AAWL,SAAS,iBACP,eACA,WACM;AACN,KAAI,OAAO,KAAK,cAAc,QAAQ,CAAC,WAAW,EAChD,QAAO,QACL,OAAO,UAAU,GAAG,cAAc,IAAI,SAAS,QAAQ,wCACxD;KAED,QAAO,QAAQ,cAAc,QAAQ"}
|
|
@@ -18,9 +18,7 @@ async function runPublishWorkflow(files, publishMap, branchId, settings) {
|
|
|
18
18
|
fileName: file.fileName
|
|
19
19
|
}));
|
|
20
20
|
if (allFileRefs.length === 0) return;
|
|
21
|
-
|
|
22
|
-
await publishStep.run(allFileRefs);
|
|
23
|
-
await publishStep.wait();
|
|
21
|
+
await new PublishStep(gt).run(allFileRefs);
|
|
24
22
|
} catch (error) {
|
|
25
23
|
logger.warn(`Failed to publish files: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
24
|
}
|