Skip to content

Commit c9a872c

Browse files
authored
adding getToken Command (#1)
Basic functionality
1 parent fa1fe70 commit c9a872c

17 files changed

+1209
-295
lines changed

.github/workflows/release.yml

+48
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
name: Release
2+
13
on:
24
push:
35
# Sequence of patterns matched against refs/tags
@@ -19,3 +21,49 @@ jobs:
1921
- run: npm publish
2022
env:
2123
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
24+
- run: npm run build-binaries
25+
- name: Zip binaries
26+
run: |
27+
zip zip --junk-paths linux index-linux
28+
zip zip --junk-paths macosx index-macos
29+
zip zip --junk-paths win index-win.exe
30+
- name: Create Release
31+
id: create_release
32+
uses: actions/create-release@v1
33+
env:
34+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35+
with:
36+
tag_name: ${{ github.ref }}
37+
release_name: Release ${{ github.ref }}
38+
draft: false
39+
prerelease: false
40+
- name: Upload Release Asset - WIN
41+
id: upload-release-asset
42+
uses: actions/upload-release-asset@v1
43+
env:
44+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45+
with:
46+
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
47+
asset_path: ./win.zip
48+
asset_name: win.zip
49+
asset_content_type: application/zip
50+
- name: Upload Release Asset - MACOSX
51+
id: upload-release-asset
52+
uses: actions/upload-release-asset@v1
53+
env:
54+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
with:
56+
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
57+
asset_path: ./macosx.zip
58+
asset_name: macosx.zip
59+
asset_content_type: application/zip
60+
- name: Upload Release Asset - linux
61+
id: upload-release-asset
62+
uses: actions/upload-release-asset@v1
63+
env:
64+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
65+
with:
66+
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
67+
asset_path: ./linux.zip
68+
asset_name: linux.zip
69+
asset_content_type: application/zip

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ coverage/
66
cli/
77
# ignore dist/ output
88
dist/
9+
# ignoring binaries
10+
index-*

README.md

+76-9
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,100 @@
11
<p align="center">
2-
<h3 align="center">Template for typescript applications</h3>
2+
<a href="https://codecov.io/gh/gagoar/github-app-installation-token">
3+
<img src="https://codecov.io/gh/gagoar/github-app-installation-token/branch/main/graph/badge.svg?token=E9CdygqJc4"/>
4+
</a>
5+
<a href="https://github.com/gagoar/github-app-installation-token/blob/main/LICENSE">
6+
<img src="https://img.shields.io/npm/l/github-app-installation-token.svg?style=flat-square" alt="MIT license" />
7+
</a>
8+
<h3 align="center">Github App Installation Token</h3>
39

410
<p align="center">
5-
⚙ Template repository for typescript applications meant to run in node ⚙
11+
npm/script and binary 📦 to get generate a token from a GitHub App.
612
<br />
7-
<a href="https://github.com/gagoar/ts-node-template#table-of-contents"><strong>Explore the docs »</strong></a>
13+
<a href="https://github.com/gagoar/github-app-installation-token#table-of-contents"><strong>Explore the docs »</strong></a>
814
<br />
9-
<a href="https://github.com/gagoar/ts-node-template/issues">Report Bug</a>
15+
<a href="https://github.com/gagoar/github-app-installation-token/issues">Report Bug</a>
1016
·
11-
<a href="https://github.com/gagoar/ts-node-template/issues">Request Feature</a>
17+
<a href="https://github.com/gagoar/github-app-installation-token/issues">Request Feature</a>
1218
</p>
1319
</p>
1420

1521
## Table of Contents
1622

1723
- [Built With](#built-with)
18-
- [Getting Started](#getting-started)
24+
- [Installation and use](#installation-and-use)
25+
26+
- [NPX](#npx)
27+
- [NPM](#npm-global)
28+
- [YARN](#yarn-global)
29+
- [GitHub Workflow](#github-Workflow)
30+
- [Binary](#binary)
31+
- [Programmatically](#programmatically)
32+
1933
- [Contributing](#contributing)
2034
- [License](#license)
2135

22-
<!-- CONTRIBUTING -->
36+
### Getting Started
37+
38+
GitHub Apps are the most powerful entity in the GitHub universe today. These Apps allow you to change a PR, add checks to a commit, trigger workflows and even (with the right permissions) commit code! But The tricky thing is, You need to generate a token every time you you want to use them.
39+
40+
This npm package / command line tool / binary will do just that!
41+
42+
## Installation and Use
43+
44+
You can install and use this package in different ways:
45+
46+
### NPX
47+
48+
```bash
49+
npx github-app-installation-token --appID <APP_ID> --installationId <INSTALLATION_ID> --privateKeyLocation <path/to/the/private.key>
50+
```
51+
52+
### NPM (global)
53+
54+
```bash
55+
npm -g install github-app-installation-token
56+
57+
npm run github-app-installation-token --appID <APP_ID> --installationId <INSTALLATION_ID> --privateKeyLocation <path/to/the/private.key>
58+
```
59+
60+
### YARN (global)
61+
62+
```bash
63+
yarn global install github-app-installation-token
64+
65+
yarn github-app-installation-token --appID <APP_ID> --installationId <INSTALLATION_ID> --privateKeyLocation <path/to/the/private.key>
66+
```
67+
68+
### Programmatically
69+
70+
```typescript
71+
const { getToken } = 'github-app-installation-token';
72+
73+
const { token } = await getToken({
74+
appId: '1234',
75+
installationId: '112345555', // https://developer.github.com/v3/apps/#list-installations-for-the-authenticated-app
76+
privateKey: '-----BEGIN RSA PRIVATE KEY----- ......-----END RSA PRIVATE KEY-----', // the private key you took from the app. https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#generating-a-private-key
77+
});
78+
```
79+
80+
#### Binary
81+
82+
If you don't want any dependencies, you can use the binary directly.
83+
84+
_commit soon_
85+
86+
### Github Workflow
87+
88+
If you are looking for a solution for your GitHub workflows, take a look at [github-app-installation-token-action](https://github.com/jnwng/github-app-installation-token-action)
2389

24-
### Built With
90+
## Built With
2591

2692
- [ncc](https://github.com/vercel/ncc/)
2793
- [jest](https://github.com/facebook/jest)
2894
- [ora](https://github.com/sindresorhus/ora)
2995
- [commander](https://github.com/tj/commander.js/)
30-
- [cosmiconfig](https://github.com/davidtheclark/cosmiconfig)
96+
- [octokit/rest](https://github.com/octokit/rest.js/)
97+
- [octokit/auth-app](https://github.com/octokit/auth-app.js/)
3198

3299
## Contributing
33100

__mocks__/ora.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const stopAndPersist = jest.fn();
2+
export const fail = jest.fn();
3+
4+
const ora = {
5+
start: jest.fn(() => ({
6+
stopAndPersist,
7+
fail,
8+
})),
9+
};
10+
export default (): typeof ora => ora;

__mocks__/private.key

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEpAIBAAKCAQEA1c7+9z5Pad7OejecsQ0bu3aozN3tihPmljnnudb9G3HECdnH
3+
lWu2/a1gB9JW5TBQ+AVpum9Okx7KfqkfBKL9mcHgSL0yWMdjMfNOqNtrQqKlN4kE
4+
p6RD++7sGbzbfZ9arwrlD/HSDAWGdGGJTSOBM6pHehyLmSC3DJoR/CTu0vTGTWXQ
5+
rO64Z8tyXQPtVPb/YXrcUhbBp8i72b9Xky0fD6PkEebOy0Ip58XVAn2UPNlNOSPS
6+
ye+Qjtius0Md4Nie4+X8kwVI2Qjk3dSm0sw/720KJkdVDmrayeljtKBx6AtNQsSX
7+
gzQbeMmiqFFkwrG1+zx6E7H7jqIQ9B6bvWKXGwIDAQABAoIBAD8kBBPL6PPhAqUB
8+
K1r1/gycfDkUCQRP4DbZHt+458JlFHm8QL6VstKzkrp8mYDRhffY0WJnYJL98tr4
9+
4tohsDbqFGwmw2mIaHjl24LuWXyyP4xpAGDpl9IcusjXBxLQLp2m4AKXbWpzb0OL
10+
Ulrfc1ZooPck2uz7xlMIZOtLlOPjLz2DuejVe24JcwwHzrQWKOfA11R/9e50DVse
11+
hnSH/w46Q763y4I0E3BIoUMsolEKzh2ydAAyzkgabGQBUuamZotNfvJoDXeCi1LD
12+
8yNCWyTlYpJZJDDXooBU5EAsCvhN1sSRoaXWrlMSDB7r/E+aQyKua4KONqvmoJuC
13+
21vSKeECgYEA7yW6wBkVoNhgXnk8XSZv3W+Q0xtdVpidJeNGBWnczlZrummt4xw3
14+
xs6zV+rGUDy59yDkKwBKjMMa42Mni7T9Fx8+EKUuhVK3PVQyajoyQqFwT1GORJNz
15+
c/eYQ6VYOCSC8OyZmsBM2p+0D4FF2/abwSPMmy0NgyFLCUFVc3OECpkCgYEA5OAm
16+
I3wt5s+clg18qS7BKR2DuOFWrzNVcHYXhjx8vOSWV033Oy3yvdUBAhu9A1LUqpwy
17+
Ma+unIgxmvmUMQEdyHQMcgBsVs10dR/g2xGjMLcwj6kn+xr3JVIZnbRT50YuPhf+
18+
ns1ScdhP6upo9I0/sRsIuN96Gb65JJx94gQ4k9MCgYBO5V6gA2aMQvZAFLUicgzT
19+
u/vGea+oYv7tQfaW0J8E/6PYwwaX93Y7Q3QNXCoCzJX5fsNnoFf36mIThGHGiHY6
20+
y5bZPPWFDI3hUMa1Hu/35XS85kYOP6sGJjf4kTLyirEcNKJUWH7CXY+00cwvTkOC
21+
S4Iz64Aas8AilIhRZ1m3eQKBgQCUW1s9azQRxgeZGFrzC3R340LL530aCeta/6FW
22+
CQVOJ9nv84DLYohTVqvVowdNDTb+9Epw/JDxtDJ7Y0YU0cVtdxPOHcocJgdUGHrX
23+
ZcJjRIt8w8g/s4X6MhKasBYm9s3owALzCuJjGzUKcDHiO2DKu1xXAb0SzRcTzUCn
24+
7daCswKBgQDOYPZ2JGmhibqKjjLFm0qzpcQ6RPvPK1/7g0NInmjPMebP0K6eSPx0
25+
9/49J6WTD++EajN7FhktUSYxukdWaCocAQJTDNYP0K88G4rtC2IYy5JFn9SWz5oh
26+
x//0u+zd/R/QRUzLOw4N72/Hu+UG6MNt5iDZFCtapRaKt6OvSBwy8w==
27+
-----END RSA PRIVATE KEY-----

__tests__/getToken.spec.ts

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { mockProcessExit } from 'jest-mock-process';
2+
import { readFileSync } from 'fs';
3+
import { stopAndPersist, fail } from '../__mocks__/ora';
4+
import nock from 'nock';
5+
6+
import { getToken, getTokenCommand } from '../';
7+
const GITHUB_URL = 'https://api.github.com';
8+
9+
describe('getToken', () => {
10+
beforeEach(() => {
11+
stopAndPersist.mockReset();
12+
fail.mockReset();
13+
});
14+
15+
const APP_ID = 1;
16+
const KEY_LOCATION = '__mocks__/private.key';
17+
18+
const PRIVATE_KEY = readFileSync(KEY_LOCATION).toString();
19+
20+
const getAccessTokensURL = (installationID: number) => `/app/installations/${installationID}/access_tokens`;
21+
const installationID = 1234;
22+
const response = {
23+
token: `secret-installation-token-${installationID}`,
24+
expires_at: '1970-01-01T01:00:00.000Z',
25+
permissions: {
26+
metadata: 'read',
27+
},
28+
repository_selection: 'all',
29+
};
30+
it('It tries to get the token, but it gets a malformed response from Github', async () => {
31+
const { token, ...responseWithoutToken } = response;
32+
const github = nock(GITHUB_URL).post(getAccessTokensURL(installationID)).reply(201, responseWithoutToken);
33+
34+
try {
35+
await getToken({
36+
appId: APP_ID,
37+
installationId: installationID,
38+
privateKey: PRIVATE_KEY,
39+
});
40+
} catch (e) {
41+
expect(e).toMatchInlineSnapshot(
42+
'[Error: Something went wrong on the token retrieval, we got instead: {"type":"token","tokenType":"installation","installationId":1234,"permissions":{"metadata":"read"},"expiresAt":"1970-01-01T01:00:00.000Z","repositorySelection":"all"}]'
43+
);
44+
}
45+
46+
expect(github.isDone()).toBe(true);
47+
});
48+
it('Retrieves the token', async () => {
49+
const github = nock(GITHUB_URL).post(getAccessTokensURL(installationID)).reply(201, response);
50+
51+
const { token } = await getToken({
52+
appId: APP_ID,
53+
installationId: installationID,
54+
privateKey: PRIVATE_KEY,
55+
});
56+
57+
expect(token).toMatchInlineSnapshot('"secret-installation-token-1234"');
58+
expect(github.isDone()).toBe(true);
59+
});
60+
61+
it('fails when invoking the command', async () => {
62+
const mockExit = mockProcessExit();
63+
const github = nock(GITHUB_URL)
64+
.post(getAccessTokensURL(installationID))
65+
.replyWithError('failed with some server error');
66+
67+
await getTokenCommand({
68+
appId: APP_ID,
69+
installationId: installationID,
70+
privateKey: PRIVATE_KEY,
71+
});
72+
73+
expect(mockExit).toHaveBeenCalled();
74+
expect(fail).toHaveBeenCalledTimes(1);
75+
expect(github.isDone()).toBe(true);
76+
});
77+
it('invokes getToken via command', async () => {
78+
const github = nock(GITHUB_URL).post(getAccessTokensURL(installationID)).reply(201, response);
79+
80+
const mockExit = mockProcessExit();
81+
await getTokenCommand({
82+
appId: APP_ID,
83+
installationId: installationID,
84+
privateKey: PRIVATE_KEY,
85+
});
86+
87+
expect(mockExit).not.toHaveBeenCalled();
88+
expect(stopAndPersist).toHaveBeenCalledTimes(1);
89+
expect(github.isDone()).toBe(true);
90+
});
91+
92+
it('invokes getToken via command providing a private.key location', async () => {
93+
const github = nock(GITHUB_URL).post(getAccessTokensURL(installationID)).reply(201, response);
94+
95+
const mockExit = mockProcessExit();
96+
await getTokenCommand({
97+
appId: APP_ID,
98+
installationId: installationID,
99+
privateKeyLocation: KEY_LOCATION,
100+
});
101+
102+
expect(mockExit).not.toHaveBeenCalled();
103+
expect(stopAndPersist).toHaveBeenCalledTimes(1);
104+
expect(stopAndPersist.mock.calls).toMatchInlineSnapshot(`
105+
Array [
106+
Array [
107+
Object {
108+
"symbol": "💫",
109+
"text": "The token is: secret-installation-token-1234",
110+
},
111+
],
112+
]
113+
`);
114+
expect(github.isDone()).toBe(true);
115+
});
116+
117+
it('invokes getToken via command not providing a private.key or private.keyLocation', async () => {
118+
const mockExit = mockProcessExit();
119+
await getTokenCommand({
120+
appId: APP_ID,
121+
installationId: installationID,
122+
});
123+
124+
expect(mockExit).toHaveBeenCalled();
125+
expect(fail.mock.calls[0][0]).toMatchInlineSnapshot(
126+
'"Input is not valid, either privateKey or privateKeyLocation should be provided"'
127+
);
128+
});
129+
});

__tests__/greet.spec.ts

-7
This file was deleted.

0 commit comments

Comments
 (0)