async function valueToTreeObject(octokit, owner, repo, path, value) { let mode = "100644"; if (value !== null && typeof value !== "string") { mode = value.mode || mode; } // Text files can be changed through the .content key if (typeof value === "string") { return { path, mode: mode, content: value, }; } // Binary files need to be created first using the git blob API, // then changed by referencing in the .sha key const { data } = await octokit.request("POST /repos/{owner}/{repo}/git/blobs", { owner, repo, ...value, }); const blobSha = data.sha; return { path, mode: mode, sha: blobSha, }; } async function createTree(state, changes) { const { octokit, owner, repo, ownerOrFork, latestCommitSha, latestCommitTreeSha, } = state; const tree = (await Promise.all(Object.keys(changes.files).map(async (path) => { const value = changes.files[path]; if (value === null) { // Deleting a non-existent file from a tree leads to an "GitRPC::BadObjectState" error, // so we only attempt to delete the file if it exists. try { // https://developer.github.com/v3/repos/contents/#get-contents await octokit.request("HEAD /repos/{owner}/{repo}/contents/:path", { owner: ownerOrFork, repo, ref: latestCommitSha, path, }); return { path, mode: "100644", sha: null, }; } catch (error) { return; } } // When passed a function, retrieve the content of the file, pass it // to the function, then return the result if (typeof value === "function") { let result; try { const { data: file } = await octokit.request("GET /repos/{owner}/{repo}/contents/:path", { owner: ownerOrFork, repo, ref: latestCommitSha, path, }); result = await value(Object.assign(file, { exists: true })); } catch (error) { // istanbul ignore if if (error.status !== 404) throw error; // @ts-ignore result = await value({ exists: false }); } if (result === null || typeof result === "undefined") return; return valueToTreeObject(octokit, ownerOrFork, repo, path, result); } return valueToTreeObject(octokit, ownerOrFork, repo, path, value); }))).filter(Boolean); if (tree.length === 0) { return null; } // https://developer.github.com/v3/git/trees/#create-a-tree const { data: { sha: newTreeSha }, } = await octokit.request("POST /repos/{owner}/{repo}/git/trees", { owner: ownerOrFork, repo, base_tree: latestCommitTreeSha, tree, }); return newTreeSha; } async function createCommit(state, treeCreated, changes) { const { octokit, repo, ownerOrFork, latestCommitSha } = state; const message = treeCreated ? changes.commit : typeof changes.emptyCommit === "string" ? changes.emptyCommit : changes.commit; // https://developer.github.com/v3/git/commits/#create-a-commit const { data: latestCommit } = await octokit.request("POST /repos/{owner}/{repo}/git/commits", { owner: ownerOrFork, repo, message, tree: state.latestCommitTreeSha, parents: [latestCommitSha], }); return latestCommit.sha; } async function composeCreatePullRequest(octokit, { owner, repo, title, body, base, head, createWhenEmpty, changes: changesOption, draft = false, forceFork = false, update = false, }) { const changes = Array.isArray(changesOption) ? changesOption : [changesOption]; if (changes.length === 0) throw new Error('[octokit-plugin-create-pull-request] "changes" cannot be an empty array'); const state = { octokit, owner, repo }; // https://developer.github.com/v3/repos/#get-a-repository const { data: repository, headers } = await octokit.request("GET /repos/{owner}/{repo}", { owner, repo, }); const isUser = !!headers["x-oauth-scopes"]; if (!repository.permissions) { throw new Error("[octokit-plugin-create-pull-request] Missing authentication"); } if (!base) { base = repository.default_branch; } state.ownerOrFork = owner; if (forceFork || (isUser && !repository.permissions.push)) { // https://developer.github.com/v3/users/#get-the-authenticated-user const user = await octokit.request("GET /user"); // https://developer.github.com/v3/repos/forks/#list-forks const forks = await octokit.request("GET /repos/{owner}/{repo}/forks", { owner, repo, }); const hasFork = forks.data.find( /* istanbul ignore next - fork owner can be null, but we don't test that */ (fork) => fork.owner && fork.owner.login === user.data.login); if (!hasFork) { // https://developer.github.com/v3/repos/forks/#create-a-fork await octokit.request("POST /repos/{owner}/{repo}/forks", { owner, repo, }); } state.ownerOrFork = user.data.login; } // https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository const { data: [latestCommit], } = await octokit.request("GET /repos/{owner}/{repo}/commits", { owner, repo, sha: base, per_page: 1, }); state.latestCommitSha = latestCommit.sha; state.latestCommitTreeSha = latestCommit.commit.tree.sha; const baseCommitTreeSha = latestCommit.commit.tree.sha; for (const change of changes) { let treeCreated = false; if (change.files && Object.keys(change.files).length) { const latestCommitTreeSha = await createTree(state, change); if (latestCommitTreeSha) { state.latestCommitTreeSha = latestCommitTreeSha; treeCreated = true; } } if (treeCreated || change.emptyCommit !== false) { state.latestCommitSha = await createCommit(state, treeCreated, change); } } const hasNoChanges = baseCommitTreeSha === state.latestCommitTreeSha; if (hasNoChanges && createWhenEmpty === false) { return null; } const branchInfo = await octokit.graphql(` query ($owner: String!, $repo: String!, $head: String!) { repository(name: $repo, owner: $owner) { ref(qualifiedName: $head) { associatedPullRequests(first: 1, states: OPEN) { edges { node { id number url } } } } } }`, { owner: state.ownerOrFork, repo, head, }); const branchExists = !!branchInfo.repository.ref; const existingPullRequest = branchInfo.repository.ref?.associatedPullRequests?.edges?.[0]?.node; if (existingPullRequest && !update) { throw new Error(`[octokit-plugin-create-pull-request] Pull request already exists: ${existingPullRequest.url}. Set update=true to enable updating`); } if (branchExists) { // https://docs.github.com/en/rest/git/refs#update-a-reference await octokit.request("PATCH /repos/{owner}/{repo}/git/refs/{ref}", { owner: state.ownerOrFork, repo, sha: state.latestCommitSha, ref: `heads/${head}`, force: true, }); } else { // https://developer.github.com/v3/git/refs/#create-a-reference await octokit.request("POST /repos/{owner}/{repo}/git/refs", { owner: state.ownerOrFork, repo, sha: state.latestCommitSha, ref: `refs/heads/${head}`, }); } const pullRequestOptions = { owner, repo, head: `${state.ownerOrFork}:${head}`, base, title, body, draft, }; if (existingPullRequest) { // https://docs.github.com/en/rest/pulls/pulls#update-a-pull-request return await octokit.request("PATCH /repos/{owner}/{repo}/pulls/{pull_number}", { pull_number: existingPullRequest.number, ...pullRequestOptions, }); } else { // https://developer.github.com/v3/pulls/#create-a-pull-request return await octokit.request("POST /repos/{owner}/{repo}/pulls", pullRequestOptions); } } const VERSION = "3.13.1"; /** * @param octokit Octokit instance */ function createPullRequest(octokit) { return { createPullRequest: composeCreatePullRequest.bind(null, octokit), }; } createPullRequest.VERSION = VERSION; export { composeCreatePullRequest, createPullRequest }; //# sourceMappingURL=index.js.map