@tolgee/cli 2.12.1 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/errorFromLoadable.js +8 -2
- package/dist/commands/pull.js +1 -0
- package/dist/commands/push.js +33 -9
- package/dist/commands/sync/sync.js +19 -4
- package/dist/commands/sync/syncUtils.js +13 -9
- package/dist/extractor/runner.js +1 -2
- package/dist/utils/getStackTrace.js +7 -5
- package/dist/utils/printFailedKeys.js +26 -0
- package/package.json +1 -1
- package/schema.json +24 -1
|
@@ -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/pull.js
CHANGED
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
|
};
|
|
@@ -24,6 +24,7 @@ function backup(client, dest) {
|
|
|
24
24
|
supportArrays: false,
|
|
25
25
|
filterState: ['UNTRANSLATED', 'TRANSLATED', 'REVIEWED'],
|
|
26
26
|
structureDelimiter: '',
|
|
27
|
+
escapeHtml: false,
|
|
27
28
|
});
|
|
28
29
|
handleLoadableError(loadable);
|
|
29
30
|
const blob = loadable.data;
|
|
@@ -46,7 +47,7 @@ function askForConfirmation(keys, operation) {
|
|
|
46
47
|
}
|
|
47
48
|
const syncHandler = (config) => function () {
|
|
48
49
|
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
-
var _a, _b, _c;
|
|
50
|
+
var _a, _b, _c, _d, _e, _f;
|
|
50
51
|
const opts = this.optsWithGlobals();
|
|
51
52
|
const rawKeys = yield loading('Analyzing code...', extractKeysOfFiles(opts));
|
|
52
53
|
const warnCount = dumpWarnings(rawKeys);
|
|
@@ -55,11 +56,24 @@ const syncHandler = (config) => function () {
|
|
|
55
56
|
process.exit(1);
|
|
56
57
|
}
|
|
57
58
|
const localKeys = filterExtractionResult(rawKeys);
|
|
59
|
+
if ((_a = opts.namespaces) === null || _a === void 0 ? void 0 : _a.length) {
|
|
60
|
+
for (const namespace of Object.keys(localKeys)) {
|
|
61
|
+
if (!((_b = opts.namespaces) === null || _b === void 0 ? void 0 : _b.includes(namespace))) {
|
|
62
|
+
localKeys[namespace].clear();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
58
66
|
const allKeysLoadable = yield opts.client.GET('/v2/projects/{projectId}/all-keys', {
|
|
59
67
|
params: { path: { projectId: opts.client.getProjectId() } },
|
|
60
68
|
});
|
|
61
69
|
handleLoadableError(allKeysLoadable);
|
|
62
|
-
|
|
70
|
+
let remoteKeys = (_e = (_d = (_c = allKeysLoadable.data) === null || _c === void 0 ? void 0 : _c._embedded) === null || _d === void 0 ? void 0 : _d.keys) !== null && _e !== void 0 ? _e : [];
|
|
71
|
+
if ((_f = opts.namespaces) === null || _f === void 0 ? void 0 : _f.length) {
|
|
72
|
+
remoteKeys = remoteKeys.filter((key) => {
|
|
73
|
+
var _a, _b;
|
|
74
|
+
return (_a = opts.namespaces) === null || _a === void 0 ? void 0 : _a.includes((_b = key.namespace) !== null && _b !== void 0 ? _b : '');
|
|
75
|
+
});
|
|
76
|
+
}
|
|
63
77
|
const diff = compareKeys(localKeys, remoteKeys);
|
|
64
78
|
if (!diff.added.length && !diff.removed.length) {
|
|
65
79
|
console.log(ansi.green('Your code project is in sync with the associated Tolgee project!'));
|
|
@@ -127,14 +141,15 @@ const syncHandler = (config) => function () {
|
|
|
127
141
|
});
|
|
128
142
|
};
|
|
129
143
|
export default (config) => {
|
|
130
|
-
var _a, _b, _c, _d, _e, _f;
|
|
144
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
131
145
|
return new Command()
|
|
132
146
|
.name('sync')
|
|
133
147
|
.description('Synchronizes the keys in your code project and in the Tolgee project, by creating missing keys and optionally deleting unused ones. For a dry-run, use `tolgee compare`.')
|
|
134
148
|
.addOption(new Option('-B, --backup <path>', 'Store translation files backup (only translation files, not states, comments, tags, etc.). If something goes wrong, the backup can be used to restore the project to its previous state.').default((_b = (_a = config.sync) === null || _a === void 0 ? void 0 : _a.backup) !== null && _b !== void 0 ? _b : false))
|
|
135
149
|
.addOption(new Option('--continue-on-warning', 'Set this flag to continue the sync if warnings are detected during string extraction. By default, as warnings may indicate an invalid extraction, the CLI will abort the sync.').default((_d = (_c = config.sync) === null || _c === void 0 ? void 0 : _c.continueOnWarning) !== null && _d !== void 0 ? _d : false))
|
|
150
|
+
.addOption(new Option('-n, --namespaces <namespaces...>', 'Specifies which namespaces should be synchronized.').default((_e = config.sync) === null || _e === void 0 ? void 0 : _e.namespaces))
|
|
136
151
|
.addOption(new Option('-Y, --yes', 'Skip prompts and automatically say yes to them. You will not be asked for confirmation before creating/deleting keys.').default(false))
|
|
137
|
-
.addOption(new Option('--remove-unused', 'Delete unused keys from the Tolgee project.').default((
|
|
152
|
+
.addOption(new Option('--remove-unused', 'Delete unused keys from the Tolgee project (within selected namespaces if specified).').default((_g = (_f = config.sync) === null || _f === void 0 ? void 0 : _f.removeUnused) !== null && _g !== void 0 ? _g : false))
|
|
138
153
|
.option('--tag-new-keys <tags...>', 'Specify tags that will be added to newly created keys.')
|
|
139
154
|
.action(syncHandler(config));
|
|
140
155
|
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { NullNamespace } from '../../extractor/runner.js';
|
|
2
1
|
import ansi from 'ansi-colors';
|
|
3
2
|
/**
|
|
4
3
|
* Prints information about a key, with coloring and formatting.
|
|
@@ -6,15 +5,20 @@ import ansi from 'ansi-colors';
|
|
|
6
5
|
* @param key The key to print.
|
|
7
6
|
* @param deletion True if the key is about to be deleted.
|
|
8
7
|
*/
|
|
9
|
-
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);
|
|
10
10
|
const namespace = key.namespace
|
|
11
11
|
? ` ${ansi.italic(`(namespace: ${key.namespace})`)}`
|
|
12
12
|
: '';
|
|
13
|
-
|
|
14
|
-
|
|
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}`);
|
|
15
19
|
}
|
|
16
20
|
else {
|
|
17
|
-
console.log(`${
|
|
21
|
+
console.log(`${colorFunc(`+ ${key.keyName}`)}${namespace}${renderedNote}`);
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
@@ -30,18 +34,18 @@ export function compareKeys(local, remote) {
|
|
|
30
34
|
const result = { added: [], removed: [] };
|
|
31
35
|
// Deleted keys
|
|
32
36
|
for (const remoteKey of remote) {
|
|
33
|
-
const namespace = remoteKey.namespace ||
|
|
37
|
+
const namespace = remoteKey.namespace || '';
|
|
34
38
|
const keyExists = (_a = local[namespace]) === null || _a === void 0 ? void 0 : _a.delete(remoteKey.name);
|
|
35
39
|
if (!keyExists) {
|
|
36
40
|
result.removed.push({
|
|
37
41
|
id: remoteKey.id,
|
|
38
42
|
keyName: remoteKey.name,
|
|
39
|
-
namespace: remoteKey.namespace ||
|
|
43
|
+
namespace: remoteKey.namespace || '',
|
|
40
44
|
});
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
// Added keys
|
|
44
|
-
const namespaces = [
|
|
48
|
+
const namespaces = [...Object.keys(local).sort()];
|
|
45
49
|
for (const namespace of namespaces) {
|
|
46
50
|
if (namespace in local && local[namespace].size) {
|
|
47
51
|
const keys = local[namespace];
|
|
@@ -49,7 +53,7 @@ export function compareKeys(local, remote) {
|
|
|
49
53
|
for (const keyName of keyNames) {
|
|
50
54
|
result.added.push({
|
|
51
55
|
keyName: keyName,
|
|
52
|
-
namespace: namespace
|
|
56
|
+
namespace: namespace || '',
|
|
53
57
|
defaultValue: keys.get(keyName) || undefined,
|
|
54
58
|
});
|
|
55
59
|
}
|
package/dist/extractor/runner.js
CHANGED
|
@@ -11,7 +11,6 @@ import { glob } from 'tinyglobby';
|
|
|
11
11
|
import { extname } from 'path';
|
|
12
12
|
import { callWorker } from './worker.js';
|
|
13
13
|
import { exitWithError } from '../utils/logger.js';
|
|
14
|
-
export const NullNamespace = Symbol('namespace.null');
|
|
15
14
|
function parseVerbose(v) {
|
|
16
15
|
return Array.isArray(v) ? v : v ? [] : undefined;
|
|
17
16
|
}
|
|
@@ -101,7 +100,7 @@ export function filterExtractionResult(data) {
|
|
|
101
100
|
const result = Object.create(null);
|
|
102
101
|
for (const { keys } of data.values()) {
|
|
103
102
|
for (const key of keys) {
|
|
104
|
-
const namespace = key.namespace ||
|
|
103
|
+
const namespace = key.namespace || '';
|
|
105
104
|
if (!(namespace in result)) {
|
|
106
105
|
result[namespace] = new Map();
|
|
107
106
|
}
|
|
@@ -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,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
|
+
}
|
package/package.json
CHANGED
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
|
},
|
|
@@ -165,8 +171,15 @@
|
|
|
165
171
|
"type": "boolean"
|
|
166
172
|
},
|
|
167
173
|
"removeUnused": {
|
|
168
|
-
"description": "Delete unused keys from the Tolgee project",
|
|
174
|
+
"description": "Delete unused keys from the Tolgee project (within selected namespaces if specified).",
|
|
169
175
|
"type": "boolean"
|
|
176
|
+
},
|
|
177
|
+
"namespaces": {
|
|
178
|
+
"description": "Specifies which namespaces should be synchronized.",
|
|
179
|
+
"type": "array",
|
|
180
|
+
"items": {
|
|
181
|
+
"type": "string"
|
|
182
|
+
}
|
|
170
183
|
}
|
|
171
184
|
}
|
|
172
185
|
},
|
|
@@ -244,6 +257,16 @@
|
|
|
244
257
|
"type": "string",
|
|
245
258
|
"enum": ["OVERRIDE", "KEEP", "NO_FORCE"]
|
|
246
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
|
+
},
|
|
247
270
|
"path": {
|
|
248
271
|
"description": "File glob specifying which files to include.",
|
|
249
272
|
"type": "string"
|