@tolgee/cli 2.8.4 → 2.10.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.
Files changed (56) hide show
  1. package/dist/cli.js +116 -94
  2. package/dist/client/ApiClient.js +40 -32
  3. package/dist/client/ExportClient.js +24 -11
  4. package/dist/client/ImportClient.js +25 -16
  5. package/dist/client/TolgeeClient.js +1 -5
  6. package/dist/client/errorFromLoadable.js +3 -2
  7. package/dist/client/getApiKeyInformation.js +16 -6
  8. package/dist/commands/extract/check.js +38 -27
  9. package/dist/commands/extract/print.js +46 -35
  10. package/dist/commands/login.js +39 -26
  11. package/dist/commands/pull.js +60 -43
  12. package/dist/commands/push.js +167 -117
  13. package/dist/commands/sync/compare.js +43 -31
  14. package/dist/commands/sync/sync.js +118 -99
  15. package/dist/commands/sync/syncUtils.js +2 -1
  16. package/dist/commands/tag.js +52 -38
  17. package/dist/config/credentials.js +110 -93
  18. package/dist/config/tolgeerc.js +51 -35
  19. package/dist/extractor/extractor.js +45 -31
  20. package/dist/extractor/parser/extractComment.js +1 -1
  21. package/dist/extractor/parser/generateReport.js +8 -6
  22. package/dist/extractor/parser/iterator.js +2 -1
  23. package/dist/extractor/parser/mergerMachine.js +2 -11
  24. package/dist/extractor/parser/nodeUtils.js +1 -1
  25. package/dist/extractor/parser/parser.js +4 -2
  26. package/dist/extractor/parser/rules/tNsSourceGeneral.js +1 -1
  27. package/dist/extractor/parser/tree/getTranslateProps.js +21 -16
  28. package/dist/extractor/parser/tree/getValue.js +1 -1
  29. package/dist/extractor/parser/tree/parseTag.js +1 -1
  30. package/dist/extractor/parserNgx/ParserNgx.js +1 -3
  31. package/dist/extractor/parserNgx/ngxMapper.js +2 -1
  32. package/dist/extractor/parserNgx/ngxTreeTransform.js +3 -2
  33. package/dist/extractor/parserNgx/rules/translatePipe.js +1 -1
  34. package/dist/extractor/parserReact/ParserReact.js +1 -3
  35. package/dist/extractor/parserSvelte/ParserSvelte.js +1 -3
  36. package/dist/extractor/parserVue/ParserVue.js +1 -3
  37. package/dist/extractor/parserVue/tokenMergers/hyphenPropsMerger.js +1 -4
  38. package/dist/extractor/parserVue/vueTreeTransform.js +13 -2
  39. package/dist/extractor/runner.js +53 -39
  40. package/dist/extractor/tokenizer.js +50 -35
  41. package/dist/extractor/visualizers/printTokens.js +2 -1
  42. package/dist/extractor/visualizers/visualizeRules.js +4 -7
  43. package/dist/extractor/warnings.js +3 -2
  44. package/dist/extractor/worker.js +29 -16
  45. package/dist/options.js +2 -1
  46. package/dist/utils/apiKeyList.js +31 -19
  47. package/dist/utils/ask.js +35 -21
  48. package/dist/utils/checkPathNotAFile.js +22 -11
  49. package/dist/utils/filesTemplate.js +147 -0
  50. package/dist/utils/mapExportFormat.js +2 -0
  51. package/dist/utils/mapImportFormat.js +1 -1
  52. package/dist/utils/moduleLoader.js +37 -23
  53. package/dist/utils/prepareDir.js +20 -9
  54. package/dist/utils/valueToArray.js +8 -0
  55. package/package.json +2 -2
  56. package/schema.json +20 -4
@@ -1,16 +1,27 @@
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
+ };
1
10
  import { stat } from 'fs/promises';
2
11
  import { exitWithError } from './logger.js';
3
- export async function checkPathNotAFile(path) {
4
- try {
5
- const stats = await stat(path);
6
- if (!stats.isDirectory()) {
7
- exitWithError('The specified path already exists and is not a directory.');
12
+ export function checkPathNotAFile(path) {
13
+ return __awaiter(this, void 0, void 0, function* () {
14
+ try {
15
+ const stats = yield stat(path);
16
+ if (!stats.isDirectory()) {
17
+ exitWithError('The specified path already exists and is not a directory.');
18
+ }
8
19
  }
9
- }
10
- catch (e) {
11
- // Ignore "file doesn't exist" error
12
- if (e.code !== 'ENOENT') {
13
- throw e;
20
+ catch (e) {
21
+ // Ignore "file doesn't exist" error
22
+ if (e.code !== 'ENOENT') {
23
+ throw e;
24
+ }
14
25
  }
15
- }
26
+ });
16
27
  }
@@ -0,0 +1,147 @@
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 { glob } from 'tinyglobby';
11
+ import { exitWithError } from './logger.js';
12
+ const GLOB_EXISTING_DOUBLE_STAR = /(\*\*)/g;
13
+ const GLOB_EXISTING_STAR = /(\*)/g;
14
+ const GLOB_EXISTING_ENUM = /\{([^}]*?,[^}]*?)\}/g;
15
+ const PLACEHOLDER_DOUBLE_ASTERISK = '__double_asterisk';
16
+ const PLACEHOLDER_ASTERISK = '__asterisk';
17
+ const PLACEHOLDER_ENUM_PREFIX = '__enum:';
18
+ export class FileMatcherException extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = this.constructor.name;
22
+ }
23
+ }
24
+ function splitToParts(template) {
25
+ return template.split(/(\{.*?\})/g).filter(Boolean);
26
+ }
27
+ function getVariableName(part) {
28
+ if (part.startsWith('{') && part.endsWith('}')) {
29
+ return part.substring(1, part.length - 1).trim();
30
+ }
31
+ return false;
32
+ }
33
+ export function sanitizeTemplate(template) {
34
+ var _a;
35
+ let value = template;
36
+ const matchedEnums = [...(((_a = value.match(GLOB_EXISTING_ENUM)) === null || _a === void 0 ? void 0 : _a.values()) || [])];
37
+ matchedEnums.forEach((val) => {
38
+ value = value.replace(val, `{${PLACEHOLDER_ENUM_PREFIX}${getVariableName(val)}}`);
39
+ });
40
+ value = value.replaceAll(GLOB_EXISTING_DOUBLE_STAR, '{' + PLACEHOLDER_DOUBLE_ASTERISK + '}');
41
+ value = value.replaceAll(GLOB_EXISTING_STAR, '{' + PLACEHOLDER_ASTERISK + '}');
42
+ return value;
43
+ }
44
+ export function getFileMatcher(file, template) {
45
+ let fileName = file;
46
+ const allVariables = {};
47
+ const templateParts = splitToParts(template);
48
+ for (const [i, part] of templateParts.entries()) {
49
+ const variable = getVariableName(part);
50
+ if (!variable) {
51
+ if (fileName.startsWith(part)) {
52
+ fileName = fileName.substring(part.length);
53
+ }
54
+ else {
55
+ throw new FileMatcherException(`Unexpected part "${part}"`);
56
+ }
57
+ }
58
+ else {
59
+ const next = templateParts[i + 1];
60
+ if (next) {
61
+ const variableEnd = fileName.indexOf(next);
62
+ if (getVariableName(next) || variableEnd === -1) {
63
+ throw new FileMatcherException(`Can't have two variables without separator (${part} + ${next})`);
64
+ }
65
+ else {
66
+ allVariables[variable] = fileName.substring(0, variableEnd);
67
+ fileName = fileName.substring(variableEnd);
68
+ }
69
+ }
70
+ else {
71
+ allVariables[variable] = fileName;
72
+ }
73
+ }
74
+ }
75
+ const result = { path: file };
76
+ for (const [variable, value] of Object.entries(allVariables)) {
77
+ if (variable === 'languageTag') {
78
+ result.language = value;
79
+ }
80
+ else if (variable === 'snakeLanguageTag') {
81
+ result.language = value.replaceAll('_', '-');
82
+ }
83
+ else if (variable === 'androidLanguageTag') {
84
+ if (value[3] === 'r') {
85
+ result.language =
86
+ value.substring(0, 3) + value.substring(4, value.length);
87
+ }
88
+ else {
89
+ result.language = value;
90
+ }
91
+ }
92
+ else if (variable === 'namespace') {
93
+ result.namespace = value;
94
+ }
95
+ else if (variable !== 'extension' &&
96
+ ![PLACEHOLDER_ASTERISK, PLACEHOLDER_DOUBLE_ASTERISK].includes(variable) &&
97
+ !variable.startsWith(PLACEHOLDER_ENUM_PREFIX)) {
98
+ throw new FileMatcherException(`Unknown variable "${variable}"`);
99
+ }
100
+ }
101
+ return result;
102
+ }
103
+ export function getGlobPattern(template) {
104
+ let value = template.replaceAll(GLOB_EXISTING_DOUBLE_STAR, '{__double_asterisk}');
105
+ value = value.replaceAll(GLOB_EXISTING_STAR, '{__asterisk}');
106
+ const parts = splitToParts(value);
107
+ const globPattern = parts
108
+ .map((part) => {
109
+ const variableName = getVariableName(part);
110
+ if (variableName) {
111
+ if (variableName === PLACEHOLDER_DOUBLE_ASTERISK) {
112
+ return '**';
113
+ }
114
+ else if (variableName.startsWith(PLACEHOLDER_ENUM_PREFIX)) {
115
+ return ('{' + variableName.substring(PLACEHOLDER_ENUM_PREFIX.length) + '}');
116
+ }
117
+ else {
118
+ return '*';
119
+ }
120
+ }
121
+ else {
122
+ return part;
123
+ }
124
+ })
125
+ .join('');
126
+ return globPattern;
127
+ }
128
+ export function findFilesByTemplate(template) {
129
+ return __awaiter(this, void 0, void 0, function* () {
130
+ try {
131
+ const sanitized = sanitizeTemplate(template);
132
+ const globPattern = getGlobPattern(sanitized);
133
+ const files = yield glob(globPattern, { onlyFiles: true, absolute: true });
134
+ return files.map((file) => {
135
+ return getFileMatcher(file, sanitized);
136
+ });
137
+ }
138
+ catch (e) {
139
+ if (e instanceof FileMatcherException) {
140
+ exitWithError(e.message + ` in template ${template}`);
141
+ }
142
+ else {
143
+ throw e;
144
+ }
145
+ }
146
+ });
147
+ }
@@ -9,6 +9,8 @@ export const mapExportFormat = (format) => {
9
9
  };
10
10
  case 'APPLE_XLIFF':
11
11
  return { format: 'APPLE_XLIFF', messageFormat: 'APPLE_SPRINTF' };
12
+ case 'APPLE_XCSTRINGS':
13
+ return { format: 'APPLE_XCSTRINGS', messageFormat: 'APPLE_SPRINTF' };
12
14
  case 'COMPOSE_XML':
13
15
  return { format: 'COMPOSE_XML', messageFormat: 'JAVA_STRING_FORMAT' };
14
16
  case 'FLUTTER_ARB':
@@ -13,6 +13,6 @@ export const mapImportFormat = (format, extension) => {
13
13
  case 'JSON_TOLGEE':
14
14
  return 'JSON_ICU';
15
15
  default:
16
- return format ?? 'JSON_ICU';
16
+ return format !== null && format !== void 0 ? format : 'JSON_ICU';
17
17
  }
18
18
  };
@@ -1,28 +1,42 @@
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
+ };
1
10
  import { pathToFileURL } from 'url';
2
11
  let jiti;
3
12
  // https://github.com/eslint/eslint/blob/6f37b0747a14dfa9a9e3bdebc5caed1f39b6b0e2/lib/config/config-loader.js#L164-L197
4
- async function importTypeScript(file) {
5
- // @ts-ignore
6
- if (!!globalThis.Bun || !!globalThis.Deno) {
7
- // We're in an env that natively supports TS
8
- return import(file);
9
- }
10
- if (!jiti) {
11
- const { createJiti } = await import('jiti').catch(() => {
12
- throw new Error("The 'jiti' library is required for loading TypeScript extractors. Make sure to install it.");
13
- });
14
- jiti = createJiti(import.meta.url);
15
- }
16
- return jiti.import(file);
13
+ function importTypeScript(file) {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ // @ts-ignore
16
+ if (!!globalThis.Bun || !!globalThis.Deno) {
17
+ // We're in an env that natively supports TS
18
+ return import(file);
19
+ }
20
+ if (!jiti) {
21
+ const { createJiti } = yield import('jiti').catch(() => {
22
+ throw new Error("The 'jiti' library is required for loading TypeScript extractors. Make sure to install it.");
23
+ });
24
+ jiti = createJiti(import.meta.url);
25
+ }
26
+ return jiti.import(file);
27
+ });
17
28
  }
18
- export async function loadModule(module) {
19
- if (module.endsWith('.ts')) {
20
- return importTypeScript(module);
21
- }
22
- const fileUrl = pathToFileURL(module);
23
- const mdl = await import(fileUrl.href);
24
- if (mdl.default?.default) {
25
- return mdl.default;
26
- }
27
- return mdl;
29
+ export function loadModule(module) {
30
+ return __awaiter(this, void 0, void 0, function* () {
31
+ var _a;
32
+ if (module.endsWith('.ts')) {
33
+ return importTypeScript(module);
34
+ }
35
+ const fileUrl = pathToFileURL(module);
36
+ const mdl = yield import(fileUrl.href);
37
+ if ((_a = mdl.default) === null || _a === void 0 ? void 0 : _a.default) {
38
+ return mdl.default;
39
+ }
40
+ return mdl;
41
+ });
28
42
  }
@@ -1,12 +1,23 @@
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
+ };
1
10
  import { mkdir, rm } from 'fs/promises';
2
11
  import { existsSync } from 'fs';
3
- export async function prepareDir(path, emptyDir) {
4
- const exists = existsSync(path);
5
- if (emptyDir && exists) {
6
- await rm(path, { recursive: true });
7
- }
8
- if (!exists || emptyDir) {
9
- // Create the directory
10
- await mkdir(path, { recursive: true });
11
- }
12
+ export function prepareDir(path, emptyDir) {
13
+ return __awaiter(this, void 0, void 0, function* () {
14
+ const exists = existsSync(path);
15
+ if (emptyDir && exists) {
16
+ yield rm(path, { recursive: true });
17
+ }
18
+ if (!exists || emptyDir) {
19
+ // Create the directory
20
+ yield mkdir(path, { recursive: true });
21
+ }
22
+ });
12
23
  }
@@ -0,0 +1,8 @@
1
+ export function valueToArray(value) {
2
+ if (Array.isArray(value) || value === undefined || value == null) {
3
+ return value;
4
+ }
5
+ else {
6
+ return [value];
7
+ }
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tolgee/cli",
3
- "version": "2.8.4",
3
+ "version": "2.10.0",
4
4
  "type": "module",
5
5
  "description": "A tool to interact with the Tolgee Platform through CLI",
6
6
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "json5": "^2.2.3",
38
38
  "jsonschema": "^1.4.1",
39
39
  "openapi-fetch": "0.13.1",
40
- "tinyglobby": "^0.2.10",
40
+ "tinyglobby": "^0.2.12",
41
41
  "unescape-js": "^1.1.4",
42
42
  "vscode-oniguruma": "^2.0.1",
43
43
  "vscode-textmate": "^9.1.0",
package/schema.json CHANGED
@@ -42,8 +42,20 @@
42
42
  "push": {
43
43
  "type": "object",
44
44
  "properties": {
45
+ "filesTemplate": {
46
+ "description": "A template that describes the structure of the local files and their location with file [structure template format](https://docs.tolgee.io/tolgee-cli/push-pull-strings#file-structure-template-format).\n\nExample: `./public/{namespace}/{languageTag}.json`",
47
+ "anyOf": [
48
+ {
49
+ "type": "string"
50
+ },
51
+ {
52
+ "type": "array",
53
+ "items": { "type": "string" }
54
+ }
55
+ ]
56
+ },
45
57
  "files": {
46
- "description": "Define, which files should be pushed and attach language/namespace to them. By default Tolgee pushes all files specified here, you can filter them by languages and namespaces properties.",
58
+ "description": "More explicit alternative to `filesTemplate`. Define, which files should be pushed and attach language/namespace to them. By default Tolgee pushes all files specified here, you can filter them by languages and namespaces properties.",
47
59
  "type": "array",
48
60
  "items": { "$ref": "#/$defs/fileMatch" }
49
61
  },
@@ -128,7 +140,7 @@
128
140
  "type": "boolean"
129
141
  },
130
142
  "fileStructureTemplate": {
131
- "description": "Defines exported file structure: https://tolgee.io/tolgee-cli/push-pull-strings#file-structure-template-format",
143
+ "description": "Defines exported file structure: https://docs.tolgee.io/tolgee-cli/push-pull-strings#file-structure-template-format",
132
144
  "type": "string"
133
145
  },
134
146
  "emptyDir": {
@@ -219,10 +231,13 @@
219
231
  "type": "object",
220
232
  "properties": {
221
233
  "path": { "$ref": "#/$defs/path" },
222
- "language": { "type": "string" },
234
+ "language": {
235
+ "description": "Explicitly map file to language.\n\nIf not provided, backend will try to detect language from language name or it's content.",
236
+ "type": "string"
237
+ },
223
238
  "namespace": { "type": "string" }
224
239
  },
225
- "required": ["path", "language"]
240
+ "required": ["path"]
226
241
  },
227
242
  "forceMode": {
228
243
  "description": "Specifies how to solve potential conflicts in the pushed data.\n\nOptions:\n\n `OVERRIDE` - update everything according to local files\n `KEEP` - create only non-existent strings, don't touch existing ones\n `NO_FORCE` - throw an error, if there are any conflict",
@@ -251,6 +266,7 @@
251
266
  "PO_PYTHON",
252
267
  "APPLE_STRINGS",
253
268
  "APPLE_XLIFF",
269
+ "APPLE_XCSTRINGS",
254
270
  "PROPERTIES_ICU",
255
271
  "PROPERTIES_JAVA",
256
272
  "ANDROID_XML",