@tolgee/cli 1.2.0 → 1.3.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.
@@ -1,32 +1,37 @@
1
+ import { STATUS_CODES } from 'http';
1
2
  export class HttpError extends Error {
2
3
  request;
3
4
  response;
4
5
  constructor(request, response, options) {
5
- super(response.statusText, options);
6
+ super(`HTTP Error ${response.statusCode} ${STATUS_CODES[response.statusCode]}`, options);
6
7
  this.request = request;
7
8
  this.response = response;
8
9
  }
9
10
  getErrorText() {
10
11
  // Unauthorized
11
- if (this.response.status === 400) {
12
+ if (this.response.statusCode === 400) {
12
13
  return 'Invalid request data.';
13
14
  }
14
15
  // Unauthorized
15
- if (this.response.status === 401) {
16
+ if (this.response.statusCode === 401) {
16
17
  return 'Missing or invalid authentication token.';
17
18
  }
18
19
  // Forbidden
19
- if (this.response.status === 403) {
20
+ if (this.response.statusCode === 403) {
20
21
  return 'You are not allowed to perform this operation.';
21
22
  }
23
+ // Rate limited
24
+ if (this.response.statusCode === 429) {
25
+ return "You've been rate limited. Please try again later.";
26
+ }
22
27
  // Service Unavailable
23
- if (this.response.status === 503) {
28
+ if (this.response.statusCode === 503) {
24
29
  return 'API is temporarily unavailable. Please try again later.';
25
30
  }
26
31
  // Server error
27
- if (this.response.status >= 500) {
28
- return `API reported a server error (${this.response.status}). Please try again later.`;
32
+ if (this.response.statusCode >= 500) {
33
+ return `API reported a server error (${this.response.statusCode}). Please try again later.`;
29
34
  }
30
- return `Unknown error (HTTP ${this.response.status})`;
35
+ return `Unknown error (HTTP ${this.response.statusCode})`;
31
36
  }
32
37
  }
@@ -8,6 +8,8 @@ export default class ExportClient {
8
8
  method: 'POST',
9
9
  path: `${this.requester.projectUrl}/export`,
10
10
  body: { ...req, zip: true },
11
+ headersTimeout: 300,
12
+ bodyTimeout: 300,
11
13
  });
12
14
  }
13
15
  async exportSingle(req) {
@@ -15,6 +17,8 @@ export default class ExportClient {
15
17
  method: 'POST',
16
18
  path: `${this.requester.projectUrl}/export`,
17
19
  body: { ...req, zip: false },
20
+ headersTimeout: 300,
21
+ bodyTimeout: 300,
18
22
  });
19
23
  }
20
24
  }
@@ -33,6 +33,8 @@ export default class ImportClient {
33
33
  method: 'PUT',
34
34
  path: `${this.requester.projectUrl}/import/apply`,
35
35
  query: { forceMode: req?.forceMode },
36
+ headersTimeout: 300,
37
+ bodyTimeout: 300,
36
38
  });
37
39
  }
38
40
  async deleteImport() {
@@ -47,7 +49,7 @@ export default class ImportClient {
47
49
  await this.deleteImport();
48
50
  }
49
51
  catch (e) {
50
- if (e instanceof HttpError && e.response.status === 404)
52
+ if (e instanceof HttpError && e.response.statusCode === 404)
51
53
  return;
52
54
  throw e;
53
55
  }
@@ -1,5 +1,5 @@
1
- import { Request } from 'undici';
2
- import { fetch } from 'undici';
1
+ import { STATUS_CODES } from 'http';
2
+ import { request } from 'undici';
3
3
  import FormData from 'form-data';
4
4
  import { HttpError } from '../errors.js';
5
5
  import { debug } from '../../utils/logger.js';
@@ -54,16 +54,17 @@ export default class Requester {
54
54
  body = JSON.stringify(req.body);
55
55
  }
56
56
  }
57
- const request = new Request(url, {
57
+ debug(`[HTTP] Requesting: ${req.method} ${url}`);
58
+ const response = await request(url, {
58
59
  method: req.method,
59
60
  headers: headers,
60
61
  body: body,
62
+ headersTimeout: req.headersTimeout,
63
+ bodyTimeout: req.bodyTimeout,
61
64
  });
62
- debug(`[HTTP] Requesting: ${request.method} ${request.url}`);
63
- const response = await fetch(request);
64
- debug(`[HTTP] ${request.method} ${request.url} -> ${response.status} ${response.statusText}`);
65
- if (!response.ok)
66
- throw new HttpError(request, response);
65
+ debug(`[HTTP] ${req.method} ${url} -> ${response.statusCode} ${STATUS_CODES[response.statusCode]}`);
66
+ if (response.statusCode >= 400)
67
+ throw new HttpError(req, response);
67
68
  return response;
68
69
  }
69
70
  /**
@@ -73,7 +74,7 @@ export default class Requester {
73
74
  * @returns The response data
74
75
  */
75
76
  async requestJson(req) {
76
- return this.request(req).then((r) => r.json());
77
+ return this.request(req).then((r) => r.body.json());
77
78
  }
78
79
  /**
79
80
  * Performs an HTTP request to the API and returns the result as a Blob
@@ -82,20 +83,13 @@ export default class Requester {
82
83
  * @returns The response blob
83
84
  */
84
85
  async requestBlob(req) {
85
- return this.request(req).then((r) => r.blob());
86
+ return this.request(req).then((r) => r.body.blob());
86
87
  }
87
88
  /**
88
- * Performs an HTTP request to the API and forces the consumption of the body, according to recommendations by Undici
89
- *
90
- * @see https://github.com/nodejs/undici#garbage-collection
91
- * @param req Request data
89
+ * Performs an HTTP request to the API.
92
90
  */
93
91
  async requestVoid(req) {
94
- const res = await this.request(req);
95
- if (res.body) {
96
- for await (const _chunk of res.body)
97
- ;
98
- }
92
+ await this.request(req);
99
93
  }
100
94
  /**
101
95
  * Performs an HTTP request to the API to a resource which is paginated.
@@ -10,7 +10,7 @@ async function loginHandler(key) {
10
10
  keyInfo = await RestClient.getApiKeyInformation(opts.apiUrl, key);
11
11
  }
12
12
  catch (e) {
13
- if (e instanceof HttpError && e.response.status === 403) {
13
+ if (e instanceof HttpError && e.response.statusCode === 401) {
14
14
  error("Couldn't log in: the API key you provided is invalid.");
15
15
  process.exit(1);
16
16
  }
@@ -1,21 +1,33 @@
1
1
  import { Command, Option } from 'commander';
2
2
  import { unzipBuffer } from '../utils/zip.js';
3
3
  import { overwriteDir } from '../utils/overwriteDir.js';
4
- import { loading, success } from '../utils/logger.js';
4
+ import { error, loading, success } from '../utils/logger.js';
5
+ import { HttpError } from '../client/errors.js';
5
6
  async function fetchZipBlob(opts) {
6
7
  return opts.client.export.export({
7
8
  format: opts.format,
8
9
  languages: opts.languages,
9
10
  filterState: opts.states,
10
11
  structureDelimiter: opts.delimiter,
12
+ filterNamespace: opts.namespaces,
11
13
  });
12
14
  }
13
15
  async function pullHandler(path) {
14
16
  const opts = this.optsWithGlobals();
15
17
  await overwriteDir(path, opts.overwrite);
16
- const zipBlob = await loading('Fetching strings from Tolgee...', fetchZipBlob(opts));
17
- await loading('Extracting strings...', unzipBuffer(zipBlob, path));
18
- success('Done!');
18
+ try {
19
+ const zipBlob = await loading('Fetching strings from Tolgee...', fetchZipBlob(opts));
20
+ await loading('Extracting strings...', unzipBuffer(zipBlob, path));
21
+ success('Done!');
22
+ }
23
+ catch (e) {
24
+ if (e instanceof HttpError && e.response.statusCode === 400) {
25
+ const res = await e.response.body.json();
26
+ error(`Please check if your parameters, including namespaces, are configured correctly. Tolgee responded with: ${res.code}`);
27
+ return;
28
+ }
29
+ throw e;
30
+ }
19
31
  }
20
32
  export default new Command()
21
33
  .name('pull')
@@ -32,5 +44,6 @@ export default new Command()
32
44
  .addOption(new Option('-d, --delimiter', 'Structure delimiter to use. By default, Tolgee interprets `.` as a nested structure. You can change the delimiter, or disable structure formatting by not specifying any value to the option')
33
45
  .default('.')
34
46
  .argParser((v) => v || ''))
47
+ .addOption(new Option('-n, --namespaces <namespaces...>', 'List of namespaces to pull. Defaults to all namespaces'))
35
48
  .option('-o, --overwrite', 'Whether to automatically overwrite existing files. BE CAREFUL, THIS WILL WIPE *ALL* THE CONTENTS OF THE TARGET FOLDER. If unspecified, the user will be prompted interactively, or the command will fail when in non-interactive')
36
49
  .action(pullHandler);
@@ -73,7 +73,7 @@ async function applyImport(client) {
73
73
  await loading('Applying changes...', client.import.applyImport());
74
74
  }
75
75
  catch (e) {
76
- if (e instanceof HttpError && e.response.status === 400) {
76
+ if (e instanceof HttpError && e.response.statusCode === 400) {
77
77
  error("Some of the imported languages weren't recognized. Please create a language with corresponding tag in the Tolgee Platform.");
78
78
  return;
79
79
  }
@@ -49,7 +49,7 @@ function parseConfig(rc) {
49
49
  if (typeof rc.delimiter !== 'string' && rc.delimiter !== null) {
50
50
  throw new Error('Invalid config: delimiter is not a string');
51
51
  }
52
- cfg.delimiter = rc.delimiter || void 0;
52
+ cfg.delimiter = rc.delimiter || '';
53
53
  }
54
54
  return cfg;
55
55
  }
package/dist/constants.js CHANGED
@@ -8,4 +8,4 @@ export const USER_AGENT = `Tolgee-CLI/${VERSION} (+https://github.com/tolgee/tol
8
8
  export const DEFAULT_API_URL = new URL('https://app.tolgee.io');
9
9
  export const API_KEY_PAT_PREFIX = 'tgpat_';
10
10
  export const API_KEY_PAK_PREFIX = 'tgpak_';
11
- export const SDKS = ['react'];
11
+ export const SDKS = ['react', 'vue', 'svelte'];
@@ -7,6 +7,7 @@ export default createMachine({
7
7
  context: {
8
8
  property: null,
9
9
  depth: 0,
10
+ jsxDepth: 0,
10
11
  static: false,
11
12
  nextDynamic: false,
12
13
  keyName: null,
@@ -268,10 +269,20 @@ export default createMachine({
268
269
  cond: 'isCloseCurly',
269
270
  },
270
271
  ],
271
- 'punctuation.definition.tag.end.tsx': {
272
- target: 'end',
273
- actions: 'markPropertyAsDynamic',
272
+ 'punctuation.definition.tag.begin.tsx': {
273
+ actions: 'incrementJsxDepth',
274
+ cond: (_ctx, evt) => evt.token === '<',
274
275
  },
276
+ 'punctuation.definition.tag.end.tsx': [
277
+ {
278
+ target: 'end',
279
+ actions: 'markPropertyAsDynamic',
280
+ cond: (ctx) => ctx.jsxDepth === 0,
281
+ },
282
+ {
283
+ actions: 'decrementJsxDepth',
284
+ },
285
+ ],
275
286
  'punctuation.definition.tag.end.svelte': {
276
287
  target: 'end',
277
288
  actions: 'markPropertyAsDynamic',
@@ -346,6 +357,12 @@ export default createMachine({
346
357
  decrementDepth: assign({
347
358
  depth: (ctx, _evt) => ctx.depth - 1,
348
359
  }),
360
+ incrementJsxDepth: assign({
361
+ jsxDepth: (ctx, _evt) => ctx.jsxDepth + 2,
362
+ }),
363
+ decrementJsxDepth: assign({
364
+ jsxDepth: (ctx, _evt) => ctx.jsxDepth - 1,
365
+ }),
349
366
  markAsStatic: assign({
350
367
  static: true,
351
368
  }),
package/dist/index.js CHANGED
@@ -112,10 +112,10 @@ async function loadConfig() {
112
112
  }
113
113
  async function handleHttpError(e) {
114
114
  error('An error occurred while requesting the API.');
115
- error(`${e.request.method} ${e.request.url}`);
115
+ error(`${e.request.method} ${e.request.path}`);
116
116
  error(e.getErrorText());
117
117
  // Remove token from store if necessary
118
- if (e.response.status === 401) {
118
+ if (e.response.statusCode === 401) {
119
119
  const removeFn = program.getOptionValue('_removeApiKeyFromStore');
120
120
  if (removeFn) {
121
121
  info('Removing the API key from the authentication store.');
@@ -129,7 +129,7 @@ async function handleHttpError(e) {
129
129
  // catastrophic failure) which means the output is completely unpredictable. While some errors are
130
130
  // formatted by the Tolgee server, reality is there's a huge chance the 5xx error hasn't been raised
131
131
  // by Tolgee's error handler.
132
- const res = await e.response.text();
132
+ const res = await e.response.body.text();
133
133
  debug(`Server response:\n\n---\n${res}\n---`);
134
134
  }
135
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tolgee/cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "type": "module",
5
5
  "description": "A tool to interact with the Tolgee Platform through CLI",
6
6
  "repository": {