forked from gautamkrishnar/blog-post-workflow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblog-post-workflow.js
251 lines (236 loc) · 8.48 KB
/
blog-post-workflow.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
const process = require('process');
let Parser = require('rss-parser');
const core = require('@actions/core');
const fs = require('fs');
const dateFormat = require('dateformat');
const exec = require('./exec');
/**
* Builds the new readme by replacing the readme's <!-- BLOG-POST-LIST:START --><!-- BLOG-POST-LIST:END --> tags
* @param previousContent {string}: actual readme content
* @param newContent {string}: content to add
* @return {string}: content after combining previousContent and newContent
*/
const buildReadme = (previousContent, newContent) => {
const tagNameInput = core.getInput('comment_tag_name');
const tagToLookFor = tagNameInput ? `<!-- ${tagNameInput}:` : `<!-- BLOG-POST-LIST:`;
const closingTag = '-->';
const tagNewlineFlag = core.getInput('tag_post_pre_newline');
const startOfOpeningTagIndex = previousContent.indexOf(
`${tagToLookFor}START`,
);
const endOfOpeningTagIndex = previousContent.indexOf(
closingTag,
startOfOpeningTagIndex,
);
const startOfClosingTagIndex = previousContent.indexOf(
`${tagToLookFor}END`,
endOfOpeningTagIndex,
);
if (
startOfOpeningTagIndex === -1 ||
endOfOpeningTagIndex === -1 ||
startOfClosingTagIndex === -1
) {
// Exit with error if comment is not found on the readme
core.error(
`Cannot find the comment tag on the readme:\n<!-- ${tagToLookFor}:START -->\n<!-- ${tagToLookFor}:END -->`
);
process.exit(1);
}
return [
previousContent.slice(0, endOfOpeningTagIndex + closingTag.length),
tagNewlineFlag ? '\n' : '',
newContent,
tagNewlineFlag ? '\n' : '',
previousContent.slice(startOfClosingTagIndex),
].join('');
};
/**
* Code to do git commit
* @return {Promise<void>}
*/
const commitReadme = async () => {
// Getting config
const committerUsername = core.getInput('committer_username');
const committerEmail = core.getInput('committer_email');
const commitMessage = core.getInput('commit_message');
// Doing commit and push
await exec('git', [
'config',
'--global',
'user.email',
committerEmail,
]);
if (GITHUB_TOKEN) {
// git remote set-url origin
await exec('git', ['remote', 'set-url', 'origin',
`https://${GITHUB_TOKEN}@github.com/${process.env.GITHUB_REPOSITORY}.git`]);
}
await exec('git', ['config', '--global', 'user.name', committerUsername]);
await exec('git', ['add', README_FILE_PATH]);
await exec('git', ['commit', '-m', commitMessage]);
await exec('git', ['push']);
core.info('Readme updated successfully in the upstream repository');
// Making job fail if one of the source fails
process.exit(jobFailFlag ? 1 : 0);
};
// Blog workflow code
const userAgent = core.getInput('user_agent');
const acceptHeader = core.getInput('accept_header');
let parser = new Parser({headers: {'User-Agent': userAgent, 'Accept': acceptHeader}});
// Total no of posts to display on readme, all sources combined, default: 5
const TOTAL_POST_COUNT = Number.parseInt(core.getInput('max_post_count'));
// Readme path, default: ./README.md
const README_FILE_PATH = core.getInput('readme_path');
const GITHUB_TOKEN = core.getInput('gh_token');
const FILTER_PARAMS = {
stackoverflow: 'Comment by $author',
stackexchange: 'Comment by $author',
};
/**
* Updates FILTER_PARAMS object with filter parameters
* @param sourceWithParam filter source with param eg: stackoverflow/Comment by $author/
* @return {string} actual source name eg: stackoverflow
*/
const updateAndParseParams = (sourceWithParam) => {
const param = sourceWithParam.split('/'); // Reading params
if (param.length === 3) {
Object.assign(FILTER_PARAMS, {[param[0]]: param[1]});
return param[0];// Returning source name
} else {
return sourceWithParam;
}
};
core.setSecret(GITHUB_TOKEN);
const COMMENT_FILTERS = core
.getInput('filter_comments')
.trim()
.split(',')
.map((item)=>{
const str = item.trim();
if (str.startsWith('stackoverflow') || str.startsWith('stackexchange')) {
return updateAndParseParams(item);
} else {
return str;
}
});
const promiseArray = []; // Runner
const runnerNameArray = []; // To show the error/success message
let postsArray = []; // Array to store posts
let jobFailFlag = false; // Job status flag
const feedObjString = core.getInput('feed_list').trim();
// Reading feed list from the workflow input
let feedList = feedObjString.split(',').map(item => item.trim());
if (feedList.length === 0) {
core.error('Please double check the value of feed_list');
process.exit(1);
}
// filters out every medium comment (PR #4)
const ignoreMediumComments = (item) => !(COMMENT_FILTERS.indexOf('medium') !== -1 &&
item.link.includes('medium.com') &&
item.categories === undefined);
// filters out stackOverflow comments (#16)
const ignoreStackOverflowComments = (item) => !(COMMENT_FILTERS.indexOf('stackoverflow') !== -1 &&
item.link.includes('stackoverflow.com') &&
item.title.startsWith(FILTER_PARAMS.stackoverflow.replace(/\$author/g, item.author)));
// filters out stackOverflow comments (#16)
const ignoreStackExchangeComments = (item) => !(COMMENT_FILTERS.indexOf('stackexchange') !== -1 &&
item.link.includes('stackexchange.com') &&
item.title.startsWith(FILTER_PARAMS.stackexchange.replace(/\$author/g, item.author)));
feedList.forEach((siteUrl) => {
runnerNameArray.push(siteUrl);
promiseArray.push(new Promise((resolve, reject) => {
parser.parseURL(siteUrl).then((data) => {
if (!data.items) {
reject('Cannot read response->item');
} else {
const responsePosts = data.items;
const posts = responsePosts
.filter(ignoreMediumComments)
.filter(ignoreStackOverflowComments)
.filter(ignoreStackExchangeComments)
.map((item) => {
// Validating keys to avoid errors
if (!item.pubDate) {
reject('Cannot read response->item->pubDate');
}
if (!item.title) {
reject('Cannot read response->item->title');
}
if (!item.link) {
reject('Cannot read response->item->link');
}
return {
title: item.title.trim(),
url: item.link.trim(),
date: new Date(item.pubDate.trim())
};
});
resolve(posts);
}
}).catch(reject);
}));
});
// Processing the generated promises
Promise.allSettled(promiseArray).then((results) => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
// Succeeded
core.info(runnerNameArray[index] + ' runner succeeded. Post count: ' + result.value.length);
postsArray.push(...result.value);
} else {
jobFailFlag = true;
// Rejected
core.error(runnerNameArray[index] + ' runner failed, please verify the configuration. Error:');
core.error(result.reason);
}
});
}).finally(() => {
// Sorting posts based on date
if (core.getInput('disable_sort') === 'false') {
postsArray.sort(function (a, b) {
return b.date - a.date;
});
}
// Slicing with the max count
postsArray = postsArray.slice(0, TOTAL_POST_COUNT);
if (postsArray.length > 0) {
try {
const readmeData = fs.readFileSync(README_FILE_PATH, 'utf8');
const template = core.getInput('template');
const postListMarkdown = postsArray.reduce((acc, cur, index) => {
if (template === 'default') {
// Default template: - [$title]($url)
return acc + `\n- [${cur.title}](${cur.url})` + (((index + 1) === postsArray.length) ? '\n' : '');
} else {
// Building with custom template
const date = dateFormat(cur.date, core.getInput('date_format')); // Formatting date
return acc + template
.replace(/\$title/g, cur.title)
.replace(/\$url/g, cur.url)
.replace(/\$date/g, date)
.replace(/\$newline/g, '\n');
}
}, '');
const newReadme = buildReadme(readmeData, postListMarkdown);
// if there's change in readme file update it
if (newReadme !== readmeData) {
core.info('Writing to ' + README_FILE_PATH);
fs.writeFileSync(README_FILE_PATH, newReadme);
if (!process.env.TEST_MODE) {
// noinspection JSIgnoredPromiseFromCall
commitReadme();
}
} else {
core.info('No change detected, skipping');
process.exit(0);
}
} catch (e) {
core.error(e);
process.exit(1);
}
} else {
core.info('0 blog posts fetched');
process.exit(jobFailFlag ? 1 : 0);
}
});