-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5637da0
commit 44a5d17
Showing
7 changed files
with
372 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
{ | ||
"GITHUB_TOKEN": "PAT_GOES_HERE" | ||
"GITHUB_TOKEN": "PAT_GOES_HERE", | ||
"OWNER": "OWNER_GOES_HERE", | ||
"REPO": "REPO_WILL_BE_SET_DURING_SETUP" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
tests/.test-repo-template/.github/workflows/release-dev.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
name: Tag Release | ||
|
||
on: | ||
workflow_dispatch: | ||
push: | ||
branches: | ||
- main | ||
|
||
concurrency: | ||
group: developemnt | ||
cancel-in-progress: false | ||
|
||
jobs: | ||
tag: | ||
name: Tag & Create Release | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
token: ${{ secrets.PAT }} | ||
|
||
- uses: wemogy/[email protected] | ||
id: get-release-version | ||
|
||
- name: Create GitHub Release | ||
uses: wemogy/[email protected] | ||
if: steps.get-release-version.outputs.has-changes == 'true' && steps.get-release-version.outputs.previous-version-name != 'v0.0.0' | ||
with: | ||
github-token: ${{ secrets.PAT }} | ||
release-version-tag: ${{ steps.get-release-version.outputs.version-name }} | ||
previous-version-tag: ${{ steps.get-release-version.outputs.previous-version-name }} | ||
release-title: Version ${{ steps.get-release-version.outputs.version }} | ||
|
||
- name: Create GitHub Release | ||
uses: wemogy/[email protected] | ||
if: steps.get-release-version.outputs.has-changes == 'true' && steps.get-release-version.outputs.previous-version-name == 'v0.0.0' | ||
with: | ||
github-token: ${{ secrets.PAT }} | ||
release-version-tag: ${{ steps.get-release-version.outputs.version-name }} | ||
previous-version-tag: "" | ||
release-title: Version ${{ steps.get-release-version.outputs.version }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import { Octokit } from "@octokit/rest"; | ||
import * as fs from "fs"; | ||
import path from "path"; | ||
import { SimpleGit } from "simple-git"; | ||
import { SodiumPlus, X25519PublicKey } from "sodium-plus"; | ||
|
||
export default class TestingUtils { | ||
public constructor( | ||
private readonly owner: string, | ||
private readonly repo: string, | ||
private readonly repoDir: string, | ||
private readonly git: SimpleGit, | ||
private readonly octokit: Octokit | ||
) {} | ||
|
||
public async implementIssue(issueNumber: number, type: "feat" | "fix") { | ||
// Destructuring | ||
const { owner, repo, repoDir, git, octokit } = this; | ||
|
||
// Create a new branch | ||
await git.checkout("main"); | ||
await git.checkoutLocalBranch(`${type}/${issueNumber}`); | ||
|
||
// Create a file for the feature | ||
const filename = path.join(repoDir, `feature-${type}-${issueNumber}.txt`); | ||
fs.writeFileSync(filename, `This is the ${type} for issue #${issueNumber}`); | ||
|
||
// Commit the file | ||
await git.add(filename); | ||
await git.commit(`feat: implement feature for issue #${issueNumber}`); | ||
|
||
// Publish the branch | ||
await git.push("origin", `${type}/${issueNumber}`); | ||
|
||
// Create a pull request | ||
const pullRequest = await octokit.pulls.create({ | ||
owner, | ||
repo, | ||
title: `Closes #${issueNumber}`, | ||
head: `${type}/${issueNumber}`, | ||
base: "main", | ||
}); | ||
|
||
// Merge the pull request | ||
await this.mergePullRequest(pullRequest.data.number); | ||
} | ||
|
||
public async createRelease() { | ||
// Destructuring | ||
const { owner, repo, git, octokit } = this; | ||
|
||
// check if branch called release exists in remote | ||
const branches = await git.branch(["-r"]); | ||
const releaseBranch = branches.all.find((b) => b === "origin/release"); | ||
if (!releaseBranch) { | ||
await git.checkout("main"); | ||
await git.checkoutLocalBranch("release"); | ||
await git.push("origin", "release"); | ||
} | ||
|
||
// Create a pull request from main to release | ||
const pullRequest = await octokit.pulls.create({ | ||
owner, | ||
repo, | ||
title: "Release", | ||
head: "main", | ||
base: "release", | ||
}); | ||
|
||
// Merge the pull request | ||
await this.mergePullRequest(pullRequest.data.number); | ||
} | ||
|
||
public async addRepositorySecret(secretName: string, secretValue: string) { | ||
// Destructuring | ||
const { owner, repo, octokit } = this; | ||
|
||
// Step 1: Get the public key of the repository | ||
const { | ||
data: { key, key_id }, | ||
} = await octokit.rest.actions.getRepoPublicKey({ | ||
owner, | ||
repo, | ||
}); | ||
|
||
// Step 2: Encrypt the secret using GitHub's public key | ||
const sodium = await SodiumPlus.auto(); | ||
const publicKey = X25519PublicKey.from(Buffer.from(key, "base64") as any); | ||
|
||
const encryptedValue = await sodium.crypto_box_seal(secretValue, publicKey); | ||
|
||
// Step 3: Store the encrypted secret | ||
await octokit.rest.actions.createOrUpdateRepoSecret({ | ||
owner, | ||
repo, | ||
secret_name: secretName, | ||
encrypted_value: Buffer.from(encryptedValue).toString("base64"), | ||
key_id: key_id, | ||
}); | ||
} | ||
|
||
public async waitForAllActionsToComplete() { | ||
// Destructuring | ||
const { owner, repo, octokit } = this; | ||
|
||
// Get all workflows | ||
const { data: workflows } = await octokit.actions.listRepoWorkflows({ | ||
owner, | ||
repo, | ||
}); | ||
|
||
// wait 10 seconds for all workflows to start | ||
await new Promise((resolve) => setTimeout(resolve, 10000)); | ||
|
||
// For each workflow, fetch all workflow runs and wait for completion | ||
for (const workflow of workflows.workflows) { | ||
// Get the workflow id | ||
const workflowId = workflow.id; | ||
|
||
// Fetch all workflow runs and wait for completion | ||
await this.fetchAllWorkflowRunsAndWaitForCompletion(workflowId); | ||
} | ||
} | ||
|
||
private async mergePullRequest(pullRequestNumber: number, retries = 0) { | ||
// Destructuring | ||
const { owner, repo, octokit } = this; | ||
|
||
// Merge the pull request | ||
try { | ||
await octokit.pulls.merge({ | ||
owner, | ||
repo, | ||
pull_number: pullRequestNumber, | ||
}); | ||
} catch (e) { | ||
if (retries >= 2) { | ||
throw e; | ||
} | ||
// wait for 5 seconds and try again | ||
await new Promise((resolve) => setTimeout(resolve, 5000)); | ||
await this.mergePullRequest(pullRequestNumber, retries + 1); | ||
} | ||
} | ||
|
||
private async fetchAllWorkflowRunsAndWaitForCompletion( | ||
workflowId: number | ||
): Promise<void> { | ||
// Destructuring | ||
const { owner, repo, octokit } = this; | ||
|
||
// Fetch all workflow runs | ||
const { data: workflowRuns } = await octokit.actions.listWorkflowRuns({ | ||
owner, | ||
repo, | ||
workflow_id: workflowId, | ||
}); | ||
|
||
// number of workflow runs in progress | ||
const inProgressRuns = workflowRuns.workflow_runs.filter( | ||
(run) => run.status === "in_progress" | ||
).length; | ||
|
||
if (inProgressRuns === 0) { | ||
return; | ||
} | ||
|
||
// Wait for all runs to complete | ||
await new Promise((resolve) => setTimeout(resolve, 10000)); | ||
|
||
await this.fetchAllWorkflowRunsAndWaitForCompletion(workflowId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,114 @@ | ||
import { Octokit } from "@octokit/rest"; | ||
import * as fs from "fs"; | ||
import path from "path"; | ||
import simpleGit, { SimpleGit } from "simple-git"; | ||
import TestingUtils from "./TestingUtils"; | ||
|
||
function copyRepoTemplateToRepository(repoDir: string) { | ||
// Copy all files from test-repo-template directory to the new repository | ||
const templateDir = path.join(__dirname, ".test-repo-template"); | ||
const files = fs.readdirSync(templateDir, { recursive: true }); | ||
for (const file of files) { | ||
if (typeof file !== "string") { | ||
continue; | ||
} | ||
|
||
// continue if file is a directory | ||
if (fs.statSync(path.join(templateDir, file)).isDirectory()) { | ||
continue; | ||
} | ||
|
||
// ensure that the directory exists | ||
const dir = path.dirname(file); | ||
if (!fs.existsSync(path.join(repoDir, dir))) { | ||
fs.mkdirSync(path.join(repoDir, dir), { recursive: true }); | ||
} | ||
|
||
const source = path.join(templateDir, file); | ||
const target = path.join(repoDir, file); | ||
fs.copyFileSync(source, target); | ||
} | ||
} | ||
|
||
const gitPath = path.join(__dirname, "../.."); | ||
|
||
module.exports = async () => { | ||
console.log("Setup tests"); | ||
// generate new unique repo name and store it in env.json | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
const envPath = path.resolve(__dirname, "../env/env.json"); | ||
const env = require(envPath); | ||
env.REPO = `test-repo-${Date.now()}`; | ||
fs.writeFileSync(envPath, JSON.stringify(env, null, 2)); | ||
|
||
// Create services | ||
const owner = env.OWNER; | ||
const repo = env.REPO; | ||
const repoDir = path.join(gitPath, repo); | ||
const octokit = new Octokit({ | ||
auth: env.GITHUB_TOKEN, | ||
}); | ||
const git: SimpleGit = simpleGit({}); | ||
const utils = new TestingUtils(owner, repo, repoDir, git, octokit); | ||
|
||
// Create a new repository | ||
const repository = await octokit.repos.createForAuthenticatedUser({ | ||
name: repo, | ||
delete_branch_on_merge: true, | ||
}); | ||
console.log("Created repository", repository.data.html_url); | ||
|
||
// Add the PAT secret to the repository | ||
await utils.addRepositorySecret("PAT", env.GITHUB_TOKEN); | ||
|
||
// Clone the repository to repoDir | ||
await git.clone(repository.data.clone_url, repoDir); | ||
|
||
// Set the working directory to the repository and checkout main branch | ||
await git.cwd(repoDir); | ||
await git.checkoutLocalBranch("main"); | ||
|
||
// Create README.md and push to the repository (initial commit) | ||
const readme = path.join(repoDir, "README.md"); | ||
fs.writeFileSync(readme, `# ${repo}`); | ||
await git.add(readme); | ||
await git.commit("Initial commit"); | ||
await git.push("origin", "main"); | ||
|
||
// Copy .test-repo-template to the repository | ||
copyRepoTemplateToRepository(repoDir); | ||
|
||
// Commit and push the files | ||
await git.add("."); | ||
await git.commit("Template files added"); | ||
await git.push("origin", "main"); | ||
|
||
await utils.waitForAllActionsToComplete(); | ||
|
||
// Feature A | ||
const issueFeatureA = await octokit.issues.create({ | ||
owner, | ||
repo, | ||
title: "Feature A", | ||
body: "This is the first feature", | ||
labels: ["enhancement"], | ||
}); | ||
await utils.implementIssue(issueFeatureA.data.number, "feat"); | ||
|
||
await utils.waitForAllActionsToComplete(); | ||
|
||
// Feature B | ||
const issueFeatureB = await octokit.issues.create({ | ||
owner, | ||
repo, | ||
title: "Feature B", | ||
body: "This is the second feature", | ||
labels: ["enhancement"], | ||
}); | ||
await utils.implementIssue(issueFeatureB.data.number, "feat"); | ||
|
||
await utils.waitForAllActionsToComplete(); | ||
await utils.waitForAllActionsToComplete(); | ||
|
||
console.log("Setup completed"); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import path from "path"; | ||
import * as env from "../env/env.json"; | ||
import * as fs from "fs"; | ||
import { Octokit } from "@octokit/rest"; | ||
|
||
const gitPath = path.join(__dirname, "../.."); | ||
|
||
module.exports = async () => { | ||
const owner = env.OWNER; | ||
const repo = env.REPO; | ||
const octokit = new Octokit({ | ||
auth: env.GITHUB_TOKEN, | ||
}); | ||
|
||
await octokit.repos.delete({ | ||
owner: owner, | ||
repo: repo, | ||
}); | ||
|
||
// delete local repository, if it exists | ||
if (fs.existsSync(path.join(gitPath, repo))) { | ||
fs.rmSync(path.join(gitPath, repo), { recursive: true }); | ||
} | ||
|
||
console.log(`Deleted repository ${repo}`); | ||
}; |
Oops, something went wrong.