diff --git a/action.yml b/action.yml index 606811d..dcf12bc 100644 --- a/action.yml +++ b/action.yml @@ -13,28 +13,46 @@ inputs: description: 'The string represetation of the repo name (eg: lemlib/lemlib)' required: false - github_token: + token: description: 'Your GitHub Access Token. Necessary to not get rate limited.' required: true default: ${{ github.token }} branch: - description: 'The branch you want to put the depot json on.' + description: 'The branch where the stable depot json will be placed.' required: true default: 'depot' + pre-release-branch: + description: | + The branch of the depot where pre release versions should be placed. + If omitted, then the pre-release versions will not placed in a depot. + required: false + default: 'depot' + path: - description: 'The path to the depot json.' + description: 'The path to the stable depot json.' required: true - default: 'depot.json' + default: 'stable.json' + + pre-release-path: + description: | + The path to the depot where pre release versions should be placed. + If omitted, then the pre-release versions will not placed in a depot. + If pre-release-branch == branch AND pre-release-path == path, + then the pre-release versions will be placed in the same depot as the stable versions. + + If pre-release-branch is not defined, then this input will be ignored. + required: false + default: 'beta.json' readable-json: description: 'Whether the depot json should be formatted to be human readable (true/false).' required: true default: false - messsage: - description: 'The commit message for the new commit depot json.' + message: + description: 'The commit message that will be used when updating the depot' required: false # Define your outputs here. diff --git a/package-lock.json b/package-lock.json index 12bce66..3c8050c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { - "name": "typescript-action", + "name": "pros-depot-from-releases", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "typescript-action", + "name": "pros-depot-from-releases", "version": "0.0.0", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", "@octokit/rest": "^20.0.2", - "adm-zip": "^0.5.10" + "adm-zip": "^0.5.10", + "semver": "^7.5.4" }, "devDependencies": { "@types/adm-zip": "^0.5.5", @@ -6186,7 +6187,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -6201,7 +6201,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -6212,8 +6211,7 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/shebang-command": { "version": "2.0.0", diff --git a/package.json b/package.json index 702eaa9..aadcabe 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,8 @@ "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", "@octokit/rest": "^20.0.2", - "adm-zip": "^0.5.10" + "adm-zip": "^0.5.10", + "semver": "^7.5.4" }, "devDependencies": { "@types/adm-zip": "^0.5.5", diff --git a/src/main.ts b/src/main.ts index e4e6133..efed177 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,54 +1,116 @@ import * as core from '@actions/core' import * as github from '@actions/github' -import { populateDepotJsonFromGithub } from './populate' +import { populateDepotJsonsFromGithub } from './populate' import { Octokit } from '@octokit/rest' import { pushDepotJsonToGithub } from './pushDepot' import { createCommitMessage } from './message' +import { DepotLocation, DepotType, RepositoryIdentifier } from './types' const repoInputRegex = /[^\/\n\s\t]+\/[^\/\n\s\t]+/ +function getRepositoryIdentifier(): RepositoryIdentifier { + const repo: { owner: string; repo: string } = { owner: '', repo: '' } + const repoInput = core.getInput('repo') + + if (repoInput.match(repoInputRegex)) { + const parsedRepoInput = repoInput.split('/') + repo.owner = parsedRepoInput[0] + repo.repo = parsedRepoInput[1] + } else throw new Error('Invalid repository input: ' + repoInput) + return repo +} + +function getDepotLocations( + repo: RepositoryIdentifier +): Record> { + const stableBranch = core.getInput('branch') + const stablePath = core.getInput('path') + const betaBranch = core.getInput('pre-release-branch') + const betaPath = core.getInput('pre-release-path') + + return { + stable: { + ...repo, + branch: stableBranch, + path: stablePath + }, + beta: { + ...repo, + branch: betaBranch, + path: betaPath + } + } +} + +function filterDepots( + locations: ReturnType, + jsons: Awaited> +): Array { + const depots: Array = [] + for (const rawType in locations) { + const type = rawType as DepotType + const location = locations[type] + const json = jsons[type] + + if (location?.path != null && location?.branch != null && json != null) { + depots.push({ + ...location, + path: location.path, + branch: location.branch, + json + }) + } + } + + return depots +} + +async function updateDepotJsons( + locations: ReturnType, + jsons: Awaited>, + client: Octokit +) { + const depots = filterDepots(locations, jsons) + const messageInput = core.getInput('message') + for (const depot of depots) { + const message = createCommitMessage(depot.json, depot, client) + if (message === undefined) continue + await pushDepotJsonToGithub( + depot.json, + depot, + messageInput ?? message, + client + ) + } + return depots +} + /** * The main function for the action. * @returns {Promise} Resolves when the action is complete. */ export async function run(): Promise { try { - const repo: { owner: string; repo: string } = { owner: '', repo: '' } - const repoInput = core.getInput('repo') - - if (repoInput.match(repoInputRegex)) { - const parsedRepoInput = repoInput.split('/') - repo.owner = parsedRepoInput[0] - repo.repo = parsedRepoInput[1] - } else throw new Error('Invalid repository input: ' + repoInput) + const repo = getRepositoryIdentifier() + const locations = getDepotLocations(repo) - const ghToken = core.getInput('token') const readableFlag = core.getInput('readable') === 'true' + const ghToken = core.getInput('token') const client = new Octokit({ auth: ghToken }) + + const unified = + locations.beta.branch === locations.stable.branch && + locations.beta.path === locations.stable.path - const json = await populateDepotJsonFromGithub(repo, client, readableFlag) - - const dest = { - ...repo, - branch: core.getInput('branch'), - path: core.getInput('path') - } - - const message = - core.getInput('message') ?? createCommitMessage(json, dest, client) - - await pushDepotJsonToGithub( - json, - { - owner: dest.owner, - repo: dest.repo, - path: dest.path, - branch: dest.branch - }, - message, - client + const jsons = await populateDepotJsonsFromGithub( + repo, + client, + readableFlag, + unified ) + + updateDepotJsons(locations, jsons, client) } catch (error) { // Fail the workflow run if an error occurs if (error instanceof Error) core.setFailed(error.message) diff --git a/src/message.ts b/src/message.ts index 34b4c48..80f41c1 100644 --- a/src/message.ts +++ b/src/message.ts @@ -6,16 +6,15 @@ function jsonToDepot(json: string): Depot { return JSON.parse(json) } -async function getOldDepot( +async function getOldJson( location: DepotLocation, client: Octokit -): Promise { +): Promise { const res = await client.repos.getContent({ ...location, ref: location.branch }) - const json = res.data.toString() - return jsonToDepot(json) + return res.data.toString() } function depotEntryIsEqual(temp1: DepotEntry, temp2: DepotEntry): boolean { @@ -34,23 +33,41 @@ function depotEntryIsEqual(temp1: DepotEntry, temp2: DepotEntry): boolean { function formatCommitMessage(message: string): string { const gitmoji = ':bookmark:' const automationMessage = `This commit was generated by an automated workflow: ${actionURL}` - return `${gitmoji} message \n\n${automationMessage}` + let msg = message + if (!msg.trim().startsWith(':')) msg = gitmoji + ' ' + msg + msg += '\n\n' + msg += automationMessage + return msg } +/** + * + * @param newJson + * @param location + * @param client + * @returns formatted commit message unless the json's are the same, in which case it returns undefined, indicating that no commit is necessary + */ export async function createCommitMessage( newJson: string, location: DepotLocation, client: Octokit -): Promise { - return formatCommitMessage(await createRawMessage(newJson, location, client)) +): Promise { + const raw = await createRawMessage(newJson, location, client) + if (raw === undefined) return raw + else return formatCommitMessage(raw) } async function createRawMessage( newJson: string, location: DepotLocation, client: Octokit -): Promise { - const oldDepot = await getOldDepot(location, client) +): Promise { + const oldJson = await getOldJson(location, client) + + if (oldJson === newJson) return undefined + if (oldJson === '') return `:tada: Create Depot: ${location.path}` + + const oldDepot = jsonToDepot(oldJson) const newDepot = jsonToDepot(newJson) const changedTemplates: DepotEntry[] = [] diff --git a/src/populate.ts b/src/populate.ts index c7e3e5e..90f0734 100644 --- a/src/populate.ts +++ b/src/populate.ts @@ -1,6 +1,7 @@ import { Octokit, RestEndpointMethodTypes } from '@octokit/rest' import AdmZip from 'adm-zip' -import { DepotEntry } from './types' +import { Depot, DepotEntry, DepotType } from './types' +import semver from 'semver' interface TemplateDetails { name: string @@ -77,21 +78,27 @@ function createDepotEntry({ version } } +function stringifyDepot(depot: Depot, readable: boolean): string { + return JSON.stringify(depot, null, readable ? 2 : undefined) +} + /** * Creates a JSON string by populating the depot with templates from a GitHub repository. * @param repoId The repository to populate the depot from. * @param client The client to use for GitHub API requests. * @param readable Whether to format the JSON string for human readability. + * @param unified Whether the beta and stable versions should be contained in a single depot. * @returns */ -export async function populateDepotJsonFromGithub( +export async function populateDepotJsonsFromGithub( repoId: { owner: string repo: string }, client: Octokit = new Octokit(), - readable: boolean = true -): Promise { + readable: boolean = true, + unified: boolean = false +): Promise & Partial>> { const rawReleases = await client.repos.listReleases(repoId) const templatePromises: Promise[] = @@ -111,6 +118,23 @@ export async function populateDepotJsonFromGithub( ) const depotEntries = templates.map(createDepotEntry) - const depotJson = JSON.stringify(depotEntries, null, readable ? 2 : undefined) - return depotJson + if (unified) return { stable: stringifyDepot(depotEntries, readable) } + + const stableEntries = [] + const betaEntries = [] + + for (const entry of depotEntries) { + if (semver.parse(entry.version)?.prerelease.length ?? 0 > 0) { + betaEntries.push(entry) + } else { + stableEntries.push(entry) + } + } + + const stableJson = stringifyDepot(stableEntries, readable) + const betaJson = stringifyDepot(betaEntries, readable) + return { + stable: stableJson, + beta: betaJson + } } diff --git a/src/types.ts b/src/types.ts index ff54010..6f5797d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,3 +20,5 @@ export interface DepotEntry { } export type Depot = DepotEntry[] + +export type DepotType = "stable" | "beta"; \ No newline at end of file