@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
@@ -0,0 +1,122 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const path_1 = require("path");
|
4
|
+
const promises_1 = require("fs/promises");
|
5
|
+
const commander_1 = require("commander");
|
6
|
+
const errors_1 = require("../client/errors");
|
7
|
+
const ask_1 = require("../utils/ask");
|
8
|
+
const logger_1 = require("../utils/logger");
|
9
|
+
async function readDirectory(directory, base = '') {
|
10
|
+
const files = [];
|
11
|
+
const dir = await (0, promises_1.readdir)(directory);
|
12
|
+
for (const file of dir) {
|
13
|
+
const filePath = (0, path_1.join)(directory, file);
|
14
|
+
const fileStat = await (0, promises_1.stat)(filePath);
|
15
|
+
if (fileStat.isDirectory()) {
|
16
|
+
const dirFiles = await readDirectory(filePath, `${file}/`);
|
17
|
+
files.push(...dirFiles);
|
18
|
+
}
|
19
|
+
else {
|
20
|
+
const blob = await (0, promises_1.readFile)(filePath);
|
21
|
+
files.push({ name: base + file, data: blob });
|
22
|
+
}
|
23
|
+
}
|
24
|
+
return files;
|
25
|
+
}
|
26
|
+
function getConflictingLanguages(result) {
|
27
|
+
const conflicts = [];
|
28
|
+
const languages = result.result?._embedded?.languages;
|
29
|
+
if (languages) {
|
30
|
+
for (const l of languages) {
|
31
|
+
if (l.conflictCount) {
|
32
|
+
conflicts.push(l.id);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
return conflicts;
|
37
|
+
}
|
38
|
+
async function promptConflicts(opts) {
|
39
|
+
const projectId = opts.client.getProjectId();
|
40
|
+
const resolveUrl = new URL(`/projects/${projectId}/import`, opts.apiUrl).href;
|
41
|
+
if (opts.forceMode === 'NO') {
|
42
|
+
(0, logger_1.error)(`There are conflicts in the import. You can resolve them and complete the import here: ${resolveUrl}.`);
|
43
|
+
process.exit(1);
|
44
|
+
}
|
45
|
+
if (opts.forceMode) {
|
46
|
+
return opts.forceMode;
|
47
|
+
}
|
48
|
+
if (!process.stdout.isTTY) {
|
49
|
+
(0, logger_1.error)(`There are conflicts in the import. Please specify a --force-mode, or resolve them in your browser at ${resolveUrl}.`);
|
50
|
+
process.exit(1);
|
51
|
+
}
|
52
|
+
(0, logger_1.warn)('There are conflicts in the import. What do you want to do?');
|
53
|
+
const resp = await (0, ask_1.askString)('Type "KEEP" to preserve the version on the server, "OVERRIDE" to use the version from the import, and nothing to abort: ');
|
54
|
+
if (resp !== 'KEEP' && resp !== 'OVERRIDE') {
|
55
|
+
(0, logger_1.error)(`Aborting. You can resolve the conflicts and complete the import here: ${resolveUrl}`);
|
56
|
+
process.exit(1);
|
57
|
+
}
|
58
|
+
return resp;
|
59
|
+
}
|
60
|
+
async function prepareImport(client, files) {
|
61
|
+
return (0, logger_1.loading)('Deleting import...', client.import.deleteImportIfExists()).then(() => (0, logger_1.loading)('Uploading files...', client.import.addFiles({ files: files })));
|
62
|
+
}
|
63
|
+
async function resolveConflicts(client, locales, method) {
|
64
|
+
for (const locale of locales) {
|
65
|
+
if (method === 'KEEP') {
|
66
|
+
await client.import.conflictsKeepExistingAll(locale);
|
67
|
+
}
|
68
|
+
else {
|
69
|
+
await client.import.conflictsOverrideAll(locale);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
async function applyImport(client) {
|
74
|
+
try {
|
75
|
+
await (0, logger_1.loading)('Applying changes...', client.import.applyImport());
|
76
|
+
}
|
77
|
+
catch (e) {
|
78
|
+
if (e instanceof errors_1.HttpError && e.response.status === 400) {
|
79
|
+
(0, logger_1.error)("Some of the imported languages weren't recognized. Please create a language with corresponding tag in the Tolgee Platform.");
|
80
|
+
return;
|
81
|
+
}
|
82
|
+
throw e;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
async function pushHandler(path) {
|
86
|
+
const opts = this.optsWithGlobals();
|
87
|
+
try {
|
88
|
+
const stats = await (0, promises_1.stat)(path);
|
89
|
+
if (!stats.isDirectory()) {
|
90
|
+
(0, logger_1.error)('The specified path is not a directory.');
|
91
|
+
process.exit(1);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
catch (e) {
|
95
|
+
if (e.code === 'ENOENT') {
|
96
|
+
(0, logger_1.error)('The specified path does not exist.');
|
97
|
+
process.exit(1);
|
98
|
+
}
|
99
|
+
throw e;
|
100
|
+
}
|
101
|
+
const files = await (0, logger_1.loading)('Reading files...', readDirectory(path));
|
102
|
+
if (files.length === 0) {
|
103
|
+
(0, logger_1.error)('Nothing to import.');
|
104
|
+
return;
|
105
|
+
}
|
106
|
+
const result = await prepareImport(opts.client, files);
|
107
|
+
const conflicts = getConflictingLanguages(result);
|
108
|
+
if (conflicts.length) {
|
109
|
+
const resolveMethod = await promptConflicts(opts);
|
110
|
+
await (0, logger_1.loading)('Resolving conflicts...', resolveConflicts(opts.client, conflicts, resolveMethod));
|
111
|
+
}
|
112
|
+
await applyImport(opts.client);
|
113
|
+
(0, logger_1.success)('Done!');
|
114
|
+
}
|
115
|
+
exports.default = new commander_1.Command()
|
116
|
+
.name('push')
|
117
|
+
.description('Pushes translations to Tolgee')
|
118
|
+
.argument('<path>', 'Path to the files to push to Tolgee')
|
119
|
+
.addOption(new commander_1.Option('-f, --force-mode <mode>', 'What should we do with possible conflicts? If unspecified, the user will be prompted interactively, or the command will fail when in non-interactive')
|
120
|
+
.choices(['OVERRIDE', 'KEEP', 'NO'])
|
121
|
+
.argParser((v) => v.toUpperCase()))
|
122
|
+
.action(pushHandler);
|
@@ -0,0 +1,48 @@
|
|
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
|
+
const commander_1 = require("commander");
|
7
|
+
const ansi_colors_1 = __importDefault(require("ansi-colors"));
|
8
|
+
const syncUtils_1 = require("./syncUtils");
|
9
|
+
const runner_1 = require("../../extractor/runner");
|
10
|
+
const warnings_1 = require("../../extractor/warnings");
|
11
|
+
const options_1 = require("../../options");
|
12
|
+
const logger_1 = require("../../utils/logger");
|
13
|
+
async function compareHandler(pattern) {
|
14
|
+
const opts = this.optsWithGlobals();
|
15
|
+
const rawKeys = await (0, logger_1.loading)('Analyzing code...', (0, runner_1.extractKeysOfFiles)(pattern, opts.extractor));
|
16
|
+
(0, warnings_1.dumpWarnings)(rawKeys);
|
17
|
+
const localKeys = (0, runner_1.filterExtractionResult)(rawKeys);
|
18
|
+
const remoteKeys = await opts.client.project.fetchAllKeys();
|
19
|
+
const diff = (0, syncUtils_1.compareKeys)(localKeys, remoteKeys);
|
20
|
+
if (!diff.added.length && !diff.removed.length) {
|
21
|
+
console.log(ansi_colors_1.default.green('Your code project is in sync with the associated Tolgee project!'));
|
22
|
+
process.exit(0);
|
23
|
+
}
|
24
|
+
console.log('Your code project and Tolgee project are out of sync.');
|
25
|
+
if (diff.added.length) {
|
26
|
+
console.log(ansi_colors_1.default.green.bold(`${diff.added.length} new strings`));
|
27
|
+
for (const key of diff.added) {
|
28
|
+
(0, syncUtils_1.printKey)(key, 'added');
|
29
|
+
}
|
30
|
+
// Line break
|
31
|
+
console.log('');
|
32
|
+
}
|
33
|
+
if (diff.removed.length) {
|
34
|
+
console.log(ansi_colors_1.default.red.bold(`${diff.removed.length} removed strings`));
|
35
|
+
for (const key of diff.removed) {
|
36
|
+
(0, syncUtils_1.printKey)(key, 'removed');
|
37
|
+
}
|
38
|
+
// Line break
|
39
|
+
console.log('');
|
40
|
+
}
|
41
|
+
console.log('Run `tolgee sync` to synchronize the projects.');
|
42
|
+
}
|
43
|
+
exports.default = new commander_1.Command()
|
44
|
+
.name('compare')
|
45
|
+
.description('Compares the keys in your code project and in the Tolgee project.')
|
46
|
+
.argument('<pattern>', 'File pattern to include (hint: make sure to escape it in quotes, or your shell might attempt to unroll some tokens like *)')
|
47
|
+
.addOption(options_1.EXTRACTOR)
|
48
|
+
.action(compareHandler);
|
@@ -0,0 +1,110 @@
|
|
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
|
+
const commander_1 = require("commander");
|
7
|
+
const ansi_colors_1 = __importDefault(require("ansi-colors"));
|
8
|
+
const runner_1 = require("../../extractor/runner");
|
9
|
+
const warnings_1 = require("../../extractor/warnings");
|
10
|
+
const syncUtils_1 = require("./syncUtils");
|
11
|
+
const overwriteDir_1 = require("../../utils/overwriteDir");
|
12
|
+
const zip_1 = require("../../utils/zip");
|
13
|
+
const ask_1 = require("../../utils/ask");
|
14
|
+
const logger_1 = require("../../utils/logger");
|
15
|
+
const options_1 = require("../../options");
|
16
|
+
async function backup(client, dest) {
|
17
|
+
const blob = await client.export.export({
|
18
|
+
format: 'JSON',
|
19
|
+
filterState: ['UNTRANSLATED', 'TRANSLATED', 'REVIEWED'],
|
20
|
+
structureDelimiter: '',
|
21
|
+
});
|
22
|
+
await (0, zip_1.unzipBuffer)(blob, dest);
|
23
|
+
}
|
24
|
+
async function askForConfirmation(keys, operation) {
|
25
|
+
if (!process.stdout.isTTY) {
|
26
|
+
(0, logger_1.error)('You must run this command interactively, or specify --yes to proceed.');
|
27
|
+
process.exit(1);
|
28
|
+
}
|
29
|
+
const str = `The following keys will be ${operation}:`;
|
30
|
+
console.log(operation === 'added' ? ansi_colors_1.default.bold.green(str) : ansi_colors_1.default.bold.red(str));
|
31
|
+
keys.forEach((k) => (0, syncUtils_1.printKey)(k, operation));
|
32
|
+
const shouldContinue = await (0, ask_1.askBoolean)('Does this look correct?', true);
|
33
|
+
if (!shouldContinue) {
|
34
|
+
(0, logger_1.error)('Aborting.');
|
35
|
+
process.exit(1);
|
36
|
+
}
|
37
|
+
}
|
38
|
+
async function syncHandler(pattern) {
|
39
|
+
const opts = this.optsWithGlobals();
|
40
|
+
const rawKeys = await (0, logger_1.loading)('Analyzing code...', (0, runner_1.extractKeysOfFiles)(pattern, opts.extractor));
|
41
|
+
const warnCount = (0, warnings_1.dumpWarnings)(rawKeys);
|
42
|
+
if (!opts.continueOnWarning && warnCount) {
|
43
|
+
console.log(ansi_colors_1.default.bold.red('Aborting as warnings have been emitted.'));
|
44
|
+
process.exit(1);
|
45
|
+
}
|
46
|
+
const localKeys = (0, runner_1.filterExtractionResult)(rawKeys);
|
47
|
+
const remoteKeys = await opts.client.project.fetchAllKeys();
|
48
|
+
const diff = (0, syncUtils_1.compareKeys)(localKeys, remoteKeys);
|
49
|
+
if (!diff.added.length && !diff.removed.length) {
|
50
|
+
console.log(ansi_colors_1.default.green('Your code project is in sync with the associated Tolgee project!'));
|
51
|
+
process.exit(0);
|
52
|
+
}
|
53
|
+
// Load project settings. We're interested in the default locale here.
|
54
|
+
const { baseLanguage } = await opts.client.project.fetchProjectInformation();
|
55
|
+
if (!baseLanguage) {
|
56
|
+
// I'm highly unsure how we could reach this state, but this is what the OAI spec tells me ¯\_(ツ)_/¯
|
57
|
+
(0, logger_1.error)('Your project does not have a base language!');
|
58
|
+
process.exit(1);
|
59
|
+
}
|
60
|
+
// Prepare backup
|
61
|
+
if (opts.backup) {
|
62
|
+
await (0, overwriteDir_1.overwriteDir)(opts.backup, opts.yes);
|
63
|
+
await (0, logger_1.loading)('Backing up Tolgee project', backup(opts.client, opts.backup));
|
64
|
+
}
|
65
|
+
// Create new keys
|
66
|
+
if (diff.added.length) {
|
67
|
+
if (!opts.yes) {
|
68
|
+
await askForConfirmation(diff.added, 'added');
|
69
|
+
}
|
70
|
+
const keys = diff.added.map((key) => ({
|
71
|
+
name: key.keyName,
|
72
|
+
namespace: key.namespace,
|
73
|
+
translations: key.defaultValue
|
74
|
+
? { [baseLanguage.tag]: key.defaultValue }
|
75
|
+
: {},
|
76
|
+
}));
|
77
|
+
await (0, logger_1.loading)('Creating missing keys...', opts.client.project.createBulkKey(keys));
|
78
|
+
}
|
79
|
+
if (opts.removeUnused) {
|
80
|
+
// Delete unused keys.
|
81
|
+
if (diff.removed.length) {
|
82
|
+
if (!opts.yes) {
|
83
|
+
await askForConfirmation(diff.removed, 'removed');
|
84
|
+
}
|
85
|
+
const ids = await diff.removed.map((k) => k.id);
|
86
|
+
await (0, logger_1.loading)('Deleting unused keys...', opts.client.project.deleteBulkKeys(ids));
|
87
|
+
}
|
88
|
+
}
|
89
|
+
console.log(ansi_colors_1.default.bold.green('Sync complete!'));
|
90
|
+
console.log(ansi_colors_1.default.green(`+ ${diff.added.length} string${diff.added.length === 1 ? '' : 's'}`));
|
91
|
+
if (opts.removeUnused) {
|
92
|
+
console.log(ansi_colors_1.default.red(`- ${diff.removed.length} string${diff.removed.length === 1 ? '' : 's'}`));
|
93
|
+
}
|
94
|
+
else {
|
95
|
+
console.log(ansi_colors_1.default.italic(`${diff.removed.length} unused key${diff.removed.length === 1 ? '' : 's'} could be deleted.`));
|
96
|
+
}
|
97
|
+
if (opts.backup) {
|
98
|
+
console.log(ansi_colors_1.default.blueBright(`A backup of the project prior to the synchronization has been dumped in ${opts.backup}.`));
|
99
|
+
}
|
100
|
+
}
|
101
|
+
exports.default = new commander_1.Command()
|
102
|
+
.name('sync')
|
103
|
+
.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`.')
|
104
|
+
.argument('<pattern>', 'File pattern to include (hint: make sure to escape it in quotes, or your shell might attempt to unroll some tokens like *)')
|
105
|
+
.addOption(options_1.EXTRACTOR)
|
106
|
+
.option('-B, --backup <path>', 'Path where a backup should be downloaded before performing the sync. If something goes wrong, the backup can be used to restore the project to its previous state.')
|
107
|
+
.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.')
|
108
|
+
.option('-Y, --yes', 'Skip prompts and automatically say yes to them. You will not be asked for confirmation before creating/deleting keys.')
|
109
|
+
.option('--remove-unused', 'Also delete unused keys from the Tolgee project.')
|
110
|
+
.action(syncHandler);
|
@@ -0,0 +1,64 @@
|
|
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.compareKeys = exports.printKey = void 0;
|
7
|
+
const runner_1 = require("../../extractor/runner");
|
8
|
+
const ansi_colors_1 = __importDefault(require("ansi-colors"));
|
9
|
+
/**
|
10
|
+
* Prints information about a key, with coloring and formatting.
|
11
|
+
*
|
12
|
+
* @param key The key to print.
|
13
|
+
* @param type Whether this is an addition or a removal.
|
14
|
+
*/
|
15
|
+
function printKey(key, type) {
|
16
|
+
const namespace = key.namespace
|
17
|
+
? ` ${ansi_colors_1.default.italic(`(namespace: ${key.namespace})`)}`
|
18
|
+
: '';
|
19
|
+
if (type === 'added') {
|
20
|
+
console.log(`${ansi_colors_1.default.green(`+ ${key.keyName}`)}${namespace}`);
|
21
|
+
}
|
22
|
+
else {
|
23
|
+
console.log(`${ansi_colors_1.default.red(`- ${key.keyName}`)}${namespace}`);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
exports.printKey = printKey;
|
27
|
+
/**
|
28
|
+
* Compares local and remote keys to detect added and deleted keys.
|
29
|
+
* **Warning**: `local` will be modified as a side-effect!
|
30
|
+
*
|
31
|
+
* @param local Local keys.
|
32
|
+
* @param remote Remote keys.
|
33
|
+
* @returns A list of added and removed keys.
|
34
|
+
*/
|
35
|
+
function compareKeys(local, remote) {
|
36
|
+
const result = { added: [], removed: [] };
|
37
|
+
// Deleted keys
|
38
|
+
for (const remoteKey of remote) {
|
39
|
+
const namespace = remoteKey.namespace || runner_1.NullNamespace;
|
40
|
+
const keyExists = local[namespace]?.delete(remoteKey.name);
|
41
|
+
if (!keyExists) {
|
42
|
+
result.removed.push({
|
43
|
+
id: remoteKey.id,
|
44
|
+
keyName: remoteKey.name,
|
45
|
+
namespace: remoteKey.namespace || undefined,
|
46
|
+
});
|
47
|
+
}
|
48
|
+
}
|
49
|
+
// Added keys
|
50
|
+
const namespaces = [...Object.keys(local), runner_1.NullNamespace];
|
51
|
+
for (const namespace of namespaces) {
|
52
|
+
if (local[namespace].size) {
|
53
|
+
for (const [keyName, defaultValue] of local[namespace].entries()) {
|
54
|
+
result.added.push({
|
55
|
+
keyName: keyName,
|
56
|
+
namespace: namespace === runner_1.NullNamespace ? undefined : namespace,
|
57
|
+
defaultValue: defaultValue || undefined,
|
58
|
+
});
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
return result;
|
63
|
+
}
|
64
|
+
exports.compareKeys = compareKeys;
|
@@ -0,0 +1,125 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.clearAuthStore = exports.removeApiKeys = exports.saveApiKey = exports.getApiKey = exports.savePak = exports.savePat = exports.API_TOKENS_FILE = void 0;
|
4
|
+
const path_1 = require("path");
|
5
|
+
const promises_1 = require("fs/promises");
|
6
|
+
const logger_1 = require("../utils/logger");
|
7
|
+
const constants_1 = require("../constants");
|
8
|
+
exports.API_TOKENS_FILE = (0, path_1.join)(constants_1.CONFIG_PATH, 'authentication.json');
|
9
|
+
async function ensureConfigPath() {
|
10
|
+
try {
|
11
|
+
await (0, promises_1.mkdir)((0, path_1.dirname)(exports.API_TOKENS_FILE));
|
12
|
+
}
|
13
|
+
catch (e) {
|
14
|
+
if (e.code !== 'EEXIST') {
|
15
|
+
throw e;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
async function loadStore() {
|
20
|
+
try {
|
21
|
+
await ensureConfigPath();
|
22
|
+
const storeData = await (0, promises_1.readFile)(exports.API_TOKENS_FILE, 'utf8');
|
23
|
+
return JSON.parse(storeData);
|
24
|
+
}
|
25
|
+
catch (e) {
|
26
|
+
if (e.code !== 'ENOENT') {
|
27
|
+
throw e;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
return {};
|
31
|
+
}
|
32
|
+
async function saveStore(store) {
|
33
|
+
const blob = JSON.stringify(store);
|
34
|
+
await (0, promises_1.writeFile)(exports.API_TOKENS_FILE, blob, {
|
35
|
+
mode: 0o600,
|
36
|
+
encoding: 'utf8',
|
37
|
+
});
|
38
|
+
}
|
39
|
+
async function storePat(store, instance, pat) {
|
40
|
+
return saveStore({
|
41
|
+
...store,
|
42
|
+
[instance.hostname]: {
|
43
|
+
...(store[instance.hostname] || {}),
|
44
|
+
user: pat,
|
45
|
+
},
|
46
|
+
});
|
47
|
+
}
|
48
|
+
async function storePak(store, instance, projectId, pak) {
|
49
|
+
return saveStore({
|
50
|
+
...store,
|
51
|
+
[instance.hostname]: {
|
52
|
+
...(store[instance.hostname] || {}),
|
53
|
+
projects: {
|
54
|
+
...(store[instance.hostname]?.projects || {}),
|
55
|
+
[projectId.toString(10)]: pak,
|
56
|
+
},
|
57
|
+
},
|
58
|
+
});
|
59
|
+
}
|
60
|
+
async function savePat(instance, pat) {
|
61
|
+
const store = await loadStore();
|
62
|
+
return storePat(store, instance, pat);
|
63
|
+
}
|
64
|
+
exports.savePat = savePat;
|
65
|
+
async function savePak(instance, projectId, pak) {
|
66
|
+
const store = await loadStore();
|
67
|
+
return storePak(store, instance, projectId, pak);
|
68
|
+
}
|
69
|
+
exports.savePak = savePak;
|
70
|
+
async function getApiKey(instance, projectId) {
|
71
|
+
const store = await loadStore();
|
72
|
+
if (!store[instance.hostname]) {
|
73
|
+
return null;
|
74
|
+
}
|
75
|
+
const scopedStore = store[instance.hostname];
|
76
|
+
if (scopedStore.user) {
|
77
|
+
if (scopedStore.user.expires !== 0 &&
|
78
|
+
Date.now() > scopedStore.user.expires) {
|
79
|
+
(0, logger_1.warn)(`Your personal access token for ${instance.hostname} expired.`);
|
80
|
+
await storePat(store, instance, undefined);
|
81
|
+
return null;
|
82
|
+
}
|
83
|
+
return scopedStore.user.token;
|
84
|
+
}
|
85
|
+
if (projectId <= 0) {
|
86
|
+
return null;
|
87
|
+
}
|
88
|
+
const pak = scopedStore.projects?.[projectId.toString(10)];
|
89
|
+
if (pak) {
|
90
|
+
if (pak.expires !== 0 && Date.now() > pak.expires) {
|
91
|
+
(0, logger_1.warn)(`Your project API key for project #${projectId} on ${instance.hostname} expired.`);
|
92
|
+
await storePak(store, instance, projectId, undefined);
|
93
|
+
return null;
|
94
|
+
}
|
95
|
+
return pak.token;
|
96
|
+
}
|
97
|
+
return null;
|
98
|
+
}
|
99
|
+
exports.getApiKey = getApiKey;
|
100
|
+
async function saveApiKey(instance, token) {
|
101
|
+
const store = await loadStore();
|
102
|
+
if (token.type === 'PAT') {
|
103
|
+
return storePat(store, instance, {
|
104
|
+
token: token.key,
|
105
|
+
expires: token.expires,
|
106
|
+
});
|
107
|
+
}
|
108
|
+
return storePak(store, instance, token.project.id, {
|
109
|
+
token: token.key,
|
110
|
+
expires: token.expires,
|
111
|
+
});
|
112
|
+
}
|
113
|
+
exports.saveApiKey = saveApiKey;
|
114
|
+
async function removeApiKeys(api) {
|
115
|
+
const store = await loadStore();
|
116
|
+
return saveStore({
|
117
|
+
...store,
|
118
|
+
[api.hostname]: {},
|
119
|
+
});
|
120
|
+
}
|
121
|
+
exports.removeApiKeys = removeApiKeys;
|
122
|
+
async function clearAuthStore() {
|
123
|
+
return saveStore({});
|
124
|
+
}
|
125
|
+
exports.clearAuthStore = clearAuthStore;
|
@@ -0,0 +1,64 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const cosmiconfig_1 = require("cosmiconfig");
|
4
|
+
const path_1 = require("path");
|
5
|
+
const fs_1 = require("fs");
|
6
|
+
const constants_1 = require("../constants");
|
7
|
+
const explorer = (0, cosmiconfig_1.cosmiconfig)('tolgee', {
|
8
|
+
loaders: {
|
9
|
+
noExt: cosmiconfig_1.defaultLoaders['.json'],
|
10
|
+
},
|
11
|
+
});
|
12
|
+
function parseConfig(rc) {
|
13
|
+
if (typeof rc !== 'object' || Array.isArray(rc)) {
|
14
|
+
throw new Error('Invalid config: config is not an object.');
|
15
|
+
}
|
16
|
+
const cfg = {};
|
17
|
+
if ('apiUrl' in rc) {
|
18
|
+
if (typeof rc.apiUrl !== 'string') {
|
19
|
+
throw new Error('Invalid config: apiUrl is not a string');
|
20
|
+
}
|
21
|
+
try {
|
22
|
+
cfg.apiUrl = new URL(rc.apiUrl);
|
23
|
+
}
|
24
|
+
catch (e) {
|
25
|
+
throw new Error('Invalid config: apiUrl is an invalid URL');
|
26
|
+
}
|
27
|
+
}
|
28
|
+
if ('projectId' in rc) {
|
29
|
+
if (typeof rc.projectId !== 'number') {
|
30
|
+
throw new Error('Invalid config: projectId is not a number');
|
31
|
+
}
|
32
|
+
cfg.projectId = rc.projectId;
|
33
|
+
}
|
34
|
+
if ('sdk' in rc) {
|
35
|
+
if (!constants_1.SDKS.includes(rc.sdk)) {
|
36
|
+
throw new Error(`Invalid config: invalid sdk. Must be one of: ${constants_1.SDKS.join(' ')}`);
|
37
|
+
}
|
38
|
+
cfg.sdk = rc.sdk;
|
39
|
+
}
|
40
|
+
if ('extractor' in rc) {
|
41
|
+
if (typeof rc.extractor !== 'string') {
|
42
|
+
throw new Error('Invalid config: extractor is not a string');
|
43
|
+
}
|
44
|
+
const extractorPath = (0, path_1.resolve)(rc.extractor);
|
45
|
+
if (!(0, fs_1.existsSync)(extractorPath)) {
|
46
|
+
throw new Error(`Invalid config: extractor points to a file that does not exists (${extractorPath})`);
|
47
|
+
}
|
48
|
+
cfg.extractor = extractorPath;
|
49
|
+
}
|
50
|
+
if ('delimiter' in rc) {
|
51
|
+
if (typeof rc.delimiter !== 'string' && rc.delimiter !== null) {
|
52
|
+
throw new Error('Invalid config: delimiter is not a string');
|
53
|
+
}
|
54
|
+
cfg.delimiter = rc.delimiter || void 0;
|
55
|
+
}
|
56
|
+
return cfg;
|
57
|
+
}
|
58
|
+
async function loadTolgeeRc() {
|
59
|
+
const res = await explorer.search();
|
60
|
+
if (!res || res.isEmpty)
|
61
|
+
return null;
|
62
|
+
return parseConfig(res.config);
|
63
|
+
}
|
64
|
+
exports.default = loadTolgeeRc;
|
@@ -0,0 +1,18 @@
|
|
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.SDKS = exports.API_KEY_PAK_PREFIX = exports.API_KEY_PAT_PREFIX = exports.DEFAULT_API_URL = exports.USER_AGENT = exports.VERSION = exports.CONFIG_PATH = void 0;
|
7
|
+
const path_1 = require("path");
|
8
|
+
const fs_1 = require("fs");
|
9
|
+
const configPath_1 = __importDefault(require("./utils/configPath"));
|
10
|
+
const packageJson = (0, path_1.join)(__dirname, '..', 'package.json');
|
11
|
+
const pkg = (0, fs_1.readFileSync)(packageJson, 'utf8');
|
12
|
+
exports.CONFIG_PATH = (0, configPath_1.default)();
|
13
|
+
exports.VERSION = JSON.parse(pkg).version;
|
14
|
+
exports.USER_AGENT = `Tolgee-CLI/${exports.VERSION} (+https://github.com/tolgee/tolgee-cli)`;
|
15
|
+
exports.DEFAULT_API_URL = new URL('https://app.tolgee.io');
|
16
|
+
exports.API_KEY_PAT_PREFIX = 'tgpat_';
|
17
|
+
exports.API_KEY_PAK_PREFIX = 'tgpak_';
|
18
|
+
exports.SDKS = ['react'];
|