Skip to content

Commit 7e1bd11

Browse files
authored
feat: ignore existing files previous to commits in push event (#19)
* feat: ignore existing files * ci: setup dependabot * docs: update docs * feat: add build
1 parent 1020606 commit 7e1bd11

File tree

5 files changed

+79
-17
lines changed

5 files changed

+79
-17
lines changed

.github/dependabot.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
version: 2
2+
updates:
3+
# Enable version updates for npm
4+
- package-ecosystem: "npm"
5+
directory: "/"
6+
schedule:
7+
interval: "weekly"

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
[![CI](https://github.com/protiumx/blogpub/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/protiumx/blogpub/actions/workflows/ci.yml)
44

5-
Github action to publish your blog articles to [Medium](https://medium.com/) or [Dev.to](http://dev.to/)
5+
Github action to publish your blog articles to [Medium](https://medium.com/) or [Dev.to](http://dev.to/).
6+
It gets the files from a `push` event and process the first `md` file that find.
7+
**The file is ignored if it already existed before the push event.**
8+
This avoid publishing again when making fixes on an existing article.
69

710
## Pre-requisites
811

build/blogpub.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type Github = ReturnType<typeof getOctokit>;
1414

1515
async function loadArticleFile(
1616
github: Github, folderName: string,
17-
): Promise<{ fileName: string, content: string}> {
17+
): Promise<{ fileName: string, content: string }> {
1818
const { owner, repo } = context.repo;
1919
// NOTE: Pagination returns 30 files by default
2020
const commit = (
@@ -37,6 +37,19 @@ async function loadArticleFile(
3737
return { fileName: newArticle.filename!, content };
3838
}
3939

40+
// Check if file already existed in a commit
41+
async function checkFileExists(github: Github, filePath: string, ref: string): Promise<boolean> {
42+
const { owner, repo } = context.repo;
43+
const res = await github.request('GET /repos/{owner}/{repo}/contents/{path}/{?ref}', {
44+
owner,
45+
repo,
46+
ref,
47+
path: filePath,
48+
});
49+
return res.status === 200;
50+
}
51+
52+
4053
export async function run() {
4154
try {
4255
const ghToken = core.getInput('gh_token', { required: true });
@@ -45,10 +58,20 @@ export async function run() {
4558
const mediumUserId = core.getInput('medium_user_id', { required: true });
4659
const mediumBaseUrl = core.getInput('medium_base_url', { required: false });
4760
const devtoApiKey = core.getInput('devto_api_key', { required: true });
61+
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
62+
const eventPayload = require(process.env.GITHUB_EVENT_PATH as string);
4863

4964
const github = getOctokit(ghToken);
5065

5166
const articleFile = await loadArticleFile(github, articlesFolder);
67+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
68+
const articleAlreadyExists = await checkFileExists(github, articleFile.fileName, eventPayload.before);
69+
/* istanbul ignore next */
70+
if (articleAlreadyExists) {
71+
/* istanbul ignore next */
72+
core.debug(`Article ${articleFile.fileName} already published. Skipping.`);
73+
return;
74+
}
5275
const rawGithubUrl = context.serverUrl
5376
.replace('//github.com', '//raw.githubusercontent.com');
5477
const { repo, owner } = context.repo;

test/main.spec.ts

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,26 @@ jest.mock('@actions/github', () => ({
3636
}));
3737
jest.mock('@actions/core');
3838

39+
process.env.GITHUB_EVENT_PATH = 'event.json';
40+
jest.mock('event.json', () => ({
41+
// __esModule: true,
42+
default: { before: 'sha' },
43+
}), { virtual: true });
44+
3945
describe('blogpub', () => {
4046
beforeEach(jest.clearAllMocks);
4147

4248
(getOctokit as jest.Mock).mockReturnValue(octokitMock);
4349

4450
const fileData = {
45-
data: {
46-
files: [
47-
{
48-
filename: 'blogs/blog-01.md',
49-
},
50-
],
51-
},
52-
};
51+
data: {
52+
files: [
53+
{
54+
filename: 'blogs/blog-01.md',
55+
},
56+
],
57+
},
58+
};
5359

5460
it('should set failed when fails to get PR info', async () => {
5561
const err = new Error('github');
@@ -99,8 +105,26 @@ describe('blogpub', () => {
99105
expect(promises.readFile).toHaveBeenCalledWith('./blogs/blog-01.md', 'utf8');
100106
expect(core.setFailed).toHaveBeenCalledWith(err);
101107
});
108+
109+
it('should skip if file already exists in repository', async () => {
110+
(core.getInput as jest.Mock).mockImplementation((key: string) => {
111+
return key === 'articles_folder' ? 'blogs' : '';
112+
});
113+
octokitMock.request.mockResolvedValue({
114+
data: {
115+
files: [
116+
{
117+
filename: 'readme.txt',
118+
},
119+
],
120+
},
121+
});
122+
octokitMock.request.mockResolvedValueOnce({ status: 200 });
123+
await run();
124+
expect(parseArticle).not.toHaveBeenCalled();
125+
});
102126

103-
it('should set failed if fails to parse article', async () => {
127+
it('should set failed if fails to parse file into article', async () => {
104128
(core.getInput as jest.Mock).mockImplementation((key: string) => {
105129
return key === 'articles_folder' ? 'blogs' : '';
106130
});
@@ -111,7 +135,8 @@ describe('blogpub', () => {
111135
(parseArticle as jest.Mock).mockImplementation(() => {
112136
throw err;
113137
});
114-
octokitMock.request.mockResolvedValue(fileData);
138+
octokitMock.request.mockResolvedValueOnce(fileData);
139+
octokitMock.request.mockResolvedValueOnce({ status: 404 });
115140

116141
await run();
117142

@@ -143,7 +168,8 @@ describe('blogpub', () => {
143168
content: 'parsed',
144169
});
145170
(medium.createArticle as jest.Mock).mockRejectedValue(err);
146-
octokitMock.request.mockResolvedValue(fileData);
171+
octokitMock.request.mockResolvedValueOnce(fileData);
172+
octokitMock.request.mockResolvedValueOnce({ status: 404 });
147173

148174
await run();
149175

@@ -168,7 +194,8 @@ describe('blogpub', () => {
168194
});
169195
(devto.createArticle as jest.Mock).mockRejectedValue(new Error(''));
170196
(medium.createArticle as jest.Mock).mockResolvedValue({ url: 'medium.com/new' });
171-
octokitMock.request.mockResolvedValue(fileData);
197+
octokitMock.request.mockResolvedValueOnce(fileData);
198+
octokitMock.request.mockResolvedValueOnce({ status: 404 });
172199

173200
await run();
174201

@@ -203,7 +230,8 @@ describe('blogpub', () => {
203230
});
204231
(medium.createArticle as jest.Mock).mockResolvedValue({ url: 'medium.com/new' });
205232
(devto.createArticle as jest.Mock).mockRejectedValue(err);
206-
octokitMock.request.mockResolvedValue(fileData);
233+
octokitMock.request.mockResolvedValueOnce(fileData);
234+
octokitMock.request.mockResolvedValueOnce({ status: 404 });
207235

208236
await run();
209237

@@ -236,7 +264,8 @@ describe('blogpub', () => {
236264
});
237265
(medium.createArticle as jest.Mock).mockResolvedValue({ url: 'medium.com/new' });
238266
(devto.createArticle as jest.Mock).mockResolvedValue({ url: 'dev.to/new' });
239-
octokitMock.request.mockResolvedValue(fileData);
267+
octokitMock.request.mockResolvedValueOnce(fileData);
268+
octokitMock.request.mockResolvedValueOnce({ status: 404 });
240269

241270
await run();
242271

0 commit comments

Comments
 (0)