From 07f71bccbb9a2596079bf9b33c1bb28ac371e389 Mon Sep 17 00:00:00 2001 From: Alexandre Philibeaux Date: Thu, 20 Oct 2022 14:13:53 +0000 Subject: [PATCH 1/5] feat(validate-icu): add a cli to validates icu locales files --- packages/validate-icu-locales/.eslintrc.cjs | 10 ++++ packages/validate-icu-locales/README.md | 20 +++++++ packages/validate-icu-locales/locales/en.json | 0 packages/validate-icu-locales/locales/en.ts | 9 ++++ packages/validate-icu-locales/package.json | 28 ++++++++++ packages/validate-icu-locales/src/index.ts | 52 +++++++++++++++++++ pnpm-lock.yaml | 21 ++++++-- 7 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 packages/validate-icu-locales/.eslintrc.cjs create mode 100644 packages/validate-icu-locales/README.md create mode 100644 packages/validate-icu-locales/locales/en.json create mode 100644 packages/validate-icu-locales/locales/en.ts create mode 100644 packages/validate-icu-locales/package.json create mode 100644 packages/validate-icu-locales/src/index.ts diff --git a/packages/validate-icu-locales/.eslintrc.cjs b/packages/validate-icu-locales/.eslintrc.cjs new file mode 100644 index 000000000..a2dbbe3d7 --- /dev/null +++ b/packages/validate-icu-locales/.eslintrc.cjs @@ -0,0 +1,10 @@ +const { join } = require('path') + +module.exports = { + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { packageDir: [__dirname, join(__dirname, '../../')] }, + ], + }, +} diff --git a/packages/validate-icu-locales/README.md b/packages/validate-icu-locales/README.md new file mode 100644 index 000000000..a99472b60 --- /dev/null +++ b/packages/validate-icu-locales/README.md @@ -0,0 +1,20 @@ +# `@scaleway/validate-icu-locales` + +## A tiny hooks to handle segment events + +use [@segment/analytics-next](https://github.com/segmentio/analytics-next) + +## Install + +```bash +$ pnpm add @scaleway/validate-icu-locales +``` + +## Usage + +### Event directory + +Create an events directory with all you specific events. + +Each event will have a format like this: + diff --git a/packages/validate-icu-locales/locales/en.json b/packages/validate-icu-locales/locales/en.json new file mode 100644 index 000000000..e69de29bb diff --git a/packages/validate-icu-locales/locales/en.ts b/packages/validate-icu-locales/locales/en.ts new file mode 100644 index 000000000..a08f33e18 --- /dev/null +++ b/packages/validate-icu-locales/locales/en.ts @@ -0,0 +1,9 @@ +export default { + // error on this one missing bracket + + 'units.minutes.label': + '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', + 'units.hours.label': '{count, plural, =0 {Hour} =1 {Hour} other {Hours}}', + 'units.days.label': '{count, plural, =0 {Day} =1 {Day} other {Days}}', + 'units.months.label': '{count, plural, =0 {Month} =1 {Month} other {Months}}', +} diff --git a/packages/validate-icu-locales/package.json b/packages/validate-icu-locales/package.json new file mode 100644 index 000000000..0526ad6b9 --- /dev/null +++ b/packages/validate-icu-locales/package.json @@ -0,0 +1,28 @@ +{ + "name": "@scaleway/validate-icu-locales", + "version": "0.0.0", + "description": "A small cli to handle icu errors on locales files", + "keywords": [ + "icu", + "i18n", + "cli" + ], + "type": "module", + "bin": "dist/index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/scaleway/scaleway-lib", + "directory": "packages/validate-icu-locales" + }, + "license": "MIT", + "dependencies": { + "@formatjs/icu-messageformat-parser": "^2.1.10", + "glob": "^8.0.3" + }, + "devDependencies": { + "@types/glob": "^8.0.0" + } +} diff --git a/packages/validate-icu-locales/src/index.ts b/packages/validate-icu-locales/src/index.ts new file mode 100644 index 000000000..fba1530c7 --- /dev/null +++ b/packages/validate-icu-locales/src/index.ts @@ -0,0 +1,52 @@ +import { parse } from '@formatjs/icu-messageformat-parser' +import glob from 'glob' + +const args = process.argv.slice(2) +const paths = args[0] + +const { error, table, info } = console + +info(paths) + +const findICUError = ( + locales: { [key: string]: string }, + filePath?: string, +) => { + Object.keys(locales).forEach(key => { + const value = locales[key] + + try { + parse(value) + } catch (err) { + error({ err, value, key, filePath }) + } + }) +} + +const readFiles = (files: string[]) => + files.forEach(file => { + import(file) + .then(({ default: locales }: { default: Record }) => { + findICUError(locales, file) + }) + .catch(err => { + error(err) + }) + }) + +const main = () => { + glob(paths, (err, files) => { + if (err) { + error({ + err, + }) + } + + if (files) { + table(files) + readFiles(files) + } + }) +} + +main() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3df9d3cc3..a48734cbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,6 +203,17 @@ importers: dependencies: '@segment/analytics-next': 1.45.0 + packages/validate-icu-locales: + specifiers: + '@formatjs/icu-messageformat-parser': ^2.1.10 + '@types/glob': ^8.0.0 + glob: ^8.0.3 + dependencies: + '@formatjs/icu-messageformat-parser': 2.1.10 + glob: 8.0.3 + devDependencies: + '@types/glob': 8.0.0 + packages: /@adobe/css-tools/4.0.1: @@ -4434,6 +4445,13 @@ packages: resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} dev: true + /@types/glob/8.0.0: + resolution: {integrity: sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA==} + dependencies: + '@types/minimatch': 3.0.5 + '@types/node': 18.11.3 + dev: true + /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: @@ -5140,7 +5158,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: true /braces/3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -6805,7 +6822,6 @@ packages: inherits: 2.0.4 minimatch: 5.1.0 once: 1.4.0 - dev: true /global-dirs/0.1.1: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} @@ -8441,7 +8457,6 @@ packages: engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 - dev: true /minimist-options/4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} From e45db3168601bb52c8dab030658e45bc7317905f Mon Sep 17 00:00:00 2001 From: Alexandre Philibeaux Date: Thu, 20 Oct 2022 22:31:56 +0000 Subject: [PATCH 2/5] feat(icu): use node instead of node-ts --- README.md | 6 + packages/validate-icu-locales/README.md | 44 ++- packages/validate-icu-locales/locales/en.json | 0 packages/validate-icu-locales/package.json | 6 +- .../src/__tests__/locales/en-1.js | 9 + .../en.ts => src/__tests__/locales/en.js} | 0 .../src/__tests__/locales/en.json | 6 + .../src/__tests__/locales/en.ts | 9 + packages/validate-icu-locales/src/index.ts | 82 ++++-- pnpm-lock.yaml | 277 +++++++++++++++++- 10 files changed, 388 insertions(+), 51 deletions(-) delete mode 100644 packages/validate-icu-locales/locales/en.json create mode 100644 packages/validate-icu-locales/src/__tests__/locales/en-1.js rename packages/validate-icu-locales/{locales/en.ts => src/__tests__/locales/en.js} (100%) create mode 100644 packages/validate-icu-locales/src/__tests__/locales/en.json create mode 100644 packages/validate-icu-locales/src/__tests__/locales/en.ts diff --git a/README.md b/README.md index 26b35a658..14894d7ad 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,12 @@ scaleway-lib is a set of NPM packages used at Scaleway. ![npm bundle size](https://img.shields.io/bundlephobia/min/@scaleway/outdated-browser) ![npm](https://img.shields.io/npm/v/@scaleway/outdated-browser) +- [`@scaleway/validate-icu-locales`](./packages/validate-icu-locales/README.md): A small cli to check ICU locales error + + ![npm](https://img.shields.io/npm/dm/@scaleway/validate-icu-locales) + ![npm bundle size](https://img.shields.io/bundlephobia/min/@scaleway/validate-icu-locales) + ![npm](https://img.shields.io/npm/v/@scaleway/validate-icu-locales) + ## Development ### Locally diff --git a/packages/validate-icu-locales/README.md b/packages/validate-icu-locales/README.md index a99472b60..7c1804bfc 100644 --- a/packages/validate-icu-locales/README.md +++ b/packages/validate-icu-locales/README.md @@ -1,20 +1,46 @@ # `@scaleway/validate-icu-locales` -## A tiny hooks to handle segment events - -use [@segment/analytics-next](https://github.com/segmentio/analytics-next) - +## A tiny cli to handle ICU errors on locales files. ## Install ```bash -$ pnpm add @scaleway/validate-icu-locales +$ pnpm add -D @scaleway/validate-icu-locales ``` - ## Usage -### Event directory +We can parse JSON, TS and JS if theses files use default export. -Create an events directory with all you specific events. -Each event will have a format like this: +``` +node @scaleway/validate-icu-locales "../**/en.json" +``` +If there is an error on a local, the CLI will throw an Error and print all errors. + + +## Error +```` +export default from: ../src/__tests__/locales/en-1.js is not an object +{ + errors: [ + { + err: [SyntaxError], + value: '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', + key: 'units.minutes.label', + filePath: '../src/__tests__/locales/en.js' + }, + { + err: [SyntaxError], + value: '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', + key: 'units.minutes.label', + filePath: '../src/__tests__/locales/en.json' + }, + { + err: [SyntaxError], + value: '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', + key: 'units.minutes.label', + filePath: '../src/__tests__/locales/en.ts' + } + ] +} +``` \ No newline at end of file diff --git a/packages/validate-icu-locales/locales/en.json b/packages/validate-icu-locales/locales/en.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/validate-icu-locales/package.json b/packages/validate-icu-locales/package.json index 0526ad6b9..84d6bc1c7 100644 --- a/packages/validate-icu-locales/package.json +++ b/packages/validate-icu-locales/package.json @@ -20,9 +20,7 @@ "license": "MIT", "dependencies": { "@formatjs/icu-messageformat-parser": "^2.1.10", - "glob": "^8.0.3" - }, - "devDependencies": { - "@types/glob": "^8.0.0" + "globby": "^13.1.2", + "module-from-string": "^3.3.0" } } diff --git a/packages/validate-icu-locales/src/__tests__/locales/en-1.js b/packages/validate-icu-locales/src/__tests__/locales/en-1.js new file mode 100644 index 000000000..8de515d93 --- /dev/null +++ b/packages/validate-icu-locales/src/__tests__/locales/en-1.js @@ -0,0 +1,9 @@ +export const locales = { + // error on this one missing bracket + + 'units.minutes.label': + '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', + 'units.hours.label': '{count, plural, =0 {Hour} =1 {Hour} other {Hours}}', + 'units.days.label': '{count, plural, =0 {Day} =1 {Day} other {Days}}', + 'units.months.label': '{count, plural, =0 {Month} =1 {Month} other {Months}}', +} diff --git a/packages/validate-icu-locales/locales/en.ts b/packages/validate-icu-locales/src/__tests__/locales/en.js similarity index 100% rename from packages/validate-icu-locales/locales/en.ts rename to packages/validate-icu-locales/src/__tests__/locales/en.js diff --git a/packages/validate-icu-locales/src/__tests__/locales/en.json b/packages/validate-icu-locales/src/__tests__/locales/en.json new file mode 100644 index 000000000..f80650343 --- /dev/null +++ b/packages/validate-icu-locales/src/__tests__/locales/en.json @@ -0,0 +1,6 @@ +{ + "units.minutes.label": "{count, plural, =0 {Minute} =1 {Minute} other {Minutes", + "units.hours.label": "{count, plural, =0 {Hour} =1 {Hour} other {Hours}}", + "units.days.label": "{count, plural, =0 {Day} =1 {Day} other {Days}}", + "units.months.label": "{count, plural, =0 {Month} =1 {Month} other {Months}}" +} diff --git a/packages/validate-icu-locales/src/__tests__/locales/en.ts b/packages/validate-icu-locales/src/__tests__/locales/en.ts new file mode 100644 index 000000000..80f0cc89d --- /dev/null +++ b/packages/validate-icu-locales/src/__tests__/locales/en.ts @@ -0,0 +1,9 @@ +export default { + // error on this one missing bracket + + 'units.minutes.label': + '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', + 'units.hours.label': '{count, plural, =0 {Hour} =1 {Hour} other {Hours}}', + 'units.days.label': '{count, plural, =0 {Day} =1 {Day} other {Days}}', + 'units.months.label': '{count, plural, =0 {Month} =1 {Month} other {Months}}', +} as const diff --git a/packages/validate-icu-locales/src/index.ts b/packages/validate-icu-locales/src/index.ts index fba1530c7..fc975d4a7 100644 --- a/packages/validate-icu-locales/src/index.ts +++ b/packages/validate-icu-locales/src/index.ts @@ -1,52 +1,82 @@ import { parse } from '@formatjs/icu-messageformat-parser' -import glob from 'glob' +import { info } from 'console' +import fs from 'fs' +import { globby } from 'globby' +import { importFromStringSync } from 'module-from-string' const args = process.argv.slice(2) -const paths = args[0] +const pattern = args[0] -const { error, table, info } = console +const { error, table } = console -info(paths) +type Err = { + err: unknown + value: string + key: string + filePath: string +} +const errors: Err[] = [] -const findICUError = ( - locales: { [key: string]: string }, - filePath?: string, -) => { +const findICUError = (locales: { [key: string]: string }, filePath: string) => { Object.keys(locales).forEach(key => { const value = locales[key] try { parse(value) } catch (err) { - error({ err, value, key, filePath }) + errors.push({ + err, + value, + key, + filePath, + }) } }) } const readFiles = (files: string[]) => files.forEach(file => { - import(file) - .then(({ default: locales }: { default: Record }) => { + const extension = file.split('.').pop() + + if (extension === 'json') { + const json = fs.readFileSync(file).toString() + try { + const locales = JSON.parse(json) as Record + findICUError(locales, file) - }) - .catch(err => { + } catch (err) { error(err) - }) - }) - -const main = () => { - glob(paths, (err, files) => { - if (err) { - error({ - err, - }) + } } - if (files) { - table(files) - readFiles(files) + if (extension === 'ts' || extension === 'js') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const d: { default: Record } = importFromStringSync( + fs.readFileSync(file).toString(), + { transformOptions: { loader: 'ts' } }, + ) + + if (typeof d.default === 'object') { + findICUError(d.default, file) + } else { + error('export default from: ', file, ' is not an object') + } } }) + +const files = await globby(pattern) + +if (files.length > 0) { + table(files) + readFiles(files) +} + +if (files.length === 0) { + info('There is no files matching this pattern', pattern) } -main() +if (errors.length > 1) { + error({ errors }) + info(new Error('There is somes ICU error')) + process.exit(1) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a48734cbd..93d14a394 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -206,13 +206,12 @@ importers: packages/validate-icu-locales: specifiers: '@formatjs/icu-messageformat-parser': ^2.1.10 - '@types/glob': ^8.0.0 - glob: ^8.0.3 + globby: ^13.1.2 + module-from-string: ^3.3.0 dependencies: '@formatjs/icu-messageformat-parser': 2.1.10 - glob: 8.0.3 - devDependencies: - '@types/glob': 8.0.0 + globby: 13.1.2 + module-from-string: 3.3.0 packages: @@ -1806,6 +1805,24 @@ packages: resolution: {integrity: sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==} dev: false + /@esbuild/android-arm/0.15.12: + resolution: {integrity: sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-loong64/0.15.12: + resolution: {integrity: sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@eslint/eslintrc/1.3.3: resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4445,13 +4462,6 @@ packages: resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} dev: true - /@types/glob/8.0.0: - resolution: {integrity: sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA==} - dependencies: - '@types/minimatch': 3.0.5 - '@types/node': 18.11.3 - dev: true - /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: @@ -5158,6 +5168,7 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 + dev: true /braces/3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -6060,6 +6071,216 @@ packages: is-symbol: 1.0.4 dev: false + /esbuild-android-64/0.15.12: + resolution: {integrity: sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /esbuild-android-arm64/0.15.12: + resolution: {integrity: sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /esbuild-darwin-64/0.15.12: + resolution: {integrity: sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /esbuild-darwin-arm64/0.15.12: + resolution: {integrity: sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /esbuild-freebsd-64/0.15.12: + resolution: {integrity: sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /esbuild-freebsd-arm64/0.15.12: + resolution: {integrity: sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /esbuild-linux-32/0.15.12: + resolution: {integrity: sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /esbuild-linux-64/0.15.12: + resolution: {integrity: sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /esbuild-linux-arm/0.15.12: + resolution: {integrity: sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /esbuild-linux-arm64/0.15.12: + resolution: {integrity: sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /esbuild-linux-mips64le/0.15.12: + resolution: {integrity: sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /esbuild-linux-ppc64le/0.15.12: + resolution: {integrity: sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /esbuild-linux-riscv64/0.15.12: + resolution: {integrity: sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /esbuild-linux-s390x/0.15.12: + resolution: {integrity: sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /esbuild-netbsd-64/0.15.12: + resolution: {integrity: sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: false + optional: true + + /esbuild-openbsd-64/0.15.12: + resolution: {integrity: sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: false + optional: true + + /esbuild-sunos-64/0.15.12: + resolution: {integrity: sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: false + optional: true + + /esbuild-windows-32/0.15.12: + resolution: {integrity: sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /esbuild-windows-64/0.15.12: + resolution: {integrity: sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /esbuild-windows-arm64/0.15.12: + resolution: {integrity: sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /esbuild/0.15.12: + resolution: {integrity: sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.15.12 + '@esbuild/linux-loong64': 0.15.12 + esbuild-android-64: 0.15.12 + esbuild-android-arm64: 0.15.12 + esbuild-darwin-64: 0.15.12 + esbuild-darwin-arm64: 0.15.12 + esbuild-freebsd-64: 0.15.12 + esbuild-freebsd-arm64: 0.15.12 + esbuild-linux-32: 0.15.12 + esbuild-linux-64: 0.15.12 + esbuild-linux-arm: 0.15.12 + esbuild-linux-arm64: 0.15.12 + esbuild-linux-mips64le: 0.15.12 + esbuild-linux-ppc64le: 0.15.12 + esbuild-linux-riscv64: 0.15.12 + esbuild-linux-s390x: 0.15.12 + esbuild-netbsd-64: 0.15.12 + esbuild-openbsd-64: 0.15.12 + esbuild-sunos-64: 0.15.12 + esbuild-windows-32: 0.15.12 + esbuild-windows-64: 0.15.12 + esbuild-windows-arm64: 0.15.12 + dev: false + /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -6822,6 +7043,7 @@ packages: inherits: 2.0.4 minimatch: 5.1.0 once: 1.4.0 + dev: true /global-dirs/0.1.1: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} @@ -6851,6 +7073,17 @@ packages: merge2: 1.4.1 slash: 3.0.0 + /globby/13.1.2: + resolution: {integrity: sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.0 + merge2: 1.4.1 + slash: 4.0.0 + dev: false + /graceful-fs/4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: true @@ -8457,6 +8690,7 @@ packages: engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 + dev: true /minimist-options/4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -8559,6 +8793,14 @@ packages: engines: {node: '>=0.10.0'} dev: true + /module-from-string/3.3.0: + resolution: {integrity: sha512-VsjwtQtXZloDF7ZpBXON53U4Zz02K1/njJmfZcK+QDlYKgdL0ETq8/FeuU0G9EHxdG5XiTaITcGaldDAqJpGXA==} + engines: {node: '>=12.20.0'} + dependencies: + esbuild: 0.15.12 + nanoid: 3.3.4 + dev: false + /ms/2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: false @@ -8584,6 +8826,12 @@ packages: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} dev: true + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + /natural-compare/1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -9867,6 +10115,11 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + /slash/4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: false + /slice-ansi/3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} From c4f397fd82822f99087f08839bcfc2aa5171f7bc Mon Sep 17 00:00:00 2001 From: Alexandre Philibeaux Date: Mon, 24 Oct 2022 17:15:51 +0000 Subject: [PATCH 3/5] fix(shebang): add rollup plugin preserve shebang --- package.json | 5 +++-- packages/validate-icu-locales/README.md | 2 +- .../src/__tests__/locales/en-ts.ts | 9 +++++++++ packages/validate-icu-locales/src/index.ts | 2 ++ pnpm-lock.yaml | 17 +++++++++++++++++ rollup.config.mjs | 14 +++++++++----- 6 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 packages/validate-icu-locales/src/__tests__/locales/en-ts.ts diff --git a/package.json b/package.json index c1e76dde6..8624e31ec 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "packages/*" ], "type": "module", + "packageManager": "pnpm@7.14.0", "devDependencies": { "@babel/core": "7.19.6", "@babel/eslint-parser": "7.19.1", @@ -42,6 +43,7 @@ "read-pkg": "7.1.0", "rollup": "3.2.3", "rollup-plugin-dts": "5.0.0", + "rollup-plugin-preserve-shebangs": "^0.2.0", "rollup-plugin-visualizer": "5.8.3", "tsd-lite": "0.6.0", "typescript": "4.8.4", @@ -117,6 +119,5 @@ } ] ] - }, - "packageManager": "pnpm@7.14.0" + } } diff --git a/packages/validate-icu-locales/README.md b/packages/validate-icu-locales/README.md index 7c1804bfc..ece640952 100644 --- a/packages/validate-icu-locales/README.md +++ b/packages/validate-icu-locales/README.md @@ -12,7 +12,7 @@ We can parse JSON, TS and JS if theses files use default export. ``` -node @scaleway/validate-icu-locales "../**/en.json" +@scaleway/validate-icu-locales "../**/en.json" ``` If there is an error on a local, the CLI will throw an Error and print all errors. diff --git a/packages/validate-icu-locales/src/__tests__/locales/en-ts.ts b/packages/validate-icu-locales/src/__tests__/locales/en-ts.ts new file mode 100644 index 000000000..80f0cc89d --- /dev/null +++ b/packages/validate-icu-locales/src/__tests__/locales/en-ts.ts @@ -0,0 +1,9 @@ +export default { + // error on this one missing bracket + + 'units.minutes.label': + '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', + 'units.hours.label': '{count, plural, =0 {Hour} =1 {Hour} other {Hours}}', + 'units.days.label': '{count, plural, =0 {Day} =1 {Day} other {Days}}', + 'units.months.label': '{count, plural, =0 {Month} =1 {Month} other {Months}}', +} as const diff --git a/packages/validate-icu-locales/src/index.ts b/packages/validate-icu-locales/src/index.ts index fc975d4a7..a1a84e2ac 100644 --- a/packages/validate-icu-locales/src/index.ts +++ b/packages/validate-icu-locales/src/index.ts @@ -1,3 +1,5 @@ +#!/usr/bin/env node + import { parse } from '@formatjs/icu-messageformat-parser' import { info } from 'console' import fs from 'fs' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93d14a394..9f4460eed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,7 @@ importers: read-pkg: 7.1.0 rollup: 3.2.3 rollup-plugin-dts: 5.0.0 + rollup-plugin-preserve-shebangs: ^0.2.0 rollup-plugin-visualizer: 5.8.3 tsd-lite: 0.6.0 typescript: 4.8.4 @@ -81,6 +82,7 @@ importers: read-pkg: 7.1.0 rollup: 3.2.3 rollup-plugin-dts: 5.0.0_655ssj4e7sdqlljrreeiqtltve + rollup-plugin-preserve-shebangs: 0.2.0_rollup@3.2.3 rollup-plugin-visualizer: 5.8.3_rollup@3.2.3 tsd-lite: 0.6.0_@tsd+typescript@4.8.4 typescript: 4.8.4 @@ -8548,6 +8550,12 @@ packages: resolution: {integrity: sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==} hasBin: true + /magic-string/0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + dependencies: + sourcemap-codec: 1.4.8 + dev: true + /magic-string/0.26.7: resolution: {integrity: sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==} engines: {node: '>=12'} @@ -9970,6 +9978,15 @@ packages: '@babel/code-frame': 7.18.6 dev: true + /rollup-plugin-preserve-shebangs/0.2.0_rollup@3.2.3: + resolution: {integrity: sha512-OEyTIfZwUJ7yUAVAbegac/bNvp1WJzgZcQNCFprWX42wwwOqlJwrev9lUmzZdYVgCWct+03xUPvZg4RfgkM9oQ==} + peerDependencies: + rollup: '*' + dependencies: + magic-string: 0.25.9 + rollup: 3.2.3 + dev: true + /rollup-plugin-visualizer/5.8.3_rollup@3.2.3: resolution: {integrity: sha512-QGJk4Bqe4AOat5AjipOh8esZH1nck5X2KFpf4VytUdSUuuuSwvIQZjMGgjcxe/zXexltqaXp5Vx1V3LmnQH15Q==} engines: {node: '>=14'} diff --git a/rollup.config.mjs b/rollup.config.mjs index eed591703..7014cf1e9 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -3,6 +3,7 @@ import { nodeResolve } from '@rollup/plugin-node-resolve' import builtins from 'builtin-modules' import { readPackage } from 'read-pkg' import dts from 'rollup-plugin-dts' +import { preserveShebangs } from 'rollup-plugin-preserve-shebangs' import { visualizer } from 'rollup-plugin-visualizer' const PROFILE = !!process.env.PROFILE @@ -38,9 +39,7 @@ const getConfig = (pkg, isBrowser = false) => { comments: false, exclude: 'node_modules/**', extensions: ['.js', '.jsx', '.ts', '.tsx', '.es', '.mjs'], - plugins: [ - '@babel/plugin-transform-runtime', - ], + plugins: ['@babel/plugin-transform-runtime'], presets: [ ['@babel/env', { modules: false, targets }], '@babel/preset-typescript', @@ -54,9 +53,14 @@ const getConfig = (pkg, isBrowser = false) => { }), nodeResolve({ browser: isBrowser, - extensions: [ '.mjs', '.js', '.json', '.ts', '.tsx' ], + extensions: ['.mjs', '.js', '.json', '.ts', '.tsx'], preferBuiltins: true, }), + preserveShebangs(), + // shebang({ + // include: ['**/index.js'], + // shebang: '#!/usr/bin/env node' + // }), PROFILE && visualizer({ brotliSize: true, @@ -74,7 +78,7 @@ export default async () => { const doesAlsoTargetBrowser = 'browser' in pkg return [ - getConfig(pkg), + getConfig(pkg, false), doesAlsoTargetBrowser && getConfig(pkg, true), { input: './src/index.ts', From 6f0eb9b285c3cf513812a852ba1a81c171a50e58 Mon Sep 17 00:00:00 2001 From: Alexandre Philibeaux Date: Mon, 24 Oct 2022 20:50:29 +0000 Subject: [PATCH 4/5] fix(async): use async functions --- packages/validate-icu-locales/README.md | 3 + packages/validate-icu-locales/package.json | 2 +- packages/validate-icu-locales/src/index.ts | 66 ++++++++++++---------- rollup.config.mjs | 4 -- 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/packages/validate-icu-locales/README.md b/packages/validate-icu-locales/README.md index ece640952..566ff5ac8 100644 --- a/packages/validate-icu-locales/README.md +++ b/packages/validate-icu-locales/README.md @@ -3,6 +3,9 @@ ## A tiny cli to handle ICU errors on locales files. ## Install +Requirements +- Node should be installed + ```bash $ pnpm add -D @scaleway/validate-icu-locales ``` diff --git a/packages/validate-icu-locales/package.json b/packages/validate-icu-locales/package.json index 84d6bc1c7..382cf0ae9 100644 --- a/packages/validate-icu-locales/package.json +++ b/packages/validate-icu-locales/package.json @@ -1,6 +1,6 @@ { "name": "@scaleway/validate-icu-locales", - "version": "0.0.0", + "version": "1.0.0", "description": "A small cli to handle icu errors on locales files", "keywords": [ "icu", diff --git a/packages/validate-icu-locales/src/index.ts b/packages/validate-icu-locales/src/index.ts index a1a84e2ac..57f2f71f6 100644 --- a/packages/validate-icu-locales/src/index.ts +++ b/packages/validate-icu-locales/src/index.ts @@ -2,22 +2,15 @@ import { parse } from '@formatjs/icu-messageformat-parser' import { info } from 'console' -import fs from 'fs' +import { readFile } from 'fs/promises' import { globby } from 'globby' -import { importFromStringSync } from 'module-from-string' +import { importFromString } from 'module-from-string' const args = process.argv.slice(2) const pattern = args[0] - const { error, table } = console -type Err = { - err: unknown - value: string - key: string - filePath: string -} -const errors: Err[] = [] +let isICUError = false const findICUError = (locales: { [key: string]: string }, filePath: string) => { Object.keys(locales).forEach(key => { @@ -26,7 +19,8 @@ const findICUError = (locales: { [key: string]: string }, filePath: string) => { try { parse(value) } catch (err) { - errors.push({ + isICUError = true + error({ err, value, key, @@ -36,49 +30,59 @@ const findICUError = (locales: { [key: string]: string }, filePath: string) => { }) } -const readFiles = (files: string[]) => - files.forEach(file => { +const readFiles = async (files: string[]) => { + for await (const file of files) { const extension = file.split('.').pop() if (extension === 'json') { - const json = fs.readFileSync(file).toString() try { - const locales = JSON.parse(json) as Record + const data = await readFile(file) + const jsonFile = data.toString() + + const locales = JSON.parse(jsonFile) as Record findICUError(locales, file) } catch (err) { - error(err) + error({ file, err }) } } if (extension === 'ts' || extension === 'js') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const d: { default: Record } = importFromStringSync( - fs.readFileSync(file).toString(), - { transformOptions: { loader: 'ts' } }, - ) - - if (typeof d.default === 'object') { - findICUError(d.default, file) - } else { - error('export default from: ', file, ' is not an object') + try { + const data = await readFile(file) + const javascriptFile = data.toString() + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const d: { default: Record } = await importFromString( + javascriptFile, + { transformOptions: { loader: 'ts' } }, + ) + + if (typeof d.default === 'object') { + findICUError(d.default, file) + } else { + error('export default from: ', file, ' is not an object') + } + } catch (err) { + error({ err, file }) } } - }) + } +} const files = await globby(pattern) if (files.length > 0) { table(files) - readFiles(files) + await readFiles(files) } if (files.length === 0) { info('There is no files matching this pattern', pattern) } -if (errors.length > 1) { - error({ errors }) - info(new Error('There is somes ICU error')) +if (isICUError) { process.exit(1) +} else { + process.exit(0) } diff --git a/rollup.config.mjs b/rollup.config.mjs index 7014cf1e9..6c9c73652 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -57,10 +57,6 @@ const getConfig = (pkg, isBrowser = false) => { preferBuiltins: true, }), preserveShebangs(), - // shebang({ - // include: ['**/index.js'], - // shebang: '#!/usr/bin/env node' - // }), PROFILE && visualizer({ brotliSize: true, From dbac7804e197a84536baf81ef39646d362b8f7cc Mon Sep 17 00:00:00 2001 From: Alexandre Philibeaux Date: Mon, 24 Oct 2022 22:01:14 +0000 Subject: [PATCH 5/5] fix(errors): return errors --- packages/validate-icu-locales/README.md | 16 +-- packages/validate-icu-locales/package.json | 4 +- .../src/__tests__/locales/en-no-default.ts | 9 ++ packages/validate-icu-locales/src/index.ts | 112 ++++++++++++------ rollup.config.mjs | 2 +- 5 files changed, 94 insertions(+), 49 deletions(-) create mode 100644 packages/validate-icu-locales/src/__tests__/locales/en-no-default.ts diff --git a/packages/validate-icu-locales/README.md b/packages/validate-icu-locales/README.md index 566ff5ac8..90bc5bc30 100644 --- a/packages/validate-icu-locales/README.md +++ b/packages/validate-icu-locales/README.md @@ -15,34 +15,34 @@ We can parse JSON, TS and JS if theses files use default export. ``` -@scaleway/validate-icu-locales "../**/en.json" +validate-icu "../**/en.json" ``` If there is an error on a local, the CLI will throw an Error and print all errors. ## Error -```` +``` export default from: ../src/__tests__/locales/en-1.js is not an object { errors: [ { - err: [SyntaxError], + message: 'EXPECT_ARGUMENT_CLOSING_BRACE', value: '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', key: 'units.minutes.label', - filePath: '../src/__tests__/locales/en.js' + filePath: '../../packages/validate-icu-locales/src/__tests__/locales/en.js' }, { - err: [SyntaxError], + message: 'EXPECT_ARGUMENT_CLOSING_BRACE', value: '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', key: 'units.minutes.label', - filePath: '../src/__tests__/locales/en.json' + filePath: '../../packages/validate-icu-locales/src/__tests__/locales/en.json' }, { - err: [SyntaxError], + message: 'EXPECT_ARGUMENT_CLOSING_BRACE', value: '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', key: 'units.minutes.label', - filePath: '../src/__tests__/locales/en.ts' + filePath: '../../packages/validate-icu-locales/src/__tests__/locales/en.ts' } ] } diff --git a/packages/validate-icu-locales/package.json b/packages/validate-icu-locales/package.json index 382cf0ae9..1a283f455 100644 --- a/packages/validate-icu-locales/package.json +++ b/packages/validate-icu-locales/package.json @@ -8,7 +8,9 @@ "cli" ], "type": "module", - "bin": "dist/index.js", + "bin": { + "validate-icu": "dist/index.js" + }, "publishConfig": { "access": "public" }, diff --git a/packages/validate-icu-locales/src/__tests__/locales/en-no-default.ts b/packages/validate-icu-locales/src/__tests__/locales/en-no-default.ts new file mode 100644 index 000000000..e2c06bb72 --- /dev/null +++ b/packages/validate-icu-locales/src/__tests__/locales/en-no-default.ts @@ -0,0 +1,9 @@ +export const toto = { + // error on this one missing bracket + + 'units.minutes.label': + '{count, plural, =0 {Minute} =1 {Minute} other {Minutes', + 'units.hours.label': '{count, plural, =0 {Hour} =1 {Hour} other {Hours}}', + 'units.days.label': '{count, plural, =0 {Day} =1 {Day} other {Days}}', + 'units.months.label': '{count, plural, =0 {Month} =1 {Month} other {Months}}', +} as const diff --git a/packages/validate-icu-locales/src/index.ts b/packages/validate-icu-locales/src/index.ts index 57f2f71f6..6d6df1f24 100644 --- a/packages/validate-icu-locales/src/index.ts +++ b/packages/validate-icu-locales/src/index.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node import { parse } from '@formatjs/icu-messageformat-parser' -import { info } from 'console' +import { ParserError } from '@formatjs/icu-messageformat-parser/error' import { readFile } from 'fs/promises' import { globby } from 'globby' import { importFromString } from 'module-from-string' @@ -10,27 +10,52 @@ const args = process.argv.slice(2) const pattern = args[0] const { error, table } = console -let isICUError = false - -const findICUError = (locales: { [key: string]: string }, filePath: string) => { - Object.keys(locales).forEach(key => { - const value = locales[key] - - try { - parse(value) - } catch (err) { - isICUError = true - error({ - err, - value, - key, - filePath, - }) - } - }) +type Locales = Record +type ErrorICU = { + message: ParserError['message'] + value: string + key: string + filePath: string +} + +type ErrorsICU = (ErrorICU | undefined)[] + +const isObject = (obj: unknown): obj is Record => + obj === Object(obj) + +const findICUErrors = ( + locales: { [key: string]: string }, + filePath: string, +): ErrorsICU => { + const keys = Object.keys(locales) + + const errors = keys + .map(key => { + const value = locales[key] + + try { + parse(value) + + return undefined + } catch (err) { + const { message } = err as ParserError + + return { + message, + value, + key, + filePath, + } + } + }) + .filter(Boolean) + + return errors } -const readFiles = async (files: string[]) => { +const readFiles = async (files: string[]): Promise => { + const errors = [] + for await (const file of files) { const extension = file.split('.').pop() @@ -39,9 +64,10 @@ const readFiles = async (files: string[]) => { const data = await readFile(file) const jsonFile = data.toString() - const locales = JSON.parse(jsonFile) as Record + const locales = JSON.parse(jsonFile) as Locales - findICUError(locales, file) + const ICUErrors = findICUErrors(locales, file) + errors.push(...ICUErrors) } catch (err) { error({ file, err }) } @@ -52,37 +78,45 @@ const readFiles = async (files: string[]) => { const data = await readFile(file) const javascriptFile = data.toString() - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const d: { default: Record } = await importFromString( - javascriptFile, - { transformOptions: { loader: 'ts' } }, - ) + const mod: unknown = await importFromString(javascriptFile, { + transformOptions: { loader: 'ts' }, + }) + + if (isObject(mod)) { + if ('default' in mod) { + const { default: locales } = mod as { default: Locales } - if (typeof d.default === 'object') { - findICUError(d.default, file) + const ICUErrors = findICUErrors(locales, file) + errors.push(...ICUErrors) + } else { + error('export default from: ', file, ' is not an object') + } } else { - error('export default from: ', file, ' is not an object') + error(file, ' is not an object') } } catch (err) { error({ err, file }) } } } + + return errors } const files = await globby(pattern) -if (files.length > 0) { - table(files) - await readFiles(files) -} - if (files.length === 0) { - info('There is no files matching this pattern', pattern) + error('There is no files matching this pattern', pattern) + process.exit(1) } -if (isICUError) { +table(files) + +const errors = await readFiles(files) + +if (errors.length > 0) { + error({ + errors, + }) process.exit(1) -} else { - process.exit(0) } diff --git a/rollup.config.mjs b/rollup.config.mjs index 6c9c73652..484c3ad2b 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -74,7 +74,7 @@ export default async () => { const doesAlsoTargetBrowser = 'browser' in pkg return [ - getConfig(pkg, false), + getConfig(pkg), doesAlsoTargetBrowser && getConfig(pkg, true), { input: './src/index.ts',