Repository: lingui/js-lingui
Pull request #1299 (2022-11-11 01:36:32)

feat: `lingui extract` should fail with a non-zero exit code when there are extraction failures
`lingui extract` prints extraction failures (such as `Cannot process file [path]: Different defaults for the same message ID.`) to the console but then finishes with a zero exit code. When run unattended in a CI job, this means files with failures are skipped during extraction and their messages may then be missing in a production build.

This PR propagates the `boolean` result(s) from the extractor all the way up into the CLI command. There was already code to handle it and do `process.exit(1)` when the result is `false`.

vercel[bot]: **The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated |
| :--- | :----- | :------ | :------ |
| **js-lingui** | 🔄 Building ([Inspect](https://vercel.com/lingui/js-lingui/J3ZMgfrLo7L6wb3QtrwUp2HDir3q)) |  | Nov 11, 2022 at 1:36AM (UTC) |


codecov[bot]: # [Codecov](https://codecov.io/gh/lingui/js-lingui/pull/1299?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui) Report
Base: **82.52**% // Head: **82.47**% // Decreases project coverage by **`-0.04%`** :warning:
> Coverage data is based on head [(`9bf7b83`)](https://codecov.io/gh/lingui/js-lingui/pull/1299?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui) compared to base [(`7c85cb7`)](https://codecov.io/gh/lingui/js-lingui/commit/7c85cb70b3b791951a9239c037aa73bb890c26e6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui).
> Patch coverage: 72.72% of modified lines in pull request are covered.

> :exclamation: Current head 9bf7b83 differs from pull request most recent head cdd46e4. Consider uploading reports for the commit cdd46e4 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1299      +/-   ##
==========================================
- Coverage   82.52%   82.47%   -0.05%     
==========================================
  Files          56       56              
  Lines        1785     1792       +7     
  Branches      500      503       +3     
==========================================
+ Hits         1473     1478       +5     
  Misses        184      184              
- Partials      128      130       +2     
```


| [Impacted Files](https://codecov.io/gh/lingui/js-lingui/pull/1299?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui) | Coverage Δ | |
|---|---|---|
| [packages/cli/src/api/extractors/index.ts](https://codecov.io/gh/lingui/js-lingui/pull/1299/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui#diff-cGFja2FnZXMvY2xpL3NyYy9hcGkvZXh0cmFjdG9ycy9pbmRleC50cw==) | `57.89% <66.66%> (ø)` | |
| [packages/cli/src/api/catalog.ts](https://codecov.io/gh/lingui/js-lingui/pull/1299/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui#diff-cGFja2FnZXMvY2xpL3NyYy9hcGkvY2F0YWxvZy50cw==) | `84.95% <75.00%> (-0.44%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/lingui/js-lingui/pull/1299?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lingui).


Patch:
diff --git a/packages/cli/src/api/catalog.ts b/packages/cli/src/api/catalog.ts
index 4af1ccd6..6a5b6f5f 100644
--- a/packages/cli/src/api/catalog.ts
+++ b/packages/cli/src/api/catalog.ts
@@ -99,8 +99,9 @@ export class Catalog {
     this.format = getFormat(config.format)
   }
 
-  async make(options: MakeOptions) {
+  async make(options: MakeOptions): Promise<boolean> {
     const nextCatalog = await this.collect(options)
+    if (!nextCatalog) return false
     const prevCatalogs = this.readAll()
 
     const catalogs = this.merge(prevCatalogs, nextCatalog, {
@@ -125,18 +126,21 @@ export class Catalog {
     } else {
       this.writeAll(sortedCatalogs)
     }
+    return true
   }
 
-  async makeTemplate(options: MakeTemplateOptions) {
+  async makeTemplate(options: MakeTemplateOptions): Promise<boolean> {
     const catalog = await this.collect(options)
+    if (!catalog) return false
     const sort = order(options.orderBy) as (catalog: CatalogType) => CatalogType
     this.writeTemplate(sort(catalog as CatalogType))
+    return true
   }
 
   /**
    * Collect messages from source paths. Return a raw message catalog as JSON.
    */
-  async collect(options: CollectOptions) {
+  async collect(options: CollectOptions): Promise<CatalogType | undefined> {
     const tmpDir = path.join(os.tmpdir(), `lingui-${process.pid}`)
 
     if (fs.existsSync(tmpDir)) {
@@ -153,15 +157,18 @@ export class Catalog {
         paths = paths.filter((path: string) => regex.test(path))
       }
 
+      let catalogSuccess = true
       for (let filename of paths) {
-        await extract(filename, tmpDir, {
+        const fileSuccess = await extract(filename, tmpDir, {
           verbose: options.verbose,
           configPath: options.configPath,
           babelOptions: this.config.extractBabelOptions,
           extractors: options.extractors,
           projectType: options.projectType,
         })
+        catalogSuccess &&= fileSuccess
       }
+      if (!catalogSuccess) return undefined
 
       return (function traverse(directory) {
         return fs
diff --git a/packages/cli/src/api/extractors/index.ts b/packages/cli/src/api/extractors/index.ts
index 530d5767..849b3b48 100644
--- a/packages/cli/src/api/extractors/index.ts
+++ b/packages/cli/src/api/extractors/index.ts
@@ -45,18 +45,18 @@ export default async function extract(
 
     try {
       await ext.extract(filename, targetPath, options)
+
+      if (options.verbose && spinner) spinner.succeed()
+      return true
     } catch (e) {
       if (options.verbose && spinner) {
         spinner.fail(e.message)
       } else {
         console.error(`Cannot process file ${e.message}`)
       }
-      return true
+      return false
     }
-
-    if (options.verbose && spinner) spinner.succeed()
-    return true
   }
 
-  return false
+  return true
 }
diff --git a/packages/cli/src/lingui-extract.ts b/packages/cli/src/lingui-extract.ts
index 6c86d63c..e7d4d06e 100644
--- a/packages/cli/src/lingui-extract.ts
+++ b/packages/cli/src/lingui-extract.ts
@@ -41,8 +41,9 @@ export default async function command(
 
   const catalogs = getCatalogs(config)
   const catalogStats: { [path: string]: AllCatalogsType } = {}
+  let commandSuccess = true
   for (let catalog of catalogs) {
-    await catalog.make({
+    const catalogSuccess = await catalog.make({
       ...options as CliExtractOptions,
       orderBy: config.orderBy,
       extractors: config.extractors,
@@ -50,6 +51,7 @@ export default async function command(
     })
 
     catalogStats[catalog.path] = catalog.readAll()
+    commandSuccess &&= catalogSuccess
   }
 
   Object.entries(catalogStats).forEach(([key, value]) => {
@@ -80,7 +82,7 @@ export default async function command(
       .catch(err => console.error(`Can't load service module ${moduleName}`, err))
   }
 
-  return true
+  return commandSuccess
 }
 
 if (require.main === module) {
diff --git a/packages/macro/index.d.ts b/packages/macro/index.d.ts
index 298b979d..d95374d5 100644
--- a/packages/macro/index.d.ts
+++ b/packages/macro/index.d.ts
@@ -175,6 +175,7 @@ export type TransProps = {
   children?: React.ReactNode
   component?: React.ComponentType<TransRenderProps>
   render?: (props: TransRenderProps) => ReactElement<any, any> | null
+  i18n?: I18n
 }
 
 export type ChoiceProps = {


Test patch:
diff --git a/packages/cli/src/api/__snapshots__/catalog.test.ts.snap b/packages/cli/src/api/__snapshots__/catalog.test.ts.snap
index 7a19a761..69d5e117 100644
--- a/packages/cli/src/api/__snapshots__/catalog.test.ts.snap
+++ b/packages/cli/src/api/__snapshots__/catalog.test.ts.snap
@@ -93,7 +93,7 @@ Object {
 }
 `;
 
-exports[`Catalog collect should handle errors 1`] = `Object {}`;
+exports[`Catalog collect should handle errors 1`] = `undefined`;
 
 exports[`Catalog make should collect and write catalogs 1`] = `
 Object {