Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More โ†’
Socket
Sign inDemoInstall
Socket

@tolgee/cli

Package Overview
Dependencies
Maintainers
2
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tolgee/cli - npm Package Compare versions

Comparing version 1.1.0 to 1.1.1

6

dist/client/errors.js

@@ -1,5 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpError = void 0;
class HttpError extends Error {
export class HttpError extends Error {
request;

@@ -36,2 +33,1 @@ response;

}
exports.HttpError = HttpError;

5

dist/client/export.js

@@ -1,4 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class ExportClient {
export default class ExportClient {
requester;

@@ -23,2 +21,1 @@ constructor(requester) {

}
exports.default = ExportClient;

@@ -1,9 +0,4 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const form_data_1 = __importDefault(require("form-data"));
const errors_1 = require("./errors");
class ImportClient {
import FormData from 'form-data';
import { HttpError } from './errors.js';
export default class ImportClient {
requester;

@@ -14,3 +9,3 @@ constructor(requester) {

async addFiles(req) {
const body = new form_data_1.default();
const body = new FormData();
for (const file of req.files) {

@@ -56,3 +51,3 @@ body.append('files', file.data, { filepath: file.name });

catch (e) {
if (e instanceof errors_1.HttpError && e.response.status === 404)
if (e instanceof HttpError && e.response.status === 404)
return;

@@ -63,2 +58,1 @@ throw e;

}
exports.default = ImportClient;

@@ -1,14 +0,9 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const base32_decode_1 = __importDefault(require("base32-decode"));
const requester_1 = __importDefault(require("./internal/requester"));
const project_1 = __importDefault(require("./project"));
const languages_1 = __importDefault(require("./languages"));
const import_1 = __importDefault(require("./import"));
const export_1 = __importDefault(require("./export"));
const constants_1 = require("../constants");
class RestClient {
import base32Decode from 'base32-decode';
import Requester from './internal/requester.js';
import ProjectClient from './project.js';
import LanguagesClient from './languages.js';
import ImportClient from './import.js';
import ExportClient from './export.js';
import { API_KEY_PAK_PREFIX } from '../constants.js';
export default class RestClient {
params;

@@ -22,7 +17,7 @@ requester;

this.params = params;
this.requester = new requester_1.default(params);
this.project = new project_1.default(this.requester);
this.languages = new languages_1.default(this.requester);
this.import = new import_1.default(this.requester);
this.export = new export_1.default(this.requester);
this.requester = new Requester(params);
this.project = new ProjectClient(this.requester);
this.languages = new LanguagesClient(this.requester);
this.import = new ImportClient(this.requester);
this.export = new ExportClient(this.requester);
}

@@ -39,3 +34,3 @@ async getProjectApiKeyInformation() {

static projectIdFromKey(key) {
const keyBuffer = (0, base32_decode_1.default)(key.slice(constants_1.API_KEY_PAK_PREFIX.length).toUpperCase(), 'RFC4648');
const keyBuffer = base32Decode(key.slice(API_KEY_PAK_PREFIX.length).toUpperCase(), 'RFC4648');
const decoded = Buffer.from(keyBuffer).toString('utf8');

@@ -45,3 +40,3 @@ return Number(decoded.split('_')[0]);

static getProjectApiKeyInformation(api, key) {
return new requester_1.default({ apiUrl: api, apiKey: key }).requestJson({
return new Requester({ apiUrl: api, apiKey: key }).requestJson({
path: '/v2/api-keys/current',

@@ -52,3 +47,3 @@ method: 'GET',

static getPersonalAccessTokenInformation(api, key) {
return new requester_1.default({ apiUrl: api, apiKey: key }).requestJson({
return new Requester({ apiUrl: api, apiKey: key }).requestJson({
path: '/v2/pats/current',

@@ -59,3 +54,3 @@ method: 'GET',

static async getApiKeyInformation(api, key) {
if (key.startsWith(constants_1.API_KEY_PAK_PREFIX)) {
if (key.startsWith(API_KEY_PAK_PREFIX)) {
const info = await RestClient.getProjectApiKeyInformation(api, key);

@@ -84,2 +79,1 @@ const username = info.userFullName || info.username || '<unknown user>';

}
exports.default = RestClient;

@@ -1,13 +0,8 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const undici_1 = require("undici");
const undici_2 = require("undici");
const form_data_1 = __importDefault(require("form-data"));
const errors_1 = require("../errors");
const logger_1 = require("../../utils/logger");
const constants_1 = require("../../constants");
class Requester {
import { Request } from 'undici';
import { fetch } from 'undici';
import FormData from 'form-data';
import { HttpError } from '../errors.js';
import { debug } from '../../utils/logger.js';
import { USER_AGENT } from '../../constants.js';
export default class Requester {
params;

@@ -47,3 +42,3 @@ constructor(params) {

...(req.headers || {}),
'user-agent': constants_1.USER_AGENT,
'user-agent': USER_AGENT,
'x-api-key': this.params.apiKey,

@@ -53,3 +48,3 @@ };

if (req.body) {
if (req.body instanceof form_data_1.default) {
if (req.body instanceof FormData) {
const header = `multipart/form-data; boundary=${req.body.getBoundary()}`;

@@ -64,3 +59,3 @@ headers['content-type'] = header;

}
const request = new undici_1.Request(url, {
const request = new Request(url, {
method: req.method,

@@ -70,7 +65,7 @@ headers: headers,

});
(0, logger_1.debug)(`[HTTP] Requesting: ${request.method} ${request.url}`);
const response = await (0, undici_2.fetch)(request);
(0, logger_1.debug)(`[HTTP] ${request.method} ${request.url} -> ${response.status} ${response.statusText}`);
debug(`[HTTP] Requesting: ${request.method} ${request.url}`);
const response = await fetch(request);
debug(`[HTTP] ${request.method} ${request.url} -> ${response.status} ${response.statusText}`);
if (!response.ok)
throw new errors_1.HttpError(request, response);
throw new HttpError(request, response);
return response;

@@ -149,2 +144,1 @@ }

}
exports.default = Requester;

@@ -1,2 +0,1 @@

"use strict";
/**

@@ -6,2 +5,2 @@ * This file was auto-generated by openapi-typescript.

*/
Object.defineProperty(exports, "__esModule", { value: true });
export {};

@@ -1,2 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
export {};

@@ -1,4 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class LanguagesClient {
export default class LanguagesClient {
requester;

@@ -16,2 +14,1 @@ constructor(requester) {

}
exports.default = LanguagesClient;

@@ -1,4 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class ProjectClient {
export default class ProjectClient {
requester;

@@ -44,2 +42,1 @@ constructor(requester) {

}
exports.default = ProjectClient;

@@ -1,14 +0,9 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const print_1 = __importDefault(require("./extract/print"));
const check_1 = __importDefault(require("./extract/check"));
const options_1 = require("../options");
exports.default = new commander_1.Command('extract')
import { Command } from 'commander';
import extractPrint from './extract/print.js';
import extractCheck from './extract/check.js';
import { EXTRACTOR } from '../options.js';
export default new Command('extract')
.description('Extracts strings from your projects')
.addOption(options_1.EXTRACTOR)
.addCommand(print_1.default)
.addCommand(check_1.default);
.addOption(EXTRACTOR)
.addCommand(extractPrint)
.addCommand(extractCheck);

@@ -1,11 +0,9 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const commander_1 = require("commander");
const runner_1 = require("../../extractor/runner");
const warnings_1 = require("../../extractor/warnings");
const logger_1 = require("../../utils/logger");
import { relative } from 'path';
import { Command } from 'commander';
import { extractKeysOfFiles } from '../../extractor/runner.js';
import { WarningMessages, emitGitHubWarning, } from '../../extractor/warnings.js';
import { loading } from '../../utils/logger.js';
async function lintHandler(filesPattern) {
const opts = this.optsWithGlobals();
const extracted = await (0, logger_1.loading)('Analyzing code...', (0, runner_1.extractKeysOfFiles)(filesPattern, opts.extractor));
const extracted = await loading('Analyzing code...', extractKeysOfFiles(filesPattern, opts.extractor));
let warningCount = 0;

@@ -17,7 +15,7 @@ let filesCount = 0;

filesCount++;
const relFile = (0, path_1.relative)(process.cwd(), file);
const relFile = relative(process.cwd(), file);
console.log('%s:', relFile);
for (const warning of warnings) {
if (warning.warning in warnings_1.WarningMessages) {
const { name } = warnings_1.WarningMessages[warning.warning];
if (warning.warning in WarningMessages) {
const { name } = WarningMessages[warning.warning];
console.log('\tline %d: %s', warning.line, name);

@@ -28,3 +26,3 @@ }

}
(0, warnings_1.emitGitHubWarning)(warning.warning, file, warning.line);
emitGitHubWarning(warning.warning, file, warning.line);
}

@@ -40,5 +38,5 @@ }

}
exports.default = new commander_1.Command('check')
export default new Command('check')
.description('Checks if the keys can be extracted automatically, and reports problems if any')
.argument('<pattern>', 'File pattern to include (hint: make sure to escape it in quotes, or your shell might attempt to unroll some tokens like *)')
.action(lintHandler);

@@ -1,11 +0,9 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const commander_1 = require("commander");
const runner_1 = require("../../extractor/runner");
const warnings_1 = require("../../extractor/warnings");
const logger_1 = require("../../utils/logger");
import { relative } from 'path';
import { Command } from 'commander';
import { extractKeysOfFiles } from '../../extractor/runner.js';
import { WarningMessages } from '../../extractor/warnings.js';
import { loading } from '../../utils/logger.js';
async function printHandler(filesPattern) {
const opts = this.optsWithGlobals();
const extracted = await (0, logger_1.loading)('Analyzing code...', (0, runner_1.extractKeysOfFiles)(filesPattern, opts.extractor));
const extracted = await loading('Analyzing code...', extractKeysOfFiles(filesPattern, opts.extractor));
let warningCount = 0;

@@ -15,3 +13,3 @@ const keySet = new Set();

if (keys.length) {
const relFile = (0, path_1.relative)(process.cwd(), file);
const relFile = relative(process.cwd(), file);
console.log('%d key%s found in %s:', keys.length, keys.length !== 1 ? 's' : '', relFile);

@@ -33,4 +31,4 @@ for (const key of keys) {

for (const warning of warnings) {
if (warning.warning in warnings_1.WarningMessages) {
const { name } = warnings_1.WarningMessages[warning.warning];
if (warning.warning in WarningMessages) {
const { name } = WarningMessages[warning.warning];
console.log('\tline %d: %s', warning.line, name);

@@ -50,5 +48,5 @@ }

}
exports.default = new commander_1.Command('print')
export default new Command('print')
.description('Prints extracted data to the console')
.argument('<pattern>', 'File glob pattern to include (hint: make sure to escape it in quotes, or your shell might attempt to unroll some tokens like *)')
.action(printHandler);

@@ -1,12 +0,6 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Logout = exports.Login = void 0;
const commander_1 = require("commander");
const client_1 = __importDefault(require("../client"));
const errors_1 = require("../client/errors");
const credentials_1 = require("../config/credentials");
const logger_1 = require("../utils/logger");
import { Command } from 'commander';
import RestClient from '../client/index.js';
import { HttpError } from '../client/errors.js';
import { saveApiKey, removeApiKeys, clearAuthStore, } from '../config/credentials.js';
import { success, error } from '../utils/logger.js';
async function loginHandler(key) {

@@ -16,7 +10,7 @@ const opts = this.optsWithGlobals();

try {
keyInfo = await client_1.default.getApiKeyInformation(opts.apiUrl, key);
keyInfo = await RestClient.getApiKeyInformation(opts.apiUrl, key);
}
catch (e) {
if (e instanceof errors_1.HttpError && e.response.status === 403) {
(0, logger_1.error)("Couldn't log in: the API key you provided is invalid.");
if (e instanceof HttpError && e.response.status === 403) {
error("Couldn't log in: the API key you provided is invalid.");
process.exit(1);

@@ -26,4 +20,4 @@ }

}
await (0, credentials_1.saveApiKey)(opts.apiUrl, keyInfo);
(0, logger_1.success)(keyInfo.type === 'PAK'
await saveApiKey(opts.apiUrl, keyInfo);
success(keyInfo.type === 'PAK'
? `Logged in as ${keyInfo.username} on ${opts.apiUrl.hostname} for project ${keyInfo.project.name} (#${keyInfo.project.id}). Welcome back!`

@@ -35,10 +29,10 @@ : `Logged in as ${keyInfo.username} on ${opts.apiUrl.hostname}. Welcome back!`);

if (opts.all) {
await (0, credentials_1.clearAuthStore)();
(0, logger_1.success)("You've been logged out of all Tolgee instances you were logged in.");
await clearAuthStore();
success("You've been logged out of all Tolgee instances you were logged in.");
return;
}
await (0, credentials_1.removeApiKeys)(opts.apiUrl);
(0, logger_1.success)(`You're now logged out of ${opts.apiUrl.hostname}.`);
await removeApiKeys(opts.apiUrl);
success(`You're now logged out of ${opts.apiUrl.hostname}.`);
}
exports.Login = new commander_1.Command()
export const Login = new Command()
.name('login')

@@ -48,3 +42,3 @@ .description('Login to Tolgee with an API key. You can be logged into multiple Tolgee instances at the same time by using --api-url')

.action(loginHandler);
exports.Logout = new commander_1.Command()
export const Logout = new Command()
.name('logout')

@@ -51,0 +45,0 @@ .description('Logs out of Tolgee')

@@ -1,7 +0,5 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const zip_1 = require("../utils/zip");
const overwriteDir_1 = require("../utils/overwriteDir");
const logger_1 = require("../utils/logger");
import { Command, Option } from 'commander';
import { unzipBuffer } from '../utils/zip.js';
import { overwriteDir } from '../utils/overwriteDir.js';
import { loading, success } from '../utils/logger.js';
async function fetchZipBlob(opts) {

@@ -17,12 +15,12 @@ return opts.client.export.export({

const opts = this.optsWithGlobals();
await (0, overwriteDir_1.overwriteDir)(path, opts.overwrite);
const zipBlob = await (0, logger_1.loading)('Fetching strings from Tolgee...', fetchZipBlob(opts));
await (0, logger_1.loading)('Extracting strings...', (0, zip_1.unzipBuffer)(zipBlob, path));
(0, logger_1.success)('Done!');
await overwriteDir(path, opts.overwrite);
const zipBlob = await loading('Fetching strings from Tolgee...', fetchZipBlob(opts));
await loading('Extracting strings...', unzipBuffer(zipBlob, path));
success('Done!');
}
exports.default = new commander_1.Command()
export default new Command()
.name('pull')
.description('Pulls translations to Tolgee')
.argument('<path>', 'Destination path where translation files will be stored in')
.addOption(new commander_1.Option('-f, --format <format>', 'Format of the exported files')
.addOption(new Option('-f, --format <format>', 'Format of the exported files')
.choices(['JSON', 'XLIFF'])

@@ -32,6 +30,6 @@ .default('JSON')

.option('-l, --languages <languages...>', 'List of languages to pull. Leave unspecified to export them all')
.addOption(new commander_1.Option('-s, --states <states...>', 'List of translation states to include. Defaults all except untranslated')
.addOption(new Option('-s, --states <states...>', 'List of translation states to include. Defaults all except untranslated')
.choices(['UNTRANSLATED', 'TRANSLATED', 'REVIEWED'])
.argParser((v, a) => [v.toUpperCase(), ...(a || [])]))
.addOption(new commander_1.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')
.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')
.default('.')

@@ -38,0 +36,0 @@ .argParser((v) => v || ''))

@@ -1,15 +0,13 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const promises_1 = require("fs/promises");
const commander_1 = require("commander");
const errors_1 = require("../client/errors");
const ask_1 = require("../utils/ask");
const logger_1 = require("../utils/logger");
import { join } from 'path';
import { readdir, readFile, stat } from 'fs/promises';
import { Command, Option } from 'commander';
import { HttpError } from '../client/errors.js';
import { askString } from '../utils/ask.js';
import { loading, success, warn, error } from '../utils/logger.js';
async function readDirectory(directory, base = '') {
const files = [];
const dir = await (0, promises_1.readdir)(directory);
const dir = await readdir(directory);
for (const file of dir) {
const filePath = (0, path_1.join)(directory, file);
const fileStat = await (0, promises_1.stat)(filePath);
const filePath = join(directory, file);
const fileStat = await stat(filePath);
if (fileStat.isDirectory()) {

@@ -20,3 +18,3 @@ const dirFiles = await readDirectory(filePath, `${file}/`);

else {
const blob = await (0, promises_1.readFile)(filePath);
const blob = await readFile(filePath);
files.push({ name: base + file, data: blob });

@@ -43,3 +41,3 @@ }

if (opts.forceMode === 'NO') {
(0, logger_1.error)(`There are conflicts in the import. You can resolve them and complete the import here: ${resolveUrl}.`);
error(`There are conflicts in the import. You can resolve them and complete the import here: ${resolveUrl}.`);
process.exit(1);

@@ -51,9 +49,9 @@ }

if (!process.stdout.isTTY) {
(0, logger_1.error)(`There are conflicts in the import. Please specify a --force-mode, or resolve them in your browser at ${resolveUrl}.`);
error(`There are conflicts in the import. Please specify a --force-mode, or resolve them in your browser at ${resolveUrl}.`);
process.exit(1);
}
(0, logger_1.warn)('There are conflicts in the import. What do you want to do?');
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: ');
warn('There are conflicts in the import. What do you want to do?');
const resp = await askString('Type "KEEP" to preserve the version on the server, "OVERRIDE" to use the version from the import, and nothing to abort: ');
if (resp !== 'KEEP' && resp !== 'OVERRIDE') {
(0, logger_1.error)(`Aborting. You can resolve the conflicts and complete the import here: ${resolveUrl}`);
error(`Aborting. You can resolve the conflicts and complete the import here: ${resolveUrl}`);
process.exit(1);

@@ -64,3 +62,3 @@ }

async function prepareImport(client, files) {
return (0, logger_1.loading)('Deleting import...', client.import.deleteImportIfExists()).then(() => (0, logger_1.loading)('Uploading files...', client.import.addFiles({ files: files })));
return loading('Deleting import...', client.import.deleteImportIfExists()).then(() => loading('Uploading files...', client.import.addFiles({ files: files })));
}

@@ -79,7 +77,7 @@ async function resolveConflicts(client, locales, method) {

try {
await (0, logger_1.loading)('Applying changes...', client.import.applyImport());
await loading('Applying changes...', client.import.applyImport());
}
catch (e) {
if (e instanceof errors_1.HttpError && e.response.status === 400) {
(0, logger_1.error)("Some of the imported languages weren't recognized. Please create a language with corresponding tag in the Tolgee Platform.");
if (e instanceof HttpError && e.response.status === 400) {
error("Some of the imported languages weren't recognized. Please create a language with corresponding tag in the Tolgee Platform.");
return;

@@ -93,5 +91,5 @@ }

try {
const stats = await (0, promises_1.stat)(path);
const stats = await stat(path);
if (!stats.isDirectory()) {
(0, logger_1.error)('The specified path is not a directory.');
error('The specified path is not a directory.');
process.exit(1);

@@ -102,3 +100,3 @@ }

if (e.code === 'ENOENT') {
(0, logger_1.error)('The specified path does not exist.');
error('The specified path does not exist.');
process.exit(1);

@@ -108,5 +106,5 @@ }

}
const files = await (0, logger_1.loading)('Reading files...', readDirectory(path));
const files = await loading('Reading files...', readDirectory(path));
if (files.length === 0) {
(0, logger_1.error)('Nothing to import.');
error('Nothing to import.');
return;

@@ -118,14 +116,14 @@ }

const resolveMethod = await promptConflicts(opts);
await (0, logger_1.loading)('Resolving conflicts...', resolveConflicts(opts.client, conflicts, resolveMethod));
await loading('Resolving conflicts...', resolveConflicts(opts.client, conflicts, resolveMethod));
}
await applyImport(opts.client);
(0, logger_1.success)('Done!');
success('Done!');
}
exports.default = new commander_1.Command()
export default new Command()
.name('push')
.description('Pushes translations to Tolgee')
.argument('<path>', 'Path to the files to push to Tolgee')
.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')
.addOption(new 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')
.choices(['OVERRIDE', 'KEEP', 'NO'])
.argParser((v) => v.toUpperCase()))
.action(pushHandler);

@@ -1,22 +0,17 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const ansi_colors_1 = __importDefault(require("ansi-colors"));
const syncUtils_1 = require("./syncUtils");
const runner_1 = require("../../extractor/runner");
const warnings_1 = require("../../extractor/warnings");
const options_1 = require("../../options");
const logger_1 = require("../../utils/logger");
import { Command } from 'commander';
import ansi from 'ansi-colors';
import { compareKeys, printKey } from './syncUtils.js';
import { extractKeysOfFiles, filterExtractionResult, } from '../../extractor/runner.js';
import { dumpWarnings } from '../../extractor/warnings.js';
import { EXTRACTOR } from '../../options.js';
import { loading } from '../../utils/logger.js';
async function compareHandler(pattern) {
const opts = this.optsWithGlobals();
const rawKeys = await (0, logger_1.loading)('Analyzing code...', (0, runner_1.extractKeysOfFiles)(pattern, opts.extractor));
(0, warnings_1.dumpWarnings)(rawKeys);
const localKeys = (0, runner_1.filterExtractionResult)(rawKeys);
const rawKeys = await loading('Analyzing code...', extractKeysOfFiles(pattern, opts.extractor));
dumpWarnings(rawKeys);
const localKeys = filterExtractionResult(rawKeys);
const remoteKeys = await opts.client.project.fetchAllKeys();
const diff = (0, syncUtils_1.compareKeys)(localKeys, remoteKeys);
const diff = compareKeys(localKeys, remoteKeys);
if (!diff.added.length && !diff.removed.length) {
console.log(ansi_colors_1.default.green('Your code project is in sync with the associated Tolgee project!'));
console.log(ansi.green('Your code project is in sync with the associated Tolgee project!'));
process.exit(0);

@@ -27,5 +22,5 @@ }

const key = diff.added.length === 1 ? 'key' : 'keys';
console.log(ansi_colors_1.default.green.bold(`${diff.added.length} new ${key} found`));
console.log(ansi.green.bold(`${diff.added.length} new ${key} found`));
for (const key of diff.added) {
(0, syncUtils_1.printKey)(key, false);
printKey(key, false);
}

@@ -37,5 +32,5 @@ // Line break

const key = diff.removed.length === 1 ? 'key' : 'keys';
console.log(ansi_colors_1.default.red.bold(`${diff.removed.length} unused ${key}`));
console.log(ansi.red.bold(`${diff.removed.length} unused ${key}`));
for (const key of diff.removed) {
(0, syncUtils_1.printKey)(key, true);
printKey(key, true);
}

@@ -47,7 +42,7 @@ // Line break

}
exports.default = new commander_1.Command()
export default new Command()
.name('compare')
.description('Compares the keys in your code project and in the Tolgee project.')
.argument('<pattern>', 'File pattern to include (hint: make sure to escape it in quotes, or your shell might attempt to unroll some tokens like *)')
.addOption(options_1.EXTRACTOR)
.addOption(EXTRACTOR)
.action(compareHandler);

@@ -1,16 +0,11 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const ansi_colors_1 = __importDefault(require("ansi-colors"));
const runner_1 = require("../../extractor/runner");
const warnings_1 = require("../../extractor/warnings");
const syncUtils_1 = require("./syncUtils");
const overwriteDir_1 = require("../../utils/overwriteDir");
const zip_1 = require("../../utils/zip");
const ask_1 = require("../../utils/ask");
const logger_1 = require("../../utils/logger");
const options_1 = require("../../options");
import { Command } from 'commander';
import ansi from 'ansi-colors';
import { extractKeysOfFiles, filterExtractionResult, } from '../../extractor/runner.js';
import { dumpWarnings } from '../../extractor/warnings.js';
import { compareKeys, printKey } from './syncUtils.js';
import { overwriteDir } from '../../utils/overwriteDir.js';
import { unzipBuffer } from '../../utils/zip.js';
import { askBoolean } from '../../utils/ask.js';
import { loading, error } from '../../utils/logger.js';
import { EXTRACTOR } from '../../options.js';
async function backup(client, dest) {

@@ -22,15 +17,15 @@ const blob = await client.export.export({

});
await (0, zip_1.unzipBuffer)(blob, dest);
await unzipBuffer(blob, dest);
}
async function askForConfirmation(keys, operation) {
if (!process.stdout.isTTY) {
(0, logger_1.error)('You must run this command interactively, or specify --yes to proceed.');
error('You must run this command interactively, or specify --yes to proceed.');
process.exit(1);
}
const str = `The following keys will be ${operation}:`;
console.log(operation === 'created' ? ansi_colors_1.default.bold.green(str) : ansi_colors_1.default.bold.red(str));
keys.forEach((k) => (0, syncUtils_1.printKey)(k, operation === 'deleted'));
const shouldContinue = await (0, ask_1.askBoolean)('Does this look correct?', true);
console.log(operation === 'created' ? ansi.bold.green(str) : ansi.bold.red(str));
keys.forEach((k) => printKey(k, operation === 'deleted'));
const shouldContinue = await askBoolean('Does this look correct?', true);
if (!shouldContinue) {
(0, logger_1.error)('Aborting.');
error('Aborting.');
process.exit(1);

@@ -41,13 +36,13 @@ }

const opts = this.optsWithGlobals();
const rawKeys = await (0, logger_1.loading)('Analyzing code...', (0, runner_1.extractKeysOfFiles)(pattern, opts.extractor));
const warnCount = (0, warnings_1.dumpWarnings)(rawKeys);
const rawKeys = await loading('Analyzing code...', extractKeysOfFiles(pattern, opts.extractor));
const warnCount = dumpWarnings(rawKeys);
if (!opts.continueOnWarning && warnCount) {
console.log(ansi_colors_1.default.bold.red('Aborting as warnings have been emitted.'));
console.log(ansi.bold.red('Aborting as warnings have been emitted.'));
process.exit(1);
}
const localKeys = (0, runner_1.filterExtractionResult)(rawKeys);
const localKeys = filterExtractionResult(rawKeys);
const remoteKeys = await opts.client.project.fetchAllKeys();
const diff = (0, syncUtils_1.compareKeys)(localKeys, remoteKeys);
const diff = compareKeys(localKeys, remoteKeys);
if (!diff.added.length && !diff.removed.length) {
console.log(ansi_colors_1.default.green('Your code project is in sync with the associated Tolgee project!'));
console.log(ansi.green('Your code project is in sync with the associated Tolgee project!'));
process.exit(0);

@@ -59,3 +54,3 @@ }

// I'm highly unsure how we could reach this state, but this is what the OAI spec tells me ยฏ\_(ใƒ„)_/ยฏ
(0, logger_1.error)('Your project does not have a base language!');
error('Your project does not have a base language!');
process.exit(1);

@@ -65,4 +60,4 @@ }

if (opts.backup) {
await (0, overwriteDir_1.overwriteDir)(opts.backup, opts.yes);
await (0, logger_1.loading)('Backing up Tolgee project', backup(opts.client, opts.backup));
await overwriteDir(opts.backup, opts.yes);
await loading('Backing up Tolgee project', backup(opts.client, opts.backup));
}

@@ -81,3 +76,3 @@ // Create new keys

}));
await (0, logger_1.loading)('Creating missing keys...', opts.client.project.createBulkKey(keys));
await loading('Creating missing keys...', opts.client.project.createBulkKey(keys));
}

@@ -91,22 +86,22 @@ if (opts.removeUnused) {

const ids = await diff.removed.map((k) => k.id);
await (0, logger_1.loading)('Deleting unused keys...', opts.client.project.deleteBulkKeys(ids));
await loading('Deleting unused keys...', opts.client.project.deleteBulkKeys(ids));
}
}
console.log(ansi_colors_1.default.bold.green('Sync complete!'));
console.log(ansi_colors_1.default.green(`+ ${diff.added.length} string${diff.added.length === 1 ? '' : 's'}`));
console.log(ansi.bold.green('Sync complete!'));
console.log(ansi.green(`+ ${diff.added.length} string${diff.added.length === 1 ? '' : 's'}`));
if (opts.removeUnused) {
console.log(ansi_colors_1.default.red(`- ${diff.removed.length} string${diff.removed.length === 1 ? '' : 's'}`));
console.log(ansi.red(`- ${diff.removed.length} string${diff.removed.length === 1 ? '' : 's'}`));
}
else {
console.log(ansi_colors_1.default.italic(`${diff.removed.length} unused key${diff.removed.length === 1 ? '' : 's'} could be deleted.`));
console.log(ansi.italic(`${diff.removed.length} unused key${diff.removed.length === 1 ? '' : 's'} could be deleted.`));
}
if (opts.backup) {
console.log(ansi_colors_1.default.blueBright(`A backup of the project prior to the synchronization has been dumped in ${opts.backup}.`));
console.log(ansi.blueBright(`A backup of the project prior to the synchronization has been dumped in ${opts.backup}.`));
}
}
exports.default = new commander_1.Command()
export default new Command()
.name('sync')
.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`.')
.argument('<pattern>', 'File pattern to include (hint: make sure to escape it in quotes, or your shell might attempt to unroll some tokens like *)')
.addOption(options_1.EXTRACTOR)
.addOption(EXTRACTOR)
.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.')

@@ -113,0 +108,0 @@ .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.')

@@ -1,9 +0,3 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.compareKeys = exports.printKey = void 0;
const runner_1 = require("../../extractor/runner");
const ansi_colors_1 = __importDefault(require("ansi-colors"));
import { NullNamespace } from '../../extractor/runner.js';
import ansi from 'ansi-colors';
/**

@@ -15,14 +9,13 @@ * Prints information about a key, with coloring and formatting.

*/
function printKey(key, deletion) {
export function printKey(key, deletion) {
const namespace = key.namespace
? ` ${ansi_colors_1.default.italic(`(namespace: ${key.namespace})`)}`
? ` ${ansi.italic(`(namespace: ${key.namespace})`)}`
: '';
if (deletion) {
console.log(`${ansi_colors_1.default.red(`- ${key.keyName}`)}${namespace}`);
console.log(`${ansi.red(`- ${key.keyName}`)}${namespace}`);
}
else {
console.log(`${ansi_colors_1.default.green(`+ ${key.keyName}`)}${namespace}`);
console.log(`${ansi.green(`+ ${key.keyName}`)}${namespace}`);
}
}
exports.printKey = printKey;
/**

@@ -36,7 +29,7 @@ * Compares local and remote keys to detect added and deleted keys.

*/
function compareKeys(local, remote) {
export function compareKeys(local, remote) {
const result = { added: [], removed: [] };
// Deleted keys
for (const remoteKey of remote) {
const namespace = remoteKey.namespace || runner_1.NullNamespace;
const namespace = remoteKey.namespace || NullNamespace;
const keyExists = local[namespace]?.delete(remoteKey.name);

@@ -52,3 +45,3 @@ if (!keyExists) {

// Added keys
const namespaces = [runner_1.NullNamespace, ...Object.keys(local).sort()];
const namespaces = [NullNamespace, ...Object.keys(local).sort()];
for (const namespace of namespaces) {

@@ -61,3 +54,3 @@ if (namespace in local && local[namespace].size) {

keyName: keyName,
namespace: namespace === runner_1.NullNamespace ? undefined : namespace,
namespace: namespace === NullNamespace ? undefined : namespace,
defaultValue: keys.get(keyName) || undefined,

@@ -86,2 +79,1 @@ });

}
exports.compareKeys = compareKeys;

@@ -1,12 +0,9 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.clearAuthStore = exports.removeApiKeys = exports.saveApiKey = exports.getApiKey = exports.savePak = exports.savePat = exports.API_TOKENS_FILE = void 0;
const path_1 = require("path");
const promises_1 = require("fs/promises");
const logger_1 = require("../utils/logger");
const constants_1 = require("../constants");
exports.API_TOKENS_FILE = (0, path_1.join)(constants_1.CONFIG_PATH, 'authentication.json');
import { join, dirname } from 'path';
import { mkdir, readFile, writeFile } from 'fs/promises';
import { warn } from '../utils/logger.js';
import { CONFIG_PATH } from '../constants.js';
const API_TOKENS_FILE = join(CONFIG_PATH, 'authentication.json');
async function ensureConfigPath() {
try {
await (0, promises_1.mkdir)((0, path_1.dirname)(exports.API_TOKENS_FILE));
await mkdir(dirname(API_TOKENS_FILE));
}

@@ -22,3 +19,3 @@ catch (e) {

await ensureConfigPath();
const storeData = await (0, promises_1.readFile)(exports.API_TOKENS_FILE, 'utf8');
const storeData = await readFile(API_TOKENS_FILE, 'utf8');
return JSON.parse(storeData);

@@ -35,3 +32,3 @@ }

const blob = JSON.stringify(store);
await (0, promises_1.writeFile)(exports.API_TOKENS_FILE, blob, {
await writeFile(API_TOKENS_FILE, blob, {
mode: 0o600,

@@ -62,13 +59,11 @@ encoding: 'utf8',

}
async function savePat(instance, pat) {
export async function savePat(instance, pat) {
const store = await loadStore();
return storePat(store, instance, pat);
}
exports.savePat = savePat;
async function savePak(instance, projectId, pak) {
export async function savePak(instance, projectId, pak) {
const store = await loadStore();
return storePak(store, instance, projectId, pak);
}
exports.savePak = savePak;
async function getApiKey(instance, projectId) {
export async function getApiKey(instance, projectId) {
const store = await loadStore();

@@ -82,3 +77,3 @@ if (!store[instance.hostname]) {

Date.now() > scopedStore.user.expires) {
(0, logger_1.warn)(`Your personal access token for ${instance.hostname} expired.`);
warn(`Your personal access token for ${instance.hostname} expired.`);
await storePat(store, instance, undefined);

@@ -95,3 +90,3 @@ return null;

if (pak.expires !== 0 && Date.now() > pak.expires) {
(0, logger_1.warn)(`Your project API key for project #${projectId} on ${instance.hostname} expired.`);
warn(`Your project API key for project #${projectId} on ${instance.hostname} expired.`);
await storePak(store, instance, projectId, undefined);

@@ -104,4 +99,3 @@ return null;

}
exports.getApiKey = getApiKey;
async function saveApiKey(instance, token) {
export async function saveApiKey(instance, token) {
const store = await loadStore();

@@ -119,4 +113,3 @@ if (token.type === 'PAT') {

}
exports.saveApiKey = saveApiKey;
async function removeApiKeys(api) {
export async function removeApiKeys(api) {
const store = await loadStore();

@@ -128,6 +121,4 @@ return saveStore({

}
exports.removeApiKeys = removeApiKeys;
async function clearAuthStore() {
export async function clearAuthStore() {
return saveStore({});
}
exports.clearAuthStore = clearAuthStore;

@@ -1,10 +0,8 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const cosmiconfig_1 = require("cosmiconfig");
const path_1 = require("path");
const fs_1 = require("fs");
const constants_1 = require("../constants");
const explorer = (0, cosmiconfig_1.cosmiconfig)('tolgee', {
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
import { resolve } from 'path';
import { existsSync } from 'fs';
import { SDKS } from '../constants.js';
const explorer = cosmiconfig('tolgee', {
loaders: {
noExt: cosmiconfig_1.defaultLoaders['.json'],
noExt: defaultLoaders['.json'],
},

@@ -35,4 +33,4 @@ });

if ('sdk' in rc) {
if (!constants_1.SDKS.includes(rc.sdk)) {
throw new Error(`Invalid config: invalid sdk. Must be one of: ${constants_1.SDKS.join(' ')}`);
if (!SDKS.includes(rc.sdk)) {
throw new Error(`Invalid config: invalid sdk. Must be one of: ${SDKS.join(' ')}`);
}

@@ -45,4 +43,4 @@ cfg.sdk = rc.sdk;

}
const extractorPath = (0, path_1.resolve)(rc.extractor);
if (!(0, fs_1.existsSync)(extractorPath)) {
const extractorPath = resolve(rc.extractor);
if (!existsSync(extractorPath)) {
throw new Error(`Invalid config: extractor points to a file that does not exists (${extractorPath})`);

@@ -60,3 +58,3 @@ }

}
async function loadTolgeeRc() {
export default async function loadTolgeeRc() {
const res = await explorer.search();

@@ -67,2 +65,1 @@ if (!res || res.isEmpty)

}
exports.default = loadTolgeeRc;

@@ -1,18 +0,11 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
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;
const path_1 = require("path");
const fs_1 = require("fs");
const configPath_1 = __importDefault(require("./utils/configPath"));
const packageJson = (0, path_1.join)(__dirname, '..', 'package.json');
const pkg = (0, fs_1.readFileSync)(packageJson, 'utf8');
exports.CONFIG_PATH = (0, configPath_1.default)();
exports.VERSION = JSON.parse(pkg).version;
exports.USER_AGENT = `Tolgee-CLI/${exports.VERSION} (+https://github.com/tolgee/tolgee-cli)`;
exports.DEFAULT_API_URL = new URL('https://app.tolgee.io');
exports.API_KEY_PAT_PREFIX = 'tgpat_';
exports.API_KEY_PAK_PREFIX = 'tgpak_';
exports.SDKS = ['react'];
import { readFileSync } from 'fs';
import getConfigPath from './utils/configPath.js';
const packageJson = new URL('../package.json', import.meta.url);
const pkg = readFileSync(packageJson, 'utf8');
export const CONFIG_PATH = getConfigPath();
export const VERSION = JSON.parse(pkg).version;
export const USER_AGENT = `Tolgee-CLI/${VERSION} (+https://github.com/tolgee/tolgee-cli)`;
export const DEFAULT_API_URL = new URL('https://app.tolgee.io');
export const API_KEY_PAT_PREFIX = 'tgpat_';
export const API_KEY_PAK_PREFIX = 'tgpak_';
export const SDKS = ['react'];

@@ -1,12 +0,7 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const xstate_1 = require("xstate");
const react_1 = __importDefault(require("./machines/react"));
const svelte_1 = __importDefault(require("./machines/svelte"));
const comments_1 = __importDefault(require("./machines/comments"));
const tokenizer_1 = __importDefault(require("./tokenizer"));
import { extname } from 'path';
import { interpret } from 'xstate';
import reactExtractorMachine from './machines/react.js';
import svelteExtractorMachine from './machines/svelte.js';
import commentsExtractorMachine from './machines/comments.js';
import tokenizer from './tokenizer.js';
const REACT_EXTS = [

@@ -34,16 +29,16 @@ '.js',

function pickMachine(code, fileName) {
const ext = (0, path_1.extname)(fileName);
const ext = extname(fileName);
if (REACT_EXTS.includes(ext) && code.includes('@tolgee/react')) {
return react_1.default;
return reactExtractorMachine;
}
if (ext === '.svelte' && code.includes('@tolgee/svelte')) {
return svelte_1.default;
return svelteExtractorMachine;
}
if (ALL_EXTS.includes(ext) &&
(code.includes('@tolgee-key') || code.includes('@tolgee-ignore'))) {
return comments_1.default;
return commentsExtractorMachine;
}
return null;
}
async function extractor(code, fileName) {
export default async function extractor(code, fileName) {
const machineSpec = pickMachine(code, fileName);

@@ -53,5 +48,5 @@ if (!machineSpec) {

}
const tokens = await (0, tokenizer_1.default)(code, fileName);
const tokens = await tokenizer(code, fileName);
// @ts-ignore -- Types are whacky, complains about withConfig but it's not a problem here.
const machine = (0, xstate_1.interpret)(machineSpec);
const machine = interpret(machineSpec);
machine.start();

@@ -67,2 +62,1 @@ for (const token of tokens) {

}
exports.default = extractor;

@@ -1,2 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
export {};

@@ -1,9 +0,4 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const xstate_1 = require("xstate");
const comments_1 = __importDefault(require("./shared/comments"));
exports.default = (0, xstate_1.createMachine)({
import { createMachine, assign, send } from 'xstate';
import commentsService from './shared/comments.js';
export default createMachine({
predictableActionArguments: true,

@@ -17,3 +12,3 @@ id: 'commentsExtractor',

id: 'comments',
src: () => comments_1.default,
src: () => commentsService,
},

@@ -37,3 +32,3 @@ on: {

'comment.line.double-slash.ts': {
actions: (0, xstate_1.send)((_ctx, evt) => ({
actions: send((_ctx, evt) => ({
type: 'COMMENT',

@@ -45,3 +40,3 @@ data: evt.token,

'comment.block.ts': {
actions: (0, xstate_1.send)((_ctx, evt) => ({
actions: send((_ctx, evt) => ({
type: 'COMMENT',

@@ -53,3 +48,3 @@ data: evt.token,

'comment.block.svelte': {
actions: (0, xstate_1.send)((_ctx, evt) => ({
actions: send((_ctx, evt) => ({
type: 'COMMENT',

@@ -63,3 +58,3 @@ data: evt.token,

actions: {
warnUnusedIgnore: (0, xstate_1.assign)({
warnUnusedIgnore: assign({
warnings: (ctx, evt) => [

@@ -70,3 +65,3 @@ ...ctx.warnings,

}),
pushKey: (0, xstate_1.assign)({
pushKey: assign({
keys: (ctx, evt) => [

@@ -82,3 +77,3 @@ ...ctx.keys,

}),
pushWarning: (0, xstate_1.assign)({
pushWarning: assign({
warnings: (ctx, evt) => [

@@ -85,0 +80,0 @@ ...ctx.warnings,

@@ -1,11 +0,6 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const xstate_1 = require("xstate");
const properties_1 = __importDefault(require("./shared/properties"));
const comments_1 = __importDefault(require("./shared/comments"));
import { createMachine, assign, send, forwardTo } from 'xstate';
import propertiesMachine from './shared/properties.js';
import commentsService from './shared/comments.js';
const VOID_KEY = { keyName: '', line: -1 };
exports.default = (0, xstate_1.createMachine)({
export default createMachine({
predictableActionArguments: true,

@@ -28,3 +23,3 @@ id: 'reactExtractor',

id: 'comments',
src: () => comments_1.default,
src: () => commentsService,
},

@@ -48,3 +43,3 @@ on: {

'comment.line.double-slash.ts': {
actions: (0, xstate_1.send)((_ctx, evt) => ({
actions: send((_ctx, evt) => ({
type: 'COMMENT',

@@ -56,3 +51,3 @@ data: evt.token,

'comment.block.ts': {
actions: (0, xstate_1.send)((_ctx, evt) => ({
actions: send((_ctx, evt) => ({
type: 'COMMENT',

@@ -227,3 +222,3 @@ data: evt.token,

id: 'propertiesMachine',
src: properties_1.default,
src: propertiesMachine,
data: {

@@ -252,3 +247,3 @@ depth: 1,

'*': {
actions: (0, xstate_1.forwardTo)('propertiesMachine'),
actions: forwardTo('propertiesMachine'),
},

@@ -353,3 +348,3 @@ },

id: 'propertiesMachine',
src: properties_1.default,
src: propertiesMachine,
onDone: [

@@ -376,3 +371,3 @@ {

'*': {
actions: (0, xstate_1.forwardTo)('propertiesMachine'),
actions: forwardTo('propertiesMachine'),
},

@@ -557,3 +552,3 @@ },

id: 'propertiesMachine',
src: properties_1.default,
src: propertiesMachine,
data: {

@@ -576,3 +571,3 @@ depth: 1,

'*': {
actions: (0, xstate_1.forwardTo)('propertiesMachine'),
actions: forwardTo('propertiesMachine'),
},

@@ -589,19 +584,19 @@ },

actions: {
incrementDepth: (0, xstate_1.assign)({
incrementDepth: assign({
blockDepth: (ctx) => ctx.blockDepth + 1,
}),
decrementDepth: (0, xstate_1.assign)({
decrementDepth: assign({
blockDepth: (ctx) => ctx.blockDepth - 1,
hooks: (ctx) => ctx.hooks.filter((n) => n.depth !== ctx.blockDepth),
}),
storeLine: (0, xstate_1.assign)({
storeLine: assign({
line: (_ctx, evt) => evt.line,
}),
ignoreNextLine: (0, xstate_1.assign)({
ignoreNextLine: assign({
ignore: (_ctx, evt) => ({ type: 'ignore', line: evt.line + 1 }),
}),
consumeIgnoredLine: (0, xstate_1.assign)({
consumeIgnoredLine: assign({
ignore: (_ctx, _evt) => null,
}),
warnUnusedIgnore: (0, xstate_1.assign)({
warnUnusedIgnore: assign({
warnings: (ctx, evt) => [

@@ -612,6 +607,6 @@ ...ctx.warnings,

}),
pushHook: (0, xstate_1.assign)({
pushHook: assign({
hooks: (ctx) => [...ctx.hooks, { depth: ctx.blockDepth }],
}),
pushNamespacedHook: (0, xstate_1.assign)({
pushNamespacedHook: assign({
hooks: (ctx, evt) => [

@@ -622,3 +617,3 @@ ...ctx.hooks,

}),
markHookAsDynamic: (0, xstate_1.assign)({
markHookAsDynamic: assign({
hooks: (ctx, _evt) => [

@@ -633,3 +628,3 @@ ...ctx.hooks.slice(0, -1),

}),
consumeParameters: (0, xstate_1.assign)({
consumeParameters: assign({
key: (ctx, evt) => ({

@@ -654,3 +649,3 @@ // We don't want the key and default value to be overridable

}),
emitWarningFromParameters: (0, xstate_1.assign)({
emitWarningFromParameters: assign({
warnings: (ctx, evt) => [

@@ -667,6 +662,6 @@ ...ctx.warnings,

}),
appendChildren: (0, xstate_1.assign)({
appendChildren: assign({
children: (ctx, evt) => (ctx.children ?? '') + evt.token,
}),
consumeChildren: (0, xstate_1.assign)({
consumeChildren: assign({
key: (ctx, _evt) => ({

@@ -679,9 +674,9 @@ ...ctx.key,

}),
storeKeyName: (0, xstate_1.assign)({
storeKeyName: assign({
key: (ctx, evt) => ({ ...ctx.key, keyName: evt.token }),
}),
storeKeyDefault: (0, xstate_1.assign)({
storeKeyDefault: assign({
key: (ctx, evt) => ({ ...ctx.key, defaultValue: evt.token }),
}),
storeKeyCurrentNamespace: (0, xstate_1.assign)({
storeKeyCurrentNamespace: assign({
key: (ctx, _evt) => ({

@@ -694,3 +689,3 @@ ...ctx.key,

}),
dynamicKeyName: (0, xstate_1.assign)({
dynamicKeyName: assign({
warnings: (ctx, _evt) => [

@@ -702,3 +697,3 @@ ...ctx.warnings,

}),
dynamicKeyDefault: (0, xstate_1.assign)({
dynamicKeyDefault: assign({
key: (ctx, _evt) => ({ ...ctx.key, defaultValue: undefined }),

@@ -710,3 +705,3 @@ warnings: (ctx, _evt) => [

}),
dynamicOptions: (0, xstate_1.assign)({
dynamicOptions: assign({
key: (ctx, _evt) => VOID_KEY,

@@ -718,3 +713,3 @@ warnings: (ctx, _evt) => [

}),
dynamicChildren: (0, xstate_1.assign)({
dynamicChildren: assign({
key: (ctx, _evt) => ctx.key.keyName ? { ...ctx.key, defaultValue: undefined } : VOID_KEY,

@@ -731,3 +726,3 @@ warnings: (ctx, _evt) => [

}),
pushKey: (0, xstate_1.assign)({
pushKey: assign({
warnings: (ctx, _evt) => {

@@ -756,3 +751,3 @@ if (!ctx.key.keyName || ctx.key.namespace !== false)

}),
pushImmediateKey: (0, xstate_1.assign)({
pushImmediateKey: assign({
ignore: (_ctx, evt) => ({ type: 'key', line: evt.line + 1 }),

@@ -769,3 +764,3 @@ keys: (ctx, evt) => [

}),
pushWarning: (0, xstate_1.assign)({
pushWarning: assign({
warnings: (ctx, evt) => [

@@ -772,0 +767,0 @@ ...ctx.warnings,

@@ -1,4 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const json5_1 = require("json5");
import JSON5 from 'json5';
function isValidKeyOverride(data) {

@@ -20,3 +18,3 @@ if (!('key' in data)) {

// This service is responsible for emitting events when magic comments are encountered
function default_1(callback, onReceive) {
export default function (callback, onReceive) {
onReceive((evt) => {

@@ -45,3 +43,3 @@ const comment = evt.data.trim();

try {
const key = (0, json5_1.parse)(data);
const key = JSON5.parse(data);
if (!isValidKeyOverride(key)) {

@@ -84,2 +82,1 @@ // No key in the struct; invalid override

}
exports.default = default_1;

@@ -1,6 +0,4 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const xstate_1 = require("xstate");
import { createMachine, send, assign } from 'xstate';
// This state machine is responsible for extracting translation key properties from an object/props
exports.default = (0, xstate_1.createMachine)({
export default createMachine({
predictableActionArguments: true,

@@ -119,3 +117,3 @@ id: 'properties',

'clearPropertyType',
(0, xstate_1.send)((_ctx, evt) => evt),
send((_ctx, evt) => evt),
],

@@ -242,9 +240,9 @@ },

actions: {
storePropertyType: (0, xstate_1.assign)({
storePropertyType: assign({
property: (_ctx, evt) => evt.token,
}),
clearPropertyType: (0, xstate_1.assign)({
clearPropertyType: assign({
property: (_ctx, _evt) => null,
}),
storePropertyValue: (0, xstate_1.assign)({
storePropertyValue: assign({
keyName: (ctx, evt) => ctx.property === 'key' || ctx.property === 'keyName'

@@ -256,3 +254,3 @@ ? evt.token

}),
storeEmptyPropertyValue: (0, xstate_1.assign)({
storeEmptyPropertyValue: assign({
keyName: (ctx) => ctx.property === 'key' || ctx.property === 'keyName'

@@ -264,3 +262,3 @@ ? ''

}),
markPropertyAsDynamic: (0, xstate_1.assign)({
markPropertyAsDynamic: assign({
keyName: (ctx, _evt) => ctx.property === 'key' || ctx.property === 'keyName'

@@ -272,3 +270,3 @@ ? false

}),
markImmediatePropertyAsDynamic: (0, xstate_1.assign)({
markImmediatePropertyAsDynamic: assign({
keyName: (ctx, evt) => evt.token === 'key' || evt.token === 'keyName' ? false : ctx.keyName,

@@ -278,12 +276,12 @@ defaultValue: (ctx, evt) => evt.token === 'defaultValue' ? false : ctx.defaultValue,

}),
incrementDepth: (0, xstate_1.assign)({
incrementDepth: assign({
depth: (ctx, _evt) => ctx.depth + 1,
}),
decrementDepth: (0, xstate_1.assign)({
decrementDepth: assign({
depth: (ctx, _evt) => ctx.depth - 1,
}),
markAsStatic: (0, xstate_1.assign)({
markAsStatic: assign({
static: (_ctx, _evt) => true,
}),
unmarkAsStatic: (0, xstate_1.assign)({
unmarkAsStatic: assign({
static: (_ctx, _evt) => false,

@@ -290,0 +288,0 @@ }),

@@ -1,11 +0,6 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const xstate_1 = require("xstate");
const properties_1 = __importDefault(require("./shared/properties"));
const comments_1 = __importDefault(require("./shared/comments"));
import { createMachine, assign, send, forwardTo } from 'xstate';
import propertiesMachine from './shared/properties.js';
import commentsService from './shared/comments.js';
const VOID_KEY = { keyName: '', line: -1 };
exports.default = (0, xstate_1.createMachine)({
export default createMachine({
predictableActionArguments: true,

@@ -27,3 +22,3 @@ id: 'svelteExtractor',

id: 'comments',
src: () => comments_1.default,
src: () => commentsService,
},

@@ -47,3 +42,3 @@ on: {

'comment.line.double-slash.ts': {
actions: (0, xstate_1.send)((_ctx, evt) => ({
actions: send((_ctx, evt) => ({
type: 'COMMENT',

@@ -55,3 +50,3 @@ data: evt.token,

'comment.block.ts': {
actions: (0, xstate_1.send)((_ctx, evt) => ({
actions: send((_ctx, evt) => ({
type: 'COMMENT',

@@ -63,3 +58,3 @@ data: evt.token,

'comment.block.svelte': {
actions: (0, xstate_1.send)((_ctx, evt) => ({
actions: send((_ctx, evt) => ({
type: 'COMMENT',

@@ -181,3 +176,3 @@ data: evt.token,

id: 'propertiesMachine',
src: properties_1.default,
src: propertiesMachine,
onDone: [

@@ -204,3 +199,3 @@ {

'*': {
actions: (0, xstate_1.forwardTo)('propertiesMachine'),
actions: forwardTo('propertiesMachine'),
},

@@ -337,3 +332,3 @@ },

id: 'propertiesMachine',
src: properties_1.default,
src: propertiesMachine,
data: {

@@ -356,3 +351,3 @@ depth: 1,

'*': {
actions: (0, xstate_1.forwardTo)('propertiesMachine'),
actions: forwardTo('propertiesMachine'),
},

@@ -369,12 +364,12 @@ },

actions: {
storeLine: (0, xstate_1.assign)({
storeLine: assign({
line: (_ctx, evt) => evt.line,
}),
ignoreNextLine: (0, xstate_1.assign)({
ignoreNextLine: assign({
ignore: (_ctx, evt) => ({ type: 'ignore', line: evt.line + 1 }),
}),
consumeIgnoredLine: (0, xstate_1.assign)({
consumeIgnoredLine: assign({
ignore: (_ctx, _evt) => null,
}),
warnUnusedIgnore: (0, xstate_1.assign)({
warnUnusedIgnore: assign({
warnings: (ctx, evt) => [

@@ -385,9 +380,9 @@ ...ctx.warnings,

}),
storeUseTranslate: (0, xstate_1.assign)({
storeUseTranslate: assign({
useTranslate: (_ctx, _evt) => '',
}),
storeNamespacedUseTranslate: (0, xstate_1.assign)({
storeNamespacedUseTranslate: assign({
useTranslate: (_ctx, evt) => evt.token,
}),
markUseTranslateAsDynamic: (0, xstate_1.assign)({
markUseTranslateAsDynamic: assign({
useTranslate: (_ctx, _evt) => false,

@@ -399,3 +394,3 @@ warnings: (ctx, _evt) => [

}),
consumeParameters: (0, xstate_1.assign)({
consumeParameters: assign({
key: (ctx, evt) => ({

@@ -416,3 +411,3 @@ keyName: ctx.key.keyName || evt.data.keyName,

}),
emitWarningFromParameters: (0, xstate_1.assign)({
emitWarningFromParameters: assign({
warnings: (ctx, evt) => [

@@ -429,9 +424,9 @@ ...ctx.warnings,

}),
storeKeyName: (0, xstate_1.assign)({
storeKeyName: assign({
key: (ctx, evt) => ({ ...ctx.key, keyName: evt.token }),
}),
storeKeyDefault: (0, xstate_1.assign)({
storeKeyDefault: assign({
key: (ctx, evt) => ({ ...ctx.key, defaultValue: evt.token }),
}),
storeKeyCurrentNamespace: (0, xstate_1.assign)({
storeKeyCurrentNamespace: assign({
key: (ctx, _evt) => ({

@@ -442,3 +437,3 @@ ...ctx.key,

}),
dynamicKeyName: (0, xstate_1.assign)({
dynamicKeyName: assign({
warnings: (ctx, _evt) => [

@@ -450,3 +445,3 @@ ...ctx.warnings,

}),
dynamicKeyDefault: (0, xstate_1.assign)({
dynamicKeyDefault: assign({
key: (ctx, _evt) => ({ ...ctx.key, defaultValue: undefined }),

@@ -458,3 +453,3 @@ warnings: (ctx, _evt) => [

}),
dynamicOptions: (0, xstate_1.assign)({
dynamicOptions: assign({
key: (_ctx, _evt) => VOID_KEY,

@@ -466,3 +461,3 @@ warnings: (ctx, _evt) => [

}),
pushKey: (0, xstate_1.assign)({
pushKey: assign({
warnings: (ctx, _evt) => {

@@ -493,3 +488,3 @@ if (!ctx.key.keyName || ctx.key.namespace !== false)

}),
pushImmediateKey: (0, xstate_1.assign)({
pushImmediateKey: assign({
ignore: (_ctx, evt) => ({ type: 'key', line: evt.line + 1 }),

@@ -506,3 +501,3 @@ keys: (ctx, evt) => [

}),
pushWarning: (0, xstate_1.assign)({
pushWarning: assign({
warnings: (ctx, evt) => [

@@ -509,0 +504,0 @@ ...ctx.warnings,

@@ -1,11 +0,6 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.filterExtractionResult = exports.extractKeysOfFiles = exports.extractKeysFromFile = exports.NullNamespace = void 0;
const glob_1 = require("glob");
const worker_1 = require("./worker");
const util_1 = require("util");
const glob = (0, util_1.promisify)(glob_1.glob);
exports.NullNamespace = Symbol('namespace.null');
async function extractKeysFromFile(file, extractor) {
return (0, worker_1.callWorker)({
import { glob } from 'glob';
import { callWorker } from './worker.js';
export const NullNamespace = Symbol('namespace.null');
export async function extractKeysFromFile(file, extractor) {
return callWorker({
extractor: extractor,

@@ -15,4 +10,3 @@ file: file,

}
exports.extractKeysFromFile = extractKeysFromFile;
async function extractKeysOfFiles(filesPattern, extractor) {
export async function extractKeysOfFiles(filesPattern, extractor) {
const files = await glob(filesPattern, { nodir: true });

@@ -26,8 +20,7 @@ const result = new Map();

}
exports.extractKeysOfFiles = extractKeysOfFiles;
function filterExtractionResult(data) {
export function filterExtractionResult(data) {
const result = Object.create(null);
for (const { keys } of data.values()) {
for (const key of keys) {
const namespace = key.namespace || exports.NullNamespace;
const namespace = key.namespace || NullNamespace;
if (!(namespace in result)) {

@@ -41,2 +34,1 @@ result[namespace] = new Map();

}
exports.filterExtractionResult = filterExtractionResult;

@@ -1,12 +0,11 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const promises_1 = require("fs/promises");
const vscode_textmate_1 = require("vscode-textmate");
const vscode_oniguruma_1 = require("vscode-oniguruma");
const GRAMMAR_PATH = (0, path_1.join)(__dirname, '..', '..', 'textmate');
import { extname } from 'path';
import { readFile } from 'fs/promises';
import { createRequire } from 'module';
import TextMate from 'vscode-textmate';
import Oniguruma from 'vscode-oniguruma';
const GRAMMAR_PATH = new URL('../../textmate/', import.meta.url);
const GrammarFiles = {
["source.ts" /* Grammar.TYPESCRIPT */]: (0, path_1.join)(GRAMMAR_PATH, 'TypeScript.tmLanguage'),
["source.tsx" /* Grammar.TYPESCRIPT_TSX */]: (0, path_1.join)(GRAMMAR_PATH, 'TypeScriptReact.tmLanguage'),
["source.svelte" /* Grammar.SVELTE */]: (0, path_1.join)(GRAMMAR_PATH, 'Svelte.tmLanguage'),
["source.ts" /* Grammar.TYPESCRIPT */]: new URL('TypeScript.tmLanguage', GRAMMAR_PATH),
["source.tsx" /* Grammar.TYPESCRIPT_TSX */]: new URL('TypeScriptReact.tmLanguage', GRAMMAR_PATH),
["source.svelte" /* Grammar.SVELTE */]: new URL('Svelte.tmLanguage', GRAMMAR_PATH),
};

@@ -16,10 +15,11 @@ let oniguruma;

async function initializeOniguruma() {
const require = createRequire(import.meta.url);
const wasmBlobPath = require
.resolve('vscode-oniguruma')
.replace('main.js', 'onig.wasm');
const wasmBlob = await (0, promises_1.readFile)(wasmBlobPath);
await (0, vscode_oniguruma_1.loadWASM)(wasmBlob);
const wasmBlob = await readFile(wasmBlobPath);
await Oniguruma.loadWASM(wasmBlob);
return {
createOnigScanner: (patterns) => new vscode_oniguruma_1.OnigScanner(patterns),
createOnigString: (s) => new vscode_oniguruma_1.OnigString(s),
createOnigScanner: (patterns) => new Oniguruma.OnigScanner(patterns),
createOnigString: (s) => new Oniguruma.OnigString(s),
};

@@ -31,6 +31,6 @@ }

return null;
const grammar = await (0, promises_1.readFile)(file, 'utf8');
const grammar = await readFile(file, 'utf8');
return grammar.startsWith('{')
? JSON.parse(grammar)
: (0, vscode_textmate_1.parseRawGrammar)(grammar);
: TextMate.parseRawGrammar(grammar);
}

@@ -54,3 +54,3 @@ function extnameToGrammar(extname) {

function tokenize(code, grammar) {
let stack = vscode_textmate_1.INITIAL;
let stack = TextMate.INITIAL;
let linePtr = 0;

@@ -90,7 +90,7 @@ const lines = code.split('\n');

}
async function default_1(code, fileName) {
export default async function (code, fileName) {
if (!oniguruma) {
// Lazily initialize the WebAssembly runtime
oniguruma = initializeOniguruma();
registry = new vscode_textmate_1.Registry({
registry = new TextMate.Registry({
onigLib: oniguruma,

@@ -100,3 +100,3 @@ loadGrammar: loadGrammar,

}
const fileType = (0, path_1.extname)(fileName);
const fileType = extname(fileName);
const grammarName = extnameToGrammar(fileType);

@@ -112,2 +112,1 @@ if (!grammarName) {

}
exports.default = default_1;

@@ -1,6 +0,3 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.emitGitHubWarning = exports.dumpWarnings = exports.WarningMessages = void 0;
const path_1 = require("path");
exports.WarningMessages = {
import { relative } from 'path';
export const WarningMessages = {
W_DYNAMIC_KEY: {

@@ -45,3 +42,3 @@ name: 'Dynamic key',

*/
function dumpWarnings(extractionResult) {
export function dumpWarnings(extractionResult) {
let warningCount = 0;

@@ -55,4 +52,4 @@ for (const [file, { warnings }] of extractionResult.entries()) {

for (const warning of warnings) {
const warnText = warning.warning in exports.WarningMessages
? exports.WarningMessages[warning.warning].name
const warnText = warning.warning in WarningMessages
? WarningMessages[warning.warning].name
: warning.warning;

@@ -70,9 +67,8 @@ console.error('\tline %d: %s', warning.line, warnText);

}
exports.dumpWarnings = dumpWarnings;
function emitGitHubWarning(warning, file, line) {
export function emitGitHubWarning(warning, file, line) {
if (!process.env.GITHUB_ACTIONS)
return;
file = (0, path_1.relative)(process.env.GITHUB_WORKSPACE ?? process.cwd(), file);
if (warning in exports.WarningMessages) {
const { name, description } = exports.WarningMessages[warning];
file = relative(process.env.GITHUB_WORKSPACE ?? process.cwd(), file);
if (warning in WarningMessages) {
const { name, description } = WarningMessages[warning];
const encodedDescription = description

@@ -90,2 +86,1 @@ .replaceAll('%', '%25')

}
exports.emitGitHubWarning = emitGitHubWarning;

@@ -1,14 +0,8 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.callWorker = void 0;
const path_1 = require("path");
const worker_threads_1 = require("worker_threads");
const promises_1 = require("fs/promises");
const extractor_1 = __importDefault(require("./extractor"));
const moduleLoader_1 = require("../utils/moduleLoader");
const deferred_1 = require("../utils/deferred");
const IS_TS_NODE = (0, path_1.extname)(__filename) === '.ts';
import { resolve, extname } from 'path';
import { Worker, isMainThread, parentPort } from 'worker_threads';
import { readFile } from 'fs/promises';
import internalExtractor from './extractor.js';
import { loadModule } from '../utils/moduleLoader.js';
import { createDeferred } from '../utils/deferred.js';
const IS_TS_NODE = extname(import.meta.url) === '.ts';
// --- Worker functions

@@ -21,17 +15,17 @@ let loadedExtractor = Symbol('unloaded');

extractor = args.extractor
? await (0, moduleLoader_1.loadModule)(args.extractor).then((mdl) => mdl.default)
: extractor_1.default;
? await loadModule(args.extractor).then((mdl) => mdl.default)
: internalExtractor;
}
const file = (0, path_1.resolve)(args.file);
const code = await (0, promises_1.readFile)(file, 'utf8');
const file = resolve(args.file);
const code = await readFile(file, 'utf8');
return extractor(code, file);
}
async function workerInit() {
worker_threads_1.parentPort.on('message', (params) => {
parentPort.on('message', (params) => {
handleJob(params)
.then((res) => worker_threads_1.parentPort.postMessage({ data: res }))
.catch((e) => worker_threads_1.parentPort.postMessage({ err: e }));
.then((res) => parentPort.postMessage({ data: res }))
.catch((e) => parentPort.postMessage({ err: e }));
});
}
if (!worker_threads_1.isMainThread)
if (!isMainThread)
workerInit();

@@ -42,6 +36,8 @@ // --- Main thread functions

function createWorker() {
const worker = new worker_threads_1.Worker(__filename, {
// ts-node workaround
execArgv: IS_TS_NODE ? ['--require', 'ts-node/register'] : undefined,
});
const worker = IS_TS_NODE
? new Worker(new URL(import.meta.url).pathname.replace('.ts', '.js'), {
// ts-node workaround
execArgv: ['--require', 'ts-node/register'],
})
: new Worker(new URL(import.meta.url).pathname);
let timeout;

@@ -75,4 +71,4 @@ let currentDeferred;

}
async function callWorker(params) {
const deferred = (0, deferred_1.createDeferred)();
export async function callWorker(params) {
const deferred = createDeferred();
jobQueue.push([params, deferred]);

@@ -84,2 +80,1 @@ if (!worker) {

}
exports.callWorker = callWorker;
#!/usr/bin/env node
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const ansi_colors_1 = __importDefault(require("ansi-colors"));
const credentials_1 = require("./config/credentials");
const tolgeerc_1 = __importDefault(require("./config/tolgeerc"));
const client_1 = __importDefault(require("./client"));
const errors_1 = require("./client/errors");
const logger_1 = require("./utils/logger");
const options_1 = require("./options");
const constants_1 = require("./constants");
const login_1 = require("./commands/login");
const push_1 = __importDefault(require("./commands/push"));
const pull_1 = __importDefault(require("./commands/pull"));
const extract_1 = __importDefault(require("./commands/extract"));
const compare_1 = __importDefault(require("./commands/sync/compare"));
const sync_1 = __importDefault(require("./commands/sync/sync"));
import { Command } from 'commander';
import ansi from 'ansi-colors';
import { getApiKey, savePak, savePat } from './config/credentials.js';
import loadTolgeeRc from './config/tolgeerc.js';
import RestClient from './client/index.js';
import { HttpError } from './client/errors.js';
import { setDebug, isDebugEnabled, debug, info, error, } from './utils/logger.js';
import { API_KEY_OPT, API_URL_OPT, PROJECT_ID_OPT } from './options.js';
import { API_KEY_PAK_PREFIX, API_KEY_PAT_PREFIX, VERSION, } from './constants.js';
import { Login, Logout } from './commands/login.js';
import PushCommand from './commands/push.js';
import PullCommand from './commands/pull.js';
import ExtractCommand from './commands/extract.js';
import CompareCommand from './commands/sync/compare.js';
import SyncCommand from './commands/sync/sync.js';
const NO_KEY_COMMANDS = ['login', 'logout', 'extract'];
ansi_colors_1.default.enabled = process.stdout.isTTY;
ansi.enabled = process.stdout.isTTY;
function topLevelName(command) {

@@ -36,3 +31,3 @@ return command.parent && command.parent.parent

// This is not done as part of the init routine or via the mandatory flag, as this is dependent on the API URL.
const key = await (0, credentials_1.getApiKey)(opts.apiUrl, opts.projectId);
const key = await getApiKey(opts.apiUrl, opts.projectId);
// No key in store, stop here.

@@ -43,7 +38,7 @@ if (!key)

program.setOptionValue('_removeApiKeyFromStore', () => {
if (key.startsWith(constants_1.API_KEY_PAT_PREFIX)) {
(0, credentials_1.savePat)(opts.apiUrl);
if (key.startsWith(API_KEY_PAT_PREFIX)) {
savePat(opts.apiUrl);
}
else {
(0, credentials_1.savePak)(opts.apiUrl, opts.projectId);
savePak(opts.apiUrl, opts.projectId);
}

@@ -54,10 +49,10 @@ });

const opts = cmd.optsWithGlobals();
if (opts.apiKey.startsWith(constants_1.API_KEY_PAK_PREFIX)) {
if (opts.apiKey?.startsWith(API_KEY_PAK_PREFIX)) {
// Parse the key and ensure we can access the specified Project ID
const projectId = client_1.default.projectIdFromKey(opts.apiKey);
const projectId = RestClient.projectIdFromKey(opts.apiKey);
program.setOptionValue('projectId', projectId);
if (opts.projectId !== -1 && opts.projectId !== projectId) {
(0, logger_1.error)('The specified API key cannot be used to perform operations on the specified project.');
(0, logger_1.info)(`The API key you specified is tied to project #${projectId}, you tried to perform operations on project #${opts.projectId}.`);
(0, logger_1.info)('Learn more about how API keys in Tolgee work here: https://tolgee.io/platform/account_settings/api_keys_and_pat_tokens');
error('The specified API key cannot be used to perform operations on the specified project.');
info(`The API key you specified is tied to project #${projectId}, you tried to perform operations on project #${opts.projectId}.`);
info('Learn more about how API keys in Tolgee work here: https://tolgee.io/platform/account_settings/api_keys_and_pat_tokens');
process.exit(1);

@@ -70,8 +65,8 @@ }

if (opts.projectId === -1) {
(0, logger_1.error)('No Project ID have been specified. You must either provide one via --project-ir, or by setting up a `.tolgeerc` file.');
(0, logger_1.info)('Learn more about configuring the CLI here: https://tolgee.io/tolgee-cli/project-configuration');
error('No Project ID have been specified. You must either provide one via --project-id, or by setting up a `.tolgeerc` file.');
info('Learn more about configuring the CLI here: https://tolgee.io/tolgee-cli/project-configuration');
process.exit(1);
}
if (!opts.apiKey) {
(0, logger_1.error)('No API key has been provided. You must either provide one via --api-key, or login via `tolgee login`.');
error('No API key has been provided. You must either provide one via --api-key, or login via `tolgee login`.');
process.exit(1);

@@ -86,3 +81,3 @@ }

const opts = cmd.optsWithGlobals();
const client = new client_1.default({
const client = new RestClient({
apiUrl: opts.apiUrl,

@@ -95,7 +90,7 @@ apiKey: opts.apiKey,

// Apply verbosity
(0, logger_1.setDebug)(prog.opts().verbose);
setDebug(prog.opts().verbose);
}
const program = new commander_1.Command('tolgee')
.version(constants_1.VERSION)
.configureOutput({ writeErr: logger_1.error })
const program = new Command('tolgee')
.version(VERSION)
.configureOutput({ writeErr: error })
.description('Command Line Interface to interact with the Tolgee Platform')

@@ -105,15 +100,15 @@ .option('-v, --verbose', 'Enable verbose logging.')

// Global options
program.addOption(options_1.API_URL_OPT);
program.addOption(options_1.API_KEY_OPT);
program.addOption(options_1.PROJECT_ID_OPT);
program.addOption(API_URL_OPT);
program.addOption(API_KEY_OPT);
program.addOption(PROJECT_ID_OPT);
// Register commands
program.addCommand(login_1.Login);
program.addCommand(login_1.Logout);
program.addCommand(push_1.default);
program.addCommand(pull_1.default);
program.addCommand(extract_1.default);
program.addCommand(compare_1.default);
program.addCommand(sync_1.default);
program.addCommand(Login);
program.addCommand(Logout);
program.addCommand(PushCommand);
program.addCommand(PullCommand);
program.addCommand(ExtractCommand);
program.addCommand(CompareCommand);
program.addCommand(SyncCommand);
async function loadConfig() {
const tgConfig = await (0, tolgeerc_1.default)();
const tgConfig = await loadTolgeeRc();
if (tgConfig) {

@@ -126,5 +121,5 @@ for (const [key, value] of Object.entries(tgConfig)) {

async function handleHttpError(e) {
(0, logger_1.error)('An error occurred while requesting the API.');
(0, logger_1.error)(`${e.request.method} ${e.request.url}`);
(0, logger_1.error)(e.getErrorText());
error('An error occurred while requesting the API.');
error(`${e.request.method} ${e.request.url}`);
error(e.getErrorText());
// Remove token from store if necessary

@@ -134,3 +129,3 @@ if (e.response.status === 401) {

if (removeFn) {
(0, logger_1.info)('Removing the API key from the authentication store.');
info('Removing the API key from the authentication store.');
removeFn();

@@ -140,3 +135,3 @@ }

// Print server output for server errors
if ((0, logger_1.isDebugEnabled)()) {
if (isDebugEnabled()) {
// We cannot parse the response as JSON and pull error codes here as we may be here due to a 5xx error:

@@ -148,3 +143,3 @@ // by nature 5xx class errors can happen for a lot of reasons (e.g. upstream issues, server issues,

const res = await e.response.text();
(0, logger_1.debug)(`Server response:\n\n---\n${res}\n---`);
debug(`Server response:\n\n---\n${res}\n---`);
}

@@ -158,3 +153,3 @@ }

catch (e) {
if (e instanceof errors_1.HttpError) {
if (e instanceof HttpError) {
await handleHttpError(e);

@@ -167,4 +162,4 @@ process.exit(1);

// - Something went wrong with the code
(0, logger_1.error)('An unexpected error occurred while running the command.');
(0, logger_1.error)('Please report this to our issue tracker: https://github.com/tolgee/tolgee-cli/issues');
error('An unexpected error occurred while running the command.');
error('Please report this to our issue tracker: https://github.com/tolgee/tolgee-cli/issues');
console.log(e.stack);

@@ -171,0 +166,0 @@ process.exit(1);

@@ -1,12 +0,9 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EXTRACTOR = exports.API_URL_OPT = exports.PROJECT_ID_OPT = exports.API_KEY_OPT = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const commander_1 = require("commander");
const constants_1 = require("./constants");
import { existsSync } from 'fs';
import { resolve } from 'path';
import { Option, InvalidArgumentError } from 'commander';
import { DEFAULT_API_URL } from './constants.js';
function parseProjectId(v) {
const val = Number(v);
if (!Number.isInteger(val) || val < 1) {
throw new commander_1.InvalidArgumentError('Not a valid project ID.');
throw new InvalidArgumentError('Not a valid project ID.');
}

@@ -20,19 +17,19 @@ return val;

catch {
throw new commander_1.InvalidArgumentError('Malformed URL.');
throw new InvalidArgumentError('Malformed URL.');
}
}
function parsePath(v) {
const path = (0, path_1.resolve)(v);
if (!(0, fs_1.existsSync)(path)) {
throw new commander_1.InvalidArgumentError(`The specified path "${v}" does not exist.`);
const path = resolve(v);
if (!existsSync(path)) {
throw new InvalidArgumentError(`The specified path "${v}" does not exist.`);
}
return path;
}
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');
exports.PROJECT_ID_OPT = new commander_1.Option('-p, --project-id <id>', 'Project ID. Only required when using a Personal Access Token.')
export const API_KEY_OPT = new Option('-ak, --api-key <key>', 'Tolgee API Key. Can be a Project API Key or a Personal Access Token.').env('TOLGEE_API_KEY');
export const PROJECT_ID_OPT = new Option('-p, --project-id <id>', 'Project ID. Only required when using a Personal Access Token.')
.default(-1)
.argParser(parseProjectId);
exports.API_URL_OPT = new commander_1.Option('-au, --api-url <url>', 'The url of Tolgee API.')
.default(constants_1.DEFAULT_API_URL)
export const API_URL_OPT = new Option('-au, --api-url <url>', 'The url of Tolgee API.')
.default(DEFAULT_API_URL)
.argParser(parseUrlArgument);
exports.EXTRACTOR = new commander_1.Option('-e, --extractor <extractor>', `A path to a custom extractor to use instead of the default one.`).argParser(parsePath);
export const EXTRACTOR = new Option('-e, --extractor <extractor>', `A path to a custom extractor to use instead of the default one.`).argParser(parsePath);

@@ -1,11 +0,5 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.askBoolean = exports.askString = void 0;
const readline_1 = __importDefault(require("readline")); // readline/promises is Node 17, currently supporting Node 16+
async function askString(question) {
import readline from 'readline'; // readline/promises is Node 17, currently supporting Node 16+
export async function askString(question) {
return new Promise((resolve) => {
const rl = readline_1.default.createInterface({
const rl = readline.createInterface({
input: process.stdin,

@@ -20,4 +14,3 @@ output: process.stdout,

}
exports.askString = askString;
async function askBoolean(question, def = false) {
export async function askBoolean(question, def = false) {
const yn = def === true ? '[Y/n]' : '[y/N]';

@@ -35,2 +28,1 @@ let res = def;

}
exports.askBoolean = askBoolean;

@@ -1,17 +0,17 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const os_1 = require("os");
const path_1 = require("path");
function getConfigPath() {
import { homedir } from 'os';
import { resolve } from 'path';
export default function getConfigPath() {
if (process.env.TOLGEE_CLI_CONFIG_PATH) {
return process.env.TOLGEE_CLI_CONFIG_PATH;
}
switch (process.platform) {
case 'win32':
return (0, path_1.resolve)(process.env.APPDATA, 'tolgee');
return resolve(process.env.APPDATA, 'tolgee');
case 'darwin':
return (0, path_1.resolve)((0, os_1.homedir)(), 'Library', 'Application Support', 'tolgee');
return resolve(homedir(), 'Library', 'Application Support', 'tolgee');
default:
return process.env.XDG_CONFIG_HOME
? (0, path_1.resolve)(process.env.XDG_CONFIG_HOME, 'tolgee')
: (0, path_1.resolve)((0, os_1.homedir)(), '.config', 'tolgee');
? resolve(process.env.XDG_CONFIG_HOME, 'tolgee')
: resolve(homedir(), '.config', 'tolgee');
}
}
exports.default = getConfigPath;

@@ -1,5 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createDeferred = void 0;
function createDeferred() {
export function createDeferred() {
const deferred = {};

@@ -11,2 +8,1 @@ deferred.promise = new Promise((resolve, reject) => {

}
exports.createDeferred = createDeferred;

@@ -1,4 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loading = exports.error = exports.warn = exports.success = exports.info = exports.debug = exports.isDebugEnabled = exports.setDebug = void 0;
const SYMBOLS = [' ๐Ÿ', ' ๐Ÿ ', ' ๐Ÿ ', '๐Ÿ '];

@@ -11,6 +8,5 @@ let debugEnabled = false;

*/
function setDebug(enabled) {
export function setDebug(enabled) {
debugEnabled = enabled;
}
exports.setDebug = setDebug;
/**

@@ -21,6 +17,5 @@ * Gets the current status of debug logging.

*/
function isDebugEnabled() {
export function isDebugEnabled() {
return debugEnabled;
}
exports.isDebugEnabled = isDebugEnabled;
/**

@@ -31,3 +26,3 @@ * Logs a debug message to the console if debugging is enabled.

*/
function debug(msg) {
export function debug(msg) {
if (debugEnabled) {

@@ -37,3 +32,2 @@ console.log(`โšช ${msg}`);

}
exports.debug = debug;
/**

@@ -44,6 +38,5 @@ * Logs an informative message to the console.

*/
function info(msg) {
export function info(msg) {
console.log(`๐Ÿ”ต ${msg}`);
}
exports.info = info;
/**

@@ -54,6 +47,5 @@ * Logs a success to the console.

*/
function success(msg) {
export function success(msg) {
console.log(`โœ… ${msg}`);
}
exports.success = success;
/**

@@ -64,6 +56,5 @@ * Logs a warning message to the console.

*/
function warn(msg) {
export function warn(msg) {
console.log(`๐ŸŸก ${msg}`);
}
exports.warn = warn;
/**

@@ -74,6 +65,5 @@ * Logs an error message to the console.

*/
function error(msg) {
export function error(msg) {
console.log(`๐Ÿ”ด ${msg}`);
}
exports.error = error;
/**

@@ -86,3 +76,3 @@ * Shows a loading indicator for a Promise until it resolves.

*/
function loading(comment, promise) {
export function loading(comment, promise) {
if (!process.stdout.isTTY) {

@@ -108,2 +98,1 @@ // Simple stdout without animations

}
exports.loading = loading;

@@ -1,14 +0,7 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadModule = void 0;
const path_1 = require("path");
import { extname } from 'path';
let tsService;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function realImport(file) {
return eval('import(file)');
}
async function registerTsNode() {
if (!tsService) {
try {
const tsNode = require('ts-node');
const tsNode = await import('ts-node');
tsService = tsNode.register({ compilerOptions: { module: 'CommonJS' } });

@@ -25,16 +18,16 @@ }

async function importTypeScript(file) {
if ((0, path_1.extname)(__filename) === '.ts') {
return require(file);
if (extname(import.meta.url) === '.ts') {
return import(file);
}
await registerTsNode();
tsService.enabled(true);
const mdl = await realImport(file);
const mdl = await import(file);
tsService.enabled(false);
return mdl;
}
async function loadModule(module) {
export async function loadModule(module) {
if (module.endsWith('.ts')) {
return importTypeScript(module);
}
const mdl = await realImport(module);
const mdl = await import(module);
if (mdl.default?.default) {

@@ -45,2 +38,1 @@ return mdl.default;

}
exports.loadModule = loadModule;

@@ -1,13 +0,10 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.overwriteDir = void 0;
const path_1 = require("path");
const promises_1 = require("fs/promises");
const ask_1 = require("./ask");
const logger_1 = require("./logger");
async function overwriteDir(path, overwrite) {
import { resolve } from 'path';
import { stat, rm, mkdir } from 'fs/promises';
import { askBoolean } from './ask.js';
import { warn, error } from './logger.js';
export async function overwriteDir(path, overwrite) {
try {
const stats = await (0, promises_1.stat)(path);
const stats = await stat(path);
if (!stats.isDirectory()) {
(0, logger_1.error)('The specified path already exists and is not a directory.');
error('The specified path already exists and is not a directory.');
process.exit(1);

@@ -17,13 +14,13 @@ }

if (!process.stdout.isTTY) {
(0, logger_1.error)('The specified path already exists.');
error('The specified path already exists.');
process.exit(1);
}
(0, logger_1.warn)(`The specified path ${(0, path_1.resolve)(path)} already exists.`);
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*.');
warn(`The specified path ${resolve(path)} already exists.`);
const userOverwrite = await askBoolean('Do you want to overwrite data? *BE CAREFUL, ALL THE CONTENTS OF THE DESTINATION FOLDER WILL BE DESTROYED*.');
if (!userOverwrite) {
(0, logger_1.error)('Aborting.');
error('Aborting.');
process.exit(1);
}
// Purge data as requested.
await (0, promises_1.rm)(path, { recursive: true });
await rm(path, { recursive: true });
}

@@ -37,4 +34,3 @@ }

// Create the directory
await (0, promises_1.mkdir)(path, { recursive: true });
await mkdir(path, { recursive: true });
}
exports.overwriteDir = overwriteDir;

@@ -1,8 +0,5 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.unzip = exports.unzipBuffer = void 0;
const fs_1 = require("fs");
const promises_1 = require("fs/promises");
const path_1 = require("path");
const yauzl_1 = require("yauzl");
import { createWriteStream } from 'fs';
import { mkdir } from 'fs/promises';
import { join, dirname } from 'path';
import { fromBuffer } from 'yauzl';
const ZIP_PARSER_OPTS = {

@@ -17,3 +14,3 @@ strictFileNames: true,

throw err;
const writeStream = (0, fs_1.createWriteStream)(dest);
const writeStream = createWriteStream(dest);
stream.pipe(writeStream);

@@ -30,7 +27,7 @@ // Unlock reading loop

*/
function unzipBuffer(zipBlob, dest) {
export function unzipBuffer(zipBlob, dest) {
return new Promise((resolve, reject) => {
zipBlob.arrayBuffer().then((buffer) => {
const nodeBuffer = Buffer.from(buffer);
(0, yauzl_1.fromBuffer)(nodeBuffer, ZIP_PARSER_OPTS, (err, zip) => {
fromBuffer(nodeBuffer, ZIP_PARSER_OPTS, (err, zip) => {
if (err) {

@@ -44,3 +41,2 @@ return reject(err);

}
exports.unzipBuffer = unzipBuffer;
/**

@@ -52,3 +48,3 @@ * Unzips a ZIP file loaded in memory to a destination on disk.

*/
function unzip(zip, dest) {
export function unzip(zip, dest) {
// Enforce expected & security options.

@@ -75,7 +71,7 @@ // Lazy entries is what this reader is based upon.

}
const entryPath = (0, path_1.join)(dest, entry.fileName);
const entryPath = join(dest, entry.fileName);
// Handle directory creation
const entryDirName = (0, path_1.dirname)(entryPath);
const entryDirName = dirname(entryPath);
if (!seenDirectories.has(entryDirName)) {
(0, promises_1.mkdir)(entryDirName, { recursive: true }).then(() => dumpFile(zip, entry, entryPath));
mkdir(entryDirName, { recursive: true }).then(() => dumpFile(zip, entry, entryPath));
}

@@ -88,2 +84,1 @@ else {

}
exports.unzip = unzip;
{
"name": "@tolgee/cli",
"version": "1.1.0",
"type": "commonjs",
"version": "1.1.1",
"type": "module",
"description": "A tool to interact with the Tolgee Platform through CLI",
"repository": {
"type": "git",
"url": "https://github.com/tolgee/tolgee-cli.git"
},
"bin": {

@@ -12,13 +16,13 @@ "tolgee": "./dist/index.js"

"test": "npm run test:unit && npm run test:e2e && npm run test:package",
"test:unit": "jest -c jest.unit.config.ts",
"test:unit": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest -c jest.unit.config.ts",
"pretest:e2e": "npm run build && npm run tolgee:start",
"posttest:e2e": "npm run tolgee:stop",
"test:e2e": "jest -c jest.e2e.config.ts --runInBand",
"test:e2e-run": "jest -c jest.e2e.config.ts --runInBand",
"test:package": "node scripts/validatePackage.mjs",
"tolgee:start": "node scripts/startDocker.mjs",
"test:e2e": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest -c jest.e2e.config.ts --runInBand",
"test:e2e-run": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest -c jest.e2e.config.ts --runInBand",
"test:package": "node scripts/validatePackage.js",
"tolgee:start": "node scripts/startDocker.js",
"tolgee:stop": "docker stop tolgee_cli_e2e",
"lint": "eslint --ext .ts --ext .js --ext .mjs ./src ./test ./scripts jest.config.ts jest.*.config.ts",
"lint": "eslint --ext .ts --ext .js --ext .cjs ./src ./test ./scripts jest.config.ts jest.*.config.ts",
"prettier": "prettier --write ./src ./test ./scripts jest.config.ts jest.*.config.ts",
"run-dev": "ts-node ./src/index.ts",
"run-dev": "cross-env NODE_OPTIONS=\"--loader=ts-node/esm\" node ./src/index.ts",
"schema": "openapi-typescript http://localhost:22222/v3/api-docs/All%20Internal%20-%20for%20Tolgee%20Web%20application --output src/client/internal/schema.generated.ts",

@@ -32,35 +36,34 @@ "release": "semantic-release"

"base32-decode": "^1.0.0",
"commander": "^10.0.0",
"cosmiconfig": "^8.0.0",
"commander": "^10.0.1",
"cosmiconfig": "^8.1.3",
"form-data": "^4.0.0",
"glob": "^8.1.0",
"glob": "^10.2.5",
"json5": "^2.2.3",
"undici": "^5.15.0",
"undici": "^5.22.1",
"vscode-oniguruma": "^1.7.0",
"vscode-textmate": "^8.0.0",
"xstate": "^4.35.2",
"vscode-textmate": "^9.0.0",
"xstate": "^4.37.2",
"yauzl": "^2.10.0"
},
"devDependencies": {
"@jest/types": "^29.3.1",
"@semantic-release/changelog": "^6.0.2",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/glob": "^8.0.0",
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"@types/jest": "^29.5.1",
"@types/node": "^20.2.1",
"@types/yauzl": "^2.10.0",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"eslint": "^8.39.0",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
"cross-env": "^7.0.3",
"eslint": "^8.40.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.3.1",
"jest": "^29.5.0",
"js-yaml": "^4.1.0",
"openapi-typescript": "^6.1.0",
"prettier": "^2.8.3",
"rimraf": "^4.0.7",
"semantic-release": "^20.0.2",
"ts-jest": "^29.0.5",
"openapi-typescript": "^6.2.4",
"prettier": "^2.8.8",
"rimraf": "^5.0.1",
"semantic-release": "^21.0.2",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
"typescript": "^5.0.4"
},

@@ -67,0 +70,0 @@ "engines": {

# Tolgee CLI ๐Ÿ
An experimental ๐Ÿงช command line tool to interact with Tolgee directly from your terminal.
A CLI tool to interact with Tolgee directly from your terminal.
The CLI lets you pull strings from the Tolgee platform into your projects, push local strings to the Tolgee platform,
The Tolgee CLI lets you pull strings from the Tolgee platform into your projects, push local strings to the Tolgee platform,
extract strings from your code, and much more!
- Pull requests welcome! ๐Ÿคฉ
![Tolgee CLI screenshot](tolgee-cli-screenshot.png)
## Installation
The Tolgee CLI is published as a NPM package. You simply need to install it, and you're good to go!
```sh
# npm
npm i --global @tolgee/cli
# Yarn
yarn global add @tolgee/cli
# pnpm
pnpm add --global @tolgee/cli
```
> **Warning**: The Tolgee CLI is currently experimental and subject to bugs. Breaking changes may happen before stable release!
>
> Help us reach stable version faster by reporting any bug you encounter on the [issue tracker](https://github.com/tolgee/tolgee-cli/issues/new?labels=bug).
> Feedback is also greatly appreciated!
See our [documentation](https://tolgee.io/tolgee-cli/installation) for more information.

@@ -22,0 +25,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with โšก๏ธ by Socket Inc