Skip to content

Commit 4b7ebb1

Browse files
committed
feat: complete overall flow of app
1 parent c567478 commit 4b7ebb1

File tree

15 files changed

+528
-208
lines changed

15 files changed

+528
-208
lines changed

.eslintrc.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
module.exports = {
2-
extends: ['@tophat', '@tophat/eslint-config/jest']
2+
extends: ['@tophat', '@tophat/eslint-config/jest'],
3+
rules: {
4+
camelcase: 0
5+
}
36
}

src/CommentReply/index.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Queues all replies, and sends in one message
3+
*/
4+
class CommentReply {
5+
constructor({ context }) {
6+
this.context = context
7+
this.message = ''
8+
}
9+
10+
replyingToWho() {
11+
return this.context.payload.comment.user.login
12+
}
13+
14+
replyingToWhere() {
15+
return this.context.payload.comment.html_url
16+
}
17+
18+
reply(message) {
19+
this.message = `\n\n${message}`
20+
}
21+
22+
async send() {
23+
const fromUser = this.replyingToWho()
24+
const body = `@${fromUser} ${this.message}`
25+
const issueComment = this.context.issue({ body })
26+
return this.context.github.issues.createComment(issueComment)
27+
}
28+
}
29+
30+
module.exports = CommentReply

src/ContentFiles/index.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const { generate: generateContentFile } = require('all-contributors-cli')
2+
3+
/*
4+
* Fetches, stores, generates, and updates the readme content files for the contributors list
5+
*/
6+
class ContentFiles {
7+
constructor({ context, repository }) {
8+
this.context = context
9+
this.repository = repository
10+
this.contentFilesByPath = null
11+
}
12+
13+
async fetch(optionsConfig) {
14+
const options = optionsConfig.get()
15+
if (Array.isArray(options.files)) {
16+
this.contentFilesByPath = this.repository.getMultipleFileContents(
17+
options.files,
18+
)
19+
} else {
20+
this.contentFilesByPath = this.repository.getMultipleFileContents([
21+
'README.md',
22+
])
23+
}
24+
}
25+
26+
async generate(optionsConfig) {
27+
const options = optionsConfig.get()
28+
const newReadmeFileContentsByPath = {}
29+
Object.entires(this.contentFilesByPath).forEach(
30+
([filePath, fileContents]) => {
31+
const newFileContents = generateContentFile(
32+
options,
33+
options.contributors,
34+
fileContents,
35+
)
36+
newReadmeFileContentsByPath[filePath] = newFileContents
37+
},
38+
)
39+
this.contentFilesByPath = newReadmeFileContentsByPath
40+
}
41+
}
42+
43+
module.exports = ContentFiles

src/OptionsConfig/index.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const ALL_CONTRIBUTORS_RC = '.all-contributorsrc'
2+
3+
const { addContributorWithDetails } = require('all-contributors-cli')
4+
5+
const { ResourceNotFoundError } = require('../utils/errors')
6+
7+
class OptionsConfig {
8+
constructor({ context, repository, commentReply }) {
9+
this.context = context
10+
this.repository = repository
11+
this.commentReply = commentReply
12+
this.options = null
13+
}
14+
15+
async fetch() {
16+
try {
17+
const rawOptionsFileContent = await this.repository.getFileContents(
18+
ALL_CONTRIBUTORS_RC,
19+
)
20+
try {
21+
const optionsConfig = JSON.parse(rawOptionsFileContent)
22+
this.options = optionsConfig
23+
return optionsConfig
24+
} catch (error) {
25+
if (error instanceof SyntaxError) {
26+
this.commentReply.reply(
27+
`This project's configuration file has malformed JSON: ${ALL_CONTRIBUTORS_RC}. Error:: ${
28+
error.message
29+
}`,
30+
)
31+
this.context.log(error)
32+
error.handled = true
33+
throw error
34+
}
35+
}
36+
} catch (error) {
37+
if (error instanceof ResourceNotFoundError) {
38+
this.commentReply
39+
.reply(`This project is not yet setup for [all-contributors](https://github.com/all-contributors/all-contributors).\n
40+
You will need to first setup [${
41+
this.repository.repo
42+
}](https://github.com/${this.repository.getFullname()}) using the [all-contributors-cli](https://github.com/all-contributors/all-contributors-cli) tool.`)
43+
error.handled = true
44+
throw error
45+
}
46+
}
47+
}
48+
49+
get() {
50+
return this.options
51+
}
52+
53+
getRaw() {
54+
return JSON.stringify(this.options)
55+
}
56+
57+
getPath() {
58+
return ALL_CONTRIBUTORS_RC
59+
}
60+
61+
async addContributor({ login, contributions, name, avatar_url, profile }) {
62+
const newContributorsList = await addContributorWithDetails({
63+
options: this.options,
64+
login,
65+
contributions,
66+
name,
67+
avatar_url,
68+
profile,
69+
})
70+
const newOptions = {
71+
...this.options,
72+
contributors: newContributorsList,
73+
}
74+
this.options = newOptions
75+
return newOptions
76+
}
77+
}
78+
79+
module.exports = OptionsConfig

src/repository/index.js renamed to src/Repository/index.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
class ResourceNotFoundError extends Error {
2-
constructor(filepath, fullRepoName) {
3-
super(`File ${filepath} was not found in repository (${fullRepoName}).`)
4-
this.name = this.constructor.name
5-
}
6-
}
1+
const { ResourceNotFoundError } = require('../utils/errors')
72

83
class Repository {
94
constructor(context) {
@@ -38,6 +33,7 @@ class Repository {
3833
}
3934

4035
async getMultipleFileContents(filePathsArray) {
36+
// TODO: can probably optimise this instead of sending a request per file
4137
const repository = this
4238
if (filePathsArray.length > 5) {
4339
throw new Error(`Cannot fetch more than 5 files.`)
@@ -58,9 +54,17 @@ class Repository {
5854

5955
return
6056
}
61-
}
6257

63-
module.exports = {
64-
Repository,
65-
ResourceNotFoundError,
58+
async createPullRequest({title, body, fileContentsByPath}) {
59+
// TODO: Create branch, update files
60+
// GET master state when we read files
61+
// https://octokit.github.io/rest.js/#api-Git-createRef
62+
// https://octokit.github.io/rest.js/#api-Repos-updateFile
63+
64+
// TODO: post pull request
65+
// https://octokit.github.io/rest.js/#api-Pulls-createFromIssue
66+
return pullRequestNumber
67+
}
6668
}
69+
70+
module.exports = Repository

src/index.js

Lines changed: 3 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -1,189 +1,10 @@
1-
/* eslint-disable camelcase */
2-
3-
const {
4-
addContributorWithDetails,
5-
generate: generateContentFile,
6-
} = require('all-contributors-cli')
7-
8-
const { Repository, ResourceNotFoundError } = require('./repository')
9-
// const parseComment = require('./parse-comment')
10-
11-
const GIHUB_BOT_NAME = '@AllContributorsBot'
12-
const ALL_CONTRIBUTORS_RC = '.all-contributorsrc'
13-
14-
async function createComment({ context, body }) {
15-
const issueComment = context.issue({ body })
16-
return context.github.issues.createComment(issueComment)
17-
}
18-
19-
async function getReadmeFileContentsByPath({ repository, files }) {
20-
if (Array.isArray(files)) {
21-
return repository.getMultipleFileContents(files)
22-
} else {
23-
// default 'files' is ['README.md']
24-
return repository.getMultipleFileContents(['README.md'])
25-
}
26-
}
27-
28-
async function getUserDetials({ context, username }) {
29-
// TODO: optimzation, if commenting user is the user we're adding we can avoid an api call
30-
// const commentUser = context.payload.comment.user.login
31-
// if (user === commentUser) {
32-
// return {
33-
// name: context.payload.comment.user.name
34-
// avatarUrl: context.payload.comment.avatar_url
35-
// profile:
36-
// }
37-
// }
38-
39-
const result = await context.github.users.getByUsername({ username })
40-
const { avatar_url, blog, html_url, name } = result.data
41-
42-
return {
43-
name: name || username,
44-
avatar_url,
45-
profile: blog || html_url,
46-
}
47-
}
48-
49-
async function addContributor({
50-
options,
51-
login,
52-
contributions,
53-
name,
54-
avatar_url,
55-
profile,
56-
}) {
57-
const newContributorsList = await addContributorWithDetails({
58-
options,
59-
login,
60-
contributions,
61-
name,
62-
avatar_url,
63-
profile,
64-
})
65-
return { ...options, contributors: newContributorsList }
66-
}
67-
68-
async function generateContentFiles({ options, readmeFileContentsByPath }) {
69-
const newReadmeFileContentsByPath = {}
70-
Object.entires(readmeFileContentsByPath).forEach(
71-
([filePath, fileContents]) => {
72-
const newFileContents = generateContentFile(
73-
options,
74-
options.contributors,
75-
fileContents,
76-
)
77-
newReadmeFileContentsByPath[filePath] = newFileContents
78-
},
79-
)
80-
return newReadmeFileContentsByPath
81-
}
82-
83-
async function processNewIssueComment(context) {
84-
if (context.isBot) {
85-
context.log('From a bot, exiting')
86-
return
87-
}
88-
89-
const fromUser = context.payload.comment.user.login
90-
const commentBody = context.payload.comment.body
91-
const hasMentionedBotName = commentBody.includes(GIHUB_BOT_NAME)
92-
93-
if (!hasMentionedBotName) {
94-
context.log('Message not for us, exiting')
95-
return
96-
}
97-
98-
const repository = new Repository(context)
99-
100-
let optionsFileContent
101-
try {
102-
const rawOptionsFileContent = await repository.getFileContents(
103-
ALL_CONTRIBUTORS_RC,
104-
)
105-
optionsFileContent = JSON.parse(rawOptionsFileContent)
106-
// TODO: if JSON has error report that
107-
} catch (error) {
108-
if (error instanceof ResourceNotFoundError) {
109-
await createComment({
110-
context,
111-
body: `@${fromUser} This project is not yet setup for [all-contributors](https://github.com/all-contributors/all-contributors).\n
112-
You will need to first setup [${
113-
repository.repo
114-
}](https://github.com/${repository.getFullname()}) using the [all-contributors-cli](https://github.com/all-contributors/all-contributors-cli) tool.`,
115-
})
116-
context.log(error)
117-
return
118-
}
119-
}
120-
context.log('Options Content')
121-
context.log(optionsFileContent)
122-
123-
// TODO parse comment and gain intentions
124-
// const { who, contributions } = parseComment(commentBody)
125-
// We had trouble reading your comment. Basic usage:\n\n\`@${GIHUB_BOT_NAME} please add jakebolam for code\`
126-
const who = 'jakebolam'
127-
const contributions = ['code']
128-
129-
const { name, avatar_url, profile } = await getUserDetials({
130-
context,
131-
username: who,
132-
})
133-
134-
const newOptionsContent = await addContributor({
135-
options: optionsFileContent,
136-
login: who,
137-
contributions,
138-
name,
139-
avatar_url,
140-
profile,
141-
})
142-
context.log('New Options Content')
143-
context.log(newOptionsContent)
144-
145-
const readmeFileContentsByPath = await getReadmeFileContentsByPath({
146-
repository,
147-
files: optionsFileContent.files,
148-
})
149-
150-
context.log('Readme file contents by path')
151-
context.log(readmeFileContentsByPath)
152-
153-
const newReadmeFileContentsByPath = await generateContentFiles({
154-
options: newOptionsContent,
155-
readmeFileContentsByPath,
156-
})
157-
context.log('New readme file contents by path')
158-
context.log(newReadmeFileContentsByPath)
159-
160-
// TODO: Create branch, update files
161-
// GET master state when we read files
162-
// https://octokit.github.io/rest.js/#api-Git-createRef
163-
// https://octokit.github.io/rest.js/#api-Repos-updateFile
164-
165-
// TODO: post pull request
166-
// https://octokit.github.io/rest.js/#api-Pulls-createFromIssue
167-
168-
// TODO: Comment back with link to pull request
169-
}
1+
const processIssueComment = require('./processIssueComment')
1702

1713
module.exports = app => {
1724
// issueComment.edited
1735
// Issue comments and PR comments both create issue_comment events
1746
app.on('issue_comment.created', async context => {
175-
try {
176-
await processNewIssueComment(context)
177-
} catch (error) {
178-
await createComment({
179-
context,
180-
body: `@${
181-
context.payload.comment.user.login
182-
} we had trouble processing your request. \n\nError: ${
183-
error.message
184-
}`,
185-
})
186-
throw error
187-
}
7+
app.log(context)
8+
await processIssueComment(context)
1889
})
18910
}

0 commit comments

Comments
 (0)