@crowdin/app-project-module 0.102.0 → 0.102.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.
@@ -15,6 +15,7 @@ const logger_1 = require("../../../util/logger");
15
15
  const webhooks_1 = require("../util/webhooks");
16
16
  const types_1 = require("../types");
17
17
  const cron_1 = require("../util/cron");
18
+ const files_1 = require("../util/files");
18
19
  function filterSyncFiles(args) {
19
20
  var _a, _b;
20
21
  return __awaiter(this, void 0, void 0, function* () {
@@ -88,7 +89,7 @@ function handle(config, integration) {
88
89
  if (!syncSettings) {
89
90
  return res.status(200).send({ message: 'Sync is not configured' });
90
91
  }
91
- const syncFileSettings = JSON.parse(syncSettings.files);
92
+ const syncFileSettings = (0, files_1.prepareSyncFiles)(JSON.parse(syncSettings.files));
92
93
  if ((_b = integration.webhooks) === null || _b === void 0 ? void 0 : _b.crowdinWebhookInterceptor) {
93
94
  filesToSync = yield integration.webhooks.crowdinWebhookInterceptor(projectId, crowdinClient.client, rootFolder, appSettings, syncSettings, req.body);
94
95
  }
@@ -76,7 +76,7 @@ function handle(config, integration) {
76
76
  else {
77
77
  yield (0, cron_1.createOrUpdateSyncSettings)({
78
78
  req,
79
- files,
79
+ files: (0, files_1.prepareSyncFiles)(files),
80
80
  provider,
81
81
  });
82
82
  }
@@ -154,7 +154,7 @@ function processSyncSettings({ config, integration, period, syncSettings, }) {
154
154
  let projectData;
155
155
  let crowdinClient;
156
156
  let token;
157
- let files = JSON.parse(syncSettings.files);
157
+ let files = (0, files_1.prepareSyncFiles)(JSON.parse(syncSettings.files));
158
158
  let newFiles = [];
159
159
  const crowdinCredentials = yield (0, storage_1.getStorage)().getCrowdinCredentials(syncSettings.crowdinId);
160
160
  const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(syncSettings.integrationId);
@@ -225,20 +225,27 @@ function processSyncSettings({ config, integration, period, syncSettings, }) {
225
225
  context.jwtPayload.context.project_identifier = projectData.identifier;
226
226
  const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, crowdinClient, projectId);
227
227
  const apiCredentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
228
- if (!integration.webhooks &&
228
+ let currentFileSnapshot = [];
229
+ const needsSnapshotForNewFiles = !integration.webhooks &&
229
230
  ((_a = integration.syncNewElements) === null || _a === void 0 ? void 0 : _a[syncSettings.provider]) &&
230
- intConfig[`new-${syncSettings.provider}-files`]) {
231
+ intConfig[`new-${syncSettings.provider}-files`];
232
+ const needsIntegrationSnapshot = integration.forcePushSources || (needsSnapshotForNewFiles && syncSettings.provider !== types_1.Provider.CROWDIN);
233
+ const needsCrowdinSnapshot = needsSnapshotForNewFiles && syncSettings.provider === types_1.Provider.CROWDIN;
234
+ if (needsIntegrationSnapshot) {
235
+ currentFileSnapshot = yield (0, snapshot_1.getIntegrationSnapshot)(integration, apiCredentials, intConfig);
236
+ }
237
+ else if (needsCrowdinSnapshot) {
238
+ currentFileSnapshot = yield (0, snapshot_1.getCrowdinSnapshot)(config, integration, crowdinClient, projectData.id, intConfig);
239
+ }
240
+ if (needsSnapshotForNewFiles) {
231
241
  try {
232
242
  newFiles = yield getAllNewFiles({
233
- config,
234
- integration,
235
- projectData,
236
- syncSettings,
237
- crowdinApiClient: crowdinClient,
238
243
  crowdinId: crowdinCredentials.id,
239
- integrationCredentials: apiCredentials,
240
244
  integrationId: integrationCredentials.id,
245
+ projectData,
241
246
  integrationSettings: intConfig,
247
+ syncSettings,
248
+ currentFileSnapshot,
242
249
  });
243
250
  }
244
251
  catch (e) {
@@ -331,6 +338,18 @@ function processSyncSettings({ config, integration, period, syncSettings, }) {
331
338
  // eslint-disable-next-line @typescript-eslint/camelcase
332
339
  node_type: file.nodeType || file.node_type }, (file.type ? { type: file.type } : {}))));
333
340
  let intFiles = allIntFiles.filter((file) => 'type' in file);
341
+ if (integration.forcePushSources === true && currentFileSnapshot.length > 0) {
342
+ const snapshotMap = new Map(currentFileSnapshot.map((f) => [f.id, f]));
343
+ intFiles = intFiles.map((file) => {
344
+ const snapshotFile = snapshotMap.get(file.id);
345
+ if (snapshotFile) {
346
+ return Object.assign(Object.assign({}, file), { updatedAt: snapshotFile.updatedAt, createdAt: snapshotFile.createdAt });
347
+ }
348
+ return file;
349
+ });
350
+ intFiles = yield (0, files_1.attachFileStatus)(intFiles, syncSettings.integrationId, syncSettings.crowdinId, integration);
351
+ intFiles = intFiles.filter((file) => file.isUpdated !== false);
352
+ }
334
353
  if (intFiles.length <= 0) {
335
354
  return;
336
355
  }
@@ -389,21 +408,13 @@ function processSyncSettings({ config, integration, period, syncSettings, }) {
389
408
  function getFileDiff(currentFiles, savedFiles) {
390
409
  return currentFiles.filter((x) => !savedFiles.some((x2) => x2.id === x.id));
391
410
  }
392
- function getAllNewFiles(args) {
411
+ function getAllNewFiles({ crowdinId, integrationId, projectData, integrationSettings, syncSettings, currentFileSnapshot, }) {
393
412
  return __awaiter(this, void 0, void 0, function* () {
394
- const { config, integration, crowdinApiClient, crowdinId, integrationCredentials, integrationId, projectData, integrationSettings, syncSettings, } = args;
395
- let currentFileSnapshot = [];
396
413
  const fileSnapshotData = yield (0, storage_1.getStorage)().getFilesSnapshot(integrationId, crowdinId, syncSettings.provider);
397
414
  const snapshotFiles = (fileSnapshotData === null || fileSnapshotData === void 0 ? void 0 : fileSnapshotData.files) ? JSON.parse(fileSnapshotData.files) : [];
398
- if (syncSettings.provider === types_1.Provider.CROWDIN) {
399
- currentFileSnapshot = yield (0, snapshot_1.getCrowdinSnapshot)(config, integration, crowdinApiClient, projectData.id, integrationSettings);
400
- }
401
- else {
402
- currentFileSnapshot = yield (0, snapshot_1.getIntegrationSnapshot)(integration, integrationCredentials, integrationSettings);
403
- }
404
415
  const difference = getFileDiff(currentFileSnapshot, snapshotFiles);
405
416
  const onlyFiles = difference.filter((file) => 'type' in file);
406
- const synFiles = JSON.parse(syncSettings.files);
417
+ const synFiles = (0, files_1.prepareSyncFiles)(JSON.parse(syncSettings.files));
407
418
  if (syncSettings.provider === types_1.Provider.INTEGRATION) {
408
419
  if (integrationSettings[`new-${syncSettings.provider}-files`]) {
409
420
  return onlyFiles;
@@ -4,6 +4,7 @@ import Crowdin from '@crowdin/crowdin-api-client';
4
4
  import { SourceFilesModel } from '@crowdin/crowdin-api-client';
5
5
  export declare function skipFilesByRegex(files: TreeItem[] | undefined, skipIntegrationNodes?: SkipIntegrationNodes): TreeItem[];
6
6
  export declare function expandFilesTree(nodes: IntegrationFile[], req: IntegrationRequest, integration: IntegrationLogic, job?: JobClient): Promise<IntegrationFile[]>;
7
+ export declare function attachFileStatus(files: any[], clientId: string, crowdinId: string, integration: IntegrationLogic): Promise<any[]>;
7
8
  export declare function isExtendedResultType<T>(data?: T | ExtendedResult<T>): data is ExtendedResult<T>;
8
9
  export declare function markUnsyncedFiles({ integrationId, crowdinId, client, files, }: {
9
10
  integrationId: string;
@@ -17,6 +18,8 @@ export declare function getExcludedTargetLanguages({ client, projectId, language
17
18
  languages: string[];
18
19
  }): Promise<string[]>;
19
20
  export declare function filterLanguages(request: UpdateIntegrationRequest, files: SourceFilesModel.File[]): UpdateIntegrationRequest;
21
+ export declare function uniqueFileLanguages(files: UpdateIntegrationRequest): UpdateIntegrationRequest;
22
+ export declare function prepareSyncFiles(files: any): any;
20
23
  export declare function buildPath(file: TreeItem, files: TreeItem[]): string;
21
24
  export declare function getParentPaths(filePath: string): Set<string>;
22
25
  export declare function getParentFiles(allFiles: TreeItem[], fileCollection: TreeItem[]): TreeItem[];
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.filterSyncedData = exports.updateSyncedData = exports.filterFilesByPath = exports.filterFilesByPatterns = exports.getParentFiles = exports.getParentPaths = exports.buildPath = exports.filterLanguages = exports.getExcludedTargetLanguages = exports.markUnsyncedFiles = exports.isExtendedResultType = exports.expandFilesTree = exports.skipFilesByRegex = void 0;
15
+ exports.filterSyncedData = exports.updateSyncedData = exports.filterFilesByPath = exports.filterFilesByPatterns = exports.getParentFiles = exports.getParentPaths = exports.buildPath = exports.prepareSyncFiles = exports.uniqueFileLanguages = exports.filterLanguages = exports.getExcludedTargetLanguages = exports.markUnsyncedFiles = exports.isExtendedResultType = exports.attachFileStatus = exports.expandFilesTree = exports.skipFilesByRegex = void 0;
16
16
  const types_1 = require("../types");
17
17
  const types_2 = require("./types");
18
18
  const storage_1 = require("../../../storage");
@@ -75,44 +75,57 @@ function expandFilesTree(nodes, req, integration, job) {
75
75
  integration.forcePushSources === true;
76
76
  if (needsFileStatus) {
77
77
  const { crowdinId, clientId } = req.crowdinContext;
78
- const syncedData = yield (0, storage_1.getStorage)().getSyncedData(clientId, crowdinId, types_1.Provider.INTEGRATION);
79
- const syncedFiles = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.files) ? JSON.parse(syncedData.files) : [];
80
- const lastSyncTimestamp = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.updatedAt) ? Number(syncedData.updatedAt) : null;
81
- return files.map((file) => {
82
- var _a, _b, _c, _d, _e, _f;
83
- if (!file.type) {
84
- return file;
85
- }
86
- const syncedFile = syncedFiles.find((syncedItem) => syncedItem.id === file.id);
87
- const notSynced = ((_b = (_a = integration.filtering) === null || _a === void 0 ? void 0 : _a.integrationFileStatus) === null || _b === void 0 ? void 0 : _b.notSynced) === true ? !syncedFile : false;
88
- const fileSyncTimestamp = (syncedFile === null || syncedFile === void 0 ? void 0 : syncedFile.syncedAt) ? Number(syncedFile.syncedAt) : lastSyncTimestamp;
89
- const isNew = ((_d = (_c = integration.filtering) === null || _c === void 0 ? void 0 : _c.integrationFileStatus) === null || _d === void 0 ? void 0 : _d.isNew) === true
90
- ? !syncedFile &&
91
- fileSyncTimestamp &&
92
- file.createdAt &&
93
- (typeof file.createdAt === 'string' ? new Date(file.createdAt).getTime() : file.createdAt) >
94
- fileSyncTimestamp
95
- : false;
96
- const shouldCalculateIsUpdated = ((_f = (_e = integration.filtering) === null || _e === void 0 ? void 0 : _e.integrationFileStatus) === null || _f === void 0 ? void 0 : _f.isUpdated) === true ||
97
- integration.forcePushSources === true;
98
- const isUpdated = file.isUpdated !== undefined
99
- ? file.isUpdated
100
- : shouldCalculateIsUpdated
101
- ? syncedFile &&
102
- fileSyncTimestamp &&
103
- file.updatedAt &&
104
- (typeof file.updatedAt === 'string' ? new Date(file.updatedAt).getTime() : file.updatedAt) >
105
- fileSyncTimestamp
106
- : false;
107
- return Object.assign(Object.assign({}, file), { isNew,
108
- notSynced,
109
- isUpdated });
110
- });
78
+ return yield attachFileStatus(files, clientId, crowdinId, integration);
111
79
  }
112
80
  return files;
113
81
  });
114
82
  }
115
83
  exports.expandFilesTree = expandFilesTree;
84
+ function attachFileStatus(files, clientId, crowdinId, integration) {
85
+ var _a, _b, _c, _d, _e, _f;
86
+ return __awaiter(this, void 0, void 0, function* () {
87
+ const needsFileStatus = ((_b = (_a = integration.filtering) === null || _a === void 0 ? void 0 : _a.integrationFileStatus) === null || _b === void 0 ? void 0 : _b.isNew) ||
88
+ ((_d = (_c = integration.filtering) === null || _c === void 0 ? void 0 : _c.integrationFileStatus) === null || _d === void 0 ? void 0 : _d.isUpdated) ||
89
+ ((_f = (_e = integration.filtering) === null || _e === void 0 ? void 0 : _e.integrationFileStatus) === null || _f === void 0 ? void 0 : _f.notSynced) ||
90
+ integration.forcePushSources === true;
91
+ if (!needsFileStatus) {
92
+ return files;
93
+ }
94
+ const syncedData = yield (0, storage_1.getStorage)().getSyncedData(clientId, crowdinId, types_1.Provider.INTEGRATION);
95
+ const syncedFiles = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.files) ? JSON.parse(syncedData.files) : [];
96
+ const lastSyncTimestamp = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.updatedAt) ? Number(syncedData.updatedAt) : null;
97
+ return files.map((file) => {
98
+ var _a, _b, _c, _d, _e, _f;
99
+ if (!file.type) {
100
+ return file;
101
+ }
102
+ const syncedFile = syncedFiles.find((syncedItem) => syncedItem.id === file.id);
103
+ const notSynced = ((_b = (_a = integration.filtering) === null || _a === void 0 ? void 0 : _a.integrationFileStatus) === null || _b === void 0 ? void 0 : _b.notSynced) === true ? !syncedFile : false;
104
+ const fileSyncTimestamp = (syncedFile === null || syncedFile === void 0 ? void 0 : syncedFile.syncedAt) ? Number(syncedFile.syncedAt) : lastSyncTimestamp;
105
+ const isNew = ((_d = (_c = integration.filtering) === null || _c === void 0 ? void 0 : _c.integrationFileStatus) === null || _d === void 0 ? void 0 : _d.isNew) === true
106
+ ? !syncedFile &&
107
+ fileSyncTimestamp &&
108
+ file.createdAt &&
109
+ (typeof file.createdAt === 'string' ? new Date(file.createdAt).getTime() : file.createdAt) >
110
+ fileSyncTimestamp
111
+ : false;
112
+ const shouldCalculateIsUpdated = ((_f = (_e = integration.filtering) === null || _e === void 0 ? void 0 : _e.integrationFileStatus) === null || _f === void 0 ? void 0 : _f.isUpdated) === true || integration.forcePushSources === true;
113
+ const isUpdated = file.isUpdated !== undefined
114
+ ? file.isUpdated
115
+ : shouldCalculateIsUpdated
116
+ ? syncedFile &&
117
+ fileSyncTimestamp &&
118
+ file.updatedAt &&
119
+ (typeof file.updatedAt === 'string' ? new Date(file.updatedAt).getTime() : file.updatedAt) >
120
+ fileSyncTimestamp
121
+ : false;
122
+ return Object.assign(Object.assign({}, file), { isNew,
123
+ notSynced,
124
+ isUpdated });
125
+ });
126
+ });
127
+ }
128
+ exports.attachFileStatus = attachFileStatus;
116
129
  function isExtendedResultType(data) {
117
130
  const dataTyped = data;
118
131
  return !!dataTyped && !Array.isArray(dataTyped);
@@ -182,6 +195,29 @@ function filterLanguages(request, files) {
182
195
  return result;
183
196
  }
184
197
  exports.filterLanguages = filterLanguages;
198
+ function uniqueFileLanguages(files) {
199
+ const result = {};
200
+ for (const fileId in files) {
201
+ const languages = files[fileId];
202
+ result[fileId] = Array.isArray(languages) ? [...new Set(languages)] : languages;
203
+ }
204
+ return result;
205
+ }
206
+ exports.uniqueFileLanguages = uniqueFileLanguages;
207
+ function prepareSyncFiles(files) {
208
+ // If it's an array (Provider.INTEGRATION format), return as is
209
+ if (Array.isArray(files)) {
210
+ return files;
211
+ }
212
+ if (files && typeof files === 'object') {
213
+ const hasLanguageArrays = Object.values(files).some((value) => Array.isArray(value));
214
+ if (hasLanguageArrays) {
215
+ return uniqueFileLanguages(files);
216
+ }
217
+ }
218
+ return files;
219
+ }
220
+ exports.prepareSyncFiles = prepareSyncFiles;
185
221
  function buildPath(file, files) {
186
222
  const filePath = `/${file.name}`;
187
223
  if (!file.parentId) {
@@ -1,6 +1,5 @@
1
- import Crowdin, { ProjectsGroupsModel } from '@crowdin/crowdin-api-client';
2
- import { Config } from '../../../types';
3
- import { IntegrationLogic, IntegrationSyncSettings } from '../types';
1
+ import { ProjectsGroupsModel } from '@crowdin/crowdin-api-client';
2
+ import { IntegrationSyncSettings, TreeItem } from '../types';
4
3
  import { ResponseObject } from '@crowdin/crowdin-api-client/out/core';
5
4
  import { TranslationsModel } from '@crowdin/crowdin-api-client/out/translations';
6
5
  import { AxiosError } from 'axios';
@@ -69,15 +68,12 @@ export type UpdateJobProgress = ({ progress, status, info, data, attempt, errors
69
68
  isCanceled: boolean;
70
69
  }>;
71
70
  export interface GetAllNewFilesArgs {
72
- config: Config;
73
- integration: IntegrationLogic;
74
- crowdinApiClient: Crowdin;
75
71
  crowdinId: string;
76
- integrationCredentials: any;
77
72
  integrationId: string;
78
73
  projectData: ProjectsGroupsModel.ProjectSettings;
79
74
  integrationSettings: any;
80
75
  syncSettings: IntegrationSyncSettings;
76
+ currentFileSnapshot: TreeItem[];
81
77
  }
82
78
  export type SaveUploadedFileTranslation = ({ fileId, translationParams, }: {
83
79
  fileId: number;
@@ -45,6 +45,7 @@ const connection_1 = require("../../../util/connection");
45
45
  const defaults_1 = require("./defaults");
46
46
  const index_1 = require("../../../util/index");
47
47
  const logger_1 = require("../../../util/logger");
48
+ const files_1 = require("./files");
48
49
  const prefetchCount = 10;
49
50
  const forceProcessDelay = 10000;
50
51
  const maxReconnectAttempts = 5;
@@ -315,7 +316,7 @@ function updateCrowdinFromWebhookRequest(args) {
315
316
  const { integration, webhookData, req } = args;
316
317
  let filesToSync = [];
317
318
  const { projectId, crowdinClient, preparedIntegrationCredentials, rootFolder, appSettings, syncSettings } = webhookData;
318
- const syncFiles = (syncSettings === null || syncSettings === void 0 ? void 0 : syncSettings.files) ? JSON.parse(syncSettings.files) : [];
319
+ const syncFiles = (syncSettings === null || syncSettings === void 0 ? void 0 : syncSettings.files) ? (0, files_1.prepareSyncFiles)(JSON.parse(syncSettings.files)) : [];
319
320
  if ((_a = integration.webhooks) === null || _a === void 0 ? void 0 : _a.integrationWebhookInterceptor) {
320
321
  filesToSync = yield ((_b = integration.webhooks) === null || _b === void 0 ? void 0 : _b.integrationWebhookInterceptor(projectId, crowdinClient.client, preparedIntegrationCredentials, rootFolder, appSettings, syncSettings, req));
321
322
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.102.0",
3
+ "version": "0.102.1",
4
4
  "description": "Module that generates for you all common endpoints for serving standalone Crowdin App",
5
5
  "main": "out/index.js",
6
6
  "types": "out/index.d.ts",