@intlayer/cli 5.8.1 → 6.0.0-canary.1

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.
Files changed (134) hide show
  1. package/dist/cjs/IntlayerEventListener.cjs +241 -0
  2. package/dist/cjs/IntlayerEventListener.cjs.map +1 -0
  3. package/dist/cjs/cli.cjs +29 -7
  4. package/dist/cjs/cli.cjs.map +1 -1
  5. package/dist/cjs/config.cjs +5 -1
  6. package/dist/cjs/config.cjs.map +1 -1
  7. package/dist/cjs/fill/autoFill.cjs +105 -0
  8. package/dist/cjs/fill/autoFill.cjs.map +1 -0
  9. package/dist/cjs/fill/formatAutoFillData.cjs +108 -0
  10. package/dist/cjs/fill/formatAutoFillData.cjs.map +1 -0
  11. package/dist/cjs/fill/formatAutoFilledFilePath.cjs +46 -0
  12. package/dist/cjs/fill/formatAutoFilledFilePath.cjs.map +1 -0
  13. package/dist/cjs/fill/getTargetDictionary.cjs +86 -0
  14. package/dist/cjs/fill/getTargetDictionary.cjs.map +1 -0
  15. package/dist/cjs/fill/index.cjs +257 -0
  16. package/dist/cjs/fill/index.cjs.map +1 -0
  17. package/dist/cjs/index.cjs +4 -2
  18. package/dist/cjs/index.cjs.map +1 -1
  19. package/dist/cjs/listContentDeclaration.cjs +37 -19
  20. package/dist/cjs/listContentDeclaration.cjs.map +1 -1
  21. package/dist/cjs/liveSync.cjs +254 -0
  22. package/dist/cjs/liveSync.cjs.map +1 -0
  23. package/dist/cjs/pull.cjs +119 -117
  24. package/dist/cjs/pull.cjs.map +1 -1
  25. package/dist/cjs/pullLog.cjs +146 -0
  26. package/dist/cjs/pullLog.cjs.map +1 -0
  27. package/dist/cjs/push.cjs +74 -88
  28. package/dist/cjs/push.cjs.map +1 -1
  29. package/dist/cjs/pushConfig.cjs +10 -25
  30. package/dist/cjs/pushConfig.cjs.map +1 -1
  31. package/dist/cjs/pushLog.cjs +130 -0
  32. package/dist/cjs/pushLog.cjs.map +1 -0
  33. package/dist/cjs/reviewDoc.cjs +45 -36
  34. package/dist/cjs/reviewDoc.cjs.map +1 -1
  35. package/dist/cjs/test/index.cjs +91 -0
  36. package/dist/cjs/test/index.cjs.map +1 -0
  37. package/dist/cjs/test/listMissingTranslations.cjs +73 -0
  38. package/dist/cjs/test/listMissingTranslations.cjs.map +1 -0
  39. package/dist/cjs/translateDoc.cjs +42 -34
  40. package/dist/cjs/translateDoc.cjs.map +1 -1
  41. package/dist/cjs/utils/checkAIAccess.cjs +5 -1
  42. package/dist/cjs/utils/checkAIAccess.cjs.map +1 -1
  43. package/dist/cjs/utils/chunkInference.cjs +7 -14
  44. package/dist/cjs/utils/chunkInference.cjs.map +1 -1
  45. package/dist/esm/IntlayerEventListener.mjs +207 -0
  46. package/dist/esm/IntlayerEventListener.mjs.map +1 -0
  47. package/dist/esm/cli.mjs +26 -4
  48. package/dist/esm/cli.mjs.map +1 -1
  49. package/dist/esm/config.mjs +5 -1
  50. package/dist/esm/config.mjs.map +1 -1
  51. package/dist/esm/fill/autoFill.mjs +92 -0
  52. package/dist/esm/fill/autoFill.mjs.map +1 -0
  53. package/dist/esm/fill/formatAutoFillData.mjs +84 -0
  54. package/dist/esm/fill/formatAutoFillData.mjs.map +1 -0
  55. package/dist/esm/fill/formatAutoFilledFilePath.mjs +22 -0
  56. package/dist/esm/fill/formatAutoFilledFilePath.mjs.map +1 -0
  57. package/dist/esm/fill/getTargetDictionary.mjs +51 -0
  58. package/dist/esm/fill/getTargetDictionary.mjs.map +1 -0
  59. package/dist/esm/fill/index.mjs +240 -0
  60. package/dist/esm/fill/index.mjs.map +1 -0
  61. package/dist/esm/index.mjs +2 -1
  62. package/dist/esm/index.mjs.map +1 -1
  63. package/dist/esm/listContentDeclaration.mjs +38 -17
  64. package/dist/esm/listContentDeclaration.mjs.map +1 -1
  65. package/dist/esm/liveSync.mjs +220 -0
  66. package/dist/esm/liveSync.mjs.map +1 -0
  67. package/dist/esm/pull.mjs +123 -108
  68. package/dist/esm/pull.mjs.map +1 -1
  69. package/dist/esm/pullLog.mjs +127 -0
  70. package/dist/esm/pullLog.mjs.map +1 -0
  71. package/dist/esm/push.mjs +81 -90
  72. package/dist/esm/push.mjs.map +1 -1
  73. package/dist/esm/pushConfig.mjs +11 -26
  74. package/dist/esm/pushConfig.mjs.map +1 -1
  75. package/dist/esm/pushLog.mjs +111 -0
  76. package/dist/esm/pushLog.mjs.map +1 -0
  77. package/dist/esm/reviewDoc.mjs +55 -38
  78. package/dist/esm/reviewDoc.mjs.map +1 -1
  79. package/dist/esm/test/index.mjs +74 -0
  80. package/dist/esm/test/index.mjs.map +1 -0
  81. package/dist/esm/test/listMissingTranslations.mjs +41 -0
  82. package/dist/esm/test/listMissingTranslations.mjs.map +1 -0
  83. package/dist/esm/translateDoc.mjs +52 -37
  84. package/dist/esm/translateDoc.mjs.map +1 -1
  85. package/dist/esm/utils/checkAIAccess.mjs +5 -1
  86. package/dist/esm/utils/checkAIAccess.mjs.map +1 -1
  87. package/dist/esm/utils/chunkInference.mjs +14 -16
  88. package/dist/esm/utils/chunkInference.mjs.map +1 -1
  89. package/dist/types/IntlayerEventListener.d.ts +85 -0
  90. package/dist/types/IntlayerEventListener.d.ts.map +1 -0
  91. package/dist/types/cli.d.ts.map +1 -1
  92. package/dist/types/config.d.ts.map +1 -1
  93. package/dist/types/fill/autoFill.d.ts +4 -0
  94. package/dist/types/fill/autoFill.d.ts.map +1 -0
  95. package/dist/types/fill/formatAutoFillData.d.ts +9 -0
  96. package/dist/types/fill/formatAutoFillData.d.ts.map +1 -0
  97. package/dist/types/fill/formatAutoFilledFilePath.d.ts +3 -0
  98. package/dist/types/fill/formatAutoFilledFilePath.d.ts.map +1 -0
  99. package/dist/types/fill/getTargetDictionary.d.ts +4 -0
  100. package/dist/types/fill/getTargetDictionary.d.ts.map +1 -0
  101. package/dist/types/{fill.d.ts → fill/index.d.ts} +2 -5
  102. package/dist/types/fill/index.d.ts.map +1 -0
  103. package/dist/types/index.d.ts +1 -0
  104. package/dist/types/index.d.ts.map +1 -1
  105. package/dist/types/listContentDeclaration.d.ts +4 -5
  106. package/dist/types/listContentDeclaration.d.ts.map +1 -1
  107. package/dist/types/liveSync.d.ts +6 -0
  108. package/dist/types/liveSync.d.ts.map +1 -0
  109. package/dist/types/pull.d.ts.map +1 -1
  110. package/dist/types/pullLog.d.ts +24 -0
  111. package/dist/types/pullLog.d.ts.map +1 -0
  112. package/dist/types/push.d.ts +1 -1
  113. package/dist/types/push.d.ts.map +1 -1
  114. package/dist/types/pushConfig.d.ts +0 -1
  115. package/dist/types/pushConfig.d.ts.map +1 -1
  116. package/dist/types/pushLog.d.ts +23 -0
  117. package/dist/types/pushLog.d.ts.map +1 -0
  118. package/dist/types/reviewDoc.d.ts +1 -1
  119. package/dist/types/reviewDoc.d.ts.map +1 -1
  120. package/dist/types/test/index.d.ts +8 -0
  121. package/dist/types/test/index.d.ts.map +1 -0
  122. package/dist/types/test/listMissingTranslations.d.ts +12 -0
  123. package/dist/types/test/listMissingTranslations.d.ts.map +1 -0
  124. package/dist/types/translateDoc.d.ts +1 -1
  125. package/dist/types/translateDoc.d.ts.map +1 -1
  126. package/dist/types/utils/checkAIAccess.d.ts.map +1 -1
  127. package/dist/types/utils/chunkInference.d.ts +2 -1
  128. package/dist/types/utils/chunkInference.d.ts.map +1 -1
  129. package/package.json +19 -15
  130. package/dist/cjs/fill.cjs +0 -405
  131. package/dist/cjs/fill.cjs.map +0 -1
  132. package/dist/esm/fill.mjs +0 -385
  133. package/dist/esm/fill.mjs.map +0 -1
  134. package/dist/types/fill.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/listContentDeclaration.ts"],"sourcesContent":["import {\n getAppLogger,\n getConfiguration,\n type GetConfigurationOptions,\n} from '@intlayer/config';\nimport fg from 'fast-glob';\n\ntype GetContentDeclarationOptions = {\n exclude?: string[];\n configOptions?: GetConfigurationOptions;\n};\n\nexport const getContentDeclaration = (\n options?: GetContentDeclarationOptions\n): string[] => {\n const {\n content: { watchedFilesPatternWithPath },\n } = getConfiguration(options?.configOptions);\n\n const contentDeclarationFilesPath: string[] = fg.sync(\n watchedFilesPatternWithPath,\n {\n ignore: options?.exclude,\n }\n );\n\n return contentDeclarationFilesPath;\n};\n\ntype ListContentDeclarationOptions = {\n configOptions?: GetConfigurationOptions;\n};\n\nexport const listContentDeclaration = (\n options?: ListContentDeclarationOptions\n) => {\n const contentDeclarationFilesPath = getContentDeclaration(options);\n\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n appLogger([contentDeclarationFilesPath]);\n};\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,OAAO,QAAQ;AAOR,MAAM,wBAAwB,CACnC,YACa;AACb,QAAM;AAAA,IACJ,SAAS,EAAE,4BAA4B;AAAA,EACzC,IAAI,iBAAiB,SAAS,aAAa;AAE3C,QAAM,8BAAwC,GAAG;AAAA,IAC/C;AAAA,IACA;AAAA,MACE,QAAQ,SAAS;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AACT;AAMO,MAAM,yBAAyB,CACpC,YACG;AACH,QAAM,8BAA8B,sBAAsB,OAAO;AAEjE,QAAM,SAAS,iBAAiB,SAAS,aAAa;AACtD,QAAM,YAAY,aAAa,MAAM;AAErC,YAAU,CAAC,2BAA2B,CAAC;AACzC;","names":[]}
1
+ {"version":3,"sources":["../../src/listContentDeclaration.ts"],"sourcesContent":["import { formatPath } from '@intlayer/chokidar';\nimport {\n colon,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n getConfiguration,\n type GetConfigurationOptions,\n} from '@intlayer/config';\nimport unmergedDictionariesRecord from '@intlayer/unmerged-dictionaries-entry';\nimport { relative } from 'path';\n\ntype ListContentDeclarationOptions = {\n configOptions?: GetConfigurationOptions;\n};\n\nexport const listContentDeclarationRows = (\n options?: ListContentDeclarationOptions\n) => {\n const config = getConfiguration(options?.configOptions);\n\n const rows = Object.values(unmergedDictionariesRecord)\n .flat()\n .map((dictionary) => ({\n key: dictionary.key ?? '',\n path: relative(config.content.baseDir, dictionary.filePath ?? 'Remote'),\n }));\n return rows;\n};\n\nexport const listContentDeclaration = (\n options?: ListContentDeclarationOptions\n) => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config, {\n config: {\n prefix: '',\n },\n });\n\n const rows = listContentDeclarationRows(options);\n\n const lines = rows.map((r) =>\n [\n colon(` - ${colorizeKey(r.key)}`, {\n colSize: rows.map((r) => r.key.length),\n maxSize: 60,\n }),\n ' - ',\n formatPath(r.path),\n ].join('')\n );\n\n appLogger(`Content declaration files:`);\n\n lines.forEach((l) => {\n appLogger(l, {\n level: 'info',\n });\n });\n\n appLogger(`Total content declaration files: ${colorizeNumber(rows.length)}`);\n};\n"],"mappings":"AAAA,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,OAAO,gCAAgC;AACvC,SAAS,gBAAgB;AAMlB,MAAM,6BAA6B,CACxC,YACG;AACH,QAAM,SAAS,iBAAiB,SAAS,aAAa;AAEtD,QAAM,OAAO,OAAO,OAAO,0BAA0B,EAClD,KAAK,EACL,IAAI,CAAC,gBAAgB;AAAA,IACpB,KAAK,WAAW,OAAO;AAAA,IACvB,MAAM,SAAS,OAAO,QAAQ,SAAS,WAAW,YAAY,QAAQ;AAAA,EACxE,EAAE;AACJ,SAAO;AACT;AAEO,MAAM,yBAAyB,CACpC,YACG;AACH,QAAM,SAAS,iBAAiB,SAAS,aAAa;AACtD,QAAM,YAAY,aAAa,QAAQ;AAAA,IACrC,QAAQ;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF,CAAC;AAED,QAAM,OAAO,2BAA2B,OAAO;AAE/C,QAAM,QAAQ,KAAK;AAAA,IAAI,CAAC,MACtB;AAAA,MACE,MAAM,MAAM,YAAY,EAAE,GAAG,CAAC,IAAI;AAAA,QAChC,SAAS,KAAK,IAAI,CAACA,OAAMA,GAAE,IAAI,MAAM;AAAA,QACrC,SAAS;AAAA,MACX,CAAC;AAAA,MACD;AAAA,MACA,WAAW,EAAE,IAAI;AAAA,IACnB,EAAE,KAAK,EAAE;AAAA,EACX;AAEA,YAAU,4BAA4B;AAEtC,QAAM,QAAQ,CAAC,MAAM;AACnB,cAAU,GAAG;AAAA,MACX,OAAO;AAAA,IACT,CAAC;AAAA,EACH,CAAC;AAED,YAAU,oCAAoC,eAAe,KAAK,MAAM,CAAC,EAAE;AAC7E;","names":["r"]}
@@ -0,0 +1,220 @@
1
+ import { IntlayerEventListener } from "./IntlayerEventListener.mjs";
2
+ import { buildDictionary } from "@intlayer/chokidar";
3
+ import { getAppLogger, getConfiguration } from "@intlayer/config";
4
+ import packageJson from "@intlayer/config/package.json";
5
+ import { getLocalisedContent } from "@intlayer/core";
6
+ import { getDictionaries } from "@intlayer/dictionaries-entry";
7
+ import { getUnmergedDictionaries } from "@intlayer/unmerged-dictionaries-entry";
8
+ import { spawn } from "child_process";
9
+ import { createServer } from "http";
10
+ const writeDictionary = async (dictionary, configuration) => {
11
+ const appLogger = getAppLogger(configuration);
12
+ appLogger(`Writing dictionary ${dictionary.key}`);
13
+ await buildDictionary([dictionary], configuration);
14
+ };
15
+ const liveSync = async (options) => {
16
+ const configuration = getConfiguration();
17
+ const appLogger = getAppLogger(configuration, {
18
+ config: {
19
+ prefix: ""
20
+ }
21
+ });
22
+ const { liveSyncPort, liveSyncURL } = configuration.editor;
23
+ let childProcess = null;
24
+ let eventListener = null;
25
+ let isHotReloadConnected = false;
26
+ let connectionStatus = "disconnected";
27
+ if (options?.process) {
28
+ const [command, ...args] = options.process.split(" ");
29
+ childProcess = spawn(command, args, {
30
+ stdio: "inherit",
31
+ shell: true
32
+ });
33
+ childProcess.on("error", (error) => {
34
+ appLogger(`Failed to start process '${options.process}':`, {
35
+ level: "error"
36
+ });
37
+ });
38
+ childProcess.on("exit", (code) => {
39
+ if (code !== 0) {
40
+ appLogger(`Process "${options.process}" exited with code ${code}`);
41
+ } else {
42
+ appLogger(`Process "${options.process}" exited successfully`);
43
+ }
44
+ });
45
+ }
46
+ if (configuration.editor.liveSync && configuration.editor.backendURL && configuration.editor.clientId && configuration.editor.clientSecret) {
47
+ eventListener = new IntlayerEventListener(configuration);
48
+ connectionStatus = "connecting";
49
+ eventListener.onConnectionOpen = () => {
50
+ connectionStatus = "connected";
51
+ isHotReloadConnected = true;
52
+ appLogger("Live sync connection established");
53
+ };
54
+ eventListener.onConnectionError = (error) => {
55
+ connectionStatus = "error";
56
+ isHotReloadConnected = false;
57
+ const errorEvent = error;
58
+ appLogger(
59
+ `Live sync connection error: ${errorEvent.message ?? "Unknown error"}`,
60
+ {
61
+ level: "warn"
62
+ }
63
+ );
64
+ if (errorEvent.message?.includes("terminated") || errorEvent.message?.includes("closed")) {
65
+ appLogger(
66
+ "Server connection was terminated, automatic reconnection will be attempted...",
67
+ {
68
+ level: "info"
69
+ }
70
+ );
71
+ connectionStatus = "reconnecting";
72
+ }
73
+ };
74
+ eventListener.onDictionaryAdded = (dictionary) => writeDictionary(dictionary, configuration);
75
+ eventListener.onDictionaryChange = (dictionary) => writeDictionary(dictionary, configuration);
76
+ eventListener.onDictionaryDeleted = (dictionary) => writeDictionary(dictionary, configuration);
77
+ try {
78
+ await eventListener.initialize();
79
+ } catch (error) {
80
+ connectionStatus = "error";
81
+ isHotReloadConnected = false;
82
+ appLogger("Failed to initialize IntlayerEventListener:", {
83
+ level: "error"
84
+ });
85
+ appLogger(
86
+ `Error: ${error instanceof Error ? error.message : String(error)}`,
87
+ {
88
+ level: "error"
89
+ }
90
+ );
91
+ }
92
+ } else if (!configuration.editor.liveSync) {
93
+ appLogger(
94
+ "Hot reload is disabled. Please enable it in the configuration (editor.liveSync)."
95
+ );
96
+ } else if (!configuration.editor.clientId || !configuration.editor.clientSecret) {
97
+ appLogger(
98
+ "Missing client credentials for hot reload. Please configure clientId and clientSecret"
99
+ );
100
+ }
101
+ const server = createServer(async (req, res) => {
102
+ if (req.method === "OPTIONS") {
103
+ res.writeHead(200, {
104
+ "Access-Control-Allow-Origin": "*",
105
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
106
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
107
+ });
108
+ res.end();
109
+ return;
110
+ }
111
+ if (req.url?.startsWith("/dictionaries")) {
112
+ res.writeHead(200, {
113
+ "Content-Type": "application/json; charset=utf-8",
114
+ "Cache-Control": "no-store",
115
+ "Access-Control-Allow-Origin": "*",
116
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
117
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
118
+ });
119
+ const dictionaries = getDictionaries();
120
+ const prefix = "/dictionaries/";
121
+ if (req.url.startsWith(prefix)) {
122
+ const [key, locale] = decodeURIComponent(req.url).slice(prefix.length).split("/");
123
+ const dictionary = dictionaries[key] ?? null;
124
+ if (locale) {
125
+ const sourceLocaleContent = getLocalisedContent(dictionary, locale, {
126
+ dictionaryKey: key,
127
+ keyPath: []
128
+ });
129
+ res.end(JSON.stringify(sourceLocaleContent));
130
+ return;
131
+ }
132
+ res.end(JSON.stringify(dictionary));
133
+ return;
134
+ }
135
+ res.end(JSON.stringify(dictionaries));
136
+ return;
137
+ }
138
+ if (req.url?.startsWith("/unmerged_dictionaries")) {
139
+ res.writeHead(200, {
140
+ "Content-Type": "application/json; charset=utf-8",
141
+ "Cache-Control": "no-store",
142
+ "Access-Control-Allow-Origin": "*",
143
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
144
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
145
+ });
146
+ const unmergedDictionaries = getUnmergedDictionaries();
147
+ const prefix = "/unmerged_dictionaries/";
148
+ if (req.url.startsWith(prefix)) {
149
+ const key = decodeURIComponent(req.url.slice(prefix.length));
150
+ const one = unmergedDictionaries[key] ?? null;
151
+ res.end(JSON.stringify(one));
152
+ return;
153
+ }
154
+ res.end(JSON.stringify(unmergedDictionaries));
155
+ return;
156
+ }
157
+ if (req.url === "/configuration") {
158
+ res.writeHead(200, {
159
+ "Content-Type": "application/json; charset=utf-8",
160
+ "Cache-Control": "no-store",
161
+ "Access-Control-Allow-Origin": "*",
162
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
163
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
164
+ });
165
+ res.end(JSON.stringify(configuration));
166
+ return;
167
+ }
168
+ if (req.url === "/health") {
169
+ res.writeHead(200, {
170
+ "Content-Type": "application/json; charset=utf-8"
171
+ });
172
+ res.end(JSON.stringify({ status: "ok" }));
173
+ return;
174
+ }
175
+ res.end("Not found");
176
+ return;
177
+ });
178
+ const getLiveSyncParam = () => {
179
+ if (!configuration.editor.liveSync) return "\x1B[31m\u2717 Disabled\x1B[0m";
180
+ return "\x1B[32m\u2713 Enabled\x1B[0m";
181
+ };
182
+ server.listen(liveSyncPort, () => {
183
+ console.log(`
184
+ \x1B[1;90mINTLAYER v${packageJson.version}\x1B[0m
185
+
186
+ Live server running at: \x1B[90m${liveSyncURL}\x1B[0m
187
+ - Backend URL: \x1B[90m${configuration.editor.backendURL ?? "-"}\x1B[0m
188
+ - Live sync: ${getLiveSyncParam()}
189
+ - Parallel process: ${options?.process === "" ? "-" : `\x1B[90m${options?.process}\x1B[0m`}
190
+ - Access key: ${configuration.editor.clientId ?? "-"}
191
+ `);
192
+ });
193
+ const cleanup = () => {
194
+ if (eventListener) {
195
+ appLogger("Closing SSE connection...");
196
+ eventListener.cleanup();
197
+ }
198
+ if (childProcess && !childProcess.killed) {
199
+ appLogger("Terminating parallel process...");
200
+ childProcess.kill("SIGTERM");
201
+ setTimeout(() => {
202
+ if (childProcess && !childProcess.killed) {
203
+ appLogger("Force killing parallel process...");
204
+ childProcess.kill("SIGKILL");
205
+ }
206
+ }, 5e3);
207
+ }
208
+ server.close(() => {
209
+ appLogger("Live sync server stopped");
210
+ process.exit(0);
211
+ });
212
+ };
213
+ process.on("SIGINT", cleanup);
214
+ process.on("SIGTERM", cleanup);
215
+ process.on("exit", cleanup);
216
+ };
217
+ export {
218
+ liveSync
219
+ };
220
+ //# sourceMappingURL=liveSync.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/liveSync.ts"],"sourcesContent":["import { IntlayerEventListener } from './IntlayerEventListener';\n// @ts-ignore: @intlayer/backend is not built yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport { buildDictionary } from '@intlayer/chokidar';\nimport type { IntlayerConfig } from '@intlayer/config';\nimport { getAppLogger, getConfiguration } from '@intlayer/config';\nimport packageJson from '@intlayer/config/package.json';\nimport { getLocalisedContent } from '@intlayer/core';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { ChildProcess, spawn } from 'child_process';\nimport { createServer } from 'http';\n\ntype LiveSyncOptions = {\n process?: string;\n};\n\nconst writeDictionary = async (\n dictionary: DictionaryAPI,\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n appLogger(`Writing dictionary ${dictionary.key}`);\n await buildDictionary([dictionary], configuration);\n};\n\nexport const liveSync = async (options?: LiveSyncOptions) => {\n const configuration = getConfiguration();\n const appLogger = getAppLogger(configuration, {\n config: {\n prefix: '',\n },\n });\n\n const { liveSyncPort, liveSyncURL } = configuration.editor;\n\n let childProcess: ChildProcess | null = null;\n let eventListener: IntlayerEventListener | null = null;\n let isHotReloadConnected = false;\n let connectionStatus = 'disconnected'; // 'connected', 'connecting', 'reconnecting', 'disconnected', 'error'\n\n // Start the parallel process if provided\n if (options?.process) {\n const [command, ...args] = options.process.split(' ');\n\n childProcess = spawn(command, args, {\n stdio: 'inherit',\n shell: true,\n });\n\n childProcess.on('error', (error) => {\n appLogger(`Failed to start process '${options.process}':`, {\n level: 'error',\n });\n });\n\n childProcess.on('exit', (code) => {\n if (code !== 0) {\n appLogger(`Process \"${options.process}\" exited with code ${code}`);\n } else {\n appLogger(`Process \"${options.process}\" exited successfully`);\n }\n });\n }\n\n // Initialize the event listener for hot reload if configured\n if (\n configuration.editor.liveSync &&\n configuration.editor.backendURL &&\n configuration.editor.clientId &&\n configuration.editor.clientSecret\n ) {\n eventListener = new IntlayerEventListener(configuration);\n connectionStatus = 'connecting';\n\n // Set up connection callbacks\n eventListener.onConnectionOpen = () => {\n connectionStatus = 'connected';\n isHotReloadConnected = true;\n appLogger('Live sync connection established');\n };\n\n eventListener.onConnectionError = (error) => {\n connectionStatus = 'error';\n isHotReloadConnected = false;\n const errorEvent = error as any;\n appLogger(\n `Live sync connection error: ${errorEvent.message ?? 'Unknown error'}`,\n {\n level: 'warn',\n }\n );\n\n // If this is a \"terminated: other side closed\" error, it's likely a server restart\n if (\n errorEvent.message?.includes('terminated') ||\n errorEvent.message?.includes('closed')\n ) {\n appLogger(\n 'Server connection was terminated, automatic reconnection will be attempted...',\n {\n level: 'info',\n }\n );\n connectionStatus = 'reconnecting';\n }\n };\n\n // Set up dictionary change callbacks\n eventListener.onDictionaryAdded = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryChange = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryDeleted = (dictionary) =>\n writeDictionary(dictionary, configuration);\n\n try {\n await eventListener.initialize();\n } catch (error) {\n connectionStatus = 'error';\n isHotReloadConnected = false;\n appLogger('Failed to initialize IntlayerEventListener:', {\n level: 'error',\n });\n appLogger(\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n {\n level: 'error',\n }\n );\n }\n } else if (!configuration.editor.liveSync) {\n appLogger(\n 'Hot reload is disabled. Please enable it in the configuration (editor.liveSync).'\n );\n } else if (\n !configuration.editor.clientId ||\n !configuration.editor.clientSecret\n ) {\n appLogger(\n 'Missing client credentials for hot reload. Please configure clientId and clientSecret'\n );\n }\n\n const server = createServer(async (req, res) => {\n // Handle CORS preflight requests\n if (req.method === 'OPTIONS') {\n res.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n\n res.end();\n return;\n }\n\n if (req.url?.startsWith('/dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const dictionaries = getDictionaries();\n\n const prefix = '/dictionaries/';\n if (req.url.startsWith(prefix)) {\n const [key, locale] = decodeURIComponent(req.url)\n .slice(prefix.length)\n .split('/');\n\n const dictionary = dictionaries[key] ?? null;\n\n if (locale) {\n const sourceLocaleContent = getLocalisedContent(dictionary, locale, {\n dictionaryKey: key,\n keyPath: [],\n });\n\n res.end(JSON.stringify(sourceLocaleContent));\n return;\n }\n\n res.end(JSON.stringify(dictionary));\n return;\n }\n\n res.end(JSON.stringify(dictionaries));\n return;\n }\n\n if (req.url?.startsWith('/unmerged_dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const unmergedDictionaries = getUnmergedDictionaries();\n\n const prefix = '/unmerged_dictionaries/';\n if (req.url.startsWith(prefix)) {\n const key = decodeURIComponent(req.url.slice(prefix.length));\n const one = unmergedDictionaries[key] ?? null;\n\n res.end(JSON.stringify(one));\n return;\n }\n\n res.end(JSON.stringify(unmergedDictionaries));\n return;\n }\n\n if (req.url === '/configuration') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n res.end(JSON.stringify(configuration));\n return;\n }\n\n if (req.url === '/health') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n });\n res.end(JSON.stringify({ status: 'ok' }));\n return;\n }\n\n res.end('Not found');\n return;\n });\n\n const getLiveSyncParam = () => {\n if (!configuration.editor.liveSync) return '\\x1b[31m✗ Disabled\\x1b[0m';\n\n return '\\x1b[32m✓ Enabled\\x1b[0m';\n };\n server.listen(liveSyncPort, () => {\n console.log(`\n \\x1b[1;90mINTLAYER v${packageJson.version}\\x1b[0m\n \n Live server running at: \\x1b[90m${liveSyncURL}\\x1b[0m\n - Backend URL: \\x1b[90m${configuration.editor.backendURL ?? '-'}\\x1b[0m\n - Live sync: ${getLiveSyncParam()}\n - Parallel process: ${options?.process === '' ? '-' : `\\x1b[90m${options?.process}\\x1b[0m`}\n - Access key: ${configuration.editor.clientId ?? '-'}\n `);\n });\n\n // Cleanup function to terminate child process and event listener when the main process exits\n const cleanup = () => {\n // Clean up event listener\n if (eventListener) {\n appLogger('Closing SSE connection...');\n eventListener.cleanup();\n }\n\n // Clean up child process\n if (childProcess && !childProcess.killed) {\n appLogger('Terminating parallel process...');\n childProcess.kill('SIGTERM');\n\n // Force kill after 5 seconds if process doesn't terminate gracefully\n setTimeout(() => {\n if (childProcess && !childProcess.killed) {\n appLogger('Force killing parallel process...');\n childProcess.kill('SIGKILL');\n }\n }, 5000);\n }\n\n server.close(() => {\n appLogger('Live sync server stopped');\n process.exit(0);\n });\n };\n\n // Handle process termination signals\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n process.on('exit', cleanup);\n};\n"],"mappings":"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","names":[]}
package/dist/esm/pull.mjs CHANGED
@@ -1,23 +1,23 @@
1
- import { getIntlayerAPI } from "@intlayer/api";
1
+ import { getIntlayerAPIProxy } from "@intlayer/api";
2
2
  import {
3
+ parallelize,
3
4
  writeContentDeclaration
4
5
  } from "@intlayer/chokidar";
5
6
  import {
7
+ ANSIColors,
8
+ ESMxCJSRequire,
6
9
  getAppLogger,
7
10
  getConfiguration
8
11
  } from "@intlayer/config";
9
- import pLimit from "p-limit";
10
- import * as readline from "readline";
11
- const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
12
- const RESET = "\x1B[0m";
13
- const GREEN = "\x1B[32m";
14
- const RED = "\x1B[31m";
15
- const BLUE = "\x1B[34m";
16
- const GREY = "\x1B[90m";
17
- const YELLOW = "\x1B[33m";
18
- const GREY_DARK = "\x1B[90m";
12
+ import { existsSync } from "fs";
13
+ import { join } from "path";
14
+ import { PullLogger } from "./pullLog.mjs";
19
15
  const pull = async (options) => {
20
- const appLogger = getAppLogger(options?.configOptions?.override);
16
+ const appLogger = getAppLogger(options?.configOptions?.override, {
17
+ config: {
18
+ prefix: ""
19
+ }
20
+ });
21
21
  try {
22
22
  const config = getConfiguration(options?.configOptions);
23
23
  const { clientId, clientSecret } = config.editor;
@@ -26,86 +26,146 @@ const pull = async (options) => {
26
26
  "Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project."
27
27
  );
28
28
  }
29
- const intlayerAPI = getIntlayerAPI(void 0, config);
30
- const oAuth2TokenResult = await intlayerAPI.oAuth.getOAuth2AccessToken();
31
- const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;
32
- const getDictionariesKeysResult = await intlayerAPI.dictionary.getDictionariesKeys({
33
- ...oAuth2AccessToken && {
34
- headers: {
35
- Authorization: `Bearer ${oAuth2AccessToken}`
36
- }
37
- }
38
- });
39
- if (!getDictionariesKeysResult.data) {
29
+ const intlayerAPI = getIntlayerAPIProxy(void 0, config);
30
+ const getDictionariesUpdateTimestampResult = await intlayerAPI.dictionary.getDictionariesUpdateTimestamp();
31
+ if (!getDictionariesUpdateTimestampResult.data) {
40
32
  throw new Error("No distant dictionaries found");
41
33
  }
42
- let distantDictionariesKeys = getDictionariesKeysResult.data;
34
+ let distantDictionariesUpdateTimeStamp = getDictionariesUpdateTimestampResult.data;
43
35
  if (options?.dictionaries) {
44
- distantDictionariesKeys = distantDictionariesKeys.filter(
45
- (dictionaryKey) => options.dictionaries.includes(dictionaryKey)
36
+ distantDictionariesUpdateTimeStamp = Object.fromEntries(
37
+ Object.entries(distantDictionariesUpdateTimeStamp).filter(
38
+ ([key]) => options.dictionaries.includes(key)
39
+ )
46
40
  );
47
41
  }
48
- if (distantDictionariesKeys.length === 0) {
42
+ const remoteDictionariesPath = join(
43
+ config.content.mainDir,
44
+ "remote_dictionaries.cjs"
45
+ );
46
+ const remoteDictionariesRecord = existsSync(
47
+ remoteDictionariesPath
48
+ ) ? ESMxCJSRequire(remoteDictionariesPath) : {};
49
+ const entries = Object.entries(distantDictionariesUpdateTimeStamp);
50
+ const keysToFetch = entries.filter(([key, remoteUpdatedAt]) => {
51
+ if (!remoteUpdatedAt) return true;
52
+ const local = remoteDictionariesRecord[key];
53
+ if (!local) return true;
54
+ const localUpdatedAtRaw = local?.updatedAt;
55
+ const localUpdatedAt = typeof localUpdatedAtRaw === "number" ? localUpdatedAtRaw : localUpdatedAtRaw ? new Date(localUpdatedAtRaw).getTime() : void 0;
56
+ if (typeof localUpdatedAt !== "number") return true;
57
+ return remoteUpdatedAt > localUpdatedAt;
58
+ }).map(([key]) => key);
59
+ const cachedKeys = entries.filter(([key, remoteUpdatedAt]) => {
60
+ const local = remoteDictionariesRecord[key];
61
+ const localUpdatedAtRaw = local?.updatedAt;
62
+ const localUpdatedAt = typeof localUpdatedAtRaw === "number" ? localUpdatedAtRaw : localUpdatedAtRaw ? new Date(localUpdatedAtRaw).getTime() : void 0;
63
+ return typeof localUpdatedAt === "number" && typeof remoteUpdatedAt === "number" && localUpdatedAt >= remoteUpdatedAt;
64
+ }).map(([key]) => key);
65
+ if (entries.length === 0) {
49
66
  appLogger("No dictionaries to fetch", {
50
67
  level: "error"
51
68
  });
52
69
  return;
53
70
  }
54
71
  appLogger("Fetching dictionaries:");
55
- const dictionariesStatuses = distantDictionariesKeys.map((dictionaryKey, index) => ({
56
- dictionaryKey,
57
- icon: getStatusIcon("pending"),
58
- status: "pending",
59
- index,
60
- spinnerFrameIndex: 0
61
- }));
62
- for (const statusObj of dictionariesStatuses) {
63
- process.stdout.write(getStatusLine(statusObj) + "\n");
64
- }
65
- const spinnerTimer = setInterval(() => {
66
- updateAllStatusLines(dictionariesStatuses);
67
- }, 100);
68
- const limit = pLimit(5);
72
+ const dictionariesStatuses = [
73
+ ...cachedKeys.map((dictionaryKey) => ({
74
+ dictionaryKey,
75
+ status: "imported"
76
+ })),
77
+ ...keysToFetch.map((dictionaryKey) => ({
78
+ dictionaryKey,
79
+ status: "pending"
80
+ }))
81
+ ];
82
+ const logger = new PullLogger();
83
+ logger.update(
84
+ dictionariesStatuses.map((s) => ({
85
+ dictionaryKey: s.dictionaryKey,
86
+ status: s.status
87
+ }))
88
+ );
69
89
  const successfullyFetchedDictionaries = [];
70
90
  const processDictionary = async (statusObj) => {
71
- statusObj.status = "fetching";
91
+ const isCached = statusObj.status === "imported" || statusObj.status === "up-to-date";
92
+ if (!isCached) {
93
+ statusObj.status = "fetching";
94
+ logger.update([
95
+ { dictionaryKey: statusObj.dictionaryKey, status: "fetching" }
96
+ ]);
97
+ }
72
98
  try {
73
- const getDictionaryResult = await intlayerAPI.dictionary.getDictionary(
74
- statusObj.dictionaryKey,
75
- void 0,
76
- {
77
- ...oAuth2AccessToken && {
78
- headers: {
79
- Authorization: `Bearer ${oAuth2AccessToken}`
80
- }
81
- }
82
- }
83
- );
84
- const distantDictionary = getDictionaryResult.data;
85
- if (!distantDictionary) {
99
+ let sourceDictionary;
100
+ if (isCached) {
101
+ sourceDictionary = remoteDictionariesRecord[statusObj.dictionaryKey];
102
+ }
103
+ if (!sourceDictionary) {
104
+ const getDictionaryResult = await intlayerAPI.dictionary.getDictionary(statusObj.dictionaryKey);
105
+ sourceDictionary = getDictionaryResult.data;
106
+ }
107
+ if (!sourceDictionary) {
86
108
  throw new Error(
87
109
  `Dictionary ${statusObj.dictionaryKey} not found on remote`
88
110
  );
89
111
  }
90
112
  const { status } = await writeContentDeclaration(
91
- distantDictionary,
113
+ sourceDictionary,
92
114
  config,
93
115
  options?.newDictionariesPath
94
116
  );
95
117
  statusObj.status = status;
96
- successfullyFetchedDictionaries.push(distantDictionary);
118
+ logger.update([{ dictionaryKey: statusObj.dictionaryKey, status }]);
119
+ successfullyFetchedDictionaries.push(sourceDictionary);
97
120
  } catch (error) {
98
121
  statusObj.status = "error";
99
122
  statusObj.error = error;
100
123
  statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;
124
+ logger.update([
125
+ { dictionaryKey: statusObj.dictionaryKey, status: "error" }
126
+ ]);
101
127
  }
102
128
  };
103
- const fetchPromises = dictionariesStatuses.map(
104
- (statusObj) => limit(() => processDictionary(statusObj))
105
- );
106
- await Promise.all(fetchPromises);
107
- clearInterval(spinnerTimer);
108
- updateAllStatusLines(dictionariesStatuses);
129
+ await parallelize(dictionariesStatuses, processDictionary, 5);
130
+ logger.finish();
131
+ const iconFor = (status) => {
132
+ switch (status) {
133
+ case "fetched":
134
+ case "imported":
135
+ case "updated":
136
+ case "up-to-date":
137
+ case "reimported in JSON":
138
+ case "new content file":
139
+ return "\u2714";
140
+ case "error":
141
+ return "\u2716";
142
+ default:
143
+ return "\u23F2";
144
+ }
145
+ };
146
+ const colorFor = (status) => {
147
+ switch (status) {
148
+ case "fetched":
149
+ case "imported":
150
+ case "updated":
151
+ case "up-to-date":
152
+ return ANSIColors.GREEN;
153
+ case "reimported in JSON":
154
+ case "new content file":
155
+ return ANSIColors.YELLOW;
156
+ case "error":
157
+ return ANSIColors.RED;
158
+ default:
159
+ return ANSIColors.BLUE;
160
+ }
161
+ };
162
+ for (const s of dictionariesStatuses) {
163
+ const icon = iconFor(s.status);
164
+ const color = colorFor(s.status);
165
+ appLogger(
166
+ ` - ${s.dictionaryKey} ${ANSIColors.GREY}[${color}${icon} ${s.status}${ANSIColors.GREY}]${ANSIColors.RESET}`
167
+ );
168
+ }
109
169
  for (const statusObj of dictionariesStatuses) {
110
170
  if (statusObj.errorMessage) {
111
171
  appLogger(statusObj.errorMessage, {
@@ -119,51 +179,6 @@ const pull = async (options) => {
119
179
  });
120
180
  }
121
181
  };
122
- const getStatusIcon = (status) => {
123
- const statusIcons = {
124
- pending: "\u23F2",
125
- fetching: "",
126
- // Spinner handled separately
127
- "up-to-date": "\u2714",
128
- updated: "\u2714",
129
- fetched: "\u2714",
130
- error: "\u2716"
131
- };
132
- return statusIcons[status] ?? "";
133
- };
134
- const getStatusLine = (statusObj) => {
135
- let icon = getStatusIcon(statusObj.status);
136
- let colorStart = "";
137
- let colorEnd = "";
138
- if (statusObj.status === "fetching") {
139
- icon = spinnerFrames[statusObj.spinnerFrameIndex % spinnerFrames.length];
140
- colorStart = BLUE;
141
- colorEnd = RESET;
142
- } else if (statusObj.status === "error") {
143
- colorStart = RED;
144
- colorEnd = RESET;
145
- } else if (statusObj.status === "fetched" || statusObj.status === "imported" || statusObj.status === "updated" || statusObj.status === "up-to-date") {
146
- colorStart = GREEN;
147
- colorEnd = RESET;
148
- } else if (statusObj.status === "reimported in JSON" || statusObj.status === "reimported in new location") {
149
- colorStart = YELLOW;
150
- colorEnd = RESET;
151
- } else {
152
- colorStart = GREY;
153
- colorEnd = RESET;
154
- }
155
- return `- ${statusObj.dictionaryKey} ${GREY_DARK}[${colorStart}${icon}${statusObj.status}${GREY_DARK}]${colorEnd}`;
156
- };
157
- const updateAllStatusLines = (dictionariesStatuses) => {
158
- readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);
159
- for (const statusObj of dictionariesStatuses) {
160
- readline.clearLine(process.stdout, 0);
161
- if (statusObj.status === "fetching") {
162
- statusObj.spinnerFrameIndex = (statusObj.spinnerFrameIndex + 1) % spinnerFrames.length;
163
- }
164
- process.stdout.write(getStatusLine(statusObj) + "\n");
165
- }
166
- };
167
182
  export {
168
183
  pull
169
184
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pull.ts"],"sourcesContent":["import { getIntlayerAPI } from '@intlayer/api';\nimport {\n writeContentDeclaration,\n type DictionaryStatus,\n} from '@intlayer/chokidar';\nimport {\n getAppLogger,\n getConfiguration,\n GetConfigurationOptions,\n} from '@intlayer/config';\n\nimport type { Dictionary } from '@intlayer/core';\nimport pLimit from 'p-limit';\nimport * as readline from 'readline';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus;\n icon: string;\n index: number;\n error?: Error;\n errorMessage?: string;\n spinnerFrameIndex?: number;\n};\n\nconst spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nconst RESET = '\\x1b[0m';\nconst GREEN = '\\x1b[32m';\nconst RED = '\\x1b[31m';\nconst BLUE = '\\x1b[34m';\nconst GREY = '\\x1b[90m';\nconst YELLOW = '\\x1b[33m';\nconst GREY_DARK = '\\x1b[90m';\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n const appLogger = getAppLogger(options?.configOptions?.override);\n\n try {\n const config = getConfiguration(options?.configOptions);\n const { clientId, clientSecret } = config.editor;\n\n if (!clientId || !clientSecret) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.'\n );\n }\n\n const intlayerAPI = getIntlayerAPI(undefined, config);\n\n const oAuth2TokenResult = await intlayerAPI.oAuth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await intlayerAPI.dictionary.getDictionariesKeys({\n ...(oAuth2AccessToken && {\n headers: {\n Authorization: `Bearer ${oAuth2AccessToken}`,\n },\n }),\n });\n\n if (!getDictionariesKeysResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesKeys: string[] = getDictionariesKeysResult.data;\n\n if (options?.dictionaries) {\n // Filter the dictionaries from the provided list of IDs\n distantDictionariesKeys = distantDictionariesKeys.filter(\n (dictionaryKey) => options.dictionaries!.includes(dictionaryKey)\n );\n }\n\n // Check if dictionaries list is empty\n if (distantDictionariesKeys.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] =\n distantDictionariesKeys.map((dictionaryKey, index) => ({\n dictionaryKey,\n icon: getStatusIcon('pending'),\n status: 'pending',\n index,\n spinnerFrameIndex: 0,\n }));\n\n // Output initial statuses\n for (const statusObj of dictionariesStatuses) {\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n\n // Start spinner timer\n const spinnerTimer = setInterval(() => {\n updateAllStatusLines(dictionariesStatuses);\n }, 100); // Update every 100ms\n\n // Process dictionaries in parallel with a concurrency limit\n const limit = pLimit(5); // Limit the number of concurrent requests\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'fetching';\n try {\n // Fetch the dictionary\n const getDictionaryResult = await intlayerAPI.dictionary.getDictionary(\n statusObj.dictionaryKey,\n undefined,\n {\n ...(oAuth2AccessToken && {\n headers: {\n Authorization: `Bearer ${oAuth2AccessToken}`,\n },\n }),\n }\n );\n\n const distantDictionary = getDictionaryResult.data;\n\n if (!distantDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Now, write the dictionary to local file\n const { status } = await writeContentDeclaration(\n distantDictionary,\n config,\n options?.newDictionariesPath\n );\n\n statusObj.status = status;\n\n successfullyFetchedDictionaries.push(distantDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n }\n };\n\n const fetchPromises = dictionariesStatuses.map((statusObj) =>\n limit(() => processDictionary(statusObj))\n );\n\n await Promise.all(fetchPromises);\n\n // Stop the spinner timer\n clearInterval(spinnerTimer);\n\n // Update statuses one last time\n updateAllStatusLines(dictionariesStatuses);\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n\nconst getStatusIcon = (status: string): string => {\n const statusIcons: Record<string, string> = {\n pending: '⏲',\n fetching: '', // Spinner handled separately\n 'up-to-date': '✔',\n updated: '✔',\n fetched: '✔',\n error: '✖',\n };\n return statusIcons[status] ?? '';\n};\n\nconst getStatusLine = (statusObj: DictionariesStatus): string => {\n let icon = getStatusIcon(statusObj.status);\n let colorStart = '';\n let colorEnd = '';\n\n if (statusObj.status === 'fetching') {\n // Use spinner frame\n icon = spinnerFrames[statusObj.spinnerFrameIndex! % spinnerFrames.length];\n colorStart = BLUE;\n colorEnd = RESET;\n } else if (statusObj.status === 'error') {\n colorStart = RED;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'fetched' ||\n statusObj.status === 'imported' ||\n statusObj.status === 'updated' ||\n statusObj.status === 'up-to-date'\n ) {\n colorStart = GREEN;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'reimported in JSON' ||\n statusObj.status === 'reimported in new location'\n ) {\n colorStart = YELLOW;\n colorEnd = RESET;\n } else {\n colorStart = GREY;\n colorEnd = RESET;\n }\n\n return `- ${statusObj.dictionaryKey} ${GREY_DARK}[${colorStart}${icon}${statusObj.status}${GREY_DARK}]${colorEnd}`;\n};\n\nconst updateAllStatusLines = (dictionariesStatuses: DictionariesStatus[]) => {\n // Move cursor up to the first status line\n readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);\n for (const statusObj of dictionariesStatuses) {\n // Clear the line\n readline.clearLine(process.stdout, 0);\n\n if (statusObj.status === 'fetching') {\n // Update spinner frame\n statusObj.spinnerFrameIndex =\n (statusObj.spinnerFrameIndex! + 1) % spinnerFrames.length;\n }\n\n // Write the status line\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/pull.ts"],"sourcesContent":["import { getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n parallelize,\n writeContentDeclaration,\n type DictionaryStatus,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n ESMxCJSRequire,\n getAppLogger,\n getConfiguration,\n GetConfigurationOptions,\n} from '@intlayer/config';\nimport type { Dictionary } from '@intlayer/core';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { PullLogger, type PullStatus } from './pullLog';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching' | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n const appLogger = getAppLogger(options?.configOptions?.override, {\n config: {\n prefix: '',\n },\n });\n\n try {\n const config = getConfiguration(options?.configOptions);\n const { clientId, clientSecret } = config.editor;\n\n if (!clientId || !clientSecret) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.'\n );\n }\n\n const intlayerAPI = getIntlayerAPIProxy(undefined, config);\n\n // Get remote update timestamps map\n const getDictionariesUpdateTimestampResult =\n await intlayerAPI.dictionary.getDictionariesUpdateTimestamp();\n\n if (!getDictionariesUpdateTimestampResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesUpdateTimeStamp: Record<string, number> =\n getDictionariesUpdateTimestampResult.data;\n\n // Optional filtering by requested dictionaries\n if (options?.dictionaries) {\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) =>\n options.dictionaries!.includes(key)\n )\n );\n }\n\n // Load local cached remote dictionaries (if any)\n const remoteDictionariesPath = join(\n config.content.mainDir,\n 'remote_dictionaries.cjs'\n );\n const remoteDictionariesRecord: Record<string, any> = existsSync(\n remoteDictionariesPath\n )\n ? (ESMxCJSRequire(remoteDictionariesPath) as any)\n : {};\n\n // Determine which keys need fetching by comparing updatedAt with local cache\n const entries = Object.entries(distantDictionariesUpdateTimeStamp);\n const keysToFetch = entries\n .filter(([key, remoteUpdatedAt]) => {\n if (!remoteUpdatedAt) return true;\n const local = (remoteDictionariesRecord as any)[key];\n if (!local) return true;\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n if (typeof localUpdatedAt !== 'number') return true;\n return remoteUpdatedAt > localUpdatedAt;\n })\n .map(([key]) => key);\n\n const cachedKeys = entries\n .filter(([key, remoteUpdatedAt]) => {\n const local = (remoteDictionariesRecord as any)[key];\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n })\n .map(([key]) => key);\n\n // Check if dictionaries list is empty\n if (entries.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = [\n ...cachedKeys.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'imported' as DictionaryStatus,\n })),\n ...keysToFetch.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'pending' as const,\n })),\n ];\n\n // Initialize aggregated logger\n const logger = new PullLogger();\n logger.update(\n dictionariesStatuses.map<PullStatus>((s) => ({\n dictionaryKey: s.dictionaryKey,\n status: s.status,\n }))\n );\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n const isCached =\n statusObj.status === 'imported' || statusObj.status === 'up-to-date';\n\n if (!isCached) {\n statusObj.status = 'fetching';\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'fetching' },\n ]);\n }\n\n try {\n let sourceDictionary: Dictionary | undefined;\n\n if (isCached) {\n sourceDictionary = remoteDictionariesRecord[\n statusObj.dictionaryKey\n ] as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n // Fetch the dictionary\n const getDictionaryResult =\n await intlayerAPI.dictionary.getDictionary(statusObj.dictionaryKey);\n\n sourceDictionary = getDictionaryResult.data as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Now, write the dictionary to local file\n const { status } = await writeContentDeclaration(\n sourceDictionary,\n config,\n options?.newDictionariesPath\n );\n\n statusObj.status = status;\n logger.update([{ dictionaryKey: statusObj.dictionaryKey, status }]);\n\n successfullyFetchedDictionaries.push(sourceDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with concurrency limit\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n // Per-dictionary summary\n const iconFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n case 'reimported in JSON':\n case 'new content file':\n return '✔';\n case 'error':\n return '✖';\n default:\n return '⏲';\n }\n };\n\n const colorFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n return ANSIColors.GREEN;\n case 'reimported in JSON':\n case 'new content file':\n return ANSIColors.YELLOW;\n case 'error':\n return ANSIColors.RED;\n default:\n return ANSIColors.BLUE;\n }\n };\n\n for (const s of dictionariesStatuses) {\n const icon = iconFor(s.status);\n const color = colorFor(s.status);\n appLogger(\n ` - ${s.dictionaryKey} ${ANSIColors.GREY}[${color}${icon} ${s.status}${ANSIColors.GREY}]${ANSIColors.RESET}`\n );\n }\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n"],"mappings":"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","names":[]}