@tolgee/cli 1.3.2 → 2.0.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/{index.js → cli.js} +54 -63
- package/dist/client/ApiClient.js +72 -0
- package/dist/client/ExportClient.js +19 -0
- package/dist/client/ImportClient.js +22 -0
- package/dist/client/TolgeeClient.js +18 -0
- package/dist/client/errorFromLoadable.js +35 -0
- package/dist/client/getApiKeyInformation.js +39 -0
- package/dist/client/internal/requester.js +2 -5
- package/dist/commands/extract/check.js +10 -7
- package/dist/commands/extract/print.js +10 -7
- package/dist/commands/extract.js +3 -5
- package/dist/commands/login.js +6 -14
- package/dist/commands/pull.js +38 -32
- package/dist/commands/push.js +89 -71
- package/dist/commands/sync/compare.js +14 -10
- package/dist/commands/sync/sync.js +41 -23
- package/dist/commands/tag.js +49 -0
- package/dist/config/tolgeerc.js +59 -36
- package/dist/constants.js +0 -1
- package/dist/extractor/machines/vue/decoder.js +1 -1
- package/dist/extractor/runner.js +2 -2
- package/dist/options.js +31 -7
- package/dist/utils/checkPathNotAFile.js +16 -0
- package/dist/utils/getSingleOption.js +10 -0
- package/dist/utils/getStackTrace.js +7 -0
- package/dist/utils/logger.js +8 -0
- package/dist/utils/mapExportFormat.js +62 -0
- package/dist/utils/mapImportFormat.js +18 -0
- package/dist/utils/prepareDir.js +12 -0
- package/dist/utils/zip.js +2 -7
- package/package.json +17 -13
- package/schema.json +175 -0
- package/dist/client/errors.js +0 -37
- package/dist/client/export.js +0 -20
- package/dist/client/import.js +0 -55
- package/dist/client/index.js +0 -73
- package/dist/client/languages.js +0 -13
- package/dist/client/project.js +0 -41
- package/dist/utils/overwriteDir.js +0 -34
package/dist/{index.js → cli.js}
RENAMED
@@ -3,17 +3,19 @@ import { Command } from 'commander';
|
|
3
3
|
import ansi from 'ansi-colors';
|
4
4
|
import { getApiKey, savePak, savePat } from './config/credentials.js';
|
5
5
|
import loadTolgeeRc from './config/tolgeerc.js';
|
6
|
-
import
|
7
|
-
import {
|
8
|
-
import {
|
9
|
-
import { API_KEY_OPT, API_URL_OPT, PROJECT_ID_OPT } from './options.js';
|
10
|
-
import { API_KEY_PAK_PREFIX, API_KEY_PAT_PREFIX, VERSION, } from './constants.js';
|
6
|
+
import { setDebug, info, error, exitWithError } from './utils/logger.js';
|
7
|
+
import { API_KEY_OPT, API_URL_OPT, CONFIG_OPT, EXTRACTOR, FILE_PATTERNS, FORMAT_OPT, PROJECT_ID_OPT, } from './options.js';
|
8
|
+
import { API_KEY_PAK_PREFIX, API_KEY_PAT_PREFIX, DEFAULT_API_URL, VERSION, } from './constants.js';
|
11
9
|
import { Login, Logout } from './commands/login.js';
|
12
10
|
import PushCommand from './commands/push.js';
|
13
11
|
import PullCommand from './commands/pull.js';
|
14
12
|
import ExtractCommand from './commands/extract.js';
|
15
13
|
import CompareCommand from './commands/sync/compare.js';
|
16
14
|
import SyncCommand from './commands/sync/sync.js';
|
15
|
+
import TagCommand from './commands/tag.js';
|
16
|
+
import { getSingleOption } from './utils/getSingleOption.js';
|
17
|
+
import { createTolgeeClient } from './client/TolgeeClient.js';
|
18
|
+
import { projectIdFromKey } from './client/ApiClient.js';
|
17
19
|
const NO_KEY_COMMANDS = ['login', 'logout', 'extract'];
|
18
20
|
ansi.enabled = process.stdout.isTTY;
|
19
21
|
function topLevelName(command) {
|
@@ -46,7 +48,7 @@ function loadProjectId(cmd) {
|
|
46
48
|
const opts = cmd.optsWithGlobals();
|
47
49
|
if (opts.apiKey?.startsWith(API_KEY_PAK_PREFIX)) {
|
48
50
|
// Parse the key and ensure we can access the specified Project ID
|
49
|
-
const projectId =
|
51
|
+
const projectId = projectIdFromKey(opts.apiKey);
|
50
52
|
program.setOptionValue('projectId', projectId);
|
51
53
|
if (opts.projectId !== -1 && opts.projectId !== projectId) {
|
52
54
|
error('The specified API key cannot be used to perform operations on the specified project.');
|
@@ -64,93 +66,82 @@ function validateOptions(cmd) {
|
|
64
66
|
process.exit(1);
|
65
67
|
}
|
66
68
|
if (!opts.apiKey) {
|
67
|
-
|
68
|
-
process.exit(1);
|
69
|
+
exitWithError('No API key has been provided. You must either provide one via --api-key, or login via `tolgee login`.');
|
69
70
|
}
|
70
71
|
}
|
71
|
-
async function
|
72
|
+
const preHandler = (config) => async function (prog, cmd) {
|
72
73
|
if (!NO_KEY_COMMANDS.includes(topLevelName(cmd))) {
|
73
74
|
await loadApiKey(cmd);
|
74
75
|
loadProjectId(cmd);
|
75
76
|
validateOptions(cmd);
|
76
77
|
const opts = cmd.optsWithGlobals();
|
77
|
-
const client =
|
78
|
-
|
78
|
+
const client = createTolgeeClient({
|
79
|
+
baseUrl: opts.apiUrl?.toString() ?? config.apiUrl?.toString(),
|
79
80
|
apiKey: opts.apiKey,
|
80
|
-
projectId: opts.projectId
|
81
|
+
projectId: opts.projectId !== undefined
|
82
|
+
? Number(opts.projectId)
|
83
|
+
: config.projectId !== undefined
|
84
|
+
? Number(config.projectId)
|
85
|
+
: undefined,
|
81
86
|
});
|
82
87
|
cmd.setOptionValue('client', client);
|
83
88
|
}
|
84
89
|
// Apply verbosity
|
85
90
|
setDebug(prog.opts().verbose);
|
86
|
-
}
|
91
|
+
};
|
87
92
|
const program = new Command('tolgee')
|
88
93
|
.version(VERSION)
|
89
94
|
.configureOutput({ writeErr: error })
|
90
95
|
.description('Command Line Interface to interact with the Tolgee Platform')
|
91
|
-
.option('-v, --verbose', 'Enable verbose logging.')
|
92
|
-
|
93
|
-
|
94
|
-
program
|
95
|
-
|
96
|
-
program.addOption(PROJECT_ID_OPT);
|
97
|
-
// Register commands
|
98
|
-
program.addCommand(Login);
|
99
|
-
program.addCommand(Logout);
|
100
|
-
program.addCommand(PushCommand);
|
101
|
-
program.addCommand(PullCommand);
|
102
|
-
program.addCommand(ExtractCommand);
|
103
|
-
program.addCommand(CompareCommand);
|
104
|
-
program.addCommand(SyncCommand);
|
105
|
-
async function loadConfig() {
|
106
|
-
const tgConfig = await loadTolgeeRc();
|
96
|
+
.option('-v, --verbose', 'Enable verbose logging.');
|
97
|
+
// get config path to update defaults
|
98
|
+
const configPath = getSingleOption(CONFIG_OPT, process.argv);
|
99
|
+
async function loadConfig(program) {
|
100
|
+
const tgConfig = await loadTolgeeRc(configPath);
|
107
101
|
if (tgConfig) {
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
if (e.response.statusCode === 401) {
|
119
|
-
const removeFn = program.getOptionValue('_removeApiKeyFromStore');
|
120
|
-
if (removeFn) {
|
121
|
-
info('Removing the API key from the authentication store.');
|
122
|
-
removeFn();
|
123
|
-
}
|
124
|
-
}
|
125
|
-
// Print server output for server errors
|
126
|
-
if (isDebugEnabled()) {
|
127
|
-
// We cannot parse the response as JSON and pull error codes here as we may be here due to a 5xx error:
|
128
|
-
// by nature 5xx class errors can happen for a lot of reasons (e.g. upstream issues, server issues,
|
129
|
-
// catastrophic failure) which means the output is completely unpredictable. While some errors are
|
130
|
-
// formatted by the Tolgee server, reality is there's a huge chance the 5xx error hasn't been raised
|
131
|
-
// by Tolgee's error handler.
|
132
|
-
const res = await e.response.body.text();
|
133
|
-
debug(`Server response:\n\n---\n${res}\n---`);
|
102
|
+
[program, ...program.commands].forEach((cmd) => cmd.options.forEach((opt) => {
|
103
|
+
const key = opt.attributeName();
|
104
|
+
const value = tgConfig[key];
|
105
|
+
if (value) {
|
106
|
+
const parsedValue = opt.parseArg
|
107
|
+
? opt.parseArg(value, undefined)
|
108
|
+
: value;
|
109
|
+
cmd.setOptionValueWithSource(key, parsedValue, 'config');
|
110
|
+
}
|
111
|
+
}));
|
134
112
|
}
|
113
|
+
return tgConfig ?? {};
|
135
114
|
}
|
136
115
|
async function run() {
|
137
116
|
try {
|
138
|
-
|
117
|
+
// Global options
|
118
|
+
program.addOption(CONFIG_OPT);
|
119
|
+
program.addOption(API_URL_OPT.default(DEFAULT_API_URL));
|
120
|
+
program.addOption(API_KEY_OPT);
|
121
|
+
program.addOption(PROJECT_ID_OPT.default(-1));
|
122
|
+
program.addOption(FORMAT_OPT.default('JSON_TOLGEE'));
|
123
|
+
program.addOption(EXTRACTOR);
|
124
|
+
program.addOption(FILE_PATTERNS);
|
125
|
+
const config = await loadConfig(program);
|
126
|
+
program.hook('preAction', preHandler(config));
|
127
|
+
// Register commands
|
128
|
+
program.addCommand(Login);
|
129
|
+
program.addCommand(Logout);
|
130
|
+
program.addCommand(PushCommand(config).configureHelp({ showGlobalOptions: true }));
|
131
|
+
program.addCommand(PullCommand(config).configureHelp({ showGlobalOptions: true }));
|
132
|
+
program.addCommand(ExtractCommand(config).configureHelp({ showGlobalOptions: true }));
|
133
|
+
program.addCommand(CompareCommand(config).configureHelp({ showGlobalOptions: true }));
|
134
|
+
program.addCommand(SyncCommand(config).configureHelp({ showGlobalOptions: true }));
|
135
|
+
program.addCommand(TagCommand(config).configureHelp({ showGlobalOptions: true }));
|
139
136
|
await program.parseAsync();
|
140
137
|
}
|
141
138
|
catch (e) {
|
142
|
-
if (e instanceof HttpError) {
|
143
|
-
await handleHttpError(e);
|
144
|
-
process.exit(1);
|
145
|
-
}
|
146
139
|
// If the error is uncaught, huge chance that either:
|
147
140
|
// - The error should be handled here but isn't
|
148
141
|
// - The error should be handled in the command but isn't
|
149
142
|
// - Something went wrong with the code
|
150
143
|
error('An unexpected error occurred while running the command.');
|
151
|
-
|
152
|
-
console.log(e.stack);
|
153
|
-
process.exit(1);
|
144
|
+
exitWithError('Please report this to our issue tracker: https://github.com/tolgee/tolgee-cli/issues');
|
154
145
|
}
|
155
146
|
}
|
156
147
|
run();
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import createClient from 'openapi-fetch';
|
2
|
+
import base32Decode from 'base32-decode';
|
3
|
+
import { API_KEY_PAK_PREFIX, USER_AGENT } from '../constants.js';
|
4
|
+
import { getApiKeyInformation } from './getApiKeyInformation.js';
|
5
|
+
import { debug } from '../utils/logger.js';
|
6
|
+
import { errorFromLoadable } from './errorFromLoadable.js';
|
7
|
+
async function parseResponse(response, parseAs) {
|
8
|
+
// handle empty content
|
9
|
+
// note: we return `{}` because we want user truthy checks for `.data` or `.error` to succeed
|
10
|
+
if (response.status === 204 ||
|
11
|
+
response.headers.get('Content-Length') === '0') {
|
12
|
+
return response.ok ? { data: {}, response } : { error: {}, response };
|
13
|
+
}
|
14
|
+
// parse response (falling back to .text() when necessary)
|
15
|
+
if (response.ok) {
|
16
|
+
// if "stream", skip parsing entirely
|
17
|
+
if (parseAs === 'stream') {
|
18
|
+
return { data: response.body, response };
|
19
|
+
}
|
20
|
+
return { data: await response[parseAs](), response };
|
21
|
+
}
|
22
|
+
// handle errors
|
23
|
+
let error = await response.text();
|
24
|
+
try {
|
25
|
+
error = JSON.parse(error); // attempt to parse as JSON
|
26
|
+
}
|
27
|
+
catch {
|
28
|
+
// noop
|
29
|
+
}
|
30
|
+
return { error, response };
|
31
|
+
}
|
32
|
+
export function projectIdFromKey(key) {
|
33
|
+
if (!key.startsWith(API_KEY_PAK_PREFIX)) {
|
34
|
+
return undefined;
|
35
|
+
}
|
36
|
+
const keyBuffer = base32Decode(key.slice(API_KEY_PAK_PREFIX.length).toUpperCase(), 'RFC4648');
|
37
|
+
const decoded = Buffer.from(keyBuffer).toString('utf8');
|
38
|
+
return Number(decoded.split('_')[0]);
|
39
|
+
}
|
40
|
+
export function createApiClient({ baseUrl, apiKey, projectId, autoThrow = false, }) {
|
41
|
+
const computedProjectId = projectId ?? (apiKey ? projectIdFromKey(apiKey) : undefined);
|
42
|
+
const apiClient = createClient({
|
43
|
+
baseUrl,
|
44
|
+
headers: {
|
45
|
+
'user-agent': USER_AGENT,
|
46
|
+
'x-api-key': apiKey,
|
47
|
+
},
|
48
|
+
});
|
49
|
+
apiClient.use({
|
50
|
+
onRequest: (req, options) => {
|
51
|
+
debug(`[HTTP] Requesting: ${req.method} ${req.url}`);
|
52
|
+
return undefined;
|
53
|
+
},
|
54
|
+
onResponse: async (res, options) => {
|
55
|
+
debug(`[HTTP] Response: ${res.url} [${res.status}]`);
|
56
|
+
if (autoThrow && !res.ok) {
|
57
|
+
const loadable = await parseResponse(res, options.parseAs);
|
58
|
+
throw new Error(`Tolgee request error ${res.url} ${errorFromLoadable(loadable)}`);
|
59
|
+
}
|
60
|
+
return undefined;
|
61
|
+
},
|
62
|
+
});
|
63
|
+
return {
|
64
|
+
...apiClient,
|
65
|
+
getProjectId() {
|
66
|
+
return computedProjectId;
|
67
|
+
},
|
68
|
+
getApiKeyInfo() {
|
69
|
+
return getApiKeyInformation(apiClient, apiKey);
|
70
|
+
},
|
71
|
+
};
|
72
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
export const createExportClient = ({ apiClient }) => {
|
2
|
+
return {
|
3
|
+
async export(req) {
|
4
|
+
const body = { ...req, zip: true };
|
5
|
+
const loadable = await apiClient.POST('/v2/projects/{projectId}/export', {
|
6
|
+
params: { path: { projectId: apiClient.getProjectId() } },
|
7
|
+
body: body,
|
8
|
+
parseAs: 'blob',
|
9
|
+
});
|
10
|
+
return { ...loadable, data: loadable.data };
|
11
|
+
},
|
12
|
+
async exportSingle(req) {
|
13
|
+
return apiClient.POST('/v2/projects/{projectId}/export', {
|
14
|
+
params: { path: { projectId: apiClient.getProjectId() } },
|
15
|
+
body: { ...req, zip: false },
|
16
|
+
});
|
17
|
+
},
|
18
|
+
};
|
19
|
+
};
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import FormData from 'form-data';
|
2
|
+
export const createImportClient = ({ apiClient }) => {
|
3
|
+
return {
|
4
|
+
async import(data) {
|
5
|
+
const body = new FormData();
|
6
|
+
for (const file of data.files) {
|
7
|
+
body.append('files', file.data, { filepath: file.name });
|
8
|
+
}
|
9
|
+
body.append('params', JSON.stringify(data.params));
|
10
|
+
return apiClient.POST('/v2/projects/{projectId}/single-step-import', {
|
11
|
+
params: { path: { projectId: apiClient.getProjectId() } },
|
12
|
+
body: body,
|
13
|
+
bodySerializer: (r) => {
|
14
|
+
return r.getBuffer();
|
15
|
+
},
|
16
|
+
headers: {
|
17
|
+
'content-type': `multipart/form-data; boundary=${body.getBoundary()}`,
|
18
|
+
},
|
19
|
+
});
|
20
|
+
},
|
21
|
+
};
|
22
|
+
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { exitWithError } from './../utils/logger.js';
|
2
|
+
import { createApiClient } from './ApiClient.js';
|
3
|
+
import { createExportClient } from './ExportClient.js';
|
4
|
+
import { createImportClient } from './ImportClient.js';
|
5
|
+
import { errorFromLoadable } from './errorFromLoadable.js';
|
6
|
+
export function createTolgeeClient(props) {
|
7
|
+
const apiClient = createApiClient(props);
|
8
|
+
return {
|
9
|
+
...apiClient,
|
10
|
+
import: createImportClient({ apiClient }),
|
11
|
+
export: createExportClient({ apiClient }),
|
12
|
+
};
|
13
|
+
}
|
14
|
+
export const handleLoadableError = (loadable) => {
|
15
|
+
if (loadable.error) {
|
16
|
+
exitWithError(errorFromLoadable(loadable));
|
17
|
+
}
|
18
|
+
};
|
@@ -0,0 +1,35 @@
|
|
1
|
+
export const addErrorDetails = (loadable, showBeError = true) => {
|
2
|
+
const items = [];
|
3
|
+
items.push(`status: ${loadable.response.status}`);
|
4
|
+
if (showBeError && loadable.error?.code) {
|
5
|
+
items.push(`code: ${loadable.error.code}`);
|
6
|
+
}
|
7
|
+
if (loadable.response.status === 403 && loadable.error?.params?.[0]) {
|
8
|
+
items.push(`missing scope: ${loadable.error.params[0]}`);
|
9
|
+
}
|
10
|
+
return `[${items.join(', ')}]`;
|
11
|
+
};
|
12
|
+
export const errorFromLoadable = (loadable) => {
|
13
|
+
switch (loadable.response.status) {
|
14
|
+
// Unauthorized
|
15
|
+
case 400:
|
16
|
+
return `Invalid request data ${addErrorDetails(loadable)}`;
|
17
|
+
// Unauthorized
|
18
|
+
case 401:
|
19
|
+
return `Missing or invalid authentication token ${addErrorDetails(loadable)}`;
|
20
|
+
// Forbidden
|
21
|
+
case 403:
|
22
|
+
return `You are not allowed to perform this operation ${addErrorDetails(loadable)}`;
|
23
|
+
// Rate limited
|
24
|
+
case 429:
|
25
|
+
return `You've been rate limited. Please try again later ${addErrorDetails(loadable)}`;
|
26
|
+
// Service Unavailable
|
27
|
+
case 503:
|
28
|
+
return `API is temporarily unavailable. Please try again later ${addErrorDetails(loadable)}`;
|
29
|
+
// Server error
|
30
|
+
case 500:
|
31
|
+
return `API reported a server error. Please try again later ${addErrorDetails(loadable)}`;
|
32
|
+
default:
|
33
|
+
return `Unknown error ${addErrorDetails(loadable)}`;
|
34
|
+
}
|
35
|
+
};
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import { API_KEY_PAK_PREFIX } from './../constants.js';
|
2
|
+
import { handleLoadableError } from './TolgeeClient.js';
|
3
|
+
import { exitWithError } from './../utils/logger.js';
|
4
|
+
export const getApiKeyInformation = async (client, key) => {
|
5
|
+
if (key.startsWith(API_KEY_PAK_PREFIX)) {
|
6
|
+
const loadable = await client.GET('/v2/api-keys/current');
|
7
|
+
if (loadable.response.status === 401) {
|
8
|
+
exitWithError("Couldn't log in: the API key you provided is invalid.");
|
9
|
+
}
|
10
|
+
handleLoadableError(loadable);
|
11
|
+
const info = loadable.data;
|
12
|
+
const username = info.userFullName || info.username || '<unknown user>';
|
13
|
+
return {
|
14
|
+
type: 'PAK',
|
15
|
+
key: key,
|
16
|
+
username: username,
|
17
|
+
project: {
|
18
|
+
id: info.projectId,
|
19
|
+
name: info.projectName,
|
20
|
+
},
|
21
|
+
expires: info.expiresAt ?? 0,
|
22
|
+
};
|
23
|
+
}
|
24
|
+
else {
|
25
|
+
const loadable = await client.GET('/v2/pats/current');
|
26
|
+
if (loadable.response.status === 401) {
|
27
|
+
exitWithError("Couldn't log in: the API key you provided is invalid.");
|
28
|
+
}
|
29
|
+
handleLoadableError(loadable);
|
30
|
+
const info = loadable.data;
|
31
|
+
const username = info.user.name || info.user.username;
|
32
|
+
return {
|
33
|
+
type: 'PAT',
|
34
|
+
key: key,
|
35
|
+
username: username,
|
36
|
+
expires: info.expiresAt ?? 0,
|
37
|
+
};
|
38
|
+
}
|
39
|
+
};
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import { STATUS_CODES } from 'http';
|
2
2
|
import { request } from 'undici';
|
3
3
|
import FormData from 'form-data';
|
4
|
-
import { HttpError } from '../errors.js';
|
5
4
|
import { debug } from '../../utils/logger.js';
|
6
5
|
import { USER_AGENT } from '../../constants.js';
|
7
6
|
export default class Requester {
|
@@ -59,12 +58,10 @@ export default class Requester {
|
|
59
58
|
method: req.method,
|
60
59
|
headers: headers,
|
61
60
|
body: body,
|
62
|
-
headersTimeout: req.headersTimeout ??
|
63
|
-
bodyTimeout: req.bodyTimeout ??
|
61
|
+
headersTimeout: req.headersTimeout ?? 300_000,
|
62
|
+
bodyTimeout: req.bodyTimeout ?? 300_000,
|
64
63
|
});
|
65
64
|
debug(`[HTTP] ${req.method} ${url} -> ${response.statusCode} ${STATUS_CODES[response.statusCode]}`);
|
66
|
-
if (response.statusCode >= 400)
|
67
|
-
throw new HttpError(req, response);
|
68
65
|
return response;
|
69
66
|
}
|
70
67
|
/**
|
@@ -2,10 +2,14 @@ import { relative } from 'path';
|
|
2
2
|
import { Command } from 'commander';
|
3
3
|
import { extractKeysOfFiles } from '../../extractor/runner.js';
|
4
4
|
import { WarningMessages, emitGitHubWarning, } from '../../extractor/warnings.js';
|
5
|
-
import { loading } from '../../utils/logger.js';
|
6
|
-
async function
|
5
|
+
import { exitWithError, loading } from '../../utils/logger.js';
|
6
|
+
const lintHandler = (config) => async function () {
|
7
7
|
const opts = this.optsWithGlobals();
|
8
|
-
const
|
8
|
+
const patterns = opts.patterns;
|
9
|
+
if (!patterns?.length) {
|
10
|
+
exitWithError('Missing option --patterns or config.patterns option');
|
11
|
+
}
|
12
|
+
const extracted = await loading('Analyzing code...', extractKeysOfFiles(patterns, opts.extractor));
|
9
13
|
let warningCount = 0;
|
10
14
|
let filesCount = 0;
|
11
15
|
for (const [file, { warnings }] of extracted) {
|
@@ -32,8 +36,7 @@ async function lintHandler(filesPattern) {
|
|
32
36
|
process.exit(1);
|
33
37
|
}
|
34
38
|
console.log('No issues found.');
|
35
|
-
}
|
36
|
-
export default new Command('check')
|
39
|
+
};
|
40
|
+
export default (config) => new Command('check')
|
37
41
|
.description('Checks if the keys can be extracted automatically, and reports problems if any')
|
38
|
-
.
|
39
|
-
.action(lintHandler);
|
42
|
+
.action(lintHandler(config));
|
@@ -2,10 +2,14 @@ import { relative } from 'path';
|
|
2
2
|
import { Command } from 'commander';
|
3
3
|
import { extractKeysOfFiles } from '../../extractor/runner.js';
|
4
4
|
import { WarningMessages } from '../../extractor/warnings.js';
|
5
|
-
import { loading } from '../../utils/logger.js';
|
6
|
-
async function
|
5
|
+
import { exitWithError, loading } from '../../utils/logger.js';
|
6
|
+
const printHandler = (config) => async function () {
|
7
7
|
const opts = this.optsWithGlobals();
|
8
|
-
const
|
8
|
+
const patterns = opts.patterns;
|
9
|
+
if (!patterns?.length) {
|
10
|
+
exitWithError('Missing option --patterns or config.patterns option');
|
11
|
+
}
|
12
|
+
const extracted = await loading('Analyzing code...', extractKeysOfFiles(patterns, opts.extractor));
|
9
13
|
let warningCount = 0;
|
10
14
|
const keySet = new Set();
|
11
15
|
for (const [file, { keys, warnings }] of extracted) {
|
@@ -42,8 +46,7 @@ async function printHandler(filesPattern) {
|
|
42
46
|
}
|
43
47
|
console.log('Total unique keys found: %d', keySet.size);
|
44
48
|
console.log('Total warnings: %d', warningCount);
|
45
|
-
}
|
46
|
-
export default new Command('print')
|
49
|
+
};
|
50
|
+
export default (config) => new Command('print')
|
47
51
|
.description('Prints extracted data to the console')
|
48
|
-
.
|
49
|
-
.action(printHandler);
|
52
|
+
.action(printHandler(config));
|
package/dist/commands/extract.js
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
import { Command } from 'commander';
|
2
2
|
import extractPrint from './extract/print.js';
|
3
3
|
import extractCheck from './extract/check.js';
|
4
|
-
|
5
|
-
export default new Command('extract')
|
4
|
+
export default (config) => new Command('extract')
|
6
5
|
.description('Extracts strings from your projects')
|
7
|
-
.
|
8
|
-
.addCommand(
|
9
|
-
.addCommand(extractCheck);
|
6
|
+
.addCommand(extractPrint(config))
|
7
|
+
.addCommand(extractCheck(config));
|
package/dist/commands/login.js
CHANGED
@@ -1,21 +1,13 @@
|
|
1
1
|
import { Command } from 'commander';
|
2
|
-
import RestClient from '../client/index.js';
|
3
|
-
import { HttpError } from '../client/errors.js';
|
4
2
|
import { saveApiKey, removeApiKeys, clearAuthStore, } from '../config/credentials.js';
|
5
|
-
import { success
|
3
|
+
import { success } from '../utils/logger.js';
|
4
|
+
import { createTolgeeClient } from '../client/TolgeeClient.js';
|
6
5
|
async function loginHandler(key) {
|
7
6
|
const opts = this.optsWithGlobals();
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
}
|
12
|
-
catch (e) {
|
13
|
-
if (e instanceof HttpError && e.response.statusCode === 401) {
|
14
|
-
error("Couldn't log in: the API key you provided is invalid.");
|
15
|
-
process.exit(1);
|
16
|
-
}
|
17
|
-
throw e;
|
18
|
-
}
|
7
|
+
const keyInfo = await createTolgeeClient({
|
8
|
+
baseUrl: opts.apiUrl.toString(),
|
9
|
+
apiKey: key,
|
10
|
+
}).getApiKeyInfo();
|
19
11
|
await saveApiKey(opts.apiUrl, keyInfo);
|
20
12
|
success(keyInfo.type === 'PAK'
|
21
13
|
? `Logged in as ${keyInfo.username} on ${opts.apiUrl.hostname} for project ${keyInfo.project.name} (#${keyInfo.project.id}). Welcome back!`
|
package/dist/commands/pull.js
CHANGED
@@ -1,49 +1,55 @@
|
|
1
1
|
import { Command, Option } from 'commander';
|
2
2
|
import { unzipBuffer } from '../utils/zip.js';
|
3
|
-
import {
|
4
|
-
import {
|
5
|
-
import {
|
3
|
+
import { prepareDir } from '../utils/prepareDir.js';
|
4
|
+
import { loading, success } from '../utils/logger.js';
|
5
|
+
import { checkPathNotAFile } from '../utils/checkPathNotAFile.js';
|
6
|
+
import { mapExportFormat } from '../utils/mapExportFormat.js';
|
7
|
+
import { handleLoadableError } from '../client/TolgeeClient.js';
|
6
8
|
async function fetchZipBlob(opts) {
|
7
|
-
|
8
|
-
|
9
|
+
const exportFormat = mapExportFormat(opts.format);
|
10
|
+
const { format, messageFormat } = exportFormat;
|
11
|
+
const loadable = await opts.client.export.export({
|
12
|
+
format,
|
13
|
+
messageFormat,
|
14
|
+
supportArrays: opts.supportArrays,
|
9
15
|
languages: opts.languages,
|
10
16
|
filterState: opts.states,
|
11
17
|
structureDelimiter: opts.delimiter,
|
12
18
|
filterNamespace: opts.namespaces,
|
19
|
+
filterTagIn: opts.tags,
|
20
|
+
filterTagNotIn: opts.excludeTags,
|
21
|
+
fileStructureTemplate: opts.fileStructureTemplate,
|
13
22
|
});
|
23
|
+
handleLoadableError(loadable);
|
24
|
+
return loadable.data;
|
14
25
|
}
|
15
|
-
async function
|
26
|
+
const pullHandler = () => async function () {
|
16
27
|
const opts = this.optsWithGlobals();
|
17
|
-
|
18
|
-
|
19
|
-
const zipBlob = await loading('Fetching strings from Tolgee...', fetchZipBlob(opts));
|
20
|
-
await loading('Extracting strings...', unzipBuffer(zipBlob, path));
|
21
|
-
success('Done!');
|
28
|
+
if (!opts.path) {
|
29
|
+
throw new Error('Missing or option --path <path>');
|
22
30
|
}
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
}
|
31
|
-
}
|
32
|
-
export default new Command()
|
31
|
+
await checkPathNotAFile(opts.path);
|
32
|
+
const zipBlob = await loading('Fetching strings from Tolgee...', fetchZipBlob(opts));
|
33
|
+
await prepareDir(opts.path, opts.emptyDir);
|
34
|
+
await loading('Extracting strings...', unzipBuffer(zipBlob, opts.path));
|
35
|
+
success('Done!');
|
36
|
+
};
|
37
|
+
export default (config) => new Command()
|
33
38
|
.name('pull')
|
34
|
-
.description('Pulls translations
|
35
|
-
.
|
36
|
-
.addOption(new Option('-
|
37
|
-
.choices(['JSON', 'XLIFF'])
|
38
|
-
.default('JSON')
|
39
|
-
.argParser((v) => v.toUpperCase()))
|
40
|
-
.option('-l, --languages <languages...>', 'List of languages to pull. Leave unspecified to export them all')
|
39
|
+
.description('Pulls translations from Tolgee')
|
40
|
+
.addOption(new Option('-p, --path <path>', 'Destination of a folder where translation files will be stored in').default(config.pull?.path))
|
41
|
+
.addOption(new Option('-l, --languages <languages...>', 'List of languages to pull. Leave unspecified to export them all').default(config.pull?.languagess))
|
41
42
|
.addOption(new Option('-s, --states <states...>', 'List of translation states to include. Defaults all except untranslated')
|
42
43
|
.choices(['UNTRANSLATED', 'TRANSLATED', 'REVIEWED'])
|
44
|
+
.default(config.pull?.states)
|
43
45
|
.argParser((v, a) => [v.toUpperCase(), ...(a || [])]))
|
44
46
|
.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')
|
45
|
-
.default('.')
|
47
|
+
.default(config.pull?.delimiter ?? '.')
|
46
48
|
.argParser((v) => v || ''))
|
47
|
-
.addOption(new Option('-n, --namespaces <namespaces...>', 'List of namespaces to pull. Defaults to all namespaces'))
|
48
|
-
.
|
49
|
-
.
|
49
|
+
.addOption(new Option('-n, --namespaces <namespaces...>', 'List of namespaces to pull. Defaults to all namespaces').default(config.pull?.namespaces))
|
50
|
+
.addOption(new Option('-t, --tags <tags...>', 'List of tags which to include. Keys tagged by at least one of these tags will be included.').default(config.pull?.tags))
|
51
|
+
.addOption(new Option('--exclude-tags <tags...>', 'List of tags which to exclude. Keys tagged by at least one of these tags will be excluded.').default(config.pull?.excludeTags))
|
52
|
+
.addOption(new Option('--support-arrays', 'Export keys with array syntax (e.g. item[0]) as arrays.').default(config.pull?.supportArrays ?? false))
|
53
|
+
.addOption(new Option('--empty-dir', 'Empty target directory before inserting pulled files.').default(config.pull?.emptyDir))
|
54
|
+
.addOption(new Option('--file-structure-template <template>', 'Defines exported file structure: https://tolgee.io/tolgee-cli/push-pull-strings#file-structure-template-format').default(config.pull?.fileStructureTemplate))
|
55
|
+
.action(pullHandler());
|