Repository: intuit/auto
Pull request #975 (2020-02-23 00:28:35)

When parsing owner/repo fallback to parsing 'origin'
# What Changed

Set up a default for owner/repo when not found from package manager plugin.

# Why

This is a nice fallback. Removes a required setup step (still recommended to configure repo in package manager)

Todo:

- [x] Add tests
- [ ] Add docs

<!-- GITHUB_RELEASE PR BODY: canary-version -->
<details>
  <summary>📦 Published PR as canary version: <code>under canary scope @auto-canary@9.13.0-canary.975.12718.0</code></summary>
  <br />
  
  :sparkles: Test out this PR locally via:
  
  ```sh
  npm install @auto-canary/auto@9.13.0-canary.975.12718.0
  npm install @auto-canary/core@9.13.0-canary.975.12718.0
  npm install @auto-canary/all-contributors@9.13.0-canary.975.12718.0
  npm install @auto-canary/chrome@9.13.0-canary.975.12718.0
  npm install @auto-canary/conventional-commits@9.13.0-canary.975.12718.0
  npm install @auto-canary/crates@9.13.0-canary.975.12718.0
  npm install @auto-canary/first-time-contributor@9.13.0-canary.975.12718.0
  npm install @auto-canary/git-tag@9.13.0-canary.975.12718.0
  npm install @auto-canary/gradle@9.13.0-canary.975.12718.0
  npm install @auto-canary/jira@9.13.0-canary.975.12718.0
  npm install @auto-canary/maven@9.13.0-canary.975.12718.0
  npm install @auto-canary/npm@9.13.0-canary.975.12718.0
  npm install @auto-canary/omit-commits@9.13.0-canary.975.12718.0
  npm install @auto-canary/omit-release-notes@9.13.0-canary.975.12718.0
  npm install @auto-canary/released@9.13.0-canary.975.12718.0
  npm install @auto-canary/s3@9.13.0-canary.975.12718.0
  npm install @auto-canary/slack@9.13.0-canary.975.12718.0
  npm install @auto-canary/twitter@9.13.0-canary.975.12718.0
  npm install @auto-canary/upload-assets@9.13.0-canary.975.12718.0
  # or 
  yarn add @auto-canary/auto@9.13.0-canary.975.12718.0
  yarn add @auto-canary/core@9.13.0-canary.975.12718.0
  yarn add @auto-canary/all-contributors@9.13.0-canary.975.12718.0
  yarn add @auto-canary/chrome@9.13.0-canary.975.12718.0
  yarn add @auto-canary/conventional-commits@9.13.0-canary.975.12718.0
  yarn add @auto-canary/crates@9.13.0-canary.975.12718.0
  yarn add @auto-canary/first-time-contributor@9.13.0-canary.975.12718.0
  yarn add @auto-canary/git-tag@9.13.0-canary.975.12718.0
  yarn add @auto-canary/gradle@9.13.0-canary.975.12718.0
  yarn add @auto-canary/jira@9.13.0-canary.975.12718.0
  yarn add @auto-canary/maven@9.13.0-canary.975.12718.0
  yarn add @auto-canary/npm@9.13.0-canary.975.12718.0
  yarn add @auto-canary/omit-commits@9.13.0-canary.975.12718.0
  yarn add @auto-canary/omit-release-notes@9.13.0-canary.975.12718.0
  yarn add @auto-canary/released@9.13.0-canary.975.12718.0
  yarn add @auto-canary/s3@9.13.0-canary.975.12718.0
  yarn add @auto-canary/slack@9.13.0-canary.975.12718.0
  yarn add @auto-canary/twitter@9.13.0-canary.975.12718.0
  yarn add @auto-canary/upload-assets@9.13.0-canary.975.12718.0
  ```
</details>
<!-- GITHUB_RELEASE PR BODY: canary-version -->



Patch:
diff --git a/docs/pages/non-npm.md b/docs/pages/non-npm.md
index 08353e2a..ea9e7d19 100644
--- a/docs/pages/non-npm.md
+++ b/docs/pages/non-npm.md
@@ -41,8 +41,6 @@ Using `auto` with any other package manager than [npm](https://npmjs.com) requir
 
    ```json
    {
-     "repo": "my-project",
-     "owner": "hipstersmoothie",
      "name": "Andrew Lisowski",
      "email": "lisowski54@gmail.com",
      "plugins": []
@@ -53,8 +51,6 @@ Using `auto` with any other package manager than [npm](https://npmjs.com) requir
 
    ```json
    {
-     "repo": "my-project",
-     "owner": "hipstersmoothie",
      "name": "Andrew Lisowski",
      "email": "lisowski54@gmail.com",
      "plugins": ["git-tag"]
diff --git a/docs/pages/troubleshooting.md b/docs/pages/troubleshooting.md
index 88bea083..9684048e 100644
--- a/docs/pages/troubleshooting.md
+++ b/docs/pages/troubleshooting.md
@@ -45,21 +45,6 @@ mkdir ~/.ssh/ && echo -e "Host github.YOUR_COMPANY.com\n\tStrictHostKeyChecking
 
 If you've encountered any of these errors you'll probably run into this problem. If the whole release process doesn't complete you can end up in a state when `auto` published the new version, but doesn't push that back to github. To fix this just bump the version number to the "previously published version".
 
-## Cannot read owner and package name from GitHub URL in package.json
-
-This means that you do not have a repository set in your package.json. Add something along the line of:
-
-```json
-{
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/intuit/auto"
-  },
-  // or simply
-  "repository": "intuit/auto"
-}
-```
-
 ## How do I auto a fork of another repo?
 
 If auto doesn't find a last release it will default to the first commit for version calculation (and a log of other things). If you have forked a repo, you fork all the merge commit messages as well. This confuses `auto` since it will look for those pull requests in your fork and not the main one.
diff --git a/packages/cli/README.md b/packages/cli/README.md
index 04b295cb..926d8d3e 100644
--- a/packages/cli/README.md
+++ b/packages/cli/README.md
@@ -169,7 +169,7 @@ Global Options
   --repo string         The repo to set the status on. Defaults to looking in the package definition
                         for the platform
   --owner string        The owner of the GitHub repo. Defaults to reading from the package definition
-                        for the platform
+                        for the platform or the remote "origin"
   --github-api string   GitHub API to use
   --plugins string[]    Plugins to load auto with. (defaults to just npm)
   -h, --help            Display the help output
diff --git a/packages/core/src/auto.ts b/packages/core/src/auto.ts
index 62544671..8c96525a 100644
--- a/packages/core/src/auto.ts
+++ b/packages/core/src/auto.ts
@@ -53,6 +53,7 @@ import { makeHooks } from './utils/make-hooks';
 import { IAuthorOptions, IRepoOptions } from './auto-args';
 import { execSync } from 'child_process';
 import { buildSearchQuery, ISearchResult } from './match-sha-to-pr';
+import getRepository from './utils/get-repository';
 
 const proxyUrl = process.env.https_proxy || process.env.http_proxy;
 const env = envCi();
@@ -342,6 +343,10 @@ export default class Auto {
     this.logger = createLog();
     this.hooks = makeHooks();
 
+    this.hooks.getRepository.tapPromise(
+      'Get repo info from origin',
+      getRepository
+    );
     this.hooks.onCreateRelease.tap('Link onCreateChangelog', release => {
       release.hooks.onCreateChangelog.tap(
         'Link onCreateChangelog',
@@ -453,18 +458,19 @@ export default class Auto {
     this.release = new Release(this.git, config, this.logger);
     this.remote = await this.getRemote();
     this.logger.verbose.info(
-      `Using remote: ${this.remote.replace(token, `****${token.substring(0, 4)}`)}`
+      `Using remote: ${this.remote.replace(
+        token,
+        `****${token.substring(0, 4)}`
+      )}`
     );
     this.hooks.onCreateRelease.call(this.release);
   }
 
   /** Determine the remote we have auth to push to. */
   private async getRemote(): Promise<string> {
-    const [, configuredRemote = 'origin'] = await on(execPromise('git', [
-      'remote',
-      'get-url',
-      'origin'
-    ]));
+    const [, configuredRemote = 'origin'] = await on(
+      execPromise('git', ['remote', 'get-url', 'origin'])
+    );
 
     if (!this.git) {
       return configuredRemote;
@@ -1658,7 +1664,24 @@ export default class Auto {
       return config as IRepoOptions & TestingToken;
     }
 
-    return this.hooks.getRepository.promise();
+    const author = await this.hooks.getRepository.promise();
+
+    if (!author || !author.owner || !author.repo) {
+      this.logger.log.error(
+        endent`
+          Cannot find project owner and repository name!
+
+          You must do one of the following: 
+
+          - configure the repo for your package manager (ex: set "repository" in package.json)
+          - configure your git remote 'origin' to point to your project on GitHub.
+        `,
+        ''
+      );
+      process.exit(1);
+    }
+
+    return author;
   }
 
   /**
diff --git a/packages/core/src/utils/get-repository.ts b/packages/core/src/utils/get-repository.ts
new file mode 100644
index 00000000..d43b8c6c
--- /dev/null
+++ b/packages/core/src/utils/get-repository.ts
@@ -0,0 +1,23 @@
+import parseGitHubUrl from 'parse-github-url';
+import on from 'await-to-js';
+
+import execPromise from './exec-promise';
+
+/**
+ * Get the owner and repo from the configure remote "origin"
+ */
+export default async function getRepository() {
+  const [, origin] = await on(
+    execPromise('git', ['remote', 'get-url', 'origin'])
+  );
+
+  if (origin) {
+    const info =
+      parseGitHubUrl(origin) || ({} as Record<string, string | undefined>);
+    const { name, owner } = info;
+
+    if (name && owner) {
+      return { repo: name, owner };
+    }
+  }
+}
diff --git a/plugins/npm/src/index.ts b/plugins/npm/src/index.ts
index bf8e1355..d641c078 100644
--- a/plugins/npm/src/index.ts
+++ b/plugins/npm/src/index.ts
@@ -491,7 +491,11 @@ export default class NPMPlugin implements IPlugin {
       auto.logger.verbose.info(
         'NPM: getting repo information from package.json'
       );
-      return getConfigFromPackageJson();
+      const repo = await getConfigFromPackageJson();
+
+      if (repo) {
+        return repo;
+      }
     });
 
     auto.hooks.onCreateRelease.tap(this.name, release => {
diff --git a/plugins/npm/src/package-config.ts b/plugins/npm/src/package-config.ts
index 8f286588..caff7b9c 100644
--- a/plugins/npm/src/package-config.ts
+++ b/plugins/npm/src/package-config.ts
@@ -9,11 +9,13 @@ export interface IRepoConfig {
 }
 
 /** Try to the the owner/repo from the package.json */
-export default async function getConfigFromPackageJson(): Promise<IRepoConfig> {
+export default async function getConfigFromPackageJson(): Promise<
+  IRepoConfig | undefined
+> {
   const { repository } = await loadPackageJson();
 
   if (!repository) {
-    throw new Error('Cannot read repo info from package.json');
+    return;
   }
 
   const { owner, name } =
@@ -22,9 +24,7 @@ export default async function getConfigFromPackageJson(): Promise<IRepoConfig> {
     ) || {};
 
   if (!owner || !name) {
-    throw new Error(
-      'Cannot read owner and package name from GitHub URL in package.json'
-    );
+    return;
   }
 
   return {


Test patch:
diff --git a/packages/core/src/utils/__tests__/get-repository.test.ts b/packages/core/src/utils/__tests__/get-repository.test.ts
new file mode 100644
index 00000000..db4a7cad
--- /dev/null
+++ b/packages/core/src/utils/__tests__/get-repository.test.ts
@@ -0,0 +1,26 @@
+import execPromise from '../exec-promise';
+import getRepository from '../get-repository';
+
+const execSpy = execPromise as jest.Mock;
+// @ts-ignore
+jest.mock('../exec-promise.ts');
+
+describe('getRepository', () => {
+  test('should do nothing without a configured remote', async () => {
+    execSpy.mockReturnValueOnce(Promise.resolve(''));
+    expect(await getRepository()).toBeUndefined();
+  });
+
+  test('should do nothing if parsing origin fails', async () => {
+    execSpy.mockReturnValueOnce(Promise.resolve('foo'));
+    expect(await getRepository()).toBeUndefined();
+  });
+
+  test('should return owner/repo if possible', async () => {
+    execSpy.mockReturnValueOnce(Promise.resolve('foo/bar'));
+    expect(await getRepository()).toStrictEqual({
+      owner: 'foo',
+      repo: 'bar'
+    });
+  });
+});
diff --git a/plugins/all-contributors/__tests__/all-contributors.test.ts b/plugins/all-contributors/__tests__/all-contributors.test.ts
index 3f0efdb1..28f23cd0 100644
--- a/plugins/all-contributors/__tests__/all-contributors.test.ts
+++ b/plugins/all-contributors/__tests__/all-contributors.test.ts
@@ -88,9 +88,9 @@ describe('All Contributors Plugin', () => {
   test('should work for single package', async () => {
     const releasedLabel = new AllContributors();
     const autoHooks = makeHooks();
-    
+
     mockRead('{ "contributors": [] }');
-    getLernaPackages.mockRejectedValueOnce(Promise.reject(new Error('test')));
+    getLernaPackages.mockRejectedValueOnce(new Error('test'));
 
     releasedLabel.apply({ hooks: autoHooks, logger: dummyLog() } as Auto.Auto);
 
diff --git a/plugins/npm/__tests__/package-config.test.ts b/plugins/npm/__tests__/package-config.test.ts
index 3b772263..9c280c41 100644
--- a/plugins/npm/__tests__/package-config.test.ts
+++ b/plugins/npm/__tests__/package-config.test.ts
@@ -1,62 +1,35 @@
 import packageConfig from '../src/package-config';
+import { loadPackageJson } from '../src/utils';
 
-let readResult = {};
+const packageJsonSpy = loadPackageJson as jest.Mock;
+jest.mock('../src/utils');
 
-jest.mock('../src/utils', () => ({
-  loadPackageJson: () => readResult
-}));
-
-let parseResult: object | undefined = {};
-
-jest.mock('parse-github-url', () => () => parseResult);
-
-test('should throw without a repo', async () => {
-  expect.assertions(1);
-
-  await expect(packageConfig()).rejects.toStrictEqual(
-    new Error('Cannot read repo info from package.json')
-  );
+test('should return nothing without a repo', async () => {
+  packageJsonSpy.mockReturnValueOnce({});
+  expect(await packageConfig()).toBeUndefined();
 });
 
-test('should throw without an owner', async () => {
-  expect.assertions(1);
-  readResult = {
+test('should return nothing without an owner', async () => {
+  packageJsonSpy.mockReturnValueOnce({
     repository: { url: 'fake.com' }
-  };
-  parseResult = undefined;
+  });
 
-  await expect(packageConfig()).rejects.toStrictEqual(
-    new Error(
-      'Cannot read owner and package name from GitHub URL in package.json'
-    )
-  );
+  expect(await packageConfig()).toBeUndefined();
 });
 
-test('should throw without an package name', async () => {
-  expect.assertions(1);
-  readResult = {
+test('should return nothing without an package name', async () => {
+  packageJsonSpy.mockReturnValueOnce({
     repository: { url: 'fake.com' }
-  };
-  parseResult = {
-    owner: 'black-panther'
-  };
+  });
 
-  await expect(packageConfig()).rejects.toStrictEqual(
-    new Error(
-      'Cannot read owner and package name from GitHub URL in package.json'
-    )
-  );
+  expect(await packageConfig()).toBeUndefined();
 });
 
 test('should correctly parse package info', async () => {
-  readResult = {
+  packageJsonSpy.mockReturnValueOnce({
     version: '1.0.0',
-    repository: { url: 'fake.com/black-panther/operation-foo' }
-  };
-  parseResult = {
-    owner: 'black-panther',
-    name: 'operation-foo'
-  };
+    repository: { url: 'https://github.com/black-panther/operation-foo' }
+  });
 
   expect(await packageConfig()).toStrictEqual({
     repo: 'operation-foo',
@@ -65,14 +38,10 @@ test('should correctly parse package info', async () => {
 });
 
 test('should correctly parse package info - string', async () => {
-  readResult = {
+  packageJsonSpy.mockReturnValueOnce({
     version: '1.0.0',
     repository: 'black-panther/operation-foo'
-  };
-  parseResult = {
-    owner: 'black-panther',
-    name: 'operation-foo'
-  };
+  });
 
   expect(await packageConfig()).toStrictEqual({
     repo: 'operation-foo',