@tolgee/cli 1.0.0-prerelease.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/LICENSE +21 -0
- package/README.md +34 -0
- package/dist/client/errors.js +36 -0
- package/dist/client/export.js +23 -0
- package/dist/client/import.js +61 -0
- package/dist/client/index.js +79 -0
- package/dist/client/internal/requester.js +145 -0
- package/dist/client/internal/schema.generated.js +6 -0
- package/dist/client/internal/schema.utils.js +2 -0
- package/dist/client/languages.js +16 -0
- package/dist/client/project.js +44 -0
- package/dist/commands/extract/check.js +41 -0
- package/dist/commands/extract/print.js +51 -0
- package/dist/commands/extract.js +14 -0
- package/dist/commands/login.js +49 -0
- package/dist/commands/pull.js +38 -0
- package/dist/commands/push.js +122 -0
- package/dist/commands/sync/compare.js +48 -0
- package/dist/commands/sync/sync.js +110 -0
- package/dist/commands/sync/syncUtils.js +64 -0
- package/dist/config/credentials.js +125 -0
- package/dist/config/tolgeerc.js +64 -0
- package/dist/constants.js +18 -0
- package/dist/extractor/index.js +2 -0
- package/dist/extractor/machines/react.js +728 -0
- package/dist/extractor/machines/shared/comments.js +82 -0
- package/dist/extractor/machines/shared/properties.js +226 -0
- package/dist/extractor/presets/react.js +29 -0
- package/dist/extractor/runner.js +39 -0
- package/dist/extractor/tokenizer.js +102 -0
- package/dist/extractor/warnings.js +89 -0
- package/dist/extractor/worker.js +82 -0
- package/dist/index.js +151 -0
- package/dist/options.js +37 -0
- package/dist/utils/ask.js +34 -0
- package/dist/utils/configPath.js +17 -0
- package/dist/utils/deferred.js +11 -0
- package/dist/utils/logger.js +93 -0
- package/dist/utils/moduleLoader.js +43 -0
- package/dist/utils/overwriteDir.js +38 -0
- package/dist/utils/zip.js +83 -0
- package/extractor.d.ts +21 -0
- package/package.json +98 -0
- package/textmate/THIRD_PARTY_NOTICE +31 -0
- package/textmate/TypeScript.tmLanguage +9728 -0
- package/textmate/TypeScriptReact.tmLanguage +10158 -0
package/dist/index.js
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
"use strict";
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
5
|
+
};
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
7
|
+
const commander_1 = require("commander");
|
8
|
+
const credentials_1 = require("./config/credentials");
|
9
|
+
const tolgeerc_1 = __importDefault(require("./config/tolgeerc"));
|
10
|
+
const client_1 = __importDefault(require("./client"));
|
11
|
+
const errors_1 = require("./client/errors");
|
12
|
+
const logger_1 = require("./utils/logger");
|
13
|
+
const options_1 = require("./options");
|
14
|
+
const constants_1 = require("./constants");
|
15
|
+
const login_1 = require("./commands/login");
|
16
|
+
const push_1 = __importDefault(require("./commands/push"));
|
17
|
+
const pull_1 = __importDefault(require("./commands/pull"));
|
18
|
+
const extract_1 = __importDefault(require("./commands/extract"));
|
19
|
+
const compare_1 = __importDefault(require("./commands/sync/compare"));
|
20
|
+
const sync_1 = __importDefault(require("./commands/sync/sync"));
|
21
|
+
const NO_KEY_COMMANDS = ['login', 'logout', 'extract'];
|
22
|
+
function topLevelName(command) {
|
23
|
+
return command.parent && command.parent.parent
|
24
|
+
? topLevelName(command.parent)
|
25
|
+
: command.name();
|
26
|
+
}
|
27
|
+
async function validateApiKey(cmd) {
|
28
|
+
const opts = cmd.optsWithGlobals();
|
29
|
+
if (!opts.apiKey) {
|
30
|
+
// Attempt to load --api-key from config store if not specified
|
31
|
+
// This is not done as part of the init routine or via the mandatory flag, as this is dependent on the API URL.
|
32
|
+
const key = await (0, credentials_1.getApiKey)(opts.apiUrl, opts.projectId);
|
33
|
+
if (!key) {
|
34
|
+
(0, logger_1.error)('No API key has been provided. You must either provide one via --api-key, or login via `tolgee login`.');
|
35
|
+
process.exit(1);
|
36
|
+
}
|
37
|
+
cmd.setOptionValue('apiKey', key);
|
38
|
+
program.setOptionValue('_removeApiKeyFromStore', () => {
|
39
|
+
if (key.startsWith(constants_1.API_KEY_PAT_PREFIX)) {
|
40
|
+
(0, credentials_1.savePat)(opts.apiUrl);
|
41
|
+
}
|
42
|
+
else {
|
43
|
+
(0, credentials_1.savePak)(opts.apiUrl, opts.projectId);
|
44
|
+
}
|
45
|
+
});
|
46
|
+
}
|
47
|
+
}
|
48
|
+
function validateProjectId(cmd) {
|
49
|
+
const opts = cmd.optsWithGlobals();
|
50
|
+
// Validate --project-id is present when using Project API keys
|
51
|
+
if (opts.projectId === -1 && opts.apiKey.startsWith(constants_1.API_KEY_PAT_PREFIX)) {
|
52
|
+
(0, logger_1.error)('You must specify a Project ID.');
|
53
|
+
process.exit(1);
|
54
|
+
}
|
55
|
+
if (opts.apiKey.startsWith(constants_1.API_KEY_PAK_PREFIX)) {
|
56
|
+
// Parse the key to ensure we can access the specified Project ID
|
57
|
+
const projectId = client_1.default.projectIdFromKey(opts.apiKey);
|
58
|
+
program.setOptionValue('projectId', projectId);
|
59
|
+
if (opts.projectId !== -1 && opts.projectId !== projectId) {
|
60
|
+
(0, logger_1.error)('The specified API key cannot be used to perform operations on the specified project.');
|
61
|
+
(0, logger_1.info)(`The API key you specified is tied to project #${projectId}, you tried to perform operations on project #${opts.projectId}.`);
|
62
|
+
(0, logger_1.info)('Learn more about how API keys in Tolgee work here: https://tolgee.io/docs/platform/api-keys-and-pat-tokens');
|
63
|
+
process.exit(1);
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
async function preHandler(prog, cmd) {
|
68
|
+
if (!NO_KEY_COMMANDS.includes(topLevelName(cmd))) {
|
69
|
+
await validateApiKey(cmd);
|
70
|
+
await validateProjectId(cmd);
|
71
|
+
const opts = cmd.optsWithGlobals();
|
72
|
+
const client = new client_1.default({
|
73
|
+
apiUrl: opts.apiUrl,
|
74
|
+
apiKey: opts.apiKey,
|
75
|
+
projectId: opts.projectId,
|
76
|
+
});
|
77
|
+
cmd.setOptionValue('client', client);
|
78
|
+
}
|
79
|
+
// Apply verbosity
|
80
|
+
(0, logger_1.setDebug)(prog.opts().verbose);
|
81
|
+
}
|
82
|
+
const program = new commander_1.Command('tolgee')
|
83
|
+
.version(constants_1.VERSION)
|
84
|
+
.configureOutput({ writeErr: logger_1.error })
|
85
|
+
.description('Command Line Interface to interact with the Tolgee Platform')
|
86
|
+
.option('-v, --verbose', 'Enable verbose logging.')
|
87
|
+
.hook('preAction', preHandler);
|
88
|
+
// Global options
|
89
|
+
program.addOption(options_1.API_URL_OPT);
|
90
|
+
program.addOption(options_1.API_KEY_OPT);
|
91
|
+
program.addOption(options_1.PROJECT_ID_OPT);
|
92
|
+
// Register commands
|
93
|
+
program.addCommand(login_1.Login);
|
94
|
+
program.addCommand(login_1.Logout);
|
95
|
+
program.addCommand(push_1.default);
|
96
|
+
program.addCommand(pull_1.default);
|
97
|
+
program.addCommand(extract_1.default);
|
98
|
+
program.addCommand(compare_1.default);
|
99
|
+
program.addCommand(sync_1.default);
|
100
|
+
async function loadConfig() {
|
101
|
+
const tgConfig = await (0, tolgeerc_1.default)();
|
102
|
+
if (tgConfig) {
|
103
|
+
for (const [key, value] of Object.entries(tgConfig)) {
|
104
|
+
program.setOptionValue(key, value);
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}
|
108
|
+
async function handleHttpError(e) {
|
109
|
+
(0, logger_1.error)('An error occurred while requesting the API.');
|
110
|
+
(0, logger_1.error)(`${e.request.method} ${e.request.url}`);
|
111
|
+
(0, logger_1.error)(e.getErrorText());
|
112
|
+
// Remove token from store if necessary
|
113
|
+
if (e.response.status === 401) {
|
114
|
+
const removeFn = program.getOptionValue('_removeApiKeyFromStore');
|
115
|
+
if (removeFn) {
|
116
|
+
(0, logger_1.info)('Removing the API key from the authentication store.');
|
117
|
+
removeFn();
|
118
|
+
}
|
119
|
+
}
|
120
|
+
// Print server output for server errors
|
121
|
+
if ((0, logger_1.isDebugEnabled)()) {
|
122
|
+
// We cannot parse the response as JSON and pull error codes here as we may be here due to a 5xx error:
|
123
|
+
// by nature 5xx class errors can happen for a lot of reasons (e.g. upstream issues, server issues,
|
124
|
+
// catastrophic failure) which means the output is completely unpredictable. While some errors are
|
125
|
+
// formatted by the Tolgee server, reality is there's a huge chance the 5xx error hasn't been raised
|
126
|
+
// by Tolgee's error handler.
|
127
|
+
const res = await e.response.text();
|
128
|
+
(0, logger_1.debug)(`Server response:\n\n---\n${res}\n---`);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
async function run() {
|
132
|
+
try {
|
133
|
+
await loadConfig();
|
134
|
+
await program.parseAsync();
|
135
|
+
}
|
136
|
+
catch (e) {
|
137
|
+
if (e instanceof errors_1.HttpError) {
|
138
|
+
await handleHttpError(e);
|
139
|
+
process.exit(1);
|
140
|
+
}
|
141
|
+
// If the error is uncaught, huge chance that either:
|
142
|
+
// - The error should be handled here but isn't
|
143
|
+
// - The error should be handled in the command but isn't
|
144
|
+
// - Something went wrong with the code
|
145
|
+
(0, logger_1.error)('An unexpected error occurred while running the command.');
|
146
|
+
(0, logger_1.error)('Please report this to our issue tracker: https://github.com/tolgee/tolgee-cli/issues');
|
147
|
+
console.log(e.stack);
|
148
|
+
process.exit(1);
|
149
|
+
}
|
150
|
+
}
|
151
|
+
run();
|
package/dist/options.js
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.EXTRACTOR = exports.API_URL_OPT = exports.PROJECT_ID_OPT = exports.API_KEY_OPT = void 0;
|
4
|
+
const fs_1 = require("fs");
|
5
|
+
const path_1 = require("path");
|
6
|
+
const commander_1 = require("commander");
|
7
|
+
const constants_1 = require("./constants");
|
8
|
+
function parseProjectId(v) {
|
9
|
+
const val = Number(v);
|
10
|
+
if (isNaN(val) || val < 1) {
|
11
|
+
throw new commander_1.InvalidArgumentError('Not a valid project ID.');
|
12
|
+
}
|
13
|
+
return val;
|
14
|
+
}
|
15
|
+
function parseUrlArgument(v) {
|
16
|
+
try {
|
17
|
+
return new URL(v);
|
18
|
+
}
|
19
|
+
catch {
|
20
|
+
throw new commander_1.InvalidArgumentError('Malformed URL.');
|
21
|
+
}
|
22
|
+
}
|
23
|
+
function parsePath(v) {
|
24
|
+
const path = (0, path_1.resolve)(v);
|
25
|
+
if (!(0, fs_1.existsSync)(path)) {
|
26
|
+
throw new commander_1.InvalidArgumentError(`The specified path "${v}" does not exist.`);
|
27
|
+
}
|
28
|
+
return path;
|
29
|
+
}
|
30
|
+
exports.API_KEY_OPT = new commander_1.Option('-ak, --api-key <key>', 'Tolgee API Key. Can be a Project API Key or a Personal Access Token.').env('TOLGEE_API_KEY');
|
31
|
+
exports.PROJECT_ID_OPT = new commander_1.Option('-p, --project-id <id>', 'Project ID. Only required when using a Personal Access Token.')
|
32
|
+
.default(-1)
|
33
|
+
.argParser(parseProjectId);
|
34
|
+
exports.API_URL_OPT = new commander_1.Option('-au, --api-url <url>', 'The url of Tolgee API.')
|
35
|
+
.default(constants_1.DEFAULT_API_URL)
|
36
|
+
.argParser(parseUrlArgument);
|
37
|
+
exports.EXTRACTOR = new commander_1.Option('-e, --extractor <extractor>', `A path to a custom extractor to use instead of the default one.`).argParser(parsePath);
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.askBoolean = exports.askString = void 0;
|
7
|
+
const readline_1 = __importDefault(require("readline")); // readline/promises is Node 17, currently supporting Node 16+
|
8
|
+
async function askString(question) {
|
9
|
+
return new Promise((resolve) => {
|
10
|
+
const rl = readline_1.default.createInterface({
|
11
|
+
input: process.stdin,
|
12
|
+
output: process.stdout,
|
13
|
+
});
|
14
|
+
rl.question(`${question} `, (a) => {
|
15
|
+
resolve(a);
|
16
|
+
rl.close();
|
17
|
+
});
|
18
|
+
});
|
19
|
+
}
|
20
|
+
exports.askString = askString;
|
21
|
+
async function askBoolean(question, def = false) {
|
22
|
+
const yn = def === true ? '[Y/n]' : '[y/N]';
|
23
|
+
let res = def;
|
24
|
+
const str = await askString(`${question} ${yn}`);
|
25
|
+
const strRes = str[0]?.toLowerCase();
|
26
|
+
if (strRes === 'y') {
|
27
|
+
res = true;
|
28
|
+
}
|
29
|
+
else if (strRes === 'n') {
|
30
|
+
res = false;
|
31
|
+
}
|
32
|
+
return res;
|
33
|
+
}
|
34
|
+
exports.askBoolean = askBoolean;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const os_1 = require("os");
|
4
|
+
const path_1 = require("path");
|
5
|
+
function getConfigPath() {
|
6
|
+
switch (process.platform) {
|
7
|
+
case 'win32':
|
8
|
+
return (0, path_1.resolve)(process.env.APPDATA, 'tolgee');
|
9
|
+
case 'darwin':
|
10
|
+
return (0, path_1.resolve)((0, os_1.homedir)(), 'Library', 'Application Support', 'tolgee');
|
11
|
+
default:
|
12
|
+
return process.env.XDG_CONFIG_HOME
|
13
|
+
? (0, path_1.resolve)(process.env.XDG_CONFIG_HOME, 'tolgee')
|
14
|
+
: (0, path_1.resolve)((0, os_1.homedir)(), '.config', 'tolgee');
|
15
|
+
}
|
16
|
+
}
|
17
|
+
exports.default = getConfigPath;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.createDeferred = void 0;
|
4
|
+
function createDeferred() {
|
5
|
+
const deferred = {};
|
6
|
+
deferred.promise = new Promise((resolve, reject) => {
|
7
|
+
Object.assign(deferred, { resolve: resolve, reject: reject });
|
8
|
+
});
|
9
|
+
return deferred;
|
10
|
+
}
|
11
|
+
exports.createDeferred = createDeferred;
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.loading = exports.error = exports.warn = exports.success = exports.info = exports.debug = exports.isDebugEnabled = exports.setDebug = void 0;
|
4
|
+
const SYMBOLS = [' 🐁', ' 🐁 ', ' 🐁 ', '🐁 '];
|
5
|
+
let debugEnabled = false;
|
6
|
+
/**
|
7
|
+
* Enables or disables debugging messages.
|
8
|
+
*
|
9
|
+
* @param enabled Whether debugging messages should be logged.
|
10
|
+
*/
|
11
|
+
function setDebug(enabled) {
|
12
|
+
debugEnabled = enabled;
|
13
|
+
}
|
14
|
+
exports.setDebug = setDebug;
|
15
|
+
/**
|
16
|
+
* Gets the current status of debug logging.
|
17
|
+
*
|
18
|
+
* @returns Whether debugging is enabled.
|
19
|
+
*/
|
20
|
+
function isDebugEnabled() {
|
21
|
+
return debugEnabled;
|
22
|
+
}
|
23
|
+
exports.isDebugEnabled = isDebugEnabled;
|
24
|
+
/**
|
25
|
+
* Logs a debug message to the console if debugging is enabled.
|
26
|
+
*
|
27
|
+
* @param msg The message.
|
28
|
+
*/
|
29
|
+
function debug(msg) {
|
30
|
+
if (debugEnabled) {
|
31
|
+
console.log(`⚪ ${msg}`);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
exports.debug = debug;
|
35
|
+
/**
|
36
|
+
* Logs an informative message to the console.
|
37
|
+
*
|
38
|
+
* @param msg The message.
|
39
|
+
*/
|
40
|
+
function info(msg) {
|
41
|
+
console.log(`🔵 ${msg}`);
|
42
|
+
}
|
43
|
+
exports.info = info;
|
44
|
+
/**
|
45
|
+
* Logs a success to the console.
|
46
|
+
*
|
47
|
+
* @param msg The message.
|
48
|
+
*/
|
49
|
+
function success(msg) {
|
50
|
+
console.log(`✅ ${msg}`);
|
51
|
+
}
|
52
|
+
exports.success = success;
|
53
|
+
/**
|
54
|
+
* Logs a warning message to the console.
|
55
|
+
*
|
56
|
+
* @param msg The message.
|
57
|
+
*/
|
58
|
+
function warn(msg) {
|
59
|
+
console.log(`🟡 ${msg}`);
|
60
|
+
}
|
61
|
+
exports.warn = warn;
|
62
|
+
/**
|
63
|
+
* Logs an error message to the console.
|
64
|
+
*
|
65
|
+
* @param msg The message.
|
66
|
+
*/
|
67
|
+
function error(msg) {
|
68
|
+
console.log(`🔴 ${msg}`);
|
69
|
+
}
|
70
|
+
exports.error = error;
|
71
|
+
/**
|
72
|
+
* Shows a loading indicator for a Promise until it resolves.
|
73
|
+
*
|
74
|
+
* @param comment Comment to display.
|
75
|
+
* @param promise The promise to watch.
|
76
|
+
* @returns The promise passed in parameter. Useful for decorating without using a buffer variable.
|
77
|
+
*/
|
78
|
+
function loading(comment, promise) {
|
79
|
+
let symbolPosition = 0;
|
80
|
+
const interval = setInterval(() => {
|
81
|
+
process.stdout.write(`\r${SYMBOLS[symbolPosition]} ${comment}`);
|
82
|
+
symbolPosition = (symbolPosition + 1) % 4;
|
83
|
+
}, 250);
|
84
|
+
promise.then(() => {
|
85
|
+
clearInterval(interval);
|
86
|
+
process.stdout.write(`\r🐭✅ ${comment}\n`);
|
87
|
+
}, () => {
|
88
|
+
clearInterval(interval);
|
89
|
+
process.stdout.write(`\r🐭🔴 ${comment}\n`);
|
90
|
+
});
|
91
|
+
return promise;
|
92
|
+
}
|
93
|
+
exports.loading = loading;
|
@@ -0,0 +1,43 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.loadModule = void 0;
|
4
|
+
const path_1 = require("path");
|
5
|
+
let tsService;
|
6
|
+
function realImport(file) {
|
7
|
+
return eval('import(file)');
|
8
|
+
}
|
9
|
+
async function registerTsNode() {
|
10
|
+
if (!tsService) {
|
11
|
+
try {
|
12
|
+
const tsNode = require('ts-node');
|
13
|
+
tsService = tsNode.register({ compilerOptions: { module: 'CommonJS' } });
|
14
|
+
}
|
15
|
+
catch (e) {
|
16
|
+
if (e.code === 'ERR_MODULE_NOT_FOUND') {
|
17
|
+
throw new Error('ts-node is required to load TypeScript files.');
|
18
|
+
}
|
19
|
+
throw e;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
async function importTypeScript(file) {
|
24
|
+
if ((0, path_1.extname)(__filename) === '.ts') {
|
25
|
+
return require(file);
|
26
|
+
}
|
27
|
+
await registerTsNode();
|
28
|
+
tsService.enabled(true);
|
29
|
+
const mdl = await realImport(file);
|
30
|
+
tsService.enabled(false);
|
31
|
+
return mdl;
|
32
|
+
}
|
33
|
+
async function loadModule(module) {
|
34
|
+
if (module.endsWith('.ts')) {
|
35
|
+
return importTypeScript(module);
|
36
|
+
}
|
37
|
+
const mdl = await realImport(module);
|
38
|
+
if (mdl.default?.default) {
|
39
|
+
return mdl.default;
|
40
|
+
}
|
41
|
+
return mdl;
|
42
|
+
}
|
43
|
+
exports.loadModule = loadModule;
|
@@ -0,0 +1,38 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.overwriteDir = void 0;
|
4
|
+
const path_1 = require("path");
|
5
|
+
const promises_1 = require("fs/promises");
|
6
|
+
const ask_1 = require("./ask");
|
7
|
+
const logger_1 = require("./logger");
|
8
|
+
async function overwriteDir(path, overwrite) {
|
9
|
+
try {
|
10
|
+
const stats = await (0, promises_1.stat)(path);
|
11
|
+
if (!stats.isDirectory()) {
|
12
|
+
(0, logger_1.error)('The specified path already exists and is not a directory.');
|
13
|
+
process.exit(1);
|
14
|
+
}
|
15
|
+
if (!overwrite) {
|
16
|
+
if (!process.stdout.isTTY) {
|
17
|
+
(0, logger_1.error)('The specified path already exists.');
|
18
|
+
process.exit(1);
|
19
|
+
}
|
20
|
+
(0, logger_1.warn)(`The specified path ${(0, path_1.resolve)(path)} already exists.`);
|
21
|
+
const userOverwrite = await (0, ask_1.askBoolean)('Do you want to overwrite data? *BE CAREFUL, ALL THE CONTENTS OF THE DESTINATION FOLDER WILL BE DESTROYED*.');
|
22
|
+
if (!userOverwrite) {
|
23
|
+
(0, logger_1.error)('Aborting.');
|
24
|
+
process.exit(1);
|
25
|
+
}
|
26
|
+
// Purge data as requested.
|
27
|
+
await (0, promises_1.rm)(path, { recursive: true });
|
28
|
+
}
|
29
|
+
}
|
30
|
+
catch (e) {
|
31
|
+
if (e.code !== 'ENOENT') {
|
32
|
+
throw e;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
// Create the directory
|
36
|
+
await (0, promises_1.mkdir)(path, { recursive: true });
|
37
|
+
}
|
38
|
+
exports.overwriteDir = overwriteDir;
|
@@ -0,0 +1,83 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.unzip = exports.unzipBuffer = void 0;
|
4
|
+
const fs_1 = require("fs");
|
5
|
+
const promises_1 = require("fs/promises");
|
6
|
+
const path_1 = require("path");
|
7
|
+
const yauzl_1 = require("yauzl");
|
8
|
+
const ZIP_PARSER_OPTS = {
|
9
|
+
strictFileNames: true,
|
10
|
+
decodeStrings: true,
|
11
|
+
lazyEntries: true,
|
12
|
+
};
|
13
|
+
function dumpFile(zip, entry, dest) {
|
14
|
+
zip.openReadStream(entry, (err, stream) => {
|
15
|
+
if (err)
|
16
|
+
throw err;
|
17
|
+
const writeStream = (0, fs_1.createWriteStream)(dest);
|
18
|
+
stream.pipe(writeStream);
|
19
|
+
// Unlock reading loop
|
20
|
+
stream.on('end', () => zip.readEntry());
|
21
|
+
});
|
22
|
+
}
|
23
|
+
/**
|
24
|
+
* Unzips a ZIP blob to a destination on disk.
|
25
|
+
*
|
26
|
+
* @param zipBlob The ZIP blob
|
27
|
+
* @param dest The destination path
|
28
|
+
*/
|
29
|
+
function unzipBuffer(zipBlob, dest) {
|
30
|
+
return new Promise((resolve, reject) => {
|
31
|
+
zipBlob.arrayBuffer().then((buffer) => {
|
32
|
+
const nodeBuffer = Buffer.from(buffer);
|
33
|
+
(0, yauzl_1.fromBuffer)(nodeBuffer, ZIP_PARSER_OPTS, (err, zip) => {
|
34
|
+
if (err) {
|
35
|
+
return reject(err);
|
36
|
+
}
|
37
|
+
resolve(unzip(zip, dest));
|
38
|
+
});
|
39
|
+
});
|
40
|
+
});
|
41
|
+
}
|
42
|
+
exports.unzipBuffer = unzipBuffer;
|
43
|
+
/**
|
44
|
+
* Unzips a ZIP file loaded in memory to a destination on disk.
|
45
|
+
*
|
46
|
+
* @param file The ZIP file
|
47
|
+
* @param dest The destination path
|
48
|
+
*/
|
49
|
+
function unzip(zip, dest) {
|
50
|
+
// Enforce expected & security options.
|
51
|
+
// Lazy entries is what this reader is based upon.
|
52
|
+
// Decode strings ensures file paths are sanitized by yazul
|
53
|
+
// and does not present any security threat to the machine.
|
54
|
+
if (!zip.lazyEntries || !zip.decodeStrings) {
|
55
|
+
throw new TypeError('Invalid ZIP file: lazyEntries and decodeStrings both must be set to true.');
|
56
|
+
}
|
57
|
+
return new Promise((resolve, reject) => {
|
58
|
+
zip.on('error', reject);
|
59
|
+
zip.on('end', resolve);
|
60
|
+
// There is no mechanism for zip files to contain directories
|
61
|
+
// by standards, and implementations diverge. Some make an explicit
|
62
|
+
// directory entry (ending with /), some don't make any specific treatment.
|
63
|
+
// The "safest" way is to check the path on files and create them as necessary.
|
64
|
+
const seenDirectories = new Set([dest]);
|
65
|
+
zip.readEntry();
|
66
|
+
zip.on('entry', (entry) => {
|
67
|
+
if (entry.fileName.endsWith('/')) {
|
68
|
+
zip.readEntry();
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
const entryPath = (0, path_1.join)(dest, entry.fileName);
|
72
|
+
// Handle directory creation
|
73
|
+
const entryDirName = (0, path_1.dirname)(entryPath);
|
74
|
+
if (!seenDirectories.has(entryDirName)) {
|
75
|
+
(0, promises_1.mkdir)(entryDirName, { recursive: true }).then(() => dumpFile(zip, entry, entryPath));
|
76
|
+
}
|
77
|
+
else {
|
78
|
+
dumpFile(zip, entry, entryPath);
|
79
|
+
}
|
80
|
+
});
|
81
|
+
});
|
82
|
+
}
|
83
|
+
exports.unzip = unzip;
|
package/extractor.d.ts
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
export type Key = {
|
2
|
+
keyName: string;
|
3
|
+
defaultValue?: string;
|
4
|
+
namespace?: string;
|
5
|
+
};
|
6
|
+
export type ExtractedKey = Key & {
|
7
|
+
line: number;
|
8
|
+
};
|
9
|
+
export type Warning = {
|
10
|
+
warning: string;
|
11
|
+
line: number;
|
12
|
+
};
|
13
|
+
export type Extractor = (fileContents: string, fileName: string) => string[];
|
14
|
+
export type ExtractionResult = {
|
15
|
+
keys: ExtractedKey[];
|
16
|
+
warnings: Warning[];
|
17
|
+
};
|
18
|
+
export type ExtractionResults = Map<string, {
|
19
|
+
keys: ExtractedKey[];
|
20
|
+
warnings: Warning[];
|
21
|
+
}>;
|
package/package.json
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
{
|
2
|
+
"name": "@tolgee/cli",
|
3
|
+
"version": "1.0.0-prerelease.1",
|
4
|
+
"type": "commonjs",
|
5
|
+
"description": "A tool to interact with the Tolgee Platform through CLI",
|
6
|
+
"bin": {
|
7
|
+
"tolgee": "./dist/index.js"
|
8
|
+
},
|
9
|
+
"scripts": {
|
10
|
+
"build": "rimraf dist dist-types && tsc && cp dist-types/extractor/index.d.ts extractor.d.ts",
|
11
|
+
"test": "npm run test:unit && npm run test:e2e && npm run test:package",
|
12
|
+
"test:unit": "jest -c jest.unit.config.ts",
|
13
|
+
"pretest:e2e": "npm run tolgee:start",
|
14
|
+
"posttest:e2e": "npm run tolgee:stop",
|
15
|
+
"test:e2e": "jest -c jest.e2e.config.ts --runInBand",
|
16
|
+
"test:e2e-run": "jest -c jest.e2e.config.ts --runInBand",
|
17
|
+
"test:package": "node scripts/validatePackage.mjs",
|
18
|
+
"tolgee:start": "node scripts/startDocker.mjs",
|
19
|
+
"tolgee:stop": "docker stop tolgee_cli_e2e",
|
20
|
+
"lint": "prettier --check ./src ./test ./scripts jest.config.ts jest.*.config.ts",
|
21
|
+
"prettier": "prettier --write ./src ./test ./scripts jest.config.ts jest.*.config.ts",
|
22
|
+
"run-dev": "ts-node ./src/index.ts",
|
23
|
+
"schema": "openapi-typescript http://localhost:22222/v3/api-docs/All%20Internal%20-%20for%20Tolgee%20Web%20application --output src/client/internal/schema.generated.ts",
|
24
|
+
"release": "semantic-release"
|
25
|
+
},
|
26
|
+
"author": "Jan Cizmar",
|
27
|
+
"license": "MIT",
|
28
|
+
"dependencies": {
|
29
|
+
"ansi-colors": "^4.1.3",
|
30
|
+
"base32-decode": "^1.0.0",
|
31
|
+
"commander": "^10.0.0",
|
32
|
+
"cosmiconfig": "^8.0.0",
|
33
|
+
"form-data": "^4.0.0",
|
34
|
+
"glob": "^8.1.0",
|
35
|
+
"json5": "^2.2.3",
|
36
|
+
"undici": "^5.15.0",
|
37
|
+
"vscode-oniguruma": "^1.7.0",
|
38
|
+
"vscode-textmate": "^8.0.0",
|
39
|
+
"xstate": "^4.35.2",
|
40
|
+
"yauzl": "^2.10.0"
|
41
|
+
},
|
42
|
+
"devDependencies": {
|
43
|
+
"@jest/types": "^29.3.1",
|
44
|
+
"@semantic-release/changelog": "^6.0.2",
|
45
|
+
"@semantic-release/git": "^10.0.1",
|
46
|
+
"@types/glob": "^8.0.0",
|
47
|
+
"@types/jest": "^29.2.5",
|
48
|
+
"@types/node": "^18.11.18",
|
49
|
+
"@types/yauzl": "^2.10.0",
|
50
|
+
"jest": "^29.3.1",
|
51
|
+
"openapi-typescript": "^6.1.0",
|
52
|
+
"prettier": "^2.8.3",
|
53
|
+
"rimraf": "^4.0.7",
|
54
|
+
"semantic-release": "^20.0.2",
|
55
|
+
"ts-jest": "^29.0.5",
|
56
|
+
"ts-node": "^10.9.1",
|
57
|
+
"typescript": "^4.9.4"
|
58
|
+
},
|
59
|
+
"engines": {
|
60
|
+
"node": ">= 18"
|
61
|
+
},
|
62
|
+
"files": [
|
63
|
+
"dist",
|
64
|
+
"textmate",
|
65
|
+
"extractor.d.ts",
|
66
|
+
"README.md",
|
67
|
+
"LICENSE"
|
68
|
+
],
|
69
|
+
"release": {
|
70
|
+
"branches": [
|
71
|
+
"main",
|
72
|
+
{
|
73
|
+
"name": "next",
|
74
|
+
"channel": "next"
|
75
|
+
},
|
76
|
+
{
|
77
|
+
"name": "prerelease",
|
78
|
+
"channel": "rc",
|
79
|
+
"prerelease": true
|
80
|
+
}
|
81
|
+
],
|
82
|
+
"plugins": [
|
83
|
+
"@semantic-release/commit-analyzer",
|
84
|
+
"@semantic-release/release-notes-generator",
|
85
|
+
[
|
86
|
+
"@semantic-release/git",
|
87
|
+
{
|
88
|
+
"assets": [
|
89
|
+
"package.json",
|
90
|
+
"CHANGELOG.md"
|
91
|
+
]
|
92
|
+
}
|
93
|
+
],
|
94
|
+
"@semantic-release/npm",
|
95
|
+
"@semantic-release/github"
|
96
|
+
]
|
97
|
+
}
|
98
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
THIRD PARTY LICENSES
|
2
|
+
===
|
3
|
+
|
4
|
+
Licensing information for the following files:
|
5
|
+
- TypeScript.tmLanguage
|
6
|
+
- TypeScriptReact.tmLanguage
|
7
|
+
|
8
|
+
Copyright (c) Microsoft Corporation
|
9
|
+
All rights reserved.
|
10
|
+
|
11
|
+
MIT License
|
12
|
+
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
15
|
+
in the Software without restriction, including without limitation the rights
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
18
|
+
furnished to do so, subject to the following conditions:
|
19
|
+
|
20
|
+
The above copyright notice and this permission notice shall be included in
|
21
|
+
all copies or substantial portions of the Software.
|
22
|
+
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
29
|
+
THE SOFTWARE.
|
30
|
+
|
31
|
+
---
|