Skip to content
This repository was archived by the owner on Sep 24, 2025. It is now read-only.

Commit 9b02f48

Browse files
authored
feat(preview): add gitInfo in /__studio.json and support staging API for preview mode (#163)
1 parent 77b8020 commit 9b02f48

File tree

7 files changed

+113
-14
lines changed

7 files changed

+113
-14
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@
4848
"dependencies": {
4949
"@nuxt/kit": "^3.9.3",
5050
"defu": "^6.1.4",
51+
"git-url-parse": "^14.0.0",
5152
"nuxt-component-meta": "^0.6.3",
53+
"parse-git-config": "^3.0.0",
54+
"pkg-types": "^1.0.3",
5255
"socket.io-client": "^4.7.4",
5356
"ufo": "^1.3.2",
5457
"untyped": "^1.4.2"

pnpm-lock.yaml

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/module.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { existsSync } from 'node:fs'
22
import path from 'path'
33
import { defu } from 'defu'
44
import { addPrerenderRoutes, installModule, defineNuxtModule, addPlugin, extendViteConfig, createResolver, logger, addComponentsDir, addServerHandler, resolveAlias, addVitePlugin } from '@nuxt/kit'
5+
import { findNearestFile } from 'pkg-types'
6+
// @ts-ignore
7+
import gitUrlParse from 'git-url-parse'
58

69
const log = logger.withTag('@nuxt/studio')
710

@@ -88,9 +91,11 @@ export default defineNuxtModule<ModuleOptions>({
8891

8992
const apiURL = process.env.NUXT_PUBLIC_STUDIO_API_URL || process.env.STUDIO_API || 'https://api.nuxt.studio'
9093
const publicToken = process.env.NUXT_PUBLIC_STUDIO_TOKENS
94+
const gitInfo = await _getLocalGitInfo(nuxt.options.rootDir) || _getGitEnv() || {}
9195
nuxt.options.runtimeConfig.studio = defu(nuxt.options.runtimeConfig.studio as any, {
9296
publicToken,
93-
project: options.project
97+
project: options.project,
98+
gitInfo
9499
})
95100
nuxt.options.runtimeConfig.public.studio = defu(nuxt.options.runtimeConfig.public.studio as any, { apiURL })
96101

@@ -138,3 +143,90 @@ export default defineNuxtModule<ModuleOptions>({
138143
})
139144
}
140145
})
146+
147+
// --- Utilities to get git info ---
148+
149+
interface GitInfo {
150+
// Repository name
151+
name: string,
152+
// Repository owner/organization
153+
owner: string,
154+
// Repository URL
155+
url: string,
156+
}
157+
158+
async function _getLocalGitInfo (rootDir: string): Promise<GitInfo | void> {
159+
const remote = await _getLocalGitRemote(rootDir)
160+
if (!remote) {
161+
return
162+
}
163+
164+
// https://www.npmjs.com/package/git-url-parse#clipboard-example
165+
const { name, owner, source } = gitUrlParse(remote) as Record<string, string>
166+
const url = `https://${source}/${owner}/${name}`
167+
168+
return {
169+
name,
170+
owner,
171+
url
172+
}
173+
}
174+
175+
async function _getLocalGitRemote (dir: string) {
176+
try {
177+
// https://www.npmjs.com/package/parse-git-config#options
178+
const parseGitConfig = await import('parse-git-config' as string).then(
179+
m => m.promise
180+
) as (opts: { path: string }) => Promise<Record<string, Record<string, string>>>
181+
const gitDir = await findNearestFile('.git/config', { startingFrom: dir })
182+
const parsed = await parseGitConfig({ path: gitDir })
183+
if (!parsed) {
184+
return
185+
}
186+
const gitRemote = parsed['remote "origin"'].url
187+
return gitRemote
188+
} catch (err) {
189+
190+
}
191+
}
192+
193+
function _getGitEnv (): GitInfo {
194+
// https://github.com/unjs/std-env/issues/59
195+
const envInfo = {
196+
// Provider
197+
provider: process.env.VERCEL_GIT_PROVIDER || // vercel
198+
(process.env.GITHUB_SERVER_URL ? 'github' : undefined) || // github
199+
'',
200+
// Owner
201+
owner: process.env.VERCEL_GIT_REPO_OWNER || // vercel
202+
process.env.GITHUB_REPOSITORY_OWNER || // github
203+
process.env.CI_PROJECT_PATH?.split('/').shift() || // gitlab
204+
'',
205+
// Name
206+
name: process.env.VERCEL_GIT_REPO_SLUG ||
207+
process.env.GITHUB_REPOSITORY?.split('/').pop() || // github
208+
process.env.CI_PROJECT_PATH?.split('/').splice(1).join('/') || // gitlab
209+
'',
210+
// Url
211+
url: process.env.REPOSITORY_URL || '' // netlify
212+
}
213+
214+
if (!envInfo.url && envInfo.provider && envInfo.owner && envInfo.name) {
215+
envInfo.url = `https://${envInfo.provider}.com/${envInfo.owner}/${envInfo.name}`
216+
}
217+
218+
// If only url available (ex: Netlify)
219+
if (!envInfo.name && !envInfo.owner && envInfo.url) {
220+
try {
221+
const { name, owner } = gitUrlParse(envInfo.url) as Record<string, string>
222+
envInfo.name = name
223+
envInfo.owner = owner
224+
} catch {}
225+
}
226+
227+
return {
228+
name: envInfo.name,
229+
owner: envInfo.owner,
230+
url: envInfo.url
231+
}
232+
}

src/runtime/components/ContentPreviewMode.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const closePreviewMode = async () => {
3737
// Remove preview token from cookie and session storage
3838
useCookie('previewToken').value = ''
3939
window.sessionStorage.removeItem('previewToken')
40+
window.sessionStorage.removeItem('previewAPI')
4041
4142
// Remove query params in url to refresh page
4243
await router.replace({ query: { preview: undefined } })

src/runtime/composables/useStudio.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const useStudio = () => {
2323
const nuxtApp = useNuxtApp()
2424
const { studio: studioConfig, content: contentConfig } = useRuntimeConfig().public
2525
const contentPathMap = {} as Record<string, ParsedContent>
26+
const apiURL = window.sessionStorage.getItem('previewAPI') || studioConfig?.apiURL
2627

2728
// App config (required)
2829
const initialAppConfig = useDefaultAppConfig()
@@ -135,7 +136,7 @@ export const useStudio = () => {
135136
const previewToken = window.sessionStorage.getItem('previewToken')
136137
// Fetch preview data from station
137138
await $fetch<PreviewResponse>('api/projects/preview/sync', {
138-
baseURL: studioConfig?.apiURL,
139+
baseURL: apiURL,
139140
method: 'POST',
140141
params: {
141142
token: previewToken
@@ -151,7 +152,7 @@ export const useStudio = () => {
151152
document.body.appendChild(el)
152153
createApp(ContentPreviewMode, {
153154
previewToken,
154-
apiURL: studioConfig?.apiURL,
155+
apiURL,
155156
syncPreview,
156157
requestPreviewSyncAPI: requestPreviewSynchronization
157158
}).mount(el)
@@ -223,7 +224,7 @@ export const useStudio = () => {
223224
}
224225

225226
return {
226-
apiURL: studioConfig?.apiURL,
227+
apiURL,
227228
contentStorage: storage,
228229

229230
syncPreviewFiles,

src/runtime/plugins/preview.client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default defineNuxtPlugin((nuxtApp) => {
2929
previewToken.value = String(route.query.preview)
3030
}
3131
window.sessionStorage.setItem('previewToken', String(previewToken.value))
32+
window.sessionStorage.setItem('previewAPI', typeof route.query.staging !== 'undefined' ? 'https://dev-api.nuxt.studio' : runtimeConfig.apiURL)
3233

3334
// Listen to `content:storage` hook to get storage instance
3435
// There is some cases that `content:storage` hook is called before initializing preview

src/runtime/server/routes/studio.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export default eventHandler(async () => {
6262
version,
6363
project: studio?.project,
6464
tokens: studio?.publicToken,
65+
gitInfo: studio?.gitInfo || {},
6566
// nuxt.schema for Nuxt Content frontmatter
6667
contentSchema: contentSchema || {},
6768
// app.config

0 commit comments

Comments
 (0)