@tolgee/cli 2.13.0 → 2.15.0-rc.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.
- package/dist/client/ExportClient.js +18 -1
- package/dist/client/TolgeeClient.js +7 -2
- package/dist/client/WebsocketClient.js +120 -0
- package/dist/client/errorFromLoadable.js +8 -2
- package/dist/commands/login.js +4 -2
- package/dist/commands/pull.js +78 -35
- package/dist/commands/push.js +33 -9
- package/dist/commands/sync/sync.js +1 -0
- package/dist/commands/sync/syncUtils.js +9 -4
- package/dist/config/credentials.js +8 -7
- package/dist/utils/eTagStorage.js +15 -0
- package/dist/utils/getStackTrace.js +7 -5
- package/dist/utils/isVersionAtLeast.js +29 -0
- package/dist/utils/logger.js +1 -1
- package/dist/utils/printFailedKeys.js +26 -0
- package/dist/utils/pullWatch/AuthErrorHandler.js +71 -0
- package/dist/utils/pullWatch/watchHandler.js +149 -0
- package/package.json +10 -2
- package/schema.json +16 -0
|
@@ -7,14 +7,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
+
var t = {};
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
+
t[p[i]] = s[p[i]];
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
};
|
|
10
21
|
export const createExportClient = ({ apiClient }) => {
|
|
11
22
|
return {
|
|
12
23
|
export(req) {
|
|
13
24
|
return __awaiter(this, void 0, void 0, function* () {
|
|
14
|
-
const
|
|
25
|
+
const { ifNoneMatch } = req, exportReq = __rest(req, ["ifNoneMatch"]);
|
|
26
|
+
const body = Object.assign(Object.assign({}, exportReq), { zip: true });
|
|
27
|
+
const headers = {};
|
|
28
|
+
if (ifNoneMatch) {
|
|
29
|
+
headers['If-None-Match'] = ifNoneMatch;
|
|
30
|
+
}
|
|
15
31
|
const loadable = yield apiClient.POST('/v2/projects/{projectId}/export', {
|
|
16
32
|
params: { path: { projectId: apiClient.getProjectId() } },
|
|
17
33
|
body: body,
|
|
34
|
+
headers,
|
|
18
35
|
parseAs: 'blob',
|
|
19
36
|
});
|
|
20
37
|
return Object.assign(Object.assign({}, loadable), { data: loadable.data });
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { exitWithError } from './../utils/logger.js';
|
|
2
1
|
import { createApiClient } from './ApiClient.js';
|
|
3
2
|
import { createExportClient } from './ExportClient.js';
|
|
4
3
|
import { createImportClient } from './ImportClient.js';
|
|
@@ -9,6 +8,12 @@ export function createTolgeeClient(props) {
|
|
|
9
8
|
}
|
|
10
9
|
export const handleLoadableError = (loadable) => {
|
|
11
10
|
if (loadable.error) {
|
|
12
|
-
|
|
11
|
+
throw new LoadableError(loadable);
|
|
13
12
|
}
|
|
14
13
|
};
|
|
14
|
+
export class LoadableError extends Error {
|
|
15
|
+
constructor(loadable) {
|
|
16
|
+
super(errorFromLoadable(loadable));
|
|
17
|
+
this.loadable = loadable;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Stomp } from '@stomp/stompjs';
|
|
2
|
+
import SockJS from 'sockjs-client';
|
|
3
|
+
import { debug, error } from '../utils/logger.js';
|
|
4
|
+
export const WebsocketClient = (options) => {
|
|
5
|
+
let client;
|
|
6
|
+
let deactivated = false;
|
|
7
|
+
let connected = false;
|
|
8
|
+
let connecting = false;
|
|
9
|
+
let subscriptions = [];
|
|
10
|
+
const resubscribe = () => {
|
|
11
|
+
if (deactivated) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (client) {
|
|
15
|
+
subscriptions.forEach((subscription) => {
|
|
16
|
+
subscribeToStompChannel(subscription);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const subscribeToStompChannel = (subscription) => {
|
|
21
|
+
if (connected && client) {
|
|
22
|
+
debug(`Subscribing to ${subscription.channel}`);
|
|
23
|
+
const stompSubscription = client.subscribe(subscription.channel, function (message) {
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(message.body);
|
|
26
|
+
subscription.callback(parsed);
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
error(`Error parsing message: ${e.message}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
subscription.unsubscribe = stompSubscription.unsubscribe;
|
|
33
|
+
subscription.id = stompSubscription.id;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function initClient() {
|
|
37
|
+
client = Stomp.over(() => new SockJS(`${options.serverUrl}/websocket`));
|
|
38
|
+
client.configure({
|
|
39
|
+
reconnectDelay: 3000,
|
|
40
|
+
debug: (msg) => {
|
|
41
|
+
debug(msg);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function connectIfNotAlready() {
|
|
46
|
+
if (deactivated || connected || connecting) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
connecting = true;
|
|
50
|
+
const client = getClient();
|
|
51
|
+
const onConnected = function (message) {
|
|
52
|
+
var _a;
|
|
53
|
+
connected = true;
|
|
54
|
+
connecting = false;
|
|
55
|
+
resubscribe();
|
|
56
|
+
(_a = options.onConnected) === null || _a === void 0 ? void 0 : _a.call(options, message);
|
|
57
|
+
};
|
|
58
|
+
const onDisconnect = function () {
|
|
59
|
+
var _a;
|
|
60
|
+
connected = false;
|
|
61
|
+
connecting = false;
|
|
62
|
+
(_a = options.onConnectionClose) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
63
|
+
};
|
|
64
|
+
const onError = (error) => {
|
|
65
|
+
var _a;
|
|
66
|
+
connecting = false;
|
|
67
|
+
(_a = options.onError) === null || _a === void 0 ? void 0 : _a.call(options, error);
|
|
68
|
+
};
|
|
69
|
+
client.connect(getAuthentication(options), onConnected, onError, onDisconnect);
|
|
70
|
+
}
|
|
71
|
+
const getClient = () => {
|
|
72
|
+
if (client !== undefined) {
|
|
73
|
+
return client;
|
|
74
|
+
}
|
|
75
|
+
initClient();
|
|
76
|
+
return client;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Subscribes to channel
|
|
80
|
+
* @param channel Channel URI
|
|
81
|
+
* @param callback Callback function to be executed when event is triggered
|
|
82
|
+
* @return Function Function unsubscribing the event listening
|
|
83
|
+
*/
|
|
84
|
+
function subscribe(channel, callback) {
|
|
85
|
+
if (deactivated) {
|
|
86
|
+
return () => { };
|
|
87
|
+
}
|
|
88
|
+
connectIfNotAlready();
|
|
89
|
+
const subscription = { channel, callback };
|
|
90
|
+
subscriptions.push(subscription);
|
|
91
|
+
subscribeToStompChannel(subscription);
|
|
92
|
+
return () => {
|
|
93
|
+
var _a;
|
|
94
|
+
(_a = subscription.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(subscription);
|
|
95
|
+
removeSubscription(subscription);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function disconnect() {
|
|
99
|
+
if (client) {
|
|
100
|
+
client.disconnect();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function deactivate() {
|
|
104
|
+
deactivated = true;
|
|
105
|
+
disconnect();
|
|
106
|
+
}
|
|
107
|
+
function removeSubscription(subscription) {
|
|
108
|
+
subscriptions = subscriptions.filter((it) => it !== subscription);
|
|
109
|
+
}
|
|
110
|
+
return Object.freeze({ subscribe, deactivate, connectIfNotAlready });
|
|
111
|
+
};
|
|
112
|
+
function getAuthentication(options) {
|
|
113
|
+
if (options.authentication.jwtToken) {
|
|
114
|
+
return { jwtToken: options.authentication.jwtToken };
|
|
115
|
+
}
|
|
116
|
+
if (options.authentication.apiKey) {
|
|
117
|
+
return { 'x-api-key': options.authentication.apiKey };
|
|
118
|
+
}
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { getUnresolvedConflictsMessage } from '../utils/printFailedKeys.js';
|
|
1
2
|
export const addErrorDetails = (loadable, showBeError = true) => {
|
|
2
|
-
var _a, _b, _c;
|
|
3
|
+
var _a, _b, _c, _d, _e;
|
|
4
|
+
let additionalInfo = '';
|
|
3
5
|
const items = [];
|
|
4
6
|
items.push(`status: ${loadable.response.status}`);
|
|
5
7
|
if (showBeError && ((_a = loadable.error) === null || _a === void 0 ? void 0 : _a.code)) {
|
|
@@ -8,7 +10,11 @@ export const addErrorDetails = (loadable, showBeError = true) => {
|
|
|
8
10
|
if (loadable.response.status === 403 && ((_c = (_b = loadable.error) === null || _b === void 0 ? void 0 : _b.params) === null || _c === void 0 ? void 0 : _c[0])) {
|
|
9
11
|
items.push(`missing scope: ${loadable.error.params[0]}`);
|
|
10
12
|
}
|
|
11
|
-
|
|
13
|
+
if (((_d = loadable.error) === null || _d === void 0 ? void 0 : _d.code) === 'conflict_is_not_resolved' &&
|
|
14
|
+
typeof ((_e = loadable.error.params) === null || _e === void 0 ? void 0 : _e[0]) === 'object') {
|
|
15
|
+
additionalInfo += getUnresolvedConflictsMessage(loadable.error.params, true);
|
|
16
|
+
}
|
|
17
|
+
return `[${items.join(', ')}]${additionalInfo ? '\n' + additionalInfo : ''}`;
|
|
12
18
|
};
|
|
13
19
|
export const errorFromLoadable = (loadable) => {
|
|
14
20
|
switch (loadable.response.status) {
|
package/dist/commands/login.js
CHANGED
|
@@ -9,10 +9,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { Command } from 'commander';
|
|
11
11
|
import ansi from 'ansi-colors';
|
|
12
|
-
import {
|
|
13
|
-
import { exitWithError, success } from '../utils/logger.js';
|
|
12
|
+
import { clearAuthStore, removeApiKeys, saveApiKey, } from '../config/credentials.js';
|
|
13
|
+
import { debug, exitWithError, success } from '../utils/logger.js';
|
|
14
14
|
import { createTolgeeClient } from '../client/TolgeeClient.js';
|
|
15
15
|
import { printApiKeyLists } from '../utils/apiKeyList.js';
|
|
16
|
+
import { getStackTrace } from '../utils/getStackTrace.js';
|
|
16
17
|
function loginHandler(key) {
|
|
17
18
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18
19
|
const opts = this.optsWithGlobals();
|
|
@@ -23,6 +24,7 @@ function loginHandler(key) {
|
|
|
23
24
|
else if (!key) {
|
|
24
25
|
exitWithError('Missing argument [API Key]');
|
|
25
26
|
}
|
|
27
|
+
debug(`Logging in with API key ${key === null || key === void 0 ? void 0 : key.slice(0, 5)}...${key === null || key === void 0 ? void 0 : key.slice(-4)}.\n${getStackTrace()}`);
|
|
26
28
|
const keyInfo = yield createTolgeeClient({
|
|
27
29
|
baseUrl: opts.apiUrl.toString(),
|
|
28
30
|
apiKey: key,
|
package/dist/commands/pull.js
CHANGED
|
@@ -10,44 +10,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { Command, Option } from 'commander';
|
|
11
11
|
import { unzipBuffer } from '../utils/zip.js';
|
|
12
12
|
import { prepareDir } from '../utils/prepareDir.js';
|
|
13
|
-
import { exitWithError, loading, success } from '../utils/logger.js';
|
|
13
|
+
import { exitWithError, info, loading, success } from '../utils/logger.js';
|
|
14
14
|
import { checkPathNotAFile } from '../utils/checkPathNotAFile.js';
|
|
15
15
|
import { mapExportFormat } from '../utils/mapExportFormat.js';
|
|
16
16
|
import { handleLoadableError } from '../client/TolgeeClient.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
var _a;
|
|
20
|
-
const exportFormat = mapExportFormat(opts.format);
|
|
21
|
-
const { format, messageFormat } = exportFormat;
|
|
22
|
-
const loadable = yield opts.client.export.export({
|
|
23
|
-
format,
|
|
24
|
-
messageFormat,
|
|
25
|
-
supportArrays: opts.supportArrays,
|
|
26
|
-
languages: opts.languages,
|
|
27
|
-
filterState: opts.states,
|
|
28
|
-
structureDelimiter: (_a = opts.delimiter) !== null && _a !== void 0 ? _a : '',
|
|
29
|
-
filterNamespace: opts.namespaces,
|
|
30
|
-
filterTagIn: opts.tags,
|
|
31
|
-
filterTagNotIn: opts.excludeTags,
|
|
32
|
-
fileStructureTemplate: opts.fileStructureTemplate,
|
|
33
|
-
});
|
|
34
|
-
handleLoadableError(loadable);
|
|
35
|
-
return loadable.data;
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
const pullHandler = () => function () {
|
|
39
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
-
const opts = this.optsWithGlobals();
|
|
41
|
-
if (!opts.path) {
|
|
42
|
-
exitWithError('Missing option --path <path> or `pull.path` in tolgee config');
|
|
43
|
-
}
|
|
44
|
-
yield checkPathNotAFile(opts.path);
|
|
45
|
-
const zipBlob = yield loading('Fetching strings from Tolgee...', fetchZipBlob(opts));
|
|
46
|
-
yield prepareDir(opts.path, opts.emptyDir);
|
|
47
|
-
yield loading('Extracting strings...', unzipBuffer(zipBlob, opts.path));
|
|
48
|
-
success('Done!');
|
|
49
|
-
});
|
|
50
|
-
};
|
|
17
|
+
import { startWatching } from '../utils/pullWatch/watchHandler.js';
|
|
18
|
+
import { getETag } from '../utils/eTagStorage.js';
|
|
51
19
|
export default (config) => {
|
|
52
20
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
53
21
|
return new Command()
|
|
@@ -66,5 +34,80 @@ export default (config) => {
|
|
|
66
34
|
.addOption(new Option('--support-arrays', 'Export keys with array syntax (e.g. item[0]) as arrays.').default((_j = (_h = config.pull) === null || _h === void 0 ? void 0 : _h.supportArrays) !== null && _j !== void 0 ? _j : false))
|
|
67
35
|
.addOption(new Option('--empty-dir', 'Empty target directory before inserting pulled files.').default((_k = config.pull) === null || _k === void 0 ? void 0 : _k.emptyDir))
|
|
68
36
|
.addOption(new Option('--file-structure-template <template>', 'Defines exported file structure: https://tolgee.io/tolgee-cli/push-pull-strings#file-structure-template-format').default((_l = config.pull) === null || _l === void 0 ? void 0 : _l.fileStructureTemplate))
|
|
37
|
+
.addOption(new Option('--watch', 'Watch for changes and re-pull automatically').default(false))
|
|
69
38
|
.action(pullHandler());
|
|
70
39
|
};
|
|
40
|
+
const pullHandler = () => function () {
|
|
41
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
const opts = this.optsWithGlobals();
|
|
43
|
+
if (!opts.path) {
|
|
44
|
+
exitWithError('Missing option --path <path> or `pull.path` in tolgee config');
|
|
45
|
+
}
|
|
46
|
+
yield checkPathNotAFile(opts.path);
|
|
47
|
+
if (!opts.watch) {
|
|
48
|
+
yield doPull(opts);
|
|
49
|
+
success('Done!');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Start watching for changes
|
|
53
|
+
yield startWatching({
|
|
54
|
+
apiUrl: opts.apiUrl,
|
|
55
|
+
apiKey: opts.apiKey,
|
|
56
|
+
projectId: opts.projectId,
|
|
57
|
+
client: opts.client,
|
|
58
|
+
doPull: () => __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
yield doPull(opts);
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
const doPull = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
const result = yield loading('Fetching strings from Tolgee...', fetchZipBlob(opts, getETag(opts.projectId)));
|
|
66
|
+
if (result.notModified) {
|
|
67
|
+
info('Exported data not changed.');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
yield prepareDir(opts.path, opts.emptyDir);
|
|
71
|
+
yield loading('Extracting strings...', unzipBuffer(result.data, opts.path));
|
|
72
|
+
// Store ETag after a successful pull
|
|
73
|
+
if (result.etag) {
|
|
74
|
+
const { setETag } = yield import('../utils/eTagStorage.js');
|
|
75
|
+
setETag(opts.projectId, result.etag);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
function fetchZipBlob(opts, ifNoneMatch) {
|
|
79
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
var _a, _b, _c;
|
|
81
|
+
const exportFormat = mapExportFormat(opts.format);
|
|
82
|
+
const { format, messageFormat } = exportFormat;
|
|
83
|
+
const loadable = yield opts.client.export.export({
|
|
84
|
+
format,
|
|
85
|
+
messageFormat,
|
|
86
|
+
supportArrays: opts.supportArrays,
|
|
87
|
+
languages: opts.languages,
|
|
88
|
+
filterState: opts.states,
|
|
89
|
+
structureDelimiter: (_a = opts.delimiter) !== null && _a !== void 0 ? _a : '',
|
|
90
|
+
filterNamespace: opts.namespaces,
|
|
91
|
+
filterTagIn: opts.tags,
|
|
92
|
+
filterTagNotIn: opts.excludeTags,
|
|
93
|
+
fileStructureTemplate: opts.fileStructureTemplate,
|
|
94
|
+
escapeHtml: false,
|
|
95
|
+
ifNoneMatch,
|
|
96
|
+
});
|
|
97
|
+
handleLoadableError(loadable);
|
|
98
|
+
const etag = loadable.response
|
|
99
|
+
? extractETagFromResponse(loadable.response)
|
|
100
|
+
: undefined;
|
|
101
|
+
return {
|
|
102
|
+
data: loadable.data,
|
|
103
|
+
etag,
|
|
104
|
+
// 412 is not modified for POST request
|
|
105
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/412
|
|
106
|
+
// 304 is not modified for GET request
|
|
107
|
+
notModified: [412, 304].includes((_c = (_b = loadable.response) === null || _b === void 0 ? void 0 : _b.status) !== null && _c !== void 0 ? _c : 0),
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function extractETagFromResponse(response) {
|
|
112
|
+
return response.headers.get('ETag') || undefined;
|
|
113
|
+
}
|
package/dist/commands/push.js
CHANGED
|
@@ -18,6 +18,7 @@ import { mapImportFormat } from '../utils/mapImportFormat.js';
|
|
|
18
18
|
import { handleLoadableError } from '../client/TolgeeClient.js';
|
|
19
19
|
import { findFilesByTemplate } from '../utils/filesTemplate.js';
|
|
20
20
|
import { valueToArray } from '../utils/valueToArray.js';
|
|
21
|
+
import { printUnresolvedConflicts } from '../utils/printFailedKeys.js';
|
|
21
22
|
function allInPattern(pattern) {
|
|
22
23
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23
24
|
const files = [];
|
|
@@ -100,7 +101,7 @@ function handleMappingError(fileMappings) {
|
|
|
100
101
|
}
|
|
101
102
|
const pushHandler = (config) => function () {
|
|
102
103
|
return __awaiter(this, void 0, void 0, function* () {
|
|
103
|
-
var _a, _b, _c, _d;
|
|
104
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
104
105
|
const opts = this.optsWithGlobals();
|
|
105
106
|
let allMatchers = [];
|
|
106
107
|
const filesTemplate = opts.filesTemplate;
|
|
@@ -130,12 +131,25 @@ const pushHandler = (config) => function () {
|
|
|
130
131
|
error('Nothing to import.');
|
|
131
132
|
return;
|
|
132
133
|
}
|
|
134
|
+
let errorOnUnresolvedConflict;
|
|
135
|
+
switch (opts.errorOnUnresolvedConflict) {
|
|
136
|
+
case 'auto':
|
|
137
|
+
errorOnUnresolvedConflict = undefined;
|
|
138
|
+
break;
|
|
139
|
+
case 'yes':
|
|
140
|
+
errorOnUnresolvedConflict = true;
|
|
141
|
+
break;
|
|
142
|
+
case 'no':
|
|
143
|
+
errorOnUnresolvedConflict = false;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
133
146
|
const params = {
|
|
134
147
|
createNewKeys: true,
|
|
135
148
|
forceMode: opts.forceMode,
|
|
136
149
|
overrideKeyDescriptions: opts.overrideKeyDescriptions,
|
|
137
150
|
convertPlaceholdersToIcu: opts.convertPlaceholdersToIcu,
|
|
138
151
|
tagNewKeys: (_d = opts.tagNewKeys) !== null && _d !== void 0 ? _d : [],
|
|
152
|
+
overrideMode: (_e = opts.overrideMode) !== null && _e !== void 0 ? _e : 'RECOMMENDED',
|
|
139
153
|
fileMappings: files.map((f) => {
|
|
140
154
|
var _a;
|
|
141
155
|
const format = mapImportFormat(opts.format, extname(f.name));
|
|
@@ -153,30 +167,34 @@ const pushHandler = (config) => function () {
|
|
|
153
167
|
};
|
|
154
168
|
}),
|
|
155
169
|
removeOtherKeys: opts.removeOtherKeys,
|
|
170
|
+
errorOnUnresolvedConflict: errorOnUnresolvedConflict,
|
|
156
171
|
};
|
|
157
|
-
|
|
172
|
+
let attempt = yield loading('Importing...', importData(opts.client, {
|
|
158
173
|
files,
|
|
159
174
|
params,
|
|
160
175
|
}));
|
|
161
|
-
if (
|
|
162
|
-
if (
|
|
176
|
+
if (attempt.error) {
|
|
177
|
+
if (attempt.error.code === 'existing_language_not_selected') {
|
|
163
178
|
handleMappingError(params.fileMappings);
|
|
164
179
|
}
|
|
165
|
-
if (
|
|
166
|
-
handleLoadableError(
|
|
180
|
+
if (attempt.error.code !== 'conflict_is_not_resolved') {
|
|
181
|
+
handleLoadableError(attempt);
|
|
167
182
|
}
|
|
168
183
|
const forceMode = yield promptConflicts(opts);
|
|
169
|
-
|
|
184
|
+
attempt = yield loading('Overriding...', importData(opts.client, {
|
|
170
185
|
files,
|
|
171
186
|
params: Object.assign(Object.assign({}, params), { forceMode }),
|
|
172
187
|
}));
|
|
173
|
-
handleLoadableError(
|
|
188
|
+
handleLoadableError(attempt);
|
|
189
|
+
}
|
|
190
|
+
if ((_g = (_f = attempt.data) === null || _f === void 0 ? void 0 : _f.unresolvedConflicts) === null || _g === void 0 ? void 0 : _g.length) {
|
|
191
|
+
printUnresolvedConflicts(attempt.data.unresolvedConflicts, false);
|
|
174
192
|
}
|
|
175
193
|
success('Done!');
|
|
176
194
|
});
|
|
177
195
|
};
|
|
178
196
|
export default (config) => {
|
|
179
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
197
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
180
198
|
return new Command()
|
|
181
199
|
.name('push')
|
|
182
200
|
.description('Pushes translations to Tolgee')
|
|
@@ -191,5 +209,11 @@ export default (config) => {
|
|
|
191
209
|
.addOption(new Option('-n, --namespaces <namespaces...>', 'Specifies which namespaces should be pushed (see push.files in config).').default((_h = config.push) === null || _h === void 0 ? void 0 : _h.namespaces))
|
|
192
210
|
.addOption(new Option('--tag-new-keys <tags...>', 'Specify tags that will be added to newly created keys.').default((_j = config.push) === null || _j === void 0 ? void 0 : _j.tagNewKeys))
|
|
193
211
|
.addOption(new Option('--remove-other-keys', 'Remove keys which are not present in the import (within imported namespaces).').default((_k = config.push) === null || _k === void 0 ? void 0 : _k.removeOtherKeys))
|
|
212
|
+
.addOption(new Option('--override-mode <mode>', 'Specifies what is considered non-overridable translation.')
|
|
213
|
+
.choices(['RECOMMENDED', 'ALL'])
|
|
214
|
+
.default((_l = config.push) === null || _l === void 0 ? void 0 : _l.overrideMode))
|
|
215
|
+
.addOption(new Option('--error-on-unresolved-conflict <choice>', 'Fail the whole import if there are unresolved conflicts.')
|
|
216
|
+
.choices(['yes', 'no', 'auto'])
|
|
217
|
+
.default((_o = (_m = config.push) === null || _m === void 0 ? void 0 : _m.errorOnUnresolvedConflict) !== null && _o !== void 0 ? _o : 'auto'))
|
|
194
218
|
.action(pushHandler(config));
|
|
195
219
|
};
|
|
@@ -5,15 +5,20 @@ import ansi from 'ansi-colors';
|
|
|
5
5
|
* @param key The key to print.
|
|
6
6
|
* @param deletion True if the key is about to be deleted.
|
|
7
7
|
*/
|
|
8
|
-
export function printKey(key, deletion) {
|
|
8
|
+
export function printKey(key, deletion, color, note) {
|
|
9
|
+
const colorFunc = color !== null && color !== void 0 ? color : (deletion ? ansi.red : ansi.green);
|
|
9
10
|
const namespace = key.namespace
|
|
10
11
|
? ` ${ansi.italic(`(namespace: ${key.namespace})`)}`
|
|
11
12
|
: '';
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const renderedNote = note ? ` ${note}` : '';
|
|
14
|
+
if (deletion === undefined) {
|
|
15
|
+
console.log(`${colorFunc(`${key.keyName}`)}${namespace}${renderedNote}`);
|
|
16
|
+
}
|
|
17
|
+
else if (deletion) {
|
|
18
|
+
console.log(`${colorFunc(`- ${key.keyName}`)}${namespace}${renderedNote}`);
|
|
14
19
|
}
|
|
15
20
|
else {
|
|
16
|
-
console.log(`${
|
|
21
|
+
console.log(`${colorFunc(`+ ${key.keyName}`)}${namespace}${renderedNote}`);
|
|
17
22
|
}
|
|
18
23
|
}
|
|
19
24
|
/**
|
|
@@ -79,19 +79,20 @@ export function savePak(instance, project, pak) {
|
|
|
79
79
|
return storePak(store, instance, project, pak);
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
export function getApiKey(
|
|
82
|
+
export function getApiKey(apiUrl, projectId) {
|
|
83
83
|
return __awaiter(this, void 0, void 0, function* () {
|
|
84
84
|
var _a;
|
|
85
85
|
const store = yield loadStore();
|
|
86
|
-
|
|
86
|
+
const apiUrlObj = new URL(apiUrl);
|
|
87
|
+
if (!store[apiUrlObj.hostname]) {
|
|
87
88
|
return null;
|
|
88
89
|
}
|
|
89
|
-
const scopedStore = store[
|
|
90
|
+
const scopedStore = store[apiUrlObj.hostname];
|
|
90
91
|
if (scopedStore.user) {
|
|
91
92
|
if (scopedStore.user.expires !== 0 &&
|
|
92
93
|
Date.now() > scopedStore.user.expires) {
|
|
93
|
-
warn(`Your personal access token for ${
|
|
94
|
-
yield storePat(store,
|
|
94
|
+
warn(`Your personal access token for ${apiUrlObj.hostname} expired.`);
|
|
95
|
+
yield storePat(store, apiUrlObj, undefined);
|
|
95
96
|
return null;
|
|
96
97
|
}
|
|
97
98
|
return scopedStore.user.token;
|
|
@@ -102,8 +103,8 @@ export function getApiKey(instance, projectId) {
|
|
|
102
103
|
const pak = (_a = scopedStore.projects) === null || _a === void 0 ? void 0 : _a[projectId.toString(10)];
|
|
103
104
|
if (pak) {
|
|
104
105
|
if (pak.expires !== 0 && Date.now() > pak.expires) {
|
|
105
|
-
warn(`Your project API key for project #${projectId} on ${
|
|
106
|
-
yield removePak(store,
|
|
106
|
+
warn(`Your project API key for project #${projectId} on ${apiUrlObj.hostname} expired.`);
|
|
107
|
+
yield removePak(store, apiUrlObj, projectId);
|
|
107
108
|
return null;
|
|
108
109
|
}
|
|
109
110
|
return pak.token;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// In-memory storage for ETag data
|
|
2
|
+
// In the future we can use filesystem to store it
|
|
3
|
+
const etagStorage = new Map();
|
|
4
|
+
export function getETag(projectId) {
|
|
5
|
+
const data = etagStorage.get(projectId);
|
|
6
|
+
if (!data) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
return data.etag;
|
|
10
|
+
}
|
|
11
|
+
export function setETag(projectId, etag) {
|
|
12
|
+
etagStorage.set(projectId, {
|
|
13
|
+
etag,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export const getStackTrace = () => {
|
|
2
|
-
|
|
3
|
-
Error.captureStackTrace
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
var _a, _b;
|
|
3
|
+
if (typeof Error.captureStackTrace === 'function') {
|
|
4
|
+
const obj = {};
|
|
5
|
+
Error.captureStackTrace(obj, getStackTrace);
|
|
6
|
+
return (_a = obj.stack) !== null && _a !== void 0 ? _a : '';
|
|
7
|
+
}
|
|
8
|
+
return (_b = new Error().stack) !== null && _b !== void 0 ? _b : '';
|
|
7
9
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { debug } from './logger.js';
|
|
2
|
+
/**
|
|
3
|
+
Compares two semantic versions (e.g., "3.143.0" vs "3.142.0")
|
|
4
|
+
Returns true if version1 >= version2
|
|
5
|
+
*/
|
|
6
|
+
export function isVersionAtLeast(required, current) {
|
|
7
|
+
debug(`Checking that server version ${current} is at least ${required}`);
|
|
8
|
+
if (current === '??') {
|
|
9
|
+
// local build, lets assume it's supported
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
// Strip optional 'v' prefix from both versions
|
|
13
|
+
const cleanRequired = required.startsWith('v') ? required.slice(1) : required;
|
|
14
|
+
const cleanServerVersion = current.startsWith('v')
|
|
15
|
+
? current.slice(1)
|
|
16
|
+
: current;
|
|
17
|
+
const requiredParts = cleanRequired.split('.').map(Number);
|
|
18
|
+
const currentParts = cleanServerVersion.split('.').map(Number);
|
|
19
|
+
const maxLength = Math.max(requiredParts.length, currentParts.length);
|
|
20
|
+
for (let i = 0; i < maxLength; i++) {
|
|
21
|
+
const requiredPart = requiredParts[i] || 0;
|
|
22
|
+
const currentPart = currentParts[i] || 0;
|
|
23
|
+
if (currentPart > requiredPart)
|
|
24
|
+
return true;
|
|
25
|
+
if (currentPart < requiredPart)
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
package/dist/utils/logger.js
CHANGED
|
@@ -113,7 +113,7 @@ export function exitWithError(err) {
|
|
|
113
113
|
export function loading(comment, promise) {
|
|
114
114
|
if (!process.stdout.isTTY) {
|
|
115
115
|
// Simple stdout without animations
|
|
116
|
-
process.stdout.write(comment);
|
|
116
|
+
process.stdout.write(comment + `\n`);
|
|
117
117
|
promise.then(() => process.stdout.write(` ✓ Success\n`), () => process.stdout.write(` ✗ Failure\n`));
|
|
118
118
|
return promise;
|
|
119
119
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import ansi from 'ansi-colors';
|
|
2
|
+
export function renderKey(key, note) {
|
|
3
|
+
const colorFunc = ansi.yellow;
|
|
4
|
+
const namespace = key.namespace
|
|
5
|
+
? ` ${ansi.italic(`(namespace: ${key.namespace})`)}`
|
|
6
|
+
: '';
|
|
7
|
+
const renderedNote = note ? ` ${note}` : '';
|
|
8
|
+
return `${colorFunc(`${key.keyName}`)}${namespace}${renderedNote}`;
|
|
9
|
+
}
|
|
10
|
+
export function getUnresolvedConflictsMessage(translations, isError) {
|
|
11
|
+
const someOverridable = Boolean(translations.find((c) => c.isOverridable));
|
|
12
|
+
const result = [''];
|
|
13
|
+
result.push(`🟡 Some translations cannot be updated:`);
|
|
14
|
+
translations.forEach((c) => {
|
|
15
|
+
result.push(renderKey({ keyName: c.keyName, namespace: c.keyNamespace }, `${c.language}` + (c.isOverridable ? ' (overridable)' : '')));
|
|
16
|
+
});
|
|
17
|
+
result.push('');
|
|
18
|
+
if (someOverridable) {
|
|
19
|
+
result.push('HINT: Overridable translations can be updated with the `--override-mode ALL`');
|
|
20
|
+
result.push('');
|
|
21
|
+
}
|
|
22
|
+
return result.join('\n');
|
|
23
|
+
}
|
|
24
|
+
export function printUnresolvedConflicts(translations, isError) {
|
|
25
|
+
console.log(getUnresolvedConflictsMessage(translations, isError));
|
|
26
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { debug, error } from '../logger.js';
|
|
11
|
+
import { isVersionAtLeast } from '../isVersionAtLeast.js';
|
|
12
|
+
export function AuthErrorHandler(client) {
|
|
13
|
+
function handleAuthErrors(err, shutdown) {
|
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
var _a, _b;
|
|
16
|
+
if (((_a = err === null || err === void 0 ? void 0 : err.headers) === null || _a === void 0 ? void 0 : _a.message) == 'Unauthenticated') {
|
|
17
|
+
yield printUnauthenticatedError();
|
|
18
|
+
shutdown();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (((_b = err === null || err === void 0 ? void 0 : err.headers) === null || _b === void 0 ? void 0 : _b.message) == 'Forbidden') {
|
|
22
|
+
error("You're not authorized. Insufficient permissions?");
|
|
23
|
+
shutdown();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function printUnauthenticatedError() {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
const { isSupported, serverVersion } = yield isAppSupportedVersion(client);
|
|
31
|
+
if (isSupported) {
|
|
32
|
+
error("You're not authenticated. Invalid API key?");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
error(`Server version ${serverVersion} does not support CLI watch mode. Please update your Tolgee server to version ${REQUIRED_VERSION} or higher.`);
|
|
36
|
+
return;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function isAppSupportedVersion(client) {
|
|
40
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
const serverVersion = yield getTolgeeServerVersion(client);
|
|
42
|
+
if (!serverVersion) {
|
|
43
|
+
debug('Could not determine server version');
|
|
44
|
+
return {
|
|
45
|
+
isSupported: false,
|
|
46
|
+
serverVersion: serverVersion,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
isSupported: isVersionAtLeast(REQUIRED_VERSION, serverVersion),
|
|
51
|
+
serverVersion: serverVersion,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function getTolgeeServerVersion(client) {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
var _a;
|
|
58
|
+
try {
|
|
59
|
+
const config = yield client.GET('/api/public/configuration');
|
|
60
|
+
const version = (_a = config.response) === null || _a === void 0 ? void 0 : _a.headers.get('x-tolgee-version');
|
|
61
|
+
return version || null;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
debug('Failed to get server version: ' + error);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return Object.freeze({ handleAuthErrors });
|
|
70
|
+
}
|
|
71
|
+
const REQUIRED_VERSION = '3.143.0';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { WebsocketClient } from '../../client/WebsocketClient.js';
|
|
11
|
+
import { debug, error, info, success } from '../logger.js';
|
|
12
|
+
import { setETag } from '../eTagStorage.js';
|
|
13
|
+
import { clearInterval } from 'node:timers';
|
|
14
|
+
import { AuthErrorHandler } from './AuthErrorHandler.js';
|
|
15
|
+
// Polling interval as backup when WebSocket is not available (in seconds)
|
|
16
|
+
const POLLING_INTERVAL_SECONDS = 60;
|
|
17
|
+
// Debounce delay for schedulePull in milliseconds
|
|
18
|
+
const SCHEDULE_PULL_DEBOUNCE_MS = 500;
|
|
19
|
+
export function startWatching(options) {
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
const { apiUrl, apiKey, projectId, doPull, client } = options;
|
|
22
|
+
// Watch mode using WebsocketClient on translation-data-modified
|
|
23
|
+
info('Watching for translation changes... Press Ctrl+C to stop.');
|
|
24
|
+
let pulling = false;
|
|
25
|
+
/**
|
|
26
|
+
* Pending handles the situation when changes are detected while the pull is
|
|
27
|
+
* already in progress.
|
|
28
|
+
*/
|
|
29
|
+
let pending = false;
|
|
30
|
+
let pendingEtag;
|
|
31
|
+
let debounceTimer;
|
|
32
|
+
let pollingTimer;
|
|
33
|
+
let lastExecutionTime = 0;
|
|
34
|
+
const executePull = (etag) => __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
if (pulling) {
|
|
36
|
+
pending = true;
|
|
37
|
+
pendingEtag = etag;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
pulling = true;
|
|
41
|
+
lastExecutionTime = Date.now();
|
|
42
|
+
try {
|
|
43
|
+
yield doPull();
|
|
44
|
+
// Store ETag after successful pull
|
|
45
|
+
if (etag) {
|
|
46
|
+
setETag(projectId, etag);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
error('Error during pull: ' + e.message);
|
|
51
|
+
debug(e);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
pulling = false;
|
|
55
|
+
// If there was a pending pull (data changed when pulling), execute it now
|
|
56
|
+
if (pending) {
|
|
57
|
+
pending = false;
|
|
58
|
+
const capturedEtag = pendingEtag;
|
|
59
|
+
pendingEtag = undefined;
|
|
60
|
+
void executePull(capturedEtag);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const schedulePull = (etag) => __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const timeSinceLastExecution = now - lastExecutionTime;
|
|
67
|
+
// If last execution was more than 500ms ago, execute immediately
|
|
68
|
+
if (timeSinceLastExecution >= SCHEDULE_PULL_DEBOUNCE_MS) {
|
|
69
|
+
yield executePull(etag);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Otherwise, schedule the update with debounce
|
|
73
|
+
if (debounceTimer)
|
|
74
|
+
clearTimeout(debounceTimer);
|
|
75
|
+
debounceTimer = setTimeout(() => executePull(etag), SCHEDULE_PULL_DEBOUNCE_MS);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// Polling mechanism as backup
|
|
79
|
+
const startPolling = () => {
|
|
80
|
+
if (pollingTimer) {
|
|
81
|
+
clearInterval(pollingTimer);
|
|
82
|
+
}
|
|
83
|
+
const poll = () => __awaiter(this, void 0, void 0, function* () {
|
|
84
|
+
if (pulling)
|
|
85
|
+
return;
|
|
86
|
+
debug('Polling for changes...');
|
|
87
|
+
yield schedulePull();
|
|
88
|
+
});
|
|
89
|
+
// Set up periodic polling
|
|
90
|
+
pollingTimer = setInterval(poll, POLLING_INTERVAL_SECONDS * 1000);
|
|
91
|
+
};
|
|
92
|
+
const wsClient = WebsocketClient({
|
|
93
|
+
serverUrl: new URL(apiUrl).origin,
|
|
94
|
+
authentication: { apiKey: apiKey },
|
|
95
|
+
onConnected: () => {
|
|
96
|
+
debug('WebSocket connected and subscriptions active. Performing initial pull...');
|
|
97
|
+
schedulePull();
|
|
98
|
+
},
|
|
99
|
+
onError: (error) => {
|
|
100
|
+
AuthErrorHandler(client)
|
|
101
|
+
.handleAuthErrors(error, shutdown)
|
|
102
|
+
.catch((err) => {
|
|
103
|
+
debug('Error in handleAuthErrors: ' + err);
|
|
104
|
+
});
|
|
105
|
+
// Non-fatal: just inform
|
|
106
|
+
info('Websocket error encountered. Reconnecting...');
|
|
107
|
+
},
|
|
108
|
+
onConnectionClose: () => {
|
|
109
|
+
info('Websocket connection closed. Attempting to reconnect...');
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
const channel = `/projects/${projectId}/translation-data-modified`;
|
|
113
|
+
let unsubscribe;
|
|
114
|
+
function subscribe() {
|
|
115
|
+
unsubscribe = wsClient.subscribe(channel, () => {
|
|
116
|
+
debug('Data change detected by websocket. Pulling now... ');
|
|
117
|
+
schedulePull();
|
|
118
|
+
startPolling();
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const shutdown = () => {
|
|
122
|
+
try {
|
|
123
|
+
unsubscribe === null || unsubscribe === void 0 ? void 0 : unsubscribe();
|
|
124
|
+
}
|
|
125
|
+
catch (_a) {
|
|
126
|
+
// Ignore errors during shutdown cleanup
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
wsClient.deactivate();
|
|
130
|
+
}
|
|
131
|
+
catch (_b) {
|
|
132
|
+
// Ignore errors during shutdown cleanup
|
|
133
|
+
}
|
|
134
|
+
if (pollingTimer) {
|
|
135
|
+
clearInterval(pollingTimer);
|
|
136
|
+
}
|
|
137
|
+
if (debounceTimer) {
|
|
138
|
+
clearTimeout(debounceTimer);
|
|
139
|
+
}
|
|
140
|
+
success('Stopped watching. Bye!');
|
|
141
|
+
process.exit(0);
|
|
142
|
+
};
|
|
143
|
+
process.on('SIGINT', shutdown);
|
|
144
|
+
process.on('SIGTERM', shutdown);
|
|
145
|
+
subscribe();
|
|
146
|
+
// Keep process alive
|
|
147
|
+
yield new Promise(() => { });
|
|
148
|
+
});
|
|
149
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tolgee/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0-rc.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A tool to interact with the Tolgee Platform through CLI",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"author": "Jan Cizmar",
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
+
"@stomp/stompjs": "^7.1.1",
|
|
33
34
|
"ajv": "^8.17.1",
|
|
34
35
|
"ansi-colors": "^4.1.3",
|
|
35
36
|
"base32-decode": "^1.0.0",
|
|
@@ -38,21 +39,29 @@
|
|
|
38
39
|
"json5": "^2.2.3",
|
|
39
40
|
"jsonschema": "^1.5.0",
|
|
40
41
|
"openapi-fetch": "0.13.1",
|
|
42
|
+
"sockjs-client": "^1.6.1",
|
|
41
43
|
"tinyglobby": "^0.2.12",
|
|
42
44
|
"unescape-js": "^1.1.4",
|
|
43
45
|
"vscode-oniguruma": "^2.0.1",
|
|
44
46
|
"vscode-textmate": "^9.1.0",
|
|
47
|
+
"ws": "^8.18.3",
|
|
45
48
|
"yauzl": "^3.2.0"
|
|
46
49
|
},
|
|
47
50
|
"devDependencies": {
|
|
48
51
|
"@eslint/js": "^9.16.0",
|
|
52
|
+
"semantic-release": "^25.0.2",
|
|
53
|
+
"@semantic-release/npm": "^13.1.3",
|
|
49
54
|
"@semantic-release/changelog": "^6.0.3",
|
|
50
55
|
"@semantic-release/git": "^10.0.1",
|
|
56
|
+
"@semantic-release/github": "^12.0.2",
|
|
57
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
58
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
51
59
|
"@tsconfig/node18": "^18.2.4",
|
|
52
60
|
"@tsconfig/recommended": "^1.0.8",
|
|
53
61
|
"@types/eslint__js": "^8.42.3",
|
|
54
62
|
"@types/js-yaml": "^4.0.9",
|
|
55
63
|
"@types/node": "^22.10.1",
|
|
64
|
+
"@types/sockjs-client": "^1.5.4",
|
|
56
65
|
"@types/yauzl": "^2.10.3",
|
|
57
66
|
"cross-env": "^7.0.3",
|
|
58
67
|
"eslint": "^9.16.0",
|
|
@@ -64,7 +73,6 @@
|
|
|
64
73
|
"openapi-typescript": "^7.4.4",
|
|
65
74
|
"premove": "^4.0.0",
|
|
66
75
|
"prettier": "^3.4.1",
|
|
67
|
-
"semantic-release": "^24.2.0",
|
|
68
76
|
"tree-cli": "^0.6.7",
|
|
69
77
|
"tsx": "^4.19.2",
|
|
70
78
|
"typescript": "~5.6.3",
|
package/schema.json
CHANGED
|
@@ -90,6 +90,12 @@
|
|
|
90
90
|
"removeOtherKeys": {
|
|
91
91
|
"description": "Remove keys which are not present in the import (within imported namespaces).",
|
|
92
92
|
"type": "boolean"
|
|
93
|
+
},
|
|
94
|
+
"errorOnUnresolvedConflict": {
|
|
95
|
+
"$ref": "#/$defs/errorOnUnresolvedConflict"
|
|
96
|
+
},
|
|
97
|
+
"overrideMode": {
|
|
98
|
+
"$ref": "#/$defs/overrideMode"
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
},
|
|
@@ -251,6 +257,16 @@
|
|
|
251
257
|
"type": "string",
|
|
252
258
|
"enum": ["OVERRIDE", "KEEP", "NO_FORCE"]
|
|
253
259
|
},
|
|
260
|
+
"overrideMode": {
|
|
261
|
+
"description": "Specifies what is considered non-overridable translation: \n - RECOMMENDED - protected reviewed translations are considered as non-overridable\n - ALL - translations that user has permissions for",
|
|
262
|
+
"type": "string",
|
|
263
|
+
"enum": ["ALL", "RECOMMENDED"]
|
|
264
|
+
},
|
|
265
|
+
"errorOnUnresolvedConflict": {
|
|
266
|
+
"description": "Fail the whole import if there are unresolved conflicts in import: \n - yes - fail if any unresolved conflict is present\n no - don't fail and just print unresolved conflicts\n auto - fails when when forceMode=KEEP, otherwise doesn't fail",
|
|
267
|
+
"type": "string",
|
|
268
|
+
"enum": ["yes", "no", "auto"]
|
|
269
|
+
},
|
|
254
270
|
"path": {
|
|
255
271
|
"description": "File glob specifying which files to include.",
|
|
256
272
|
"type": "string"
|