Skip to content

Commit 6e709a7

Browse files
Introduce Repository class for Git operations, refactor utilities to support enhanced Config, and streamline pull request handling
1 parent 33b5289 commit 6e709a7

6 files changed

Lines changed: 230 additions & 31 deletions

File tree

src/main.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { cwd, readConfig } from './utils/filesystem'
2-
import { context } from '@actions/github'
1+
import { cwd, readConfig, readFile, writeFile } from './utils/filesystem'
2+
import { context, getOctokit } from '@actions/github'
33
import { parse } from './utils/inputs'
44
import { info } from '@actions/core'
55
import { Config } from './types/config'
6+
import { Repository } from './utils/repository'
7+
import { setPreview } from './utils/preview'
8+
import { setOutputs } from './utils/outputs'
69

710
const previewUpdater = async () => {
811
// Welcome
@@ -15,28 +18,57 @@ const previewUpdater = async () => {
1518
} = parse()
1619

1720
// Load Config
18-
const config = readConfig(<Config>{
21+
const config: Config = readConfig(<Config>{
1922
repository: {
2023
owner: context.repo.owner,
2124
repo: context.repo.repo,
22-
token: token
25+
octokit: getOctokit(token)
2326
}
2427
}, configPath)
2528

2629
// Authenticate
27-
const repo = new Repository(owner, repoName, token)
28-
await repo.authenticate(commitAuthorName, commitAuthorEmail)
30+
const repo = new Repository(config)
31+
await repo.authenticate()
32+
33+
// Checkout branch
34+
const branchExists = await repo.branchExists()
35+
info(`Checkout ${ branchExists ? 'existing' : 'new' } branch named "${ repo.branchName() }"`)
36+
await repo.checkoutBranch(! branchExists)
37+
38+
// Read file
39+
const content = readFile(config, config.path.readme)
40+
const preview = setPreview(content, config)
41+
42+
if (content !== preview) {
43+
info(`Update readme in "${ config.path.readme }" file`)
44+
writeFile(config, config.path.readme, preview)
45+
} else {
46+
info(`File "${ config.path.readme }" is up to date`)
47+
}
48+
49+
// Stage and commit changes
50+
await repo.stage()
51+
await repo.commit()
52+
await repo.push()
53+
54+
// Create a Pull Request
55+
const pullRequest = await repo.createPullRequest()
2956

3057
// Variables
31-
let pullRequestNumber: number = null
32-
let pullRequestUrl: string = null
58+
const pullRequestNumber: number = pullRequest.data.number
59+
const pullRequestUrl: string = pullRequest.data.html_url
3360

34-
// Checkout branch
35-
const branchExists = await repo.branchExists(branchName)
36-
info(`Checkout ${ branchExists ? 'existing' : 'new' } branch named "${ branchName }"`)
37-
await repo.checkoutBranch(branchName, ! branchExists)
61+
if (config.repository.pullRequest.assignees.length > 0) {
62+
await repo.assignee(pullRequestNumber, config.repository.pullRequest.assignees)
63+
}
64+
65+
if (config.repository.pullRequest.labels.length > 0) {
66+
await repo.addLabels(pullRequestNumber, config.repository.pullRequest.labels)
67+
}
68+
69+
info(`Preview created in pull request #${ pullRequestNumber }: ${ pullRequestUrl }`)
3870

39-
// TODO: Stop at https://github.com/FantasticFiasco/action-update-license-year/blob/main/src/main.js#L76
71+
setOutputs(repo.branchName(), pullRequestNumber, pullRequestUrl)
4072
}
4173

4274
export default previewUpdater

src/types/config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export interface Repository
4545
{
4646
owner: string;
4747
repo: string;
48-
token: string;
48+
octokit?: any;
4949

5050
commit: Commit;
5151
pullRequest: PullRequest;
@@ -92,7 +92,6 @@ export const defaultConfig: Config = {
9292
repository: {
9393
owner: '',
9494
repo: '',
95-
token: '',
9695

9796
commit: {
9897
branch: 'preview/update-{timestamp}',

src/utils/filesystem.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as fs from 'node:fs'
22
import { Config, defaultConfig } from '../types/config'
33
import * as yaml from 'js-yaml'
44
import { deepmerge } from 'deepmerge-ts'
5+
import { exec as nodeExec, ExecException } from 'node:child_process'
56

67
export const cwd = (): string => {
78
const path = process.env.GITHUB_WORKSPACE
@@ -40,3 +41,15 @@ export const readConfig = (config: Config, userConfigPath: string): Config => {
4041

4142
return <Config>deepmerge(defaultConfig, userConfig, config)
4243
}
44+
45+
export const exec = (command: string): Promise<any> => {
46+
return new Promise((resolve, reject): void => {
47+
nodeExec(command, (error: ExecException | null, stdout: string, stderr: string): void => {
48+
if (error || stderr) {
49+
reject(error || stderr)
50+
}
51+
52+
resolve(stdout)
53+
})
54+
})
55+
}

src/utils/outputs.js.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/utils/outputs.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { setOutput } from '@actions/core'
2+
3+
export const setOutputs = (branchName: string, pullRequestNumber: number, pullRequestUrl: string): void => {
4+
setOutput('branchName', branchName)
5+
setOutput('pullRequestNumber', pullRequestNumber)
6+
setOutput('pullRequestUrl', pullRequestUrl)
7+
}

src/utils/repository.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { Config } from '../types/config'
2+
import { exec } from './filesystem'
3+
4+
export class Repository
5+
{
6+
private _config: Config
7+
private _timestamp: string = Date.now().toString()
8+
private _currentBranch: string = ''
9+
private _newBranch: boolean = false
10+
11+
constructor(config: Config)
12+
{
13+
this._config = config
14+
}
15+
16+
async authenticate()
17+
{
18+
try {
19+
const author = this._config.repository.commit.author
20+
21+
await exec(`git config user.name "${ author.name }"`)
22+
await exec(`git config user.email "${ author.email }"`)
23+
} catch (error) {
24+
// @ts-ignore
25+
error.message = `Error authenticating user "${ author.name }" with e-mail "${ author.email }": ${ error.message }`
26+
27+
throw error
28+
}
29+
}
30+
31+
async branchExists()
32+
{
33+
return await exec(`git rev-parse --verify ${ this.branchName() }`)
34+
.then(() => true)
35+
.catch(error => {
36+
throw new Error(error.message)
37+
})
38+
}
39+
40+
async checkoutBranch(isNew: boolean)
41+
{
42+
try {
43+
this._newBranch = isNew
44+
45+
await exec(`git switch ${ isNew ? '-c' : '' } "${ this.branchName() }"`)
46+
} catch (error) {
47+
// @ts-ignore
48+
error.message = `Error checking out ${ isNew ? 'new' : 'existing' } branch "${ this.branchName() }": ${ error.message }`
49+
50+
throw error
51+
}
52+
}
53+
54+
async stage()
55+
{
56+
try {
57+
await exec('git add ' + this._config.path.readme)
58+
} catch (error) {
59+
// @ts-ignore
60+
error.message = `Error staging file "${ this._config.path.readme }": ${ error.message }`
61+
62+
throw error
63+
}
64+
}
65+
66+
async commit()
67+
{
68+
try {
69+
const message = this._config.repository.commit.title + '\n' + this._config.repository.commit.body
70+
71+
exec(`git commit -m "${ message }"`)
72+
} catch (error) {
73+
// @ts-ignore
74+
error.message = `Error committing file "${ this._config.path.readme }": ${ error.message }`
75+
76+
throw error
77+
}
78+
}
79+
80+
async push()
81+
{
82+
try {
83+
let cmd = 'git push'
84+
85+
if (this._newBranch) {
86+
cmd += ` --set-upstream origin ${ this.branchName() }`
87+
}
88+
89+
exec(cmd)
90+
91+
this._newBranch = false
92+
} catch (error) {
93+
// @ts-ignore
94+
error.message = `Error pushing changes to "${ this.branchName() } branch": ${ error.message }`
95+
96+
throw error
97+
}
98+
}
99+
100+
async createPullRequest()
101+
{
102+
try {
103+
const defaultBranch = await exec(`git remote show origin | grep 'HEAD branch' | cut -d ' ' -f5`)
104+
105+
return this._config.repository.octokit.rest.pulls.create({
106+
owner: this._config.repository.owner,
107+
repo: this._config.repository.repo,
108+
title: this._config.repository.pullRequest.title,
109+
body: this._config.repository.pullRequest.body,
110+
head: this.branchName(),
111+
base: defaultBranch.trim()
112+
})
113+
} catch (error) {
114+
// @ts-ignore
115+
error.message = `Error when creating pull request from ${ this.branchName() }: ${ error.message }`
116+
117+
throw error
118+
}
119+
}
120+
121+
async assignee(issueNumber: number, assignees: string[])
122+
{
123+
try {
124+
return this._config.repository.octokit.rest.issues.addAssignees({
125+
owner: this._config.repository.owner,
126+
repo: this._config.repository.repo,
127+
issue_number: issueNumber,
128+
assignees: assignees
129+
})
130+
} catch (error) {
131+
// @ts-ignore
132+
error.message = `Error when adding assignees to issue ${ issueNumber }: ${ error.message }`
133+
134+
throw error
135+
}
136+
}
137+
138+
async addLabels(issueNumber: number, labels: string[])
139+
{
140+
try {
141+
return this._config.repository.octokit.rest.issues.addLabels({
142+
owner: this._config.repository.owner,
143+
repo: this._config.repository.repo,
144+
issue_number: issueNumber,
145+
labels
146+
})
147+
} catch (error) {
148+
// @ts-ignore
149+
error.message = `Error when adding labels to issue ${ issueNumber }: ${ error.message }`
150+
151+
throw error
152+
}
153+
}
154+
155+
branchName(): string
156+
{
157+
if (this._currentBranch === '') {
158+
this._currentBranch = this._config.repository.commit.branch
159+
.replace('{timestamp}', this._timestamp)
160+
}
161+
162+
return this._currentBranch
163+
}
164+
}

0 commit comments

Comments
 (0)