@formatjs/cli-lib 5.0.4 → 5.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/BUILD +118 -0
  2. package/CHANGELOG.md +1097 -0
  3. package/LICENSE.md +0 -0
  4. package/README.md +0 -0
  5. package/index.ts +7 -0
  6. package/main.ts +5 -0
  7. package/package.json +61 -61
  8. package/src/cli.ts +240 -0
  9. package/src/compile.ts +141 -0
  10. package/src/compile_folder.ts +15 -0
  11. package/src/console_utils.ts +78 -0
  12. package/src/extract.ts +273 -0
  13. package/src/formatters/crowdin.ts +34 -0
  14. package/src/formatters/default.ts +19 -0
  15. package/src/formatters/index.ts +46 -0
  16. package/src/formatters/lokalise.ts +33 -0
  17. package/src/formatters/simple.ts +12 -0
  18. package/src/formatters/smartling.ts +73 -0
  19. package/src/formatters/transifex.ts +33 -0
  20. package/src/parse_script.ts +49 -0
  21. package/src/pseudo_locale.ts +113 -0
  22. package/src/vue_extractor.ts +96 -0
  23. package/tests/unit/__snapshots__/unit.test.ts.snap +42 -0
  24. package/tests/unit/__snapshots__/vue_extractor.test.ts.snap +36 -0
  25. package/tests/unit/fixtures/bind.vue +46 -0
  26. package/tests/unit/fixtures/comp.vue +17 -0
  27. package/tests/unit/unit.test.ts +44 -0
  28. package/tests/unit/vue_extractor.test.ts +38 -0
  29. package/tsconfig.json +5 -0
  30. package/index.d.ts +0 -8
  31. package/index.d.ts.map +0 -1
  32. package/index.js +0 -12
  33. package/main.d.ts +0 -2
  34. package/main.d.ts.map +0 -1
  35. package/main.js +0 -3
  36. package/src/cli.d.ts +0 -3
  37. package/src/cli.d.ts.map +0 -1
  38. package/src/cli.js +0 -142
  39. package/src/compile.d.ts +0 -48
  40. package/src/compile.d.ts.map +0 -1
  41. package/src/compile.js +0 -122
  42. package/src/compile_folder.d.ts +0 -3
  43. package/src/compile_folder.d.ts.map +0 -1
  44. package/src/compile_folder.js +0 -22
  45. package/src/console_utils.d.ts +0 -9
  46. package/src/console_utils.d.ts.map +0 -1
  47. package/src/console_utils.js +0 -141
  48. package/src/extract.d.ts +0 -75
  49. package/src/extract.d.ts.map +0 -1
  50. package/src/extract.js +0 -220
  51. package/src/formatters/crowdin.d.ts +0 -8
  52. package/src/formatters/crowdin.d.ts.map +0 -1
  53. package/src/formatters/crowdin.js +0 -29
  54. package/src/formatters/default.d.ts +0 -6
  55. package/src/formatters/default.d.ts.map +0 -1
  56. package/src/formatters/default.js +0 -13
  57. package/src/formatters/index.d.ts +0 -9
  58. package/src/formatters/index.d.ts.map +0 -1
  59. package/src/formatters/index.js +0 -45
  60. package/src/formatters/lokalise.d.ts +0 -10
  61. package/src/formatters/lokalise.d.ts.map +0 -1
  62. package/src/formatters/lokalise.js +0 -26
  63. package/src/formatters/simple.d.ts +0 -5
  64. package/src/formatters/simple.d.ts.map +0 -1
  65. package/src/formatters/simple.js +0 -12
  66. package/src/formatters/smartling.d.ts +0 -24
  67. package/src/formatters/smartling.d.ts.map +0 -1
  68. package/src/formatters/smartling.js +0 -52
  69. package/src/formatters/transifex.d.ts +0 -10
  70. package/src/formatters/transifex.d.ts.map +0 -1
  71. package/src/formatters/transifex.js +0 -26
  72. package/src/parse_script.d.ts +0 -8
  73. package/src/parse_script.d.ts.map +0 -1
  74. package/src/parse_script.js +0 -50
  75. package/src/pseudo_locale.d.ts +0 -7
  76. package/src/pseudo_locale.d.ts.map +0 -1
  77. package/src/pseudo_locale.js +0 -104
  78. package/src/vue_extractor.d.ts +0 -3
  79. package/src/vue_extractor.d.ts.map +0 -1
  80. package/src/vue_extractor.js +0 -62
package/LICENSE.md CHANGED
File without changes
package/README.md CHANGED
File without changes
package/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export {default as extractAndWrite, extract} from './src/extract'
2
+ export type {ExtractCLIOptions, ExtractOpts} from './src/extract'
3
+ export type {MessageDescriptor} from '@formatjs/ts-transformer'
4
+ export type {FormatFn, CompileFn} from './src/formatters/default'
5
+ export type {Element, Comparator} from 'json-stable-stringify'
6
+ export {default as compileAndWrite, compile} from './src/compile'
7
+ export type {CompileCLIOpts, Opts as CompileOpts} from './src/compile'
package/main.ts ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict'
4
+
5
+ require('./src/cli').default(process.argv)
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
1
  {
2
- "name": "@formatjs/cli-lib",
3
- "version": "5.0.4",
4
- "description": "Lib for CLI for formatjs.",
5
- "keywords": [
6
- "intl",
7
- "i18n",
8
- "internationalization",
9
- "locale",
10
- "localization",
11
- "globalization",
12
- "react",
13
- "react-intl",
14
- "reactjs",
15
- "format",
16
- "formatjs",
17
- "formatting",
18
- "translate",
19
- "translation",
20
- "cli"
21
- ],
22
- "author": "Linjie Ding <linjie@airtable.com>",
23
- "homepage": "https://github.com/formatjs/formatjs",
24
- "license": "MIT",
25
- "main": "index.js",
26
- "repository": {
27
- "type": "git",
28
- "url": "git+ssh://git@github.com/formatjs/formatjs.git"
2
+ "name": "@formatjs/cli-lib",
3
+ "version": "5.0.7",
4
+ "description": "Lib for CLI for formatjs.",
5
+ "keywords": [
6
+ "intl",
7
+ "i18n",
8
+ "internationalization",
9
+ "locale",
10
+ "localization",
11
+ "globalization",
12
+ "react",
13
+ "react-intl",
14
+ "reactjs",
15
+ "format",
16
+ "formatjs",
17
+ "formatting",
18
+ "translate",
19
+ "translation",
20
+ "cli"
21
+ ],
22
+ "author": "Linjie Ding <linjie@airtable.com>",
23
+ "homepage": "https://github.com/formatjs/formatjs",
24
+ "license": "MIT",
25
+ "main": "index.js",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+ssh://git@github.com/formatjs/formatjs.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/formatjs/formatjs/issues"
32
+ },
33
+ "dependencies": {
34
+ "@formatjs/icu-messageformat-parser": "2.1.5",
35
+ "@formatjs/ts-transformer": "3.9.10",
36
+ "@types/estree": "^0.0.50",
37
+ "@types/fs-extra": "^9.0.1",
38
+ "@types/json-stable-stringify": "^1.0.32",
39
+ "@types/node": "14 || 16 || 17",
40
+ "chalk": "^4.0.0",
41
+ "commander": "8",
42
+ "fast-glob": "^3.2.7",
43
+ "fs-extra": "10",
44
+ "json-stable-stringify": "^1.0.1",
45
+ "loud-rejection": "^2.2.0",
46
+ "tslib": "2.4.0",
47
+ "typescript": "^4.5"
48
+ },
49
+ "peerDependencies": {
50
+ "@vue/compiler-core": "^3.2.23",
51
+ "@vue/compiler-sfc": "^3.2.34"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@vue/compiler-sfc": {
55
+ "optional": true
29
56
  },
30
- "bugs": {
31
- "url": "https://github.com/formatjs/formatjs/issues"
32
- },
33
- "dependencies": {
34
- "@formatjs/icu-messageformat-parser": "2.1.4",
35
- "@formatjs/ts-transformer": "3.9.9",
36
- "@types/estree": "^0.0.50",
37
- "@types/fs-extra": "^9.0.1",
38
- "@types/json-stable-stringify": "^1.0.32",
39
- "@types/node": "14 || 16 || 17",
40
- "chalk": "^4.0.0",
41
- "commander": "8",
42
- "fast-glob": "^3.2.7",
43
- "fs-extra": "10",
44
- "json-stable-stringify": "^1.0.1",
45
- "loud-rejection": "^2.2.0",
46
- "tslib": "2.4.0",
47
- "typescript": "^4.5"
48
- },
49
- "peerDependencies": {
50
- "@vue/compiler-core": "^3.2.23",
51
- "@vue/compiler-sfc": "^3.2.34"
52
- },
53
- "peerDependenciesMeta": {
54
- "@vue/compiler-sfc": {
55
- "optional": true
56
- },
57
- "@vue/compiler-core": {
58
- "optional": true
59
- }
60
- },
61
- "engines": {
62
- "node": ">= 16.5.0"
57
+ "@vue/compiler-core": {
58
+ "optional": true
63
59
  }
64
- }
60
+ },
61
+ "engines": {
62
+ "node": ">= 16.5.0"
63
+ }
64
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,240 @@
1
+ import {program} from 'commander'
2
+ import loudRejection from 'loud-rejection'
3
+ import extract, {ExtractCLIOptions} from './extract'
4
+ import compile, {CompileCLIOpts, Opts} from './compile'
5
+ import compileFolder from './compile_folder'
6
+ import {sync as globSync} from 'fast-glob'
7
+ import {debug} from './console_utils'
8
+
9
+ const KNOWN_COMMANDS = ['extract']
10
+
11
+ async function main(argv: string[]) {
12
+ loudRejection()
13
+
14
+ program
15
+ // TODO: fix this
16
+ .version('5.0.6', '-v, --version')
17
+ .usage('<command> [flags]')
18
+ .action(command => {
19
+ if (!KNOWN_COMMANDS.includes(command)) {
20
+ program.help()
21
+ }
22
+ })
23
+
24
+ program
25
+ .command('help', {isDefault: true})
26
+ .description('Show this help message.')
27
+ .action(() => program.help())
28
+
29
+ // Long text wrapping to available terminal columns: https://github.com/tj/commander.js/pull/956
30
+ // NOTE: please keep the help text in sync with babel-plugin-formatjs documentation.
31
+ program
32
+ .command('extract [files...]')
33
+ .description(
34
+ `Extract string messages from React components that use react-intl.
35
+ The input language is expected to be TypeScript or ES2017 with JSX.`
36
+ )
37
+ .option(
38
+ '--format <path>',
39
+ `Path to a formatter file that controls the shape of JSON file from \`--out-file\`.
40
+ The formatter file must export a function called \`format\` with the signature
41
+ \`\`\`
42
+ type FormatFn = <T = Record<string, MessageDescriptor>>(
43
+ msgs: Record<string, MessageDescriptor>
44
+ ) => T
45
+ \`\`\`
46
+ This is especially useful to convert from our extracted format to a TMS-specific format.
47
+ `
48
+ )
49
+ .option(
50
+ '--out-file <path>',
51
+ `The target file path where the plugin will output an aggregated
52
+ \`.json\` file of all the translations from the \`files\` supplied.`
53
+ )
54
+ .option(
55
+ '--id-interpolation-pattern <pattern>',
56
+ `If certain message descriptors don't have id, this \`pattern\` will be used to automatically
57
+ generate IDs for them. Default to \`[sha512:contenthash:base64:6]\` where \`contenthash\` is the hash of
58
+ \`defaultMessage\` and \`description\`.
59
+ See https://github.com/webpack/loader-utils#interpolatename for sample patterns`,
60
+ '[sha512:contenthash:base64:6]'
61
+ )
62
+ .option(
63
+ '--extract-source-location',
64
+ `Whether the metadata about the location of the message in the source file should be
65
+ extracted. If \`true\`, then \`file\`, \`start\`, and \`end\` fields will exist for each
66
+ extracted message descriptors.`,
67
+ false
68
+ )
69
+ .option(
70
+ '--remove-default-message',
71
+ 'Remove `defaultMessage` field in generated js after extraction',
72
+ false
73
+ )
74
+ .option(
75
+ '--additional-component-names <comma-separated-names>',
76
+ `Additional component names to extract messages from, e.g: \`'FormattedFooBarMessage'\`.
77
+ **NOTE**: By default we check for the fact that \`FormattedMessage\`
78
+ is imported from \`moduleSourceName\` to make sure variable alias
79
+ works. This option does not do that so it's less safe.`,
80
+ (val: string) => val.split(',')
81
+ )
82
+ .option(
83
+ '--additional-function-names <comma-separated-names>',
84
+ `Additional function names to extract messages from, e.g: \`'$t'\`.`,
85
+ (val: string) => val.split(',')
86
+ )
87
+ .option(
88
+ '--ignore <files...>',
89
+ 'List of glob paths to **not** extract translations from.'
90
+ )
91
+ .option(
92
+ '--throws',
93
+ 'Whether to throw an exception when we fail to process any file in the batch.'
94
+ )
95
+ .option(
96
+ '--pragma <pragma>',
97
+ `parse specific additional custom pragma. This allows you to tag certain file with metadata such as \`project\`. For example with this file:
98
+
99
+ \`\`\`
100
+ // @intl-meta project:my-custom-project
101
+ import {FormattedMessage} from 'react-intl';
102
+
103
+ <FormattedMessage defaultMessage="foo" id="bar" />;
104
+ \`\`\`
105
+
106
+ and with option \`{pragma: "intl-meta"}\`, we'll parse out \`// @intl-meta project:my-custom-project\` into \`{project: 'my-custom-project'}\` in the result file.`
107
+ )
108
+ .option(
109
+ '--preserve-whitespace',
110
+ 'Whether to preserve whitespace and newlines.'
111
+ )
112
+ .option(
113
+ '--flatten',
114
+ `Whether to hoist selectors & flatten sentences as much as possible. E.g:
115
+ "I have {count, plural, one{a dog} other{many dogs}}"
116
+ becomes "{count, plural, one{I have a dog} other{I have many dogs}}".
117
+ The goal is to provide as many full sentences as possible since fragmented
118
+ sentences are not translator-friendly.`
119
+ )
120
+ .action(async (filePatterns: string[], cmdObj: ExtractCLIOptions) => {
121
+ debug('File pattern:', filePatterns)
122
+ debug('Options:', cmdObj)
123
+ const files = globSync(filePatterns, {
124
+ ignore: cmdObj.ignore,
125
+ })
126
+
127
+ debug('Files to extract:', files)
128
+
129
+ await extract(files, {
130
+ outFile: cmdObj.outFile,
131
+ idInterpolationPattern:
132
+ cmdObj.idInterpolationPattern || '[sha1:contenthash:base64:6]',
133
+ extractSourceLocation: cmdObj.extractSourceLocation,
134
+ removeDefaultMessage: cmdObj.removeDefaultMessage,
135
+ additionalComponentNames: cmdObj.additionalComponentNames,
136
+ additionalFunctionNames: cmdObj.additionalFunctionNames,
137
+ throws: cmdObj.throws,
138
+ pragma: cmdObj.pragma,
139
+ format: cmdObj.format,
140
+ // It is possible that the glob pattern does NOT match anything.
141
+ // But so long as the glob pattern is provided, don't read from stdin.
142
+ readFromStdin: filePatterns.length === 0,
143
+ preserveWhitespace: cmdObj.preserveWhitespace,
144
+ flatten: cmdObj.flatten,
145
+ })
146
+ process.exit(0)
147
+ })
148
+
149
+ program
150
+ .command('compile [translation_files...]')
151
+ .description(
152
+ `Compile extracted translation file into react-intl consumable JSON
153
+ We also verify that the messages are valid ICU and not malformed.
154
+ <translation_files> can be a glob like "foo/**/en.json"`
155
+ )
156
+ .option(
157
+ '--format <path>',
158
+ `Path to a formatter file that converts \`<translation_file>\` to \`Record<string, string>\`
159
+ so we can compile. The file must export a function named \`compile\` with the signature:
160
+ \`\`\`
161
+ type CompileFn = <T = Record<string, MessageDescriptor>>(
162
+ msgs: T
163
+ ) => Record<string, string>;
164
+ \`\`\`
165
+ This is especially useful to convert from a TMS-specific format back to react-intl format
166
+ `
167
+ )
168
+ .option(
169
+ '--out-file <path>',
170
+ `Compiled translation output file.
171
+ If this is not provided, result will be printed to stdout`
172
+ )
173
+ .option(
174
+ '--ast',
175
+ `Whether to compile to AST. See https://formatjs.io/docs/guides/advanced-usage#pre-parsing-messages
176
+ for more information`
177
+ )
178
+ .option(
179
+ '--skip-errors',
180
+ `Whether to continue compiling messages after encountering an error. Any keys with errors will not be included in the output file.`
181
+ )
182
+ .option(
183
+ '--pseudo-locale <pseudoLocale>',
184
+ `Whether to generate pseudo-locale files. See https://formatjs.io/docs/tooling/cli#--pseudo-locale-pseudolocale for possible values.
185
+ "--ast" is required for this to work.`
186
+ )
187
+ .action(async (filePatterns: string[], opts: CompileCLIOpts) => {
188
+ debug('File pattern:', filePatterns)
189
+ debug('Options:', opts)
190
+ const files = globSync(filePatterns)
191
+ if (!files.length) {
192
+ throw new Error(`No input file found with pattern ${filePatterns}`)
193
+ }
194
+ debug('Files to compile:', files)
195
+ await compile(files, opts)
196
+ })
197
+
198
+ program
199
+ .command('compile-folder <folder> <outFolder>')
200
+ .description(
201
+ `Batch compile all extracted translation JSON files in <folder> to <outFolder> containing
202
+ react-intl consumable JSON. We also verify that the messages are
203
+ valid ICU and not malformed.`
204
+ )
205
+ .option(
206
+ '--format <path>',
207
+ `Path to a formatter file that converts JSON files in \`<folder>\` to \`Record<string, string>\`
208
+ so we can compile. The file must export a function named \`compile\` with the signature:
209
+ \`\`\`
210
+ type CompileFn = <T = Record<string, MessageDescriptor>>(
211
+ msgs: T
212
+ ) => Record<string, string>;
213
+ \`\`\`
214
+ This is especially useful to convert from a TMS-specific format back to react-intl format
215
+ `
216
+ )
217
+ .option(
218
+ '--ast',
219
+ `Whether to compile to AST. See https://formatjs.io/docs/guides/advanced-usage#pre-parsing-messages
220
+ for more information`
221
+ )
222
+ .action(async (folder: string, outFolder: string, opts?: Opts) => {
223
+ debug('Folder:', folder)
224
+ debug('Options:', opts)
225
+ // fast-glob expect `/` in Windows as well
226
+ const files = globSync(`${folder}/*.json`)
227
+ if (!files.length) {
228
+ throw new Error(`No JSON file found in ${folder}`)
229
+ }
230
+ debug('Files to compile:', files)
231
+ await compileFolder(files, outFolder, opts)
232
+ })
233
+
234
+ if (argv.length < 3) {
235
+ program.help()
236
+ } else {
237
+ program.parse(argv)
238
+ }
239
+ }
240
+ export default main
package/src/compile.ts ADDED
@@ -0,0 +1,141 @@
1
+ import {parse, MessageFormatElement} from '@formatjs/icu-messageformat-parser'
2
+ import {outputFile, readJSON} from 'fs-extra'
3
+ import stringify from 'json-stable-stringify'
4
+ import {debug, warn, writeStdout} from './console_utils'
5
+ import {resolveBuiltinFormatter, Formatter} from './formatters'
6
+ import {
7
+ generateXXAC,
8
+ generateXXLS,
9
+ generateXXHA,
10
+ generateENXA,
11
+ generateENXB,
12
+ } from './pseudo_locale'
13
+
14
+ export type CompileFn = (msgs: any) => Record<string, string>
15
+
16
+ export type PseudoLocale = 'xx-LS' | 'xx-AC' | 'xx-HA' | 'en-XA' | 'en-XB'
17
+
18
+ export interface CompileCLIOpts extends Opts {
19
+ /**
20
+ * The target file that contains compiled messages.
21
+ */
22
+ outFile?: string
23
+ }
24
+ export interface Opts {
25
+ /**
26
+ * Whether to compile message into AST instead of just string
27
+ */
28
+ ast?: boolean
29
+ /**
30
+ * Whether to continue compiling messages after encountering an error.
31
+ * Any keys with errors will not be included in the output file.
32
+ */
33
+ skipErrors?: boolean
34
+ /**
35
+ * Path to a formatter file that converts <translation_files> to
36
+ * `Record<string, string>` so we can compile.
37
+ */
38
+ format?: string | Formatter
39
+ /**
40
+ * Whether to compile to pseudo locale
41
+ */
42
+ pseudoLocale?: PseudoLocale
43
+ }
44
+
45
+ /**
46
+ * Aggregate `inputFiles` into a single JSON blob and compile.
47
+ * Also checks for conflicting IDs.
48
+ * Then returns the serialized result as a `string` since key order
49
+ * makes a difference in some vendor.
50
+ * @param inputFiles Input files
51
+ * @param opts Options
52
+ * @returns serialized result in string format
53
+ */
54
+ export async function compile(inputFiles: string[], opts: Opts = {}) {
55
+ debug('Compiling files:', inputFiles)
56
+ const {ast, format, pseudoLocale, skipErrors} = opts
57
+ const formatter = await resolveBuiltinFormatter(format)
58
+
59
+ const messages: Record<string, string> = {}
60
+ const messageAsts: Record<string, MessageFormatElement[]> = {}
61
+ const idsWithFileName: Record<string, string> = {}
62
+ const compiledFiles = await Promise.all(
63
+ inputFiles.map(f => readJSON(f).then(formatter.compile))
64
+ )
65
+ debug('Compiled files:', compiledFiles)
66
+ for (let i = 0; i < inputFiles.length; i++) {
67
+ const inputFile = inputFiles[i]
68
+ debug('Processing file:', inputFile)
69
+ const compiled = compiledFiles[i]
70
+ for (const id in compiled) {
71
+ if (messages[id] && messages[id] !== compiled[id]) {
72
+ throw new Error(`Conflicting ID "${id}" with different translation found in these 2 files:
73
+ ID: ${id}
74
+ Message from ${idsWithFileName[id]}: ${messages[id]}
75
+ Message from ${compiled[id]}: ${inputFile}
76
+ `)
77
+ }
78
+ try {
79
+ const msgAst = parse(compiled[id])
80
+ messages[id] = compiled[id]
81
+ switch (pseudoLocale) {
82
+ case 'xx-LS':
83
+ messageAsts[id] = generateXXLS(msgAst)
84
+ break
85
+ case 'xx-AC':
86
+ messageAsts[id] = generateXXAC(msgAst)
87
+ break
88
+ case 'xx-HA':
89
+ messageAsts[id] = generateXXHA(msgAst)
90
+ break
91
+ case 'en-XA':
92
+ messageAsts[id] = generateENXA(msgAst)
93
+ break
94
+ case 'en-XB':
95
+ messageAsts[id] = generateENXB(msgAst)
96
+ break
97
+ default:
98
+ messageAsts[id] = msgAst
99
+ break
100
+ }
101
+ idsWithFileName[id] = inputFile
102
+ } catch (e) {
103
+ warn(
104
+ 'Error validating message "%s" with ID "%s" in file "%s"',
105
+ compiled[id],
106
+ id,
107
+ inputFile
108
+ )
109
+ if (!skipErrors) {
110
+ throw e
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ return stringify(ast ? messageAsts : messages, {
117
+ space: 2,
118
+ cmp: formatter.compareMessages || undefined,
119
+ })
120
+ }
121
+
122
+ /**
123
+ * Aggregate `inputFiles` into a single JSON blob and compile.
124
+ * Also checks for conflicting IDs and write output to `outFile`.
125
+ * @param inputFiles Input files
126
+ * @param compileOpts options
127
+ * @returns A `Promise` that resolves if file was written successfully
128
+ */
129
+ export default async function compileAndWrite(
130
+ inputFiles: string[],
131
+ compileOpts: CompileCLIOpts = {}
132
+ ) {
133
+ const {outFile, ...opts} = compileOpts
134
+ const serializedResult = await compile(inputFiles, opts)
135
+ if (outFile) {
136
+ debug('Writing output file:', outFile)
137
+ return outputFile(outFile, serializedResult)
138
+ }
139
+ await writeStdout(serializedResult)
140
+ await writeStdout('\n')
141
+ }
@@ -0,0 +1,15 @@
1
+ import {Opts, compile} from './compile'
2
+ import {join, basename} from 'path'
3
+ import {outputFile} from 'fs-extra'
4
+ export default async function compileFolder(
5
+ files: string[],
6
+ outFolder: string,
7
+ opts: Opts = {}
8
+ ) {
9
+ const results = await Promise.all(files.map(f => compile([f], opts)))
10
+ const outFiles = files.map(f => join(outFolder, basename(f)))
11
+
12
+ return Promise.all(
13
+ outFiles.map((outFile, i) => outputFile(outFile, results[i]))
14
+ )
15
+ }
@@ -0,0 +1,78 @@
1
+ import {supportsColor, green, red, yellow} from 'chalk'
2
+ import readline from 'readline'
3
+ import {format, promisify} from 'util'
4
+
5
+ const CLEAR_WHOLE_LINE = 0
6
+
7
+ export const writeStderr = promisify(process.stderr.write).bind(process.stderr)
8
+ export const writeStdout = promisify(process.stdout.write).bind(process.stdout)
9
+
10
+ const nativeClearLine = promisify(readline.clearLine).bind(readline)
11
+ const nativeCursorTo = promisify(readline.cursorTo).bind(readline)
12
+
13
+ // From:
14
+ // https://github.com/yarnpkg/yarn/blob/53d8004229f543f342833310d5af63a4b6e59c8a/src/reporters/console/util.js
15
+ export async function clearLine(terminal: typeof process['stderr']) {
16
+ if (!supportsColor) {
17
+ if (terminal.isTTY) {
18
+ // terminal
19
+ if (terminal.columns > 0) {
20
+ await writeStderr(`\r${' '.repeat(terminal.columns - 1)}`)
21
+ }
22
+ await writeStderr(`\r`)
23
+ }
24
+ // ignore piping to file
25
+ } else {
26
+ await nativeClearLine(terminal, CLEAR_WHOLE_LINE)
27
+ await nativeCursorTo(terminal, 0, undefined)
28
+ }
29
+ }
30
+
31
+ const LEVEL_COLORS = {
32
+ debug: green,
33
+ warn: yellow,
34
+ error: red,
35
+ }
36
+
37
+ function label(level: keyof typeof LEVEL_COLORS, message: string) {
38
+ return `[@formatjs/cli] [${LEVEL_COLORS[level](
39
+ level.toUpperCase()
40
+ )}] ${message}`
41
+ }
42
+
43
+ export async function debug(message: string, ...args: any[]) {
44
+ if (process.env.LOG_LEVEL !== 'debug') {
45
+ return
46
+ }
47
+ await clearLine(process.stderr)
48
+ await writeStderr(format(label('debug', message), ...args))
49
+ await writeStderr('\n')
50
+ }
51
+
52
+ export async function warn(message: string, ...args: any[]) {
53
+ await clearLine(process.stderr)
54
+ await writeStderr(format(label('warn', message), ...args))
55
+ await writeStderr('\n')
56
+ }
57
+
58
+ export async function error(message: string, ...args: any[]) {
59
+ await clearLine(process.stderr)
60
+ await writeStderr(format(label('error', message), ...args))
61
+ await writeStderr('\n')
62
+ }
63
+
64
+ export function getStdinAsString(): Promise<string> {
65
+ let result = ''
66
+ return new Promise(resolve => {
67
+ process.stdin.setEncoding('utf-8')
68
+ process.stdin.on('readable', () => {
69
+ let chunk
70
+ while ((chunk = process.stdin.read())) {
71
+ result += chunk
72
+ }
73
+ })
74
+ process.stdin.on('end', () => {
75
+ resolve(result)
76
+ })
77
+ })
78
+ }