Skip to content

Commit

Permalink
Merge pull request #45 from transifex/cli-filters
Browse files Browse the repository at this point in the history
CLI: Filter content by tags, add invalidate command
  • Loading branch information
Nikos Vasileiou authored Feb 18, 2021
2 parents 0f8c327 + 5181b6a commit e93c9ea
Show file tree
Hide file tree
Showing 9 changed files with 351 additions and 29 deletions.
64 changes: 55 additions & 9 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ $ npm run push
# Commands
* [`txjs-cli help [COMMAND]`](#txjs-cli-help-command)
* [`txjs-cli push [PATTERN]`](#txjs-cli-push-pattern)
* [`txjs-cli invalidate`](#txjs-cli-invalidate)

## `txjs-cli help [COMMAND]`

Expand All @@ -70,7 +71,7 @@ OPTIONS

## `txjs-cli push [PATTERN]`

Detect translatable strings and push content to Transifex
detect and push source content to Transifex

```
USAGE
Expand All @@ -80,13 +81,15 @@ ARGUMENTS
PATTERN [default: **/*.{js,jsx,ts,tsx}] file pattern to scan for strings
OPTIONS
-v, --verbose Verbose output
--cds-host=cds-host CDS host URL
--dry-run Dry run, do not push to Transifex
--purge Purge content on Transifex
--secret=secret Native project secret
--token=token Native project public token
--tags=tags Globally add tags to strings
-v, --verbose verbose output
--append-tags=append-tags append tags to strings
--cds-host=cds-host CDS host URL
--dry-run dry run, do not push to Transifex
--purge purge content on Transifex
--secret=secret native project secret
--token=token native project public token
--with-tags-only=with-tags-only push strings with specific tags
--without-tags-only=without-tags-only push strings without specific tags
DESCRIPTION
Parse .js, .ts, .jsx and .tsx files and detect phrases marked for
Expand All @@ -108,11 +111,54 @@ DESCRIPTION
txjs-cli push /home/repo/src
txjs-cli push "*.js"
txjs-cli push --dry-run
txjs-cli push --tags="master,release:2.5"
txjs-cli push --append-tags="master,release:2.5"
txjs-cli push --with-tags-only="home,error"
txjs-cli push --without-tags-only="custom"
txjs-cli push --token=mytoken --secret=mysecret
TRANSIFEX_TOKEN=mytoken TRANSIFEX_SECRET=mysecret txjs-cli push
```

## `txjs-cli invalidate`

invalidate and refresh CDS cache

```
USAGE
$ txjs-cli invalidate
OPTIONS
--cds-host=cds-host CDS host URL
--purge force delete CDS cached content
--secret=secret native project secret
--token=token native project public token
DESCRIPTION
Content for delivery is cached in CDS and refreshed automatically every hour.
This command triggers a refresh of cached content on the fly.
By default, invalidation does not remove existing cached content, but
starts the process of updating with latest translations from Transifex.
Passing the --purge option, cached content will be forced to be deleted,
however use that with caution, as it may introduce downtime of
translation delivery to the apps until fresh content is cached in the CDS.
To invalidate translations some environment variables must be set:
TRANSIFEX_TOKEN=<Transifex Native Project Token>
TRANSIFEX_SECRET=<Transifex Native Project Secret>
(optional) TRANSIFEX_CDS_HOST=<CDS HOST>
or passed as --token=<TOKEN> --secret=<SECRET> parameters
Default CDS Host is https://cds.svc.transifex.net
Examples:
txjs-cli invalidate
txjs-cli invalidate --purge
txjs-cli invalidate --token=mytoken --secret=mysecret
TRANSIFEX_TOKEN=mytoken TRANSIFEX_SECRET=mysecret txjs-cli invalidate
```

# License

Licensed under Apache License 2.0, see [LICENSE](https://github.com/transifex/transifex-javascript/blob/HEAD/LICENSE) file.
47 changes: 40 additions & 7 deletions packages/cli/src/api/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const { stringToArray, mergeArrays } = require('./utils');
* @param {Number} params._charlimit
* @param {Number} params._tags
* @param {String} occurence
* @param {String[]} globalTags
* @param {String[]} appendTags
* @returns {Object} Payload
* @returns {String} Payload.string
* @returns {String} Payload.key
Expand All @@ -29,20 +29,44 @@ const { stringToArray, mergeArrays } = require('./utils');
* @returns {String[]} Payload.meta.tags
* @returns {String[]} Payload.meta.occurrences
*/
function createPayload(string, params, occurence, globalTags) {
function createPayload(string, params, occurence, appendTags) {
return {
string,
key: generateKey(string, params),
meta: _.omitBy({
context: stringToArray(params._context),
developer_comment: params._comment,
character_limit: params._charlimit ? parseInt(params._charlimit, 10) : undefined,
tags: mergeArrays(stringToArray(params._tags), globalTags),
tags: mergeArrays(stringToArray(params._tags), appendTags),
occurrences: [occurence],
}, _.isNil),
};
}

/**
* Check if payload coming from createPayload is valid based on tag filters
*
* @param {Object} payload
* @param {String[]} options.filterWithTags
* @param {String[]} options.filterWithoutTags
* @returns {Boolean}
*/
function isPayloadValid(payload, options = {}) {
const { filterWithTags, filterWithoutTags } = options;
let isValid = true;
_.each(filterWithTags, (tag) => {
if (!_.includes(payload.meta.tags, tag)) {
isValid = false;
}
});
_.each(filterWithoutTags, (tag) => {
if (_.includes(payload.meta.tags, tag)) {
isValid = false;
}
});
return isValid;
}

/**
* Check if callee is a valid Transifex Native function
*
Expand Down Expand Up @@ -92,10 +116,14 @@ function _parse(source) {
*
* @param {String} file absolute file path
* @param {String} relativeFile occurence
* @param {String[]} globalTags
* @param {Object} options
* @param {String[]} options.appendTags
* @param {String[]} options.filterWithTags
* @param {String[]} options.filterWithoutTags
* @returns {Object}
*/
function extractPhrases(file, relativeFile, globalTags) {
function extractPhrases(file, relativeFile, options = {}) {
const { appendTags } = options;
const HASHES = {};
const source = fs.readFileSync(file, 'utf8');
const ast = _parse(source);
Expand Down Expand Up @@ -124,7 +152,9 @@ function extractPhrases(file, relativeFile, globalTags) {
});
}

const partial = createPayload(string, params, relativeFile, globalTags);
const partial = createPayload(string, params, relativeFile, appendTags);
if (!isPayloadValid(partial, options)) return;

mergePayload(HASHES, {
[partial.key]: {
string: partial.string,
Expand Down Expand Up @@ -156,7 +186,10 @@ function extractPhrases(file, relativeFile, globalTags) {
});

if (!string) return;
const partial = createPayload(string, params, relativeFile, globalTags);

const partial = createPayload(string, params, relativeFile, appendTags);
if (!isPayloadValid(partial, options)) return;

mergePayload(HASHES, {
[partial.key]: {
string: partial.string,
Expand Down
47 changes: 47 additions & 0 deletions packages/cli/src/api/invalidate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const axios = require('axios');
const { version } = require('../../package.json');

/**
* Invalidate CDS cache
*
* @param {Object} params
* @param {String} params.url
* @param {String} params.token
* @param {String} params.secret
* @param {Boolean} params.purge
* @returns {Object} Data
* @returns {Boolean} Data.success
* @returns {String} Data.status
* @returns {Number} Data.data.count
* @returns {Number} Data.data.status
* @returns {Number} Data.data.token
*/
async function invalidateCDS(params) {
const action = params.purge ? 'purge' : 'invalidate';
try {
const res = await axios.post(`${params.url}/${action}`, {
}, {
headers: {
Authorization: `Bearer ${params.token}:${params.secret}`,
'Content-Type': 'application/json;charset=utf-8',
'X-NATIVE-SDK': `txjs/cli/${version}`,
},
});
return {
success: true,
status: res.status,
data: res.data,
};
} catch (error) {
if (error.response) {
return {
success: false,
status: error.response.status,
data: error.response.data,
};
}
throw new Error(error.message);
}
}

module.exports = invalidateCDS;
103 changes: 103 additions & 0 deletions packages/cli/src/commands/invalidate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* eslint no-shadow: 0 */

require('colors');
const { Command, flags } = require('@oclif/command');
const { cli } = require('cli-ux');
const invalidateCDS = require('../api/invalidate');

class InvalidateCommand extends Command {
async run() {
const { flags } = this.parse(InvalidateCommand);

let cdsHost = process.env.TRANSIFEX_CDS_HOST || 'https://cds.svc.transifex.net';
let projectToken = process.env.TRANSIFEX_TOKEN;
let projectSecret = process.env.TRANSIFEX_SECRET;

if (flags.token) projectToken = flags.token;
if (flags.secret) projectSecret = flags.secret;
if (flags['cds-host']) cdsHost = flags['cds-host'];

if (!projectToken || !projectSecret) {
this.log(`${'✘'.red} Cannot invalidate CDS, credentials are missing.`);
this.log('Tip: Set TRANSIFEX_TOKEN and TRANSIFEX_SECRET environment variables'.yellow);
process.exit();
}

if (flags.purge) {
cli.action.start('Invalidating and purging CDS cache', '', { stdout: true });
} else {
cli.action.start('Invalidating CDS cache', '', { stdout: true });
}

try {
const res = await invalidateCDS({
url: cdsHost,
token: projectToken,
secret: projectSecret,
purge: flags.purge,
});
if (res.success) {
cli.action.stop('Success'.green);
this.log(`${(res.data.count || 0).toString().green} records invalidated`);
this.log('Note: It might take a few minutes for fresh content to be available'.yellow);
} else {
cli.action.stop('Failed'.red);
this.log(`Status code: ${res.status}`.red);
this.error(JSON.stringify(res.data));
}
} catch (err) {
cli.action.stop('Failed'.red);
throw err;
}
}
}

InvalidateCommand.description = `invalidate and refresh CDS cache
Content for delivery is cached in CDS and refreshed automatically every hour.
This command triggers a refresh of cached content on the fly.
By default, invalidation does not remove existing cached content, but
starts the process of updating with latest translations from Transifex.
Passing the --purge option, cached content will be forced to be deleted,
however use that with caution, as it may introduce downtime of
translation delivery to the apps until fresh content is cached in the CDS.
To invalidate translations some environment variables must be set:
TRANSIFEX_TOKEN=<Transifex Native Project Token>
TRANSIFEX_SECRET=<Transifex Native Project Secret>
(optional) TRANSIFEX_CDS_HOST=<CDS HOST>
or passed as --token=<TOKEN> --secret=<SECRET> parameters
Default CDS Host is https://cds.svc.transifex.net
Examples:
txjs-cli invalidate
txjs-cli invalidate --purge
txjs-cli invalidate --token=mytoken --secret=mysecret
TRANSIFEX_TOKEN=mytoken TRANSIFEX_SECRET=mysecret txjs-cli invalidate
`;

InvalidateCommand.args = [];

InvalidateCommand.flags = {
purge: flags.boolean({
description: 'force delete CDS cached content',
default: false,
}),
token: flags.string({
description: 'native project public token',
default: '',
}),
secret: flags.string({
description: 'native project secret',
default: '',
}),
'cds-host': flags.string({
description: 'CDS host URL',
default: '',
}),
};

module.exports = InvalidateCommand;
Loading

0 comments on commit e93c9ea

Please sign in to comment.