Skip to content

feat(git): add pull-request generation from CMS UI - #511

Open
TotomInc wants to merge 2 commits into
nuxt-content:mainfrom
TotomInc:feat/mr-from-ui
Open

feat(git): add pull-request generation from CMS UI#511
TotomInc wants to merge 2 commits into
nuxt-content:mainfrom
TotomInc:feat/mr-from-ui

Conversation

@TotomInc

@TotomInc TotomInc commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Closes nuxt-content/nuxt-studio#366.

Problem

Content teams often work on a staging branch (preview environment) and promote changes to production via a pull request (GitHub) or merge request (GitLab). Before this change, Nuxt Studio could commit to a configured branch but left the PR/MR step entirely manual.

Solution

Add opt-in repository.pullRequest options. When pullRequest.base is set:

  1. Studio commits drafts to repository.branch (unchanged behavior).
  2. Studio creates or reuses an open PR/MR from that branch into pullRequest.base.
  3. The success screen links to the review request, or warns on partial failure with a direct link to the landed commit.

When pullRequest.base is omitted, behavior is unchanged: direct commit only.

Optionally, pullRequest.name sets a custom title for newly created PR/MR. When omitted, the commit message is used.

Configuration

// nuxt.config.ts
export default defineNuxtConfig({
  studio: {
    repository: {
      provider: 'github', // or 'gitlab'
      owner: 'your-org',
      repo: 'your-repo',
      branch: 'staging',   // commit target + deployed preview branch
      pullRequest: {
        base: 'main',      // PR/MR target branch
        name: 'Content updates from Studio', // optional PR/MR title
      },
    },
  },
})

Runtime override (Nuxt public runtime config):

NUXT_PUBLIC_STUDIO_REPOSITORY_PULL_REQUEST_BASE=main
NUXT_PUBLIC_STUDIO_REPOSITORY_PULL_REQUEST_NAME="Content updates from Studio"
Option Role
repository.branch Where Studio commits and what the deployed site reflects
repository.pullRequest.base Target branch for the review request
repository.pullRequest.name Optional title for newly created PR/MR (defaults to commit message)

User-facing docs: docs/content/2.setup.md, docs/content/3.git-providers.md, docs/content/9.advanced.md.

Publish flow

sequenceDiagram
  participant Review as HeaderReview
  participant Ctx as useContext.PublishBranch
  participant Provider as GitProviderAPI
  participant Remote as GitHub / GitLab API
  participant Success as /success

  Review->>Ctx: commitMessage
  Ctx->>Provider: commitFiles(rawFiles, message)
  Provider->>Remote: commit to repository.branch
  Remote-->>Provider: CommitResult
  alt pullRequest.base set and differs from branch
    Ctx->>Provider: ensureReviewRequest({ head, base, title, commitUrl })
    Provider->>Remote: GET open PR/MR
    alt none exists
      Provider->>Remote: POST PR/MR
    end
    Remote-->>Provider: ReviewRequestResult
  end
  Ctx->>Ctx: clear drafts (always after successful commit)
  Ctx-->>Review: PublishBranchResult
  Review->>Success: query params (changeCount, commitUrl, review request metadata)
Loading

Behavior matrix

Scenario Commit Drafts cleared Review request UI
No pullRequest.base Yes Yes Skipped Standard success
basebranch Yes Yes Created or reused Success + PR/MR link
Open PR/MR already exists Yes Yes Reused (state: 'existing') Success + link
base === branch Yes Yes Skipped (config warning in logs) Standard success
Commit fails No No Not attempted /error
Commit OK, PR/MR fails Yes Yes Error recorded Success + warning + commit link
ensureReviewRequest returns null Yes Yes Treated as failure Success + warning + commit link
GitHub 422 on create (race) Yes Yes Re-lookup → reuse existing Success + link

Partial failure is intentional: once content is on the staging branch, drafts are cleared even if PR/MR creation fails. The user can open the commit from the success screen and create the review request manually if needed.

Architecture notes

  • Client-side providers — same model as commitFiles: ofetch from the Studio app using the user's OAuth token or PAT. No new server routes.
  • OrchestrationPublishBranch in useContext.ts chains commit → optional review request → draft cleanup.
  • Provider contract — new GitProviderAPI.ensureReviewRequest() with provider-neutral ReviewRequestResult (kind, state, url, head, base).
  • Return typePublishBranchResult carries commit, optional reviewRequest, and optional reviewRequestError back to the UI.
  • Review request titlepullRequest.name when configured, otherwise the commit message. Only applied when creating a new PR/MR; existing open requests are reused as-is.

GitHub (ensureReviewRequest)

  1. GET /repos/{owner}/{repo}/pulls?state=open&head={owner}:{branch}&base={base}
  2. If found → return state: 'existing'
  3. Else POST /pulls with pullRequest.name or commit message as title
  4. On 422 (duplicate created concurrently) → re-run step 1 and reuse if found

GitLab (ensureReviewRequest)

  1. GET /projects/{id}/merge_requests?state=opened&source_branch={head}&target_branch={base}
  2. If found → return state: 'existing'
  3. Else POST /merge_requests with pullRequest.name or commit message as title

Key files

Area Path
Module options src/module/src/module.tsrepository.pullRequest.base, repository.pullRequest.name
Types src/app/src/types/git.ts, src/app/src/types/context.ts
Publish orchestration src/app/src/composables/useContext.ts
GitHub provider src/app/src/utils/providers/github.ts
GitLab provider src/app/src/utils/providers/gitlab.ts
Null provider (dev) src/app/src/utils/providers/null.ts
Review UI src/app/src/components/header/HeaderReview.vue
Success UI src/app/src/pages/success.vue
i18n src/app/src/locales/en.json, fr.json (studio.publishSuccess.reviewRequest.*)

Tests

File Coverage
src/app/test/integration/publish.test.ts Direct mode, PR mode, custom pullRequest.name, partial failure (throw), null result, base === branch skip, existing request reuse
src/app/test/unit/utils/providers/github.test.ts Existing PR lookup, create payload, 422 race recovery, no token
src/app/test/unit/utils/providers/gitlab.test.ts Existing MR lookup, create payload, no token
src/app/test/mocks/git.ts ensureReviewRequest mock

Run:

nr test src/app/test/integration/publish.test.ts
nr test src/app/test/unit/utils/providers/github.test.ts
nr test src/app/test/unit/utils/providers/gitlab.test.ts

Manual QA checklist

  • Publish without pullRequest.base — no PR/MR step, standard success
  • Publish with branch: staging, pullRequest.base: main on GitHub — PR link on success
  • Publish with pullRequest.name set — newly created PR/MR uses the configured title
  • Publish again while PR is still open — reuses existing PR (no duplicate)
  • Publish with pullRequest.base equal to branch — commit only, no PR attempt
  • Revoke PR creation permission / use protected base — commit lands, warning + commit link on success
  • Repeat above on GitLab — MR link on success

Out of scope (by design)

  • Per-publish feature branches
  • Editor-selectable base branch in the UI
  • Auto-merge
  • Server-side Git API proxy
  • Fork-based head refs (same-repo staging → production only)

@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@TotomInc is attempting to deploy a commit to the Nuxt Team on Vercel.

A member of the Team first needs to authorize it.

@pkg-pr-new

pkg-pr-new Bot commented Jun 25, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/nuxt-studio@511

commit: 401e346

@TotomInc

Copy link
Copy Markdown
Contributor Author

I've been able to test this PR on our Nuxt Studio website. Here's the result:

CleanShot 2026-06-29 at 22 21 45@2x

GitLab MR has been opened right after a commit has been made on the branch:

CleanShot 2026-06-29 at 22 22 58@2x

Now, content-editors only have to review the content changes on the MR and merge them.

This is important for non-technical teams, as might not have the knowledge of creating a PR/MR with the appropriate branches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle creation of MR/PR from Nuxt Studio UI when commiting changes

1 participant