gt 2.14.52 → 2.14.53

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.14.53
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1642](https://github.com/generaltranslation/gt/pull/1642) [`28b1b59`](https://github.com/generaltranslation/gt/commit/28b1b59d13422ef665490a5500cacaabaa00541d) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Skip enqueue for already translated files
8
+
3
9
  ## 2.14.52
4
10
 
5
11
  ### Patch Changes
@@ -1 +1 @@
1
- export declare const PACKAGE_VERSION = "2.14.52";
1
+ export declare const PACKAGE_VERSION = "2.14.53";
@@ -1,5 +1,5 @@
1
1
  //#region src/generated/version.ts
2
- const PACKAGE_VERSION = "2.14.52";
2
+ const PACKAGE_VERSION = "2.14.53";
3
3
  //#endregion
4
4
  export { PACKAGE_VERSION };
5
5
 
@@ -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.14.52';\n"],"mappings":";AACA,MAAa,kBAAkB"}
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.14.53';\n"],"mappings":";AACA,MAAa,kBAAkB"}
@@ -7,7 +7,12 @@ import * as fs$1 from "node:fs";
7
7
  */
8
8
  function persistPostProcessHashes(settings, includeFiles, downloadedMeta) {
9
9
  if (!includeFiles || includeFiles.size === 0 || downloadedMeta.size === 0) return;
10
- const { data, entryMap, originalV1 } = readLockfile(settings);
10
+ const branchId = findDownloadedBranchId(includeFiles, downloadedMeta);
11
+ if (!branchId) return;
12
+ const { data, entryMap, originalV1 } = readLockfile({
13
+ ...settings,
14
+ _branchId: branchId
15
+ });
11
16
  let lockUpdated = false;
12
17
  for (const filePath of includeFiles) {
13
18
  const meta = downloadedMeta.get(filePath);
@@ -26,6 +31,12 @@ function persistPostProcessHashes(settings, includeFiles, downloadedMeta) {
26
31
  }
27
32
  if (lockUpdated) writeLockfile(data, originalV1);
28
33
  }
34
+ function findDownloadedBranchId(includeFiles, downloadedMeta) {
35
+ for (const filePath of includeFiles) {
36
+ const meta = downloadedMeta.get(filePath);
37
+ if (meta) return meta.branchId;
38
+ }
39
+ }
29
40
  //#endregion
30
41
  export { persistPostProcessHashes };
31
42
 
@@ -1 +1 @@
1
- {"version":3,"file":"persistPostprocessHashes.js","names":["fs"],"sources":["../../src/utils/persistPostprocessHashes.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport {\n findOrCreateEntry,\n readLockfile,\n writeLockfile,\n} from '../fs/config/downloadedVersions.js';\nimport { hashStringSync } from './hash.js';\nimport type { Settings } from '../types/index.js';\n\ntype DownloadMeta = {\n branchId: string;\n fileId: string;\n versionId: string;\n locale: string;\n};\n\n/**\n * Persist postprocessed content hashes for recently downloaded files into gt-lock.json.\n */\nexport function persistPostProcessHashes(\n settings: Settings,\n includeFiles: Set<string> | undefined,\n downloadedMeta: Map<string, DownloadMeta>\n): void {\n if (!includeFiles || includeFiles.size === 0 || downloadedMeta.size === 0) {\n return;\n }\n\n const { data, entryMap, originalV1 } = readLockfile(settings);\n let lockUpdated = false;\n\n for (const filePath of includeFiles) {\n const meta = downloadedMeta.get(filePath);\n if (!meta) continue;\n if (!fs.existsSync(filePath)) continue;\n\n const content = fs.readFileSync(filePath, 'utf8');\n const hash = hashStringSync(content);\n\n const entry = findOrCreateEntry(\n entryMap,\n data.entries,\n meta.fileId,\n meta.versionId\n );\n\n const existing = entry.translations[meta.locale] || {};\n\n if (existing.postProcessHash !== hash) {\n entry.translations[meta.locale] = {\n ...existing,\n postProcessHash: hash,\n };\n lockUpdated = true;\n }\n }\n\n if (lockUpdated) {\n writeLockfile(data, originalV1);\n }\n}\n"],"mappings":";;;;;;;AAmBA,SAAgB,yBACd,UACA,cACA,gBACM;AACN,KAAI,CAAC,gBAAgB,aAAa,SAAS,KAAK,eAAe,SAAS,EACtE;CAGF,MAAM,EAAE,MAAM,UAAU,eAAe,aAAa,SAAS;CAC7D,IAAI,cAAc;AAElB,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,OAAO,eAAe,IAAI,SAAS;AACzC,MAAI,CAAC,KAAM;AACX,MAAI,CAACA,KAAG,WAAW,SAAS,CAAE;EAG9B,MAAM,OAAO,eADGA,KAAG,aAAa,UAAU,OACP,CAAC;EAEpC,MAAM,QAAQ,kBACZ,UACA,KAAK,SACL,KAAK,QACL,KAAK,UACN;EAED,MAAM,WAAW,MAAM,aAAa,KAAK,WAAW,EAAE;AAEtD,MAAI,SAAS,oBAAoB,MAAM;AACrC,SAAM,aAAa,KAAK,UAAU;IAChC,GAAG;IACH,iBAAiB;IAClB;AACD,iBAAc;;;AAIlB,KAAI,YACF,eAAc,MAAM,WAAW"}
1
+ {"version":3,"file":"persistPostprocessHashes.js","names":["fs"],"sources":["../../src/utils/persistPostprocessHashes.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport {\n findOrCreateEntry,\n readLockfile,\n writeLockfile,\n} from '../fs/config/downloadedVersions.js';\nimport { hashStringSync } from './hash.js';\nimport type { Settings } from '../types/index.js';\n\ntype DownloadMeta = {\n branchId: string;\n fileId: string;\n versionId: string;\n locale: string;\n};\n\n/**\n * Persist postprocessed content hashes for recently downloaded files into gt-lock.json.\n */\nexport function persistPostProcessHashes(\n settings: Settings,\n includeFiles: Set<string> | undefined,\n downloadedMeta: Map<string, DownloadMeta>\n): void {\n if (!includeFiles || includeFiles.size === 0 || downloadedMeta.size === 0) {\n return;\n }\n\n const branchId = findDownloadedBranchId(includeFiles, downloadedMeta);\n if (!branchId) return;\n\n const { data, entryMap, originalV1 } = readLockfile({\n ...settings,\n _branchId: branchId,\n });\n let lockUpdated = false;\n\n for (const filePath of includeFiles) {\n const meta = downloadedMeta.get(filePath);\n if (!meta) continue;\n if (!fs.existsSync(filePath)) continue;\n\n const content = fs.readFileSync(filePath, 'utf8');\n const hash = hashStringSync(content);\n\n const entry = findOrCreateEntry(\n entryMap,\n data.entries,\n meta.fileId,\n meta.versionId\n );\n\n const existing = entry.translations[meta.locale] || {};\n\n if (existing.postProcessHash !== hash) {\n entry.translations[meta.locale] = {\n ...existing,\n postProcessHash: hash,\n };\n lockUpdated = true;\n }\n }\n\n if (lockUpdated) {\n writeLockfile(data, originalV1);\n }\n}\n\nfunction findDownloadedBranchId(\n includeFiles: Set<string>,\n downloadedMeta: Map<string, DownloadMeta>\n): string | undefined {\n for (const filePath of includeFiles) {\n const meta = downloadedMeta.get(filePath);\n if (meta) return meta.branchId;\n }\n return undefined;\n}\n"],"mappings":";;;;;;;AAmBA,SAAgB,yBACd,UACA,cACA,gBACM;AACN,KAAI,CAAC,gBAAgB,aAAa,SAAS,KAAK,eAAe,SAAS,EACtE;CAGF,MAAM,WAAW,uBAAuB,cAAc,eAAe;AACrE,KAAI,CAAC,SAAU;CAEf,MAAM,EAAE,MAAM,UAAU,eAAe,aAAa;EAClD,GAAG;EACH,WAAW;EACZ,CAAC;CACF,IAAI,cAAc;AAElB,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,OAAO,eAAe,IAAI,SAAS;AACzC,MAAI,CAAC,KAAM;AACX,MAAI,CAACA,KAAG,WAAW,SAAS,CAAE;EAG9B,MAAM,OAAO,eADGA,KAAG,aAAa,UAAU,OACP,CAAC;EAEpC,MAAM,QAAQ,kBACZ,UACA,KAAK,SACL,KAAK,QACL,KAAK,UACN;EAED,MAAM,WAAW,MAAM,aAAa,KAAK,WAAW,EAAE;AAEtD,MAAI,SAAS,oBAAoB,MAAM;AACrC,SAAM,aAAa,KAAK,UAAU;IAChC,GAAG;IACH,iBAAiB;IAClB;AACD,iBAAc;;;AAIlB,KAAI,YACF,eAAc,MAAM,WAAW;;AAInC,SAAS,uBACP,cACA,gBACoB;AACpB,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,OAAO,eAAe,IAAI,SAAS;AACzC,MAAI,KAAM,QAAO,KAAK"}
@@ -30,15 +30,11 @@ async function runDownloadWorkflow({ fileVersionData, jobData, branchData, local
30
30
  if (!branchResult) return logErrorAndExit(branchResolutionError);
31
31
  branchData = branchResult;
32
32
  }
33
+ const settingsForBranch = {
34
+ ...options,
35
+ _branchId: branchData.currentBranch.id
36
+ };
33
37
  const fileQueryData = prepareFileQueryData(fileVersionData, locales, branchData);
34
- if (options.options?.experimentalClearLocaleDirs === true && fileQueryData.length > 0) {
35
- const translatedFiles = new Set(fileQueryData.map((file) => {
36
- const outputPath = resolveOutputPath(file.fileName, file.locale);
37
- return outputPath !== null && outputPath !== file.fileName ? outputPath : null;
38
- }).filter((path) => path !== null));
39
- const cwd = path.dirname(options.config);
40
- await clearLocaleDirs(translatedFiles, locales, options.options?.clearLocaleDirsExclude, cwd);
41
- }
42
38
  const fileTracker = {
43
39
  completed: /* @__PURE__ */ new Map(),
44
40
  inProgress: /* @__PURE__ */ new Map(),
@@ -67,7 +63,13 @@ async function runDownloadWorkflow({ fileVersionData, jobData, branchData, local
67
63
  else return false;
68
64
  }
69
65
  } else for (const file of fileQueryData) fileTracker.completed.set(`${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`, file);
70
- const downloadStep = new DownloadTranslationsStep(gt, options);
66
+ await clearCompletedLocaleDirs({
67
+ fileTracker,
68
+ locales,
69
+ resolveOutputPath,
70
+ options
71
+ });
72
+ const downloadStep = new DownloadTranslationsStep(gt, settingsForBranch);
71
73
  const downloadResult = await downloadStep.run({
72
74
  fileTracker,
73
75
  resolveOutputPath,
@@ -77,6 +79,16 @@ async function runDownloadWorkflow({ fileVersionData, jobData, branchData, local
77
79
  if (pollTimedOut) return false;
78
80
  return downloadResult;
79
81
  }
82
+ async function clearCompletedLocaleDirs({ fileTracker, locales, resolveOutputPath, options }) {
83
+ if (options.options?.experimentalClearLocaleDirs !== true || fileTracker.completed.size === 0) return;
84
+ const translatedFiles = new Set(Array.from(fileTracker.completed.values()).map((file) => {
85
+ const outputPath = resolveOutputPath(file.fileName, file.locale);
86
+ return outputPath !== null && outputPath !== file.fileName ? outputPath : null;
87
+ }).filter((filePath) => filePath !== null));
88
+ if (translatedFiles.size === 0) return;
89
+ const cwd = path.dirname(options.config);
90
+ await clearLocaleDirs(translatedFiles, locales, options.options?.clearLocaleDirsExclude, cwd);
91
+ }
80
92
  /**
81
93
  * Prepares the file query data from input data and locales
82
94
  */
@@ -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 };\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 await branchStep.wait();\n if (!branchResult) {\n return logErrorAndExit(branchResolutionError);\n }\n branchData = branchResult;\n }\n // Prepare the query data\n const fileQueryData = prepareFileQueryData(\n fileVersionData,\n locales,\n branchData\n );\n\n // Clear translated files before any downloads (if enabled)\n if (\n options.options?.experimentalClearLocaleDirs === true &&\n fileQueryData.length > 0\n ) {\n const translatedFiles = new Set(\n fileQueryData\n .map((file) => {\n const outputPath = resolveOutputPath(file.fileName, file.locale);\n // Only clear if the output path is different from the source (i.e., there's a transform)\n return outputPath !== null && outputPath !== file.fileName\n ? outputPath\n : null;\n })\n .filter((path): path is string => path !== null)\n );\n\n // Derive cwd from config path\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 // 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 await pollStep.wait();\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 // Step 2: Download translations\n const downloadStep = new DownloadTranslationsStep(gt, options);\n const downloadResult = await downloadStep.run({\n fileTracker,\n resolveOutputPath,\n forceDownload,\n });\n await downloadStep.wait();\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\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 });\n }\n }\n\n return fileQueryData;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAsCA,eAAsB,oBAAoB,EACxC,iBACA,SACA,YACA,SACA,iBACA,mBACA,SACA,oBACA,iBAWmB;AACnB,KAAI,CAAC,YAAY;EAEf,MAAM,aAAa,IAAI,WAAW,IAAI,QAAQ;EAC9C,MAAM,eAAe,MAAM,WAAW,KAAK;AAC3C,QAAM,WAAW,MAAM;AACvB,MAAI,CAAC,aACH,QAAO,gBAAgB,sBAAsB;AAE/C,eAAa;;CAGf,MAAM,gBAAgB,qBACpB,iBACA,SACA,WACD;AAGD,KACE,QAAQ,SAAS,gCAAgC,QACjD,cAAc,SAAS,GACvB;EACA,MAAM,kBAAkB,IAAI,IAC1B,cACG,KAAK,SAAS;GACb,MAAM,aAAa,kBAAkB,KAAK,UAAU,KAAK,OAAO;AAEhE,UAAO,eAAe,QAAQ,eAAe,KAAK,WAC9C,aACA;IACJ,CACD,QAAQ,SAAyB,SAAS,KAAK,CACnD;EAGD,MAAM,MAAM,KAAK,QAAQ,QAAQ,OAAO;AAExC,QAAM,gBACJ,iBACA,SACA,QAAQ,SAAS,wBACjB,IACD;;CAIH,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;EACX,MAAM,WAAW,IAAI,wBAAwB,GAAG;EAChD,MAAM,aAAa,MAAM,SAAS,IAAI;GACpC;GACA;GACA;GACA;GACA;GACD,CAAC;AACF,QAAM,SAAS,MAAM;AAErB,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;CAKL,MAAM,eAAe,IAAI,yBAAyB,IAAI,QAAQ;CAC9D,MAAM,iBAAiB,MAAM,aAAa,IAAI;EAC5C;EACA;EACA;EACD,CAAC;AACF,OAAM,aAAa,MAAM;AAGzB,KAAI,aACF,QAAO;AAGT,QAAO;;;;;AAMT,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;EACpC,CAAC;AAIN,QAAO"}
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 };\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 await branchStep.wait();\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 await pollStep.wait();\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 await downloadStep.wait();\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 });\n }\n }\n\n return fileQueryData;\n}\n"],"mappings":"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"}
@@ -4,6 +4,7 @@ import { logCollectedFiles, logErrorAndExit } from "../console/logging.js";
4
4
  import { branchResolutionError, withOriginalError } from "../console/index.js";
5
5
  import { BranchStep } from "./steps/BranchStep.js";
6
6
  import { EnqueueStep } from "./steps/EnqueueStep.js";
7
+ import { filterFilesForEnqueue } from "./utils/filterFilesForEnqueue.js";
7
8
  //#region src/workflows/enqueue.ts
8
9
  /**
9
10
  * Enqueues translations for a given set of files
@@ -25,13 +26,20 @@ async function runEnqueueWorkflow({ files, options, settings }) {
25
26
  await branchStep.wait();
26
27
  if (!branchData) return logErrorAndExit(branchResolutionError);
27
28
  logger.debug("Branch data: " + JSON.stringify(branchData, null, 2));
28
- const enqueueResult = await enqueueStep.run(files.map((files) => ({
29
- branchId: branchData.currentBranch.id,
30
- ...files
31
- })));
29
+ const { filesToEnqueue, skippedFiles } = await filterFilesForEnqueue({
30
+ gt,
31
+ files: files.map((files) => ({
32
+ branchId: branchData.currentBranch.id,
33
+ ...files
34
+ })),
35
+ locales: settings.locales,
36
+ force: options.force
37
+ });
38
+ if (skippedFiles.length > 0) logger.info(`Skipped enqueue for ${skippedFiles.length} already translated file${skippedFiles.length === 1 ? "" : "s"}`);
39
+ const enqueueResult = await enqueueStep.run(filesToEnqueue);
32
40
  await enqueueStep.wait();
33
41
  logger.debug("Enqueue result: " + JSON.stringify(enqueueResult, null, 2));
34
- logEnqueueResult(enqueueResult, files);
42
+ logEnqueueResult(enqueueResult, filesToEnqueue.length === 0 ? files.length : filesToEnqueue.length);
35
43
  return enqueueResult;
36
44
  } catch (error) {
37
45
  return logErrorAndExit(withOriginalError("Translations could not be enqueued. Check the files, branch configuration, and API credentials, then try again.", error));
@@ -42,8 +50,8 @@ async function runEnqueueWorkflow({ files, options, settings }) {
42
50
  * @param enqueueResult - The enqueue result
43
51
  * @returns void
44
52
  */
45
- function logEnqueueResult(enqueueResult, files) {
46
- if (Object.keys(enqueueResult.jobData).length === 0) logger.success(`All ${files.length} files already translated. 0 files enqueued.`);
53
+ function logEnqueueResult(enqueueResult, fileCount) {
54
+ if (Object.keys(enqueueResult.jobData).length === 0) logger.success(`All ${fileCount} ${fileCount === 1 ? "file" : "files"} already translated. 0 files enqueued.`);
47
55
  else logger.success(enqueueResult.message);
48
56
  }
49
57
  //#endregion
@@ -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';\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 await branchStep.wait();\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 enqueueResult = await enqueueStep.run(\n files.map((files) => ({\n branchId: branchData.currentBranch.id,\n ...files,\n }))\n );\n await enqueueStep.wait();\n\n logger.debug('Enqueue result: ' + JSON.stringify(enqueueResult, null, 2));\n\n logEnqueueResult(enqueueResult, files);\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 files: FileToUpload[]\n): void {\n if (Object.keys(enqueueResult.jobData).length === 0) {\n logger.success(\n `All ${files.length} files already translated. 0 files enqueued.`\n );\n } else {\n logger.success(enqueueResult.message);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBA,eAAsB,mBAAmB,EACvC,OACA,SACA,YAK8B;AAC9B,KAAI;AAEF,oBAAkB,MAAM;AAExB,SAAO,MAAM,YAAY,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC;EAGxD,MAAM,aAAa,IAAI,WAAW,IAAI,SAAS;EAE/C,MAAM,cAAc,IAAI,YAAY,IAAI,UAAU,QAAQ,MAAM;EAGhE,MAAM,aAAa,MAAM,WAAW,KAAK;AACzC,QAAM,WAAW,MAAM;AACvB,MAAI,CAAC,WACH,QAAO,gBAAgB,sBAAsB;AAE/C,SAAO,MAAM,kBAAkB,KAAK,UAAU,YAAY,MAAM,EAAE,CAAC;EAGnE,MAAM,gBAAgB,MAAM,YAAY,IACtC,MAAM,KAAK,WAAW;GACpB,UAAU,WAAW,cAAc;GACnC,GAAG;GACJ,EAAE,CACJ;AACD,QAAM,YAAY,MAAM;AAExB,SAAO,MAAM,qBAAqB,KAAK,UAAU,eAAe,MAAM,EAAE,CAAC;AAEzE,mBAAiB,eAAe,MAAM;AACtC,SAAO;UACA,OAAO;AACd,SAAO,gBACL,kBACE,mHACA,MACD,CACF;;;;;;;;AAWL,SAAS,iBACP,eACA,OACM;AACN,KAAI,OAAO,KAAK,cAAc,QAAQ,CAAC,WAAW,EAChD,QAAO,QACL,OAAO,MAAM,OAAO,8CACrB;KAED,QAAO,QAAQ,cAAc,QAAQ"}
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 await branchStep.wait();\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 await enqueueStep.wait();\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":";;;;;;;;;;;;;;;;;;AAoBA,eAAsB,mBAAmB,EACvC,OACA,SACA,YAK8B;AAC9B,KAAI;AAEF,oBAAkB,MAAM;AAExB,SAAO,MAAM,YAAY,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC;EAGxD,MAAM,aAAa,IAAI,WAAW,IAAI,SAAS;EAE/C,MAAM,cAAc,IAAI,YAAY,IAAI,UAAU,QAAQ,MAAM;EAGhE,MAAM,aAAa,MAAM,WAAW,KAAK;AACzC,QAAM,WAAW,MAAM;AACvB,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;AAC3D,QAAM,YAAY,MAAM;AAExB,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"}
@@ -9,6 +9,7 @@ import { EnqueueStep } from "./steps/EnqueueStep.js";
9
9
  import { TagStep } from "./steps/TagStep.js";
10
10
  import { UserEditDiffsStep } from "./steps/UserEditDiffsStep.js";
11
11
  import { calculateTimeoutMs } from "../utils/calculateTimeoutMs.js";
12
+ import { filterFilesForEnqueue } from "./utils/filterFilesForEnqueue.js";
12
13
  //#region src/workflows/stage.ts
13
14
  /**
14
15
  * Sends multiple files for translation to the API using a workflow pattern
@@ -47,7 +48,14 @@ async function runStageFilesWorkflow({ files, options, settings }) {
47
48
  }
48
49
  await setupStep.run(uploadedFiles);
49
50
  await setupStep.wait();
50
- const enqueueResult = await enqueueStep.run(uploadedFiles);
51
+ const { filesToEnqueue, skippedFiles } = await filterFilesForEnqueue({
52
+ gt,
53
+ files: uploadedFiles,
54
+ locales: settings.locales,
55
+ force: options.force
56
+ });
57
+ if (skippedFiles.length > 0) logger.info(`Skipped enqueue for ${skippedFiles.length} already translated file${skippedFiles.length === 1 ? "" : "s"}`);
58
+ const enqueueResult = await enqueueStep.run(filesToEnqueue);
51
59
  await enqueueStep.wait();
52
60
  return {
53
61
  branchData,
@@ -1 +1 @@
1
- {"version":3,"file":"stage.js","names":[],"sources":["../../src/workflows/stage.ts"],"sourcesContent":["import { logCollectedFiles, logErrorAndExit } from '../console/logging.js';\nimport { branchResolutionError, withOriginalError } from '../console/index.js';\nimport { logger } from '../console/logger.js';\nimport { Settings, TranslateFlags } from '../types/index.js';\nimport { gt } from '../utils/gt.js';\nimport { EnqueueFilesResult, FileToUpload } from 'generaltranslation/types';\nimport { UploadSourcesStep } from './steps/UploadSourcesStep.js';\nimport { SetupStep } from './steps/SetupStep.js';\nimport { EnqueueStep } from './steps/EnqueueStep.js';\nimport { BranchStep } from './steps/BranchStep.js';\nimport { TagStep } from './steps/TagStep.js';\nimport { UserEditDiffsStep } from './steps/UserEditDiffsStep.js';\nimport { BranchData } from '../types/branch.js';\nimport { calculateTimeoutMs } from '../utils/calculateTimeoutMs.js';\n\n/**\n * Sends multiple files for translation to the API using a workflow pattern\n * @param files - Array of file objects to translate\n * @param options - The options for the API call\n * @param settings - Settings configuration\n * @returns The translated content or version ID\n */\nexport async function runStageFilesWorkflow({\n files,\n options,\n settings,\n}: {\n files: FileToUpload[];\n options: TranslateFlags;\n settings: Settings;\n}): Promise<{\n branchData: BranchData;\n enqueueResult: EnqueueFilesResult;\n}> {\n try {\n // Log files to be translated\n logCollectedFiles(files);\n\n // Calculate timeout for setup step\n const timeoutMs = calculateTimeoutMs(options.timeout);\n\n // Create workflow with steps\n const branchStep = new BranchStep(gt, settings);\n const uploadStep = new UploadSourcesStep(gt, settings);\n const userEditDiffsStep = new UserEditDiffsStep(settings);\n const setupStep = new SetupStep(gt, settings, timeoutMs);\n const enqueueStep = new EnqueueStep(gt, settings, options.force);\n\n // first run the branch step\n const branchData = await branchStep.run();\n await branchStep.wait();\n if (!branchData) {\n return logErrorAndExit(branchResolutionError);\n }\n\n // then run the upload step\n const uploadedFiles = await uploadStep.run({ files, branchData });\n await uploadStep.wait();\n\n // optionally run the user edit diffs step\n if (options?.saveLocal) {\n await userEditDiffsStep.run(uploadedFiles);\n await userEditDiffsStep.wait();\n }\n\n // then run the tag step (non-fatal — tagging failure should not block translations)\n if (settings.tag) {\n try {\n const userProvidedTag = !!options.tag;\n const tagStep = new TagStep(gt, settings, userProvidedTag);\n await tagStep.run(uploadedFiles);\n await tagStep.wait();\n } catch {\n logger.warn('Failed to create translation tag. Continuing...');\n }\n }\n\n // then run the setup step\n await setupStep.run(uploadedFiles);\n await setupStep.wait();\n\n // then run the enqueue step\n const enqueueResult = await enqueueStep.run(uploadedFiles);\n await enqueueStep.wait();\n\n return { branchData, enqueueResult };\n } catch (error) {\n return logErrorAndExit(\n withOriginalError(\n 'Files could not be sent for translation. Check the files, branch configuration, and API credentials, then try again.',\n error\n )\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsBA,eAAsB,sBAAsB,EAC1C,OACA,SACA,YAQC;AACD,KAAI;AAEF,oBAAkB,MAAM;EAGxB,MAAM,YAAY,mBAAmB,QAAQ,QAAQ;EAGrD,MAAM,aAAa,IAAI,WAAW,IAAI,SAAS;EAC/C,MAAM,aAAa,IAAI,kBAAkB,IAAI,SAAS;EACtD,MAAM,oBAAoB,IAAI,kBAAkB,SAAS;EACzD,MAAM,YAAY,IAAI,UAAU,IAAI,UAAU,UAAU;EACxD,MAAM,cAAc,IAAI,YAAY,IAAI,UAAU,QAAQ,MAAM;EAGhE,MAAM,aAAa,MAAM,WAAW,KAAK;AACzC,QAAM,WAAW,MAAM;AACvB,MAAI,CAAC,WACH,QAAO,gBAAgB,sBAAsB;EAI/C,MAAM,gBAAgB,MAAM,WAAW,IAAI;GAAE;GAAO;GAAY,CAAC;AACjE,QAAM,WAAW,MAAM;AAGvB,MAAI,SAAS,WAAW;AACtB,SAAM,kBAAkB,IAAI,cAAc;AAC1C,SAAM,kBAAkB,MAAM;;AAIhC,MAAI,SAAS,IACX,KAAI;GAEF,MAAM,UAAU,IAAI,QAAQ,IAAI,UAAU,CADjB,CAAC,QAAQ,IACwB;AAC1D,SAAM,QAAQ,IAAI,cAAc;AAChC,SAAM,QAAQ,MAAM;UACd;AACN,UAAO,KAAK,kDAAkD;;AAKlE,QAAM,UAAU,IAAI,cAAc;AAClC,QAAM,UAAU,MAAM;EAGtB,MAAM,gBAAgB,MAAM,YAAY,IAAI,cAAc;AAC1D,QAAM,YAAY,MAAM;AAExB,SAAO;GAAE;GAAY;GAAe;UAC7B,OAAO;AACd,SAAO,gBACL,kBACE,wHACA,MACD,CACF"}
1
+ {"version":3,"file":"stage.js","names":[],"sources":["../../src/workflows/stage.ts"],"sourcesContent":["import { logCollectedFiles, logErrorAndExit } from '../console/logging.js';\nimport { branchResolutionError, withOriginalError } from '../console/index.js';\nimport { logger } from '../console/logger.js';\nimport { Settings, TranslateFlags } from '../types/index.js';\nimport { gt } from '../utils/gt.js';\nimport { EnqueueFilesResult, FileToUpload } from 'generaltranslation/types';\nimport { UploadSourcesStep } from './steps/UploadSourcesStep.js';\nimport { SetupStep } from './steps/SetupStep.js';\nimport { EnqueueStep } from './steps/EnqueueStep.js';\nimport { BranchStep } from './steps/BranchStep.js';\nimport { TagStep } from './steps/TagStep.js';\nimport { UserEditDiffsStep } from './steps/UserEditDiffsStep.js';\nimport { BranchData } from '../types/branch.js';\nimport { calculateTimeoutMs } from '../utils/calculateTimeoutMs.js';\nimport { filterFilesForEnqueue } from './utils/filterFilesForEnqueue.js';\n\n/**\n * Sends multiple files for translation to the API using a workflow pattern\n * @param files - Array of file objects to translate\n * @param options - The options for the API call\n * @param settings - Settings configuration\n * @returns The translated content or version ID\n */\nexport async function runStageFilesWorkflow({\n files,\n options,\n settings,\n}: {\n files: FileToUpload[];\n options: TranslateFlags;\n settings: Settings;\n}): Promise<{\n branchData: BranchData;\n enqueueResult: EnqueueFilesResult;\n}> {\n try {\n // Log files to be translated\n logCollectedFiles(files);\n\n // Calculate timeout for setup step\n const timeoutMs = calculateTimeoutMs(options.timeout);\n\n // Create workflow with steps\n const branchStep = new BranchStep(gt, settings);\n const uploadStep = new UploadSourcesStep(gt, settings);\n const userEditDiffsStep = new UserEditDiffsStep(settings);\n const setupStep = new SetupStep(gt, settings, timeoutMs);\n const enqueueStep = new EnqueueStep(gt, settings, options.force);\n\n // first run the branch step\n const branchData = await branchStep.run();\n await branchStep.wait();\n if (!branchData) {\n return logErrorAndExit(branchResolutionError);\n }\n\n // then run the upload step\n const uploadedFiles = await uploadStep.run({ files, branchData });\n await uploadStep.wait();\n\n // optionally run the user edit diffs step\n if (options?.saveLocal) {\n await userEditDiffsStep.run(uploadedFiles);\n await userEditDiffsStep.wait();\n }\n\n // then run the tag step (non-fatal — tagging failure should not block translations)\n if (settings.tag) {\n try {\n const userProvidedTag = !!options.tag;\n const tagStep = new TagStep(gt, settings, userProvidedTag);\n await tagStep.run(uploadedFiles);\n await tagStep.wait();\n } catch {\n logger.warn('Failed to create translation tag. Continuing...');\n }\n }\n\n // then run the setup step\n await setupStep.run(uploadedFiles);\n await setupStep.wait();\n\n // then run the enqueue step\n const { filesToEnqueue, skippedFiles } = await filterFilesForEnqueue({\n gt,\n files: uploadedFiles,\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 await enqueueStep.wait();\n\n return { branchData, enqueueResult };\n } catch (error) {\n return logErrorAndExit(\n withOriginalError(\n 'Files could not be sent for translation. Check the files, branch configuration, and API credentials, then try again.',\n error\n )\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,eAAsB,sBAAsB,EAC1C,OACA,SACA,YAQC;AACD,KAAI;AAEF,oBAAkB,MAAM;EAGxB,MAAM,YAAY,mBAAmB,QAAQ,QAAQ;EAGrD,MAAM,aAAa,IAAI,WAAW,IAAI,SAAS;EAC/C,MAAM,aAAa,IAAI,kBAAkB,IAAI,SAAS;EACtD,MAAM,oBAAoB,IAAI,kBAAkB,SAAS;EACzD,MAAM,YAAY,IAAI,UAAU,IAAI,UAAU,UAAU;EACxD,MAAM,cAAc,IAAI,YAAY,IAAI,UAAU,QAAQ,MAAM;EAGhE,MAAM,aAAa,MAAM,WAAW,KAAK;AACzC,QAAM,WAAW,MAAM;AACvB,MAAI,CAAC,WACH,QAAO,gBAAgB,sBAAsB;EAI/C,MAAM,gBAAgB,MAAM,WAAW,IAAI;GAAE;GAAO;GAAY,CAAC;AACjE,QAAM,WAAW,MAAM;AAGvB,MAAI,SAAS,WAAW;AACtB,SAAM,kBAAkB,IAAI,cAAc;AAC1C,SAAM,kBAAkB,MAAM;;AAIhC,MAAI,SAAS,IACX,KAAI;GAEF,MAAM,UAAU,IAAI,QAAQ,IAAI,UAAU,CADjB,CAAC,QAAQ,IACwB;AAC1D,SAAM,QAAQ,IAAI,cAAc;AAChC,SAAM,QAAQ,MAAM;UACd;AACN,UAAO,KAAK,kDAAkD;;AAKlE,QAAM,UAAU,IAAI,cAAc;AAClC,QAAM,UAAU,MAAM;EAGtB,MAAM,EAAE,gBAAgB,iBAAiB,MAAM,sBAAsB;GACnE;GACA,OAAO;GACP,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;AAC3D,QAAM,YAAY,MAAM;AAExB,SAAO;GAAE;GAAY;GAAe;UAC7B,OAAO;AACd,SAAO,gBACL,kBACE,wHACA,MACD,CACF"}
@@ -9,6 +9,7 @@ export declare class EnqueueStep extends WorkflowStep<FileReference[], EnqueueFi
9
9
  private force?;
10
10
  private spinner;
11
11
  private result;
12
+ private spinnerStarted;
12
13
  constructor(gt: GT, settings: Settings, force?: boolean | undefined);
13
14
  run(files: FileReference[]): Promise<EnqueueFilesResult>;
14
15
  wait(): Promise<void>;
@@ -5,6 +5,7 @@ import chalk from "chalk";
5
5
  var EnqueueStep = class extends WorkflowStep {
6
6
  spinner = logger.createSpinner("dots");
7
7
  result = null;
8
+ spinnerStarted = false;
8
9
  constructor(gt, settings, force) {
9
10
  super();
10
11
  this.gt = gt;
@@ -12,7 +13,16 @@ var EnqueueStep = class extends WorkflowStep {
12
13
  this.force = force;
13
14
  }
14
15
  async run(files) {
16
+ if (files.length === 0) {
17
+ this.result = {
18
+ jobData: {},
19
+ locales: this.settings.locales,
20
+ message: "No files need to be enqueued"
21
+ };
22
+ return this.result;
23
+ }
15
24
  this.spinner.start("Enqueuing translations...");
25
+ this.spinnerStarted = true;
16
26
  this.result = await this.gt.enqueueFiles(files, {
17
27
  sourceLocale: this.settings.defaultLocale,
18
28
  targetLocales: this.settings.locales,
@@ -23,7 +33,7 @@ var EnqueueStep = class extends WorkflowStep {
23
33
  return this.result;
24
34
  }
25
35
  async wait() {
26
- if (this.result) this.spinner.stop(chalk.green("Translations enqueued successfully"));
36
+ if (this.result && this.spinnerStarted) this.spinner.stop(chalk.green("Translations enqueued successfully"));
27
37
  }
28
38
  };
29
39
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"EnqueueStep.js","names":[],"sources":["../../../src/workflows/steps/EnqueueStep.ts"],"sourcesContent":["import type { EnqueueFilesResult } from 'generaltranslation/types';\nimport { WorkflowStep } from './WorkflowStep.js';\nimport { logger } from '../../console/logger.js';\nimport { GT } from 'generaltranslation';\nimport { Settings } from '../../types/index.js';\nimport type { FileReference } from 'generaltranslation/types';\nimport chalk from 'chalk';\n\nexport class EnqueueStep extends WorkflowStep<\n FileReference[],\n EnqueueFilesResult\n> {\n private spinner = logger.createSpinner('dots');\n private result: EnqueueFilesResult | null = null;\n\n constructor(\n private gt: GT,\n private settings: Settings,\n private force?: boolean\n ) {\n super();\n }\n\n async run(files: FileReference[]): Promise<EnqueueFilesResult> {\n this.spinner.start('Enqueuing translations...');\n\n this.result = await this.gt.enqueueFiles(files, {\n sourceLocale: this.settings.defaultLocale,\n targetLocales: this.settings.locales,\n requireApproval: this.settings.stageTranslations,\n modelProvider: this.settings.modelProvider,\n force: this.force,\n });\n\n return this.result;\n }\n\n async wait(): Promise<void> {\n if (this.result) {\n this.spinner.stop(chalk.green('Translations enqueued successfully'));\n }\n }\n}\n"],"mappings":";;;;AAQA,IAAa,cAAb,cAAiC,aAG/B;CACA,UAAkB,OAAO,cAAc,OAAO;CAC9C,SAA4C;CAE5C,YACE,IACA,UACA,OACA;AACA,SAAO;AAJC,OAAA,KAAA;AACA,OAAA,WAAA;AACA,OAAA,QAAA;;CAKV,MAAM,IAAI,OAAqD;AAC7D,OAAK,QAAQ,MAAM,4BAA4B;AAE/C,OAAK,SAAS,MAAM,KAAK,GAAG,aAAa,OAAO;GAC9C,cAAc,KAAK,SAAS;GAC5B,eAAe,KAAK,SAAS;GAC7B,iBAAiB,KAAK,SAAS;GAC/B,eAAe,KAAK,SAAS;GAC7B,OAAO,KAAK;GACb,CAAC;AAEF,SAAO,KAAK;;CAGd,MAAM,OAAsB;AAC1B,MAAI,KAAK,OACP,MAAK,QAAQ,KAAK,MAAM,MAAM,qCAAqC,CAAC"}
1
+ {"version":3,"file":"EnqueueStep.js","names":[],"sources":["../../../src/workflows/steps/EnqueueStep.ts"],"sourcesContent":["import type { EnqueueFilesResult } from 'generaltranslation/types';\nimport { WorkflowStep } from './WorkflowStep.js';\nimport { logger } from '../../console/logger.js';\nimport { GT } from 'generaltranslation';\nimport { Settings } from '../../types/index.js';\nimport type { FileReference } from 'generaltranslation/types';\nimport chalk from 'chalk';\n\nexport class EnqueueStep extends WorkflowStep<\n FileReference[],\n EnqueueFilesResult\n> {\n private spinner = logger.createSpinner('dots');\n private result: EnqueueFilesResult | null = null;\n private spinnerStarted = false;\n\n constructor(\n private gt: GT,\n private settings: Settings,\n private force?: boolean\n ) {\n super();\n }\n\n async run(files: FileReference[]): Promise<EnqueueFilesResult> {\n if (files.length === 0) {\n this.result = {\n jobData: {},\n locales: this.settings.locales,\n message: 'No files need to be enqueued',\n };\n return this.result;\n }\n\n this.spinner.start('Enqueuing translations...');\n this.spinnerStarted = true;\n\n this.result = await this.gt.enqueueFiles(files, {\n sourceLocale: this.settings.defaultLocale,\n targetLocales: this.settings.locales,\n requireApproval: this.settings.stageTranslations,\n modelProvider: this.settings.modelProvider,\n force: this.force,\n });\n\n return this.result;\n }\n\n async wait(): Promise<void> {\n if (this.result && this.spinnerStarted) {\n this.spinner.stop(chalk.green('Translations enqueued successfully'));\n }\n }\n}\n"],"mappings":";;;;AAQA,IAAa,cAAb,cAAiC,aAG/B;CACA,UAAkB,OAAO,cAAc,OAAO;CAC9C,SAA4C;CAC5C,iBAAyB;CAEzB,YACE,IACA,UACA,OACA;AACA,SAAO;AAJC,OAAA,KAAA;AACA,OAAA,WAAA;AACA,OAAA,QAAA;;CAKV,MAAM,IAAI,OAAqD;AAC7D,MAAI,MAAM,WAAW,GAAG;AACtB,QAAK,SAAS;IACZ,SAAS,EAAE;IACX,SAAS,KAAK,SAAS;IACvB,SAAS;IACV;AACD,UAAO,KAAK;;AAGd,OAAK,QAAQ,MAAM,4BAA4B;AAC/C,OAAK,iBAAiB;AAEtB,OAAK,SAAS,MAAM,KAAK,GAAG,aAAa,OAAO;GAC9C,cAAc,KAAK,SAAS;GAC5B,eAAe,KAAK,SAAS;GAC7B,iBAAiB,KAAK,SAAS;GAC/B,eAAe,KAAK,SAAS;GAC7B,OAAO,KAAK;GACb,CAAC;AAEF,SAAO,KAAK;;CAGd,MAAM,OAAsB;AAC1B,MAAI,KAAK,UAAU,KAAK,eACtB,MAAK,QAAQ,KAAK,MAAM,MAAM,qCAAqC,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { logger } from "../../console/logger.js";
2
2
  import "../../utils/constants.js";
3
3
  import { WorkflowStep } from "./WorkflowStep.js";
4
+ import { getFileTranslationKey, queryCompletedTranslationKeys } from "../utils/queryCompletedTranslations.js";
4
5
  import chalk from "chalk";
5
6
  //#region src/workflows/steps/PollJobsStep.ts
6
7
  var PollTranslationJobsStep = class extends WorkflowStep {
@@ -17,16 +18,9 @@ var PollTranslationJobsStep = class extends WorkflowStep {
17
18
  this.spinner.start(spinnerMessage);
18
19
  const filePropertiesMap = /* @__PURE__ */ new Map();
19
20
  fileQueryData.forEach((item) => {
20
- filePropertiesMap.set(`${item.branchId}:${item.fileId}:${item.versionId}:${item.locale}`, item);
21
+ filePropertiesMap.set(getFileTranslationKey(item), item);
21
22
  });
22
- if (!forceRetranslation) ((await this.gt.queryFileData({ translatedFiles: fileQueryData.map((item) => ({
23
- fileId: item.fileId,
24
- versionId: item.versionId,
25
- branchId: item.branchId,
26
- locale: item.locale
27
- })) })).translatedFiles || []).forEach((translation) => {
28
- if (!translation.completedAt) return;
29
- const fileKey = `${translation.branchId}:${translation.fileId}:${translation.versionId}:${translation.locale}`;
23
+ if (!forceRetranslation) (await queryCompletedTranslationKeys(this.gt, fileQueryData)).forEach((fileKey) => {
30
24
  const fileProperties = filePropertiesMap.get(fileKey);
31
25
  if (fileProperties) fileTracker.completed.set(fileKey, fileProperties);
32
26
  });
@@ -51,7 +45,7 @@ var PollTranslationJobsStep = class extends WorkflowStep {
51
45
  });
52
46
  });
53
47
  for (const item of fileQueryData) {
54
- const fileKey = `${item.branchId}:${item.fileId}:${item.versionId}:${item.locale}`;
48
+ const fileKey = getFileTranslationKey(item);
55
49
  if (fileTracker.completed.get(fileKey)) continue;
56
50
  const jobKey = `${item.branchId}:${item.fileId}:${item.versionId}:${item.locale}`;
57
51
  if (jobMap.get(jobKey)) fileTracker.inProgress.set(fileKey, item);
@@ -1 +1 @@
1
- {"version":3,"file":"PollJobsStep.js","names":[],"sources":["../../../src/workflows/steps/PollJobsStep.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { WorkflowStep } from './WorkflowStep.js';\nimport { logger } from '../../console/logger.js';\nimport { GT } from 'generaltranslation';\nimport { EnqueueFilesResult } from 'generaltranslation/types';\nimport { TEMPLATE_FILE_NAME } from '../../utils/constants.js';\nimport type { FileProperties } from '../../types/files.js';\n\nexport type PollJobsInput = {\n fileTracker: FileStatusTracker;\n fileQueryData: FileProperties[];\n jobData: EnqueueFilesResult;\n timeoutDuration: number;\n forceRetranslation?: boolean;\n};\n\nexport type FileStatusTracker = {\n completed: Map<string, FileProperties>;\n inProgress: Map<string, FileProperties>;\n failed: Map<string, FileProperties>;\n skipped: Map<string, FileProperties>;\n};\n\nexport type PollJobsOutput = {\n success: boolean;\n fileTracker: FileStatusTracker;\n};\n\nexport class PollTranslationJobsStep extends WorkflowStep<\n PollJobsInput,\n PollJobsOutput\n> {\n private spinner: ReturnType<typeof logger.createProgressBar> | null = null;\n private previousProgress = 0;\n\n constructor(private gt: GT) {\n super();\n }\n\n async run({\n fileTracker,\n fileQueryData,\n jobData,\n timeoutDuration,\n forceRetranslation,\n }: PollJobsInput): Promise<PollJobsOutput> {\n const startTime = Date.now();\n this.spinner = logger.createProgressBar(fileQueryData.length);\n const spinnerMessage = forceRetranslation\n ? 'Waiting for retranslation...'\n : 'Waiting for translation...';\n this.spinner.start(spinnerMessage);\n\n // Build a map of branchId:fileId:versionId:locale -> FileProperties\n const filePropertiesMap = new Map<string, FileProperties>();\n fileQueryData.forEach((item) => {\n filePropertiesMap.set(\n `${item.branchId}:${item.fileId}:${item.versionId}:${item.locale}`,\n item\n );\n });\n\n // Initial query to check which files already have translations\n // Skip when force retranslation is enabled, since the server\n // no longer marks force-retranslated files as incomplete\n if (!forceRetranslation) {\n const initialFileData = await this.gt.queryFileData({\n translatedFiles: fileQueryData.map((item) => ({\n fileId: item.fileId,\n versionId: item.versionId,\n branchId: item.branchId,\n locale: item.locale,\n })),\n });\n const existingTranslations = initialFileData.translatedFiles || [];\n\n // Mark all existing translations as completed\n existingTranslations.forEach((translation) => {\n if (!translation.completedAt) {\n return;\n }\n const fileKey = `${translation.branchId}:${translation.fileId}:${translation.versionId}:${translation.locale}`;\n const fileProperties = filePropertiesMap.get(fileKey);\n if (fileProperties) {\n fileTracker.completed.set(fileKey, fileProperties);\n }\n });\n }\n\n // Build a map of jobs for quick lookup:\n // branchId:fileId:versionId:locale -> job\n const jobMap = new Map<\n string,\n (typeof jobData.jobData)[number] & { jobId: string }\n >();\n Object.entries(jobData.jobData).forEach(([jobId, job]) => {\n const jobLocale = this.gt.resolveAliasLocale(job.targetLocale);\n const key = `${job.branchId}:${job.fileId}:${job.versionId}:${jobLocale}`;\n jobMap.set(key, { ...job, jobId, targetLocale: jobLocale });\n });\n\n // Build a map of jobs for quick lookup:\n // jobId -> file data for the job\n const jobFileMap = new Map<\n string,\n {\n branchId: string;\n fileId: string;\n versionId: string;\n locale: string;\n }\n >();\n Object.entries(jobData.jobData).forEach(([jobId, job]) => {\n const jobLocale = this.gt.resolveAliasLocale(job.targetLocale);\n jobFileMap.set(jobId, {\n branchId: job.branchId,\n fileId: job.fileId,\n versionId: job.versionId,\n locale: jobLocale,\n });\n });\n\n // Categorize each file query item\n for (const item of fileQueryData) {\n const fileKey = `${item.branchId}:${item.fileId}:${item.versionId}:${item.locale}`;\n\n // Check if translation already exists (completedAt is truthy)\n const existingTranslation = fileTracker.completed.get(fileKey);\n\n if (existingTranslation) {\n continue;\n }\n\n // Check if there's a job for this file\n const jobKey = `${item.branchId}:${item.fileId}:${item.versionId}:${item.locale}`;\n const job = jobMap.get(jobKey);\n\n if (job) {\n // Job exists - mark as in progress initially\n fileTracker.inProgress.set(fileKey, item);\n } else {\n // No job and no existing translation - mark as skipped\n fileTracker.skipped.set(fileKey, item);\n }\n }\n\n // Update spinner with initial status\n this.updateSpinner(fileTracker, fileQueryData);\n\n // If force retranslation, don't skip the initial check\n if (!forceRetranslation) {\n // Check if all jobs are already complete\n if (fileTracker.inProgress.size === 0) {\n this.spinner.stop(chalk.green('All translations ready'));\n return { success: true, fileTracker };\n }\n }\n\n // Calculate time until next 5-second interval since startTime\n const msUntilNextInterval = Math.max(\n 0,\n 5000 - ((Date.now() - startTime) % 5000)\n );\n\n return new Promise<PollJobsOutput>((resolve) => {\n let intervalCheck: NodeJS.Timeout;\n\n setTimeout(() => {\n intervalCheck = setInterval(async () => {\n try {\n // Query job status\n const jobIds = Array.from(jobFileMap.keys());\n const jobStatusResponse = await this.gt.checkJobStatus(jobIds);\n\n // Update status based on job completion\n for (const job of jobStatusResponse) {\n const jobFileProperties = jobFileMap.get(job.jobId);\n if (jobFileProperties) {\n const fileKey = `${jobFileProperties.branchId}:${jobFileProperties.fileId}:${jobFileProperties.versionId}:${jobFileProperties.locale}`;\n const fileProperties = filePropertiesMap.get(fileKey);\n if (!fileProperties) {\n continue;\n }\n if (job.status === 'completed') {\n fileTracker.completed.set(fileKey, fileProperties);\n fileTracker.inProgress.delete(fileKey);\n jobFileMap.delete(job.jobId);\n } else if (job.status === 'failed') {\n fileTracker.failed.set(fileKey, fileProperties);\n fileTracker.inProgress.delete(fileKey);\n jobFileMap.delete(job.jobId);\n } else if (job.status === 'unknown') {\n fileTracker.skipped.set(fileKey, fileProperties);\n fileTracker.inProgress.delete(fileKey);\n jobFileMap.delete(job.jobId);\n }\n }\n }\n\n // Update spinner\n this.updateSpinner(fileTracker, fileQueryData);\n\n const elapsed = Date.now() - startTime;\n const allJobsProcessed = fileTracker.inProgress.size === 0;\n\n if (allJobsProcessed || elapsed >= timeoutDuration * 1000) {\n clearInterval(intervalCheck);\n\n if (fileTracker.inProgress.size === 0) {\n this.spinner!.stop(chalk.green('Translation jobs finished'));\n resolve({ success: true, fileTracker });\n } else {\n this.spinner!.stop(\n chalk.red('Timed out waiting for translation jobs')\n );\n resolve({ success: false, fileTracker });\n }\n }\n } catch (error) {\n logger.error(chalk.red('Error checking job status: ') + error);\n }\n }, 5000);\n }, msUntilNextInterval);\n });\n }\n\n private updateSpinner(\n fileTracker: FileStatusTracker,\n fileQueryData: FileProperties[]\n ): void {\n if (!this.spinner) return;\n\n const statusText = this.generateStatusSuffixText(\n fileTracker,\n fileQueryData\n );\n const currentProgress =\n fileTracker.completed.size +\n fileTracker.failed.size +\n fileTracker.skipped.size;\n const progressDelta = currentProgress - this.previousProgress;\n this.spinner.advance(progressDelta, statusText);\n this.previousProgress = currentProgress;\n }\n\n private generateStatusSuffixText(\n fileTracker: FileStatusTracker,\n fileQueryData: FileProperties[]\n ): string {\n // Simple progress indicator\n const progressText = `${chalk.green(\n `[${\n fileTracker.completed.size +\n fileTracker.failed.size +\n fileTracker.skipped.size\n }/${fileQueryData.length}]`\n )} translations completed`;\n\n // Get terminal height to adapt our output\n const terminalHeight = process.stdout.rows || 24;\n\n // If terminal is very small, just show the basic progress\n if (terminalHeight < 6) {\n return progressText;\n }\n\n const newSuffixText = [progressText];\n\n // Organize data by filename : locale\n const fileStatus = new Map<\n string,\n {\n completed: Set<string>;\n pending: Set<string>;\n failed: Set<string>;\n skipped: Set<string>;\n }\n >();\n\n // Initialize with all files and locales from fileQueryData\n for (const item of fileQueryData) {\n if (!fileStatus.has(item.fileName)) {\n fileStatus.set(item.fileName, {\n completed: new Set(),\n pending: new Set([item.locale]),\n failed: new Set(),\n skipped: new Set(),\n });\n } else {\n fileStatus.get(item.fileName)?.pending.add(item.locale);\n }\n }\n\n // Mark which ones are completed, failed, or skipped\n for (const [_, fileProperties] of fileTracker.completed) {\n const { fileName, locale } = fileProperties;\n const status = fileStatus.get(fileName);\n if (status) {\n status.pending.delete(locale);\n status.completed.add(locale);\n }\n }\n\n for (const [_, fileProperties] of fileTracker.failed) {\n const { fileName, locale } = fileProperties;\n const status = fileStatus.get(fileName);\n if (status) {\n status.pending.delete(locale);\n status.failed.add(locale);\n }\n }\n\n for (const [_, fileProperties] of fileTracker.skipped) {\n const { fileName, locale } = fileProperties;\n const status = fileStatus.get(fileName);\n if (status) {\n status.pending.delete(locale);\n status.skipped.add(locale);\n }\n }\n\n // Calculate how many files we can show based on terminal height\n const filesArray = Array.from(fileStatus.entries());\n const maxFilesToShow = Math.min(\n filesArray.length,\n terminalHeight - 3 // Header + progress + buffer\n );\n\n // Display each file with its status on a single line\n for (let i = 0; i < maxFilesToShow; i++) {\n const [fileName, status] = filesArray[i];\n\n // Create condensed locale status\n const localeStatuses: { locale: string; status: string }[] = [];\n\n // Add completed locales (green)\n if (status.completed.size > 0) {\n localeStatuses.push(\n ...Array.from(status.completed).map((locale) => ({\n locale,\n status: 'completed',\n }))\n );\n }\n\n // Add skipped locales (green)\n if (status.skipped.size > 0) {\n localeStatuses.push(\n ...Array.from(status.skipped).map((locale) => ({\n locale,\n status: 'skipped',\n }))\n );\n }\n\n // Add failed locales (red)\n if (status.failed.size > 0) {\n localeStatuses.push(\n ...Array.from(status.failed).map((locale) => ({\n locale,\n status: 'failed',\n }))\n );\n }\n\n // Add pending locales (yellow)\n if (status.pending.size > 0) {\n localeStatuses.push(\n ...Array.from(status.pending).map((locale) => ({\n locale,\n status: 'pending',\n }))\n );\n }\n\n // Sort localeStatuses by locale\n localeStatuses.sort((a, b) => a.locale.localeCompare(b.locale));\n\n // Add colors\n const localeString = localeStatuses\n .map((locale) => {\n if (locale.status === 'completed') {\n return chalk.green(locale.locale);\n } else if (locale.status === 'skipped') {\n return chalk.gray(locale.locale);\n } else if (locale.status === 'failed') {\n return chalk.red(locale.locale);\n } else if (locale.status === 'pending') {\n return chalk.yellow(locale.locale);\n }\n })\n .join(', ');\n\n // Format the line\n const prettyFileName =\n fileName === TEMPLATE_FILE_NAME ? '<React Elements>' : fileName;\n newSuffixText.push(`${chalk.bold(prettyFileName)} [${localeString}]`);\n }\n\n // If we couldn't show all files, add an indicator\n if (filesArray.length > maxFilesToShow) {\n newSuffixText.push(\n `... and ${filesArray.length - maxFilesToShow} more files`\n );\n }\n\n return newSuffixText.join('\\n');\n }\n\n async wait(): Promise<void> {\n return;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PollJobsStep.js","names":[],"sources":["../../../src/workflows/steps/PollJobsStep.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { WorkflowStep } from './WorkflowStep.js';\nimport { logger } from '../../console/logger.js';\nimport { GT } from 'generaltranslation';\nimport { EnqueueFilesResult } from 'generaltranslation/types';\nimport { TEMPLATE_FILE_NAME } from '../../utils/constants.js';\nimport type { FileProperties } from '../../types/files.js';\nimport {\n getFileTranslationKey,\n queryCompletedTranslationKeys,\n} from '../utils/queryCompletedTranslations.js';\n\nexport type PollJobsInput = {\n fileTracker: FileStatusTracker;\n fileQueryData: FileProperties[];\n jobData: EnqueueFilesResult;\n timeoutDuration: number;\n forceRetranslation?: boolean;\n};\n\nexport type FileStatusTracker = {\n completed: Map<string, FileProperties>;\n inProgress: Map<string, FileProperties>;\n failed: Map<string, FileProperties>;\n skipped: Map<string, FileProperties>;\n};\n\nexport type PollJobsOutput = {\n success: boolean;\n fileTracker: FileStatusTracker;\n};\n\nexport class PollTranslationJobsStep extends WorkflowStep<\n PollJobsInput,\n PollJobsOutput\n> {\n private spinner: ReturnType<typeof logger.createProgressBar> | null = null;\n private previousProgress = 0;\n\n constructor(private gt: GT) {\n super();\n }\n\n async run({\n fileTracker,\n fileQueryData,\n jobData,\n timeoutDuration,\n forceRetranslation,\n }: PollJobsInput): Promise<PollJobsOutput> {\n const startTime = Date.now();\n this.spinner = logger.createProgressBar(fileQueryData.length);\n const spinnerMessage = forceRetranslation\n ? 'Waiting for retranslation...'\n : 'Waiting for translation...';\n this.spinner.start(spinnerMessage);\n\n // Build a map of branchId:fileId:versionId:locale -> FileProperties\n const filePropertiesMap = new Map<string, FileProperties>();\n fileQueryData.forEach((item) => {\n filePropertiesMap.set(getFileTranslationKey(item), item);\n });\n\n // Initial query to check which files already have translations\n // Skip when force retranslation is enabled, since the server\n // no longer marks force-retranslated files as incomplete\n if (!forceRetranslation) {\n const completedKeys = await queryCompletedTranslationKeys(\n this.gt,\n fileQueryData\n );\n\n completedKeys.forEach((fileKey) => {\n const fileProperties = filePropertiesMap.get(fileKey);\n if (fileProperties) {\n fileTracker.completed.set(fileKey, fileProperties);\n }\n });\n }\n\n // Build a map of jobs for quick lookup:\n // branchId:fileId:versionId:locale -> job\n const jobMap = new Map<\n string,\n (typeof jobData.jobData)[number] & { jobId: string }\n >();\n Object.entries(jobData.jobData).forEach(([jobId, job]) => {\n const jobLocale = this.gt.resolveAliasLocale(job.targetLocale);\n const key = `${job.branchId}:${job.fileId}:${job.versionId}:${jobLocale}`;\n jobMap.set(key, { ...job, jobId, targetLocale: jobLocale });\n });\n\n // Build a map of jobs for quick lookup:\n // jobId -> file data for the job\n const jobFileMap = new Map<\n string,\n {\n branchId: string;\n fileId: string;\n versionId: string;\n locale: string;\n }\n >();\n Object.entries(jobData.jobData).forEach(([jobId, job]) => {\n const jobLocale = this.gt.resolveAliasLocale(job.targetLocale);\n jobFileMap.set(jobId, {\n branchId: job.branchId,\n fileId: job.fileId,\n versionId: job.versionId,\n locale: jobLocale,\n });\n });\n\n // Categorize each file query item\n for (const item of fileQueryData) {\n const fileKey = getFileTranslationKey(item);\n\n // Check if translation already exists (completedAt is truthy)\n const existingTranslation = fileTracker.completed.get(fileKey);\n\n if (existingTranslation) {\n continue;\n }\n\n // Check if there's a job for this file\n const jobKey = `${item.branchId}:${item.fileId}:${item.versionId}:${item.locale}`;\n const job = jobMap.get(jobKey);\n\n if (job) {\n // Job exists - mark as in progress initially\n fileTracker.inProgress.set(fileKey, item);\n } else {\n // No job and no existing translation - mark as skipped\n fileTracker.skipped.set(fileKey, item);\n }\n }\n\n // Update spinner with initial status\n this.updateSpinner(fileTracker, fileQueryData);\n\n // If force retranslation, don't skip the initial check\n if (!forceRetranslation) {\n // Check if all jobs are already complete\n if (fileTracker.inProgress.size === 0) {\n this.spinner.stop(chalk.green('All translations ready'));\n return { success: true, fileTracker };\n }\n }\n\n // Calculate time until next 5-second interval since startTime\n const msUntilNextInterval = Math.max(\n 0,\n 5000 - ((Date.now() - startTime) % 5000)\n );\n\n return new Promise<PollJobsOutput>((resolve) => {\n let intervalCheck: NodeJS.Timeout;\n\n setTimeout(() => {\n intervalCheck = setInterval(async () => {\n try {\n // Query job status\n const jobIds = Array.from(jobFileMap.keys());\n const jobStatusResponse = await this.gt.checkJobStatus(jobIds);\n\n // Update status based on job completion\n for (const job of jobStatusResponse) {\n const jobFileProperties = jobFileMap.get(job.jobId);\n if (jobFileProperties) {\n const fileKey = `${jobFileProperties.branchId}:${jobFileProperties.fileId}:${jobFileProperties.versionId}:${jobFileProperties.locale}`;\n const fileProperties = filePropertiesMap.get(fileKey);\n if (!fileProperties) {\n continue;\n }\n if (job.status === 'completed') {\n fileTracker.completed.set(fileKey, fileProperties);\n fileTracker.inProgress.delete(fileKey);\n jobFileMap.delete(job.jobId);\n } else if (job.status === 'failed') {\n fileTracker.failed.set(fileKey, fileProperties);\n fileTracker.inProgress.delete(fileKey);\n jobFileMap.delete(job.jobId);\n } else if (job.status === 'unknown') {\n fileTracker.skipped.set(fileKey, fileProperties);\n fileTracker.inProgress.delete(fileKey);\n jobFileMap.delete(job.jobId);\n }\n }\n }\n\n // Update spinner\n this.updateSpinner(fileTracker, fileQueryData);\n\n const elapsed = Date.now() - startTime;\n const allJobsProcessed = fileTracker.inProgress.size === 0;\n\n if (allJobsProcessed || elapsed >= timeoutDuration * 1000) {\n clearInterval(intervalCheck);\n\n if (fileTracker.inProgress.size === 0) {\n this.spinner!.stop(chalk.green('Translation jobs finished'));\n resolve({ success: true, fileTracker });\n } else {\n this.spinner!.stop(\n chalk.red('Timed out waiting for translation jobs')\n );\n resolve({ success: false, fileTracker });\n }\n }\n } catch (error) {\n logger.error(chalk.red('Error checking job status: ') + error);\n }\n }, 5000);\n }, msUntilNextInterval);\n });\n }\n\n private updateSpinner(\n fileTracker: FileStatusTracker,\n fileQueryData: FileProperties[]\n ): void {\n if (!this.spinner) return;\n\n const statusText = this.generateStatusSuffixText(\n fileTracker,\n fileQueryData\n );\n const currentProgress =\n fileTracker.completed.size +\n fileTracker.failed.size +\n fileTracker.skipped.size;\n const progressDelta = currentProgress - this.previousProgress;\n this.spinner.advance(progressDelta, statusText);\n this.previousProgress = currentProgress;\n }\n\n private generateStatusSuffixText(\n fileTracker: FileStatusTracker,\n fileQueryData: FileProperties[]\n ): string {\n // Simple progress indicator\n const progressText = `${chalk.green(\n `[${\n fileTracker.completed.size +\n fileTracker.failed.size +\n fileTracker.skipped.size\n }/${fileQueryData.length}]`\n )} translations completed`;\n\n // Get terminal height to adapt our output\n const terminalHeight = process.stdout.rows || 24;\n\n // If terminal is very small, just show the basic progress\n if (terminalHeight < 6) {\n return progressText;\n }\n\n const newSuffixText = [progressText];\n\n // Organize data by filename : locale\n const fileStatus = new Map<\n string,\n {\n completed: Set<string>;\n pending: Set<string>;\n failed: Set<string>;\n skipped: Set<string>;\n }\n >();\n\n // Initialize with all files and locales from fileQueryData\n for (const item of fileQueryData) {\n if (!fileStatus.has(item.fileName)) {\n fileStatus.set(item.fileName, {\n completed: new Set(),\n pending: new Set([item.locale]),\n failed: new Set(),\n skipped: new Set(),\n });\n } else {\n fileStatus.get(item.fileName)?.pending.add(item.locale);\n }\n }\n\n // Mark which ones are completed, failed, or skipped\n for (const [_, fileProperties] of fileTracker.completed) {\n const { fileName, locale } = fileProperties;\n const status = fileStatus.get(fileName);\n if (status) {\n status.pending.delete(locale);\n status.completed.add(locale);\n }\n }\n\n for (const [_, fileProperties] of fileTracker.failed) {\n const { fileName, locale } = fileProperties;\n const status = fileStatus.get(fileName);\n if (status) {\n status.pending.delete(locale);\n status.failed.add(locale);\n }\n }\n\n for (const [_, fileProperties] of fileTracker.skipped) {\n const { fileName, locale } = fileProperties;\n const status = fileStatus.get(fileName);\n if (status) {\n status.pending.delete(locale);\n status.skipped.add(locale);\n }\n }\n\n // Calculate how many files we can show based on terminal height\n const filesArray = Array.from(fileStatus.entries());\n const maxFilesToShow = Math.min(\n filesArray.length,\n terminalHeight - 3 // Header + progress + buffer\n );\n\n // Display each file with its status on a single line\n for (let i = 0; i < maxFilesToShow; i++) {\n const [fileName, status] = filesArray[i];\n\n // Create condensed locale status\n const localeStatuses: { locale: string; status: string }[] = [];\n\n // Add completed locales (green)\n if (status.completed.size > 0) {\n localeStatuses.push(\n ...Array.from(status.completed).map((locale) => ({\n locale,\n status: 'completed',\n }))\n );\n }\n\n // Add skipped locales (green)\n if (status.skipped.size > 0) {\n localeStatuses.push(\n ...Array.from(status.skipped).map((locale) => ({\n locale,\n status: 'skipped',\n }))\n );\n }\n\n // Add failed locales (red)\n if (status.failed.size > 0) {\n localeStatuses.push(\n ...Array.from(status.failed).map((locale) => ({\n locale,\n status: 'failed',\n }))\n );\n }\n\n // Add pending locales (yellow)\n if (status.pending.size > 0) {\n localeStatuses.push(\n ...Array.from(status.pending).map((locale) => ({\n locale,\n status: 'pending',\n }))\n );\n }\n\n // Sort localeStatuses by locale\n localeStatuses.sort((a, b) => a.locale.localeCompare(b.locale));\n\n // Add colors\n const localeString = localeStatuses\n .map((locale) => {\n if (locale.status === 'completed') {\n return chalk.green(locale.locale);\n } else if (locale.status === 'skipped') {\n return chalk.gray(locale.locale);\n } else if (locale.status === 'failed') {\n return chalk.red(locale.locale);\n } else if (locale.status === 'pending') {\n return chalk.yellow(locale.locale);\n }\n })\n .join(', ');\n\n // Format the line\n const prettyFileName =\n fileName === TEMPLATE_FILE_NAME ? '<React Elements>' : fileName;\n newSuffixText.push(`${chalk.bold(prettyFileName)} [${localeString}]`);\n }\n\n // If we couldn't show all files, add an indicator\n if (filesArray.length > maxFilesToShow) {\n newSuffixText.push(\n `... and ${filesArray.length - maxFilesToShow} more files`\n );\n }\n\n return newSuffixText.join('\\n');\n }\n\n async wait(): Promise<void> {\n return;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { GT } from 'generaltranslation';
2
+ import type { FileReference } from 'generaltranslation/types';
3
+ type FilterFilesForEnqueueClient = Pick<GT, 'queryFileData'>;
4
+ export type EnqueueFilterResult = {
5
+ filesToEnqueue: FileReference[];
6
+ skippedFiles: FileReference[];
7
+ };
8
+ export declare function filterFilesForEnqueue({ gt, files, locales, force, }: {
9
+ gt: FilterFilesForEnqueueClient;
10
+ files: FileReference[];
11
+ locales: string[];
12
+ force?: boolean;
13
+ }): Promise<EnqueueFilterResult>;
14
+ export {};
@@ -0,0 +1,41 @@
1
+ import { getFileTranslationKey, queryCompletedTranslationKeys } from "./queryCompletedTranslations.js";
2
+ //#region src/workflows/utils/filterFilesForEnqueue.ts
3
+ async function filterFilesForEnqueue({ gt, files, locales, force }) {
4
+ if (force || files.length === 0 || locales.length === 0) return {
5
+ filesToEnqueue: files,
6
+ skippedFiles: []
7
+ };
8
+ const fileQueryData = files.flatMap((file) => locales.map((locale) => ({
9
+ branchId: file.branchId,
10
+ fileId: file.fileId,
11
+ versionId: file.versionId,
12
+ fileName: file.fileName,
13
+ locale
14
+ })));
15
+ let completedKeys;
16
+ try {
17
+ completedKeys = await queryCompletedTranslationKeys(gt, fileQueryData);
18
+ } catch {
19
+ return {
20
+ filesToEnqueue: files,
21
+ skippedFiles: []
22
+ };
23
+ }
24
+ const filesToEnqueue = [];
25
+ const skippedFiles = [];
26
+ for (const file of files) if (locales.every((locale) => completedKeys.has(getFileTranslationKey({
27
+ branchId: file.branchId,
28
+ fileId: file.fileId,
29
+ versionId: file.versionId,
30
+ locale
31
+ })))) skippedFiles.push(file);
32
+ else filesToEnqueue.push(file);
33
+ return {
34
+ filesToEnqueue,
35
+ skippedFiles
36
+ };
37
+ }
38
+ //#endregion
39
+ export { filterFilesForEnqueue };
40
+
41
+ //# sourceMappingURL=filterFilesForEnqueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filterFilesForEnqueue.js","names":[],"sources":["../../../src/workflows/utils/filterFilesForEnqueue.ts"],"sourcesContent":["import type { GT } from 'generaltranslation';\nimport type { FileReference } from 'generaltranslation/types';\nimport type { FileProperties } from '../../types/files.js';\nimport {\n getFileTranslationKey,\n queryCompletedTranslationKeys,\n} from './queryCompletedTranslations.js';\n\ntype FilterFilesForEnqueueClient = Pick<GT, 'queryFileData'>;\n\nexport type EnqueueFilterResult = {\n filesToEnqueue: FileReference[];\n skippedFiles: FileReference[];\n};\n\nexport async function filterFilesForEnqueue({\n gt,\n files,\n locales,\n force,\n}: {\n gt: FilterFilesForEnqueueClient;\n files: FileReference[];\n locales: string[];\n force?: boolean;\n}): Promise<EnqueueFilterResult> {\n if (force || files.length === 0 || locales.length === 0) {\n return { filesToEnqueue: files, skippedFiles: [] };\n }\n\n const fileQueryData: FileProperties[] = files.flatMap((file) =>\n locales.map((locale) => ({\n branchId: file.branchId,\n fileId: file.fileId,\n versionId: file.versionId,\n fileName: file.fileName,\n locale,\n }))\n );\n\n let completedKeys: Set<string>;\n try {\n completedKeys = await queryCompletedTranslationKeys(gt, fileQueryData);\n } catch {\n return { filesToEnqueue: files, skippedFiles: [] };\n }\n\n const filesToEnqueue: FileReference[] = [];\n const skippedFiles: FileReference[] = [];\n\n for (const file of files) {\n const hasEveryLocale = locales.every((locale) =>\n completedKeys.has(\n getFileTranslationKey({\n branchId: file.branchId,\n fileId: file.fileId,\n versionId: file.versionId,\n locale,\n })\n )\n );\n\n if (hasEveryLocale) {\n skippedFiles.push(file);\n } else {\n filesToEnqueue.push(file);\n }\n }\n\n return { filesToEnqueue, skippedFiles };\n}\n"],"mappings":";;AAeA,eAAsB,sBAAsB,EAC1C,IACA,OACA,SACA,SAM+B;AAC/B,KAAI,SAAS,MAAM,WAAW,KAAK,QAAQ,WAAW,EACpD,QAAO;EAAE,gBAAgB;EAAO,cAAc,EAAE;EAAE;CAGpD,MAAM,gBAAkC,MAAM,SAAS,SACrD,QAAQ,KAAK,YAAY;EACvB,UAAU,KAAK;EACf,QAAQ,KAAK;EACb,WAAW,KAAK;EAChB,UAAU,KAAK;EACf;EACD,EAAE,CACJ;CAED,IAAI;AACJ,KAAI;AACF,kBAAgB,MAAM,8BAA8B,IAAI,cAAc;SAChE;AACN,SAAO;GAAE,gBAAgB;GAAO,cAAc,EAAE;GAAE;;CAGpD,MAAM,iBAAkC,EAAE;CAC1C,MAAM,eAAgC,EAAE;AAExC,MAAK,MAAM,QAAQ,MAYjB,KAXuB,QAAQ,OAAO,WACpC,cAAc,IACZ,sBAAsB;EACpB,UAAU,KAAK;EACf,QAAQ,KAAK;EACb,WAAW,KAAK;EAChB;EACD,CAAC,CACH,CAGe,CAChB,cAAa,KAAK,KAAK;KAEvB,gBAAe,KAAK,KAAK;AAI7B,QAAO;EAAE;EAAgB;EAAc"}
@@ -0,0 +1,5 @@
1
+ import type { GT } from 'generaltranslation';
2
+ import type { FileProperties } from '../../types/files.js';
3
+ export type QueryCompletedTranslationsClient = Pick<GT, 'queryFileData'>;
4
+ export declare function getFileTranslationKey(file: Pick<FileProperties, 'branchId' | 'fileId' | 'versionId' | 'locale'>): string;
5
+ export declare function queryCompletedTranslationKeys(gt: QueryCompletedTranslationsClient, fileQueryData: FileProperties[]): Promise<Set<string>>;
@@ -0,0 +1,18 @@
1
+ //#region src/workflows/utils/queryCompletedTranslations.ts
2
+ function getFileTranslationKey(file) {
3
+ return `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`;
4
+ }
5
+ async function queryCompletedTranslationKeys(gt, fileQueryData) {
6
+ if (fileQueryData.length === 0) return /* @__PURE__ */ new Set();
7
+ const fileData = await gt.queryFileData({ translatedFiles: fileQueryData.map((file) => ({
8
+ fileId: file.fileId,
9
+ versionId: file.versionId,
10
+ branchId: file.branchId,
11
+ locale: file.locale
12
+ })) });
13
+ return new Set((fileData.translatedFiles || []).filter((file) => !!file.completedAt).map(getFileTranslationKey));
14
+ }
15
+ //#endregion
16
+ export { getFileTranslationKey, queryCompletedTranslationKeys };
17
+
18
+ //# sourceMappingURL=queryCompletedTranslations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryCompletedTranslations.js","names":[],"sources":["../../../src/workflows/utils/queryCompletedTranslations.ts"],"sourcesContent":["import type { GT } from 'generaltranslation';\nimport type { FileProperties } from '../../types/files.js';\n\nexport type QueryCompletedTranslationsClient = Pick<GT, 'queryFileData'>;\n\nexport function getFileTranslationKey(\n file: Pick<FileProperties, 'branchId' | 'fileId' | 'versionId' | 'locale'>\n): string {\n return `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`;\n}\n\nexport async function queryCompletedTranslationKeys(\n gt: QueryCompletedTranslationsClient,\n fileQueryData: FileProperties[]\n): Promise<Set<string>> {\n if (fileQueryData.length === 0) {\n return new Set();\n }\n\n const fileData = await gt.queryFileData({\n translatedFiles: fileQueryData.map((file) => ({\n fileId: file.fileId,\n versionId: file.versionId,\n branchId: file.branchId,\n locale: file.locale,\n })),\n });\n\n return new Set(\n (fileData.translatedFiles || [])\n .filter((file) => !!file.completedAt)\n .map(getFileTranslationKey)\n );\n}\n"],"mappings":";AAKA,SAAgB,sBACd,MACQ;AACR,QAAO,GAAG,KAAK,SAAS,GAAG,KAAK,OAAO,GAAG,KAAK,UAAU,GAAG,KAAK;;AAGnE,eAAsB,8BACpB,IACA,eACsB;AACtB,KAAI,cAAc,WAAW,EAC3B,wBAAO,IAAI,KAAK;CAGlB,MAAM,WAAW,MAAM,GAAG,cAAc,EACtC,iBAAiB,cAAc,KAAK,UAAU;EAC5C,QAAQ,KAAK;EACb,WAAW,KAAK;EAChB,UAAU,KAAK;EACf,QAAQ,KAAK;EACd,EAAE,EACJ,CAAC;AAEF,QAAO,IAAI,KACR,SAAS,mBAAmB,EAAE,EAC5B,QAAQ,SAAS,CAAC,CAAC,KAAK,YAAY,CACpC,IAAI,sBAAsB,CAC9B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gt",
3
- "version": "2.14.52",
3
+ "version": "2.14.53",
4
4
  "main": "dist/index.js",
5
5
  "bin": "bin/main.js",
6
6
  "files": [