tools/move-release-notes.ts
import { program } from 'commander'
import { GITKEEP_FILE, UNRELEASED_DIR, RELEASE_NOTES_DIR } from './constants.js'
import authenticate from './github-authentication.js'
type Opts = {
deliverinoPrivateKey: string
owner: string
repo: string
branch: string
}
type Options = Opts & {
newVersionName: string
}
const moveReleaseNotes = async ({ newVersionName, deliverinoPrivateKey, owner, repo, branch }: Options) => {
const appOctokit = await authenticate({ deliverinoPrivateKey, owner, repo })
const {
data: { commit },
} = await appOctokit.repos.getBranch({ owner, repo, branch })
// Tree of the root folder of the project
const rootTreeSha = commit.sha
const rootTree = await appOctokit.git.getTree({ owner, repo, tree_sha: rootTreeSha })
// Tree of the 'release-notes' folder of the project
const releaseNoteTreeObject = rootTree.data.tree.find(it => it.path === RELEASE_NOTES_DIR)
if (!releaseNoteTreeObject || !releaseNoteTreeObject.sha) {
console.error(rootTree)
throw new Error('Root tree does not contain release notes tree, probably since the response is truncated.')
}
const releaseNoteTree = await appOctokit.git.getTree({ owner, repo, tree_sha: releaseNoteTreeObject.sha })
// Tree of the unreleased release notes
const unreleasedTreeObject = releaseNoteTree.data.tree.find(it => it.path === UNRELEASED_DIR)
if (!unreleasedTreeObject || !unreleasedTreeObject.sha) {
console.error(releaseNoteTree)
throw new Error('Release note tree does not contain unreleased tree, probably since the response is truncated.')
}
const unreleasedTree = await appOctokit.git.getTree({ owner, repo, tree_sha: unreleasedTreeObject.sha })
// Only keep file '.gitkeep' in 'unreleased' tree, move everything else to '<new-version-name>'
const unreleasedReleaseNotes = unreleasedTree.data.tree.filter(it => it.path !== GITKEEP_FILE)
const keepFile = unreleasedTree.data.tree.find(it => it.path === GITKEEP_FILE)
if (!keepFile) {
throw new Error('.gitkeep file not found!')
}
// Creating an empty tree is not possible
if (unreleasedReleaseNotes.length === 0) {
return
}
// @ts-expect-error Wrong type: mode of type string is not compatible to "100644" | "100755" | "040000" | "160000" | "120000"
const newVersionTree = await appOctokit.git.createTree({ owner, repo, tree: unreleasedReleaseNotes })
// @ts-expect-error Wrong type: mode of type string is not compatible to "100644" | "100755" | "040000" | "160000" | "120000"
const newUnreleasedTree = await appOctokit.git.createTree({ owner, repo, tree: [keepFile] })
const newReleaseNotesContent = releaseNoteTree.data.tree.map(it => {
const { size, url, ...rest } = it
if (it.path === UNRELEASED_DIR) {
return { ...rest, sha: newUnreleasedTree.data.sha }
}
return rest
})
// mode: '040000' means subdirectory(tree)
// See https://developer.github.com/v3/git/trees/#create-a-tree
newReleaseNotesContent.push({ path: newVersionName, sha: newVersionTree.data.sha, type: 'tree', mode: '040000' })
// @ts-expect-error Wrong type: mode of type string is not compatible to "100644" | "100755" | "040000" | "160000" | "120000"
const newReleaseNotesTree = await appOctokit.git.createTree({ owner, repo, tree: newReleaseNotesContent })
// Update the root tree with the new 'release-notes' tree
const rootUpdate = rootTree.data.tree.map(it => {
const { size, url, ...rest } = it
if (it.path === RELEASE_NOTES_DIR) {
return { ...rest, sha: newReleaseNotesTree.data.sha }
}
return rest
})
// @ts-expect-error Wrong type: mode of type string is not compatible to "100644" | "100755" | "040000" | "160000" | "120000"
const updatedRootTree = await appOctokit.git.createTree({ owner, repo, tree: rootUpdate })
// Commit the changes in the tree
const renameCommit = await appOctokit.git.createCommit({
owner,
repo,
message: `Move release notes to ${newVersionName}\n[skip ci]`,
tree: updatedRootTree.data.sha,
parents: [commit.sha],
})
await appOctokit.git.updateRef({
owner,
repo,
ref: `heads/${branch}`,
sha: renameCommit.data.sha,
})
}
program
.command('move-to <new-version-name>')
.description("move the release notes in 'unreleased' to a new subdirectory <new-version-name>")
.requiredOption(
'--deliverino-private-key <deliverino-private-key>',
'private key of the deliverino github app in pem format with base64 encoding',
)
.requiredOption('--owner <owner>', 'owner of the current repository, usually "digitalfabrik"')
.requiredOption('--repo <repo>', 'the current repository, usually "integreat-app"')
.requiredOption('--branch <branch>', 'the current branch')
.action(async (newVersionName: string, options: Opts) => {
try {
await moveReleaseNotes({
newVersionName,
...options,
})
} catch (e) {
console.error(e)
process.exit(1)
}
})
program.parse(process.argv)