docs-please 0.2.0-beta.0 → 0.2.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/app.config.ts +9 -1
- package/app/assets/css/main.css +2 -2
- package/app/components/app/AppHeader.vue +92 -37
- package/app/components/app/AppHeaderCenter.vue +3 -0
- package/app/components/app/AppHeaderLogo.vue +43 -0
- package/app/components/app/AppHeaderSearch.vue +3 -0
- package/app/components/app/AppSearch.vue +189 -0
- package/app/components/app/AppSearchButton.vue +51 -0
- package/app/components/content/Accordion.vue +117 -0
- package/app/components/content/AccordionItem.vue +27 -0
- package/app/components/content/Badge.vue +42 -0
- package/app/components/content/Collapsible.vue +56 -0
- package/app/components/content/ProseKbd.vue +9 -0
- package/app/components/content/ProseTable.vue +2 -2
- package/app/components/content/ProseTh.vue +1 -1
- package/app/components/content/ProseTr.vue +1 -1
- package/app/components/ui/command/Command.vue +86 -0
- package/app/components/ui/command/CommandDialog.vue +21 -0
- package/app/components/ui/command/CommandEmpty.vue +23 -0
- package/app/components/ui/command/CommandGroup.vue +44 -0
- package/app/components/ui/command/CommandInput.vue +35 -0
- package/app/components/ui/command/CommandItem.vue +75 -0
- package/app/components/ui/command/CommandList.vue +21 -0
- package/app/components/ui/command/CommandSeparator.vue +20 -0
- package/app/components/ui/command/CommandShortcut.vue +14 -0
- package/app/components/ui/command/index.ts +25 -0
- package/app/components/ui/dialog/Dialog.vue +19 -0
- package/app/components/ui/dialog/DialogClose.vue +15 -0
- package/app/components/ui/dialog/DialogContent.vue +53 -0
- package/app/components/ui/dialog/DialogDescription.vue +23 -0
- package/app/components/ui/dialog/DialogFooter.vue +15 -0
- package/app/components/ui/dialog/DialogHeader.vue +17 -0
- package/app/components/ui/dialog/DialogOverlay.vue +21 -0
- package/app/components/ui/dialog/DialogScrollContent.vue +59 -0
- package/app/components/ui/dialog/DialogTitle.vue +23 -0
- package/app/components/ui/dialog/DialogTrigger.vue +15 -0
- package/app/components/ui/dialog/index.ts +10 -0
- package/app/components/ui/kbd/Kbd.vue +21 -0
- package/app/components/ui/kbd/KbdGroup.vue +17 -0
- package/app/components/ui/kbd/index.ts +2 -0
- package/app/composables/useContentSearch.ts +52 -0
- package/app/layouts/default.vue +1 -0
- package/app/layouts/docs.vue +1 -0
- package/app/utils/navigation.ts +7 -0
- package/app/utils/prerender.ts +12 -0
- package/nuxt.config.ts +4 -0
- package/nuxt.schema.ts +44 -0
- package/package.json +9 -3
- package/server/routes/raw/[...slug].md.get.ts +45 -0
- package/utils/git.ts +59 -0
- package/utils/meta.ts +29 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
|
3
|
+
import type { HTMLAttributes } from "vue"
|
|
4
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
5
|
+
import { X } from "lucide-vue-next"
|
|
6
|
+
import {
|
|
7
|
+
DialogClose,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogOverlay,
|
|
10
|
+
DialogPortal,
|
|
11
|
+
useForwardPropsEmits,
|
|
12
|
+
} from "reka-ui"
|
|
13
|
+
import { cn } from '~/lib/utils'
|
|
14
|
+
|
|
15
|
+
defineOptions({
|
|
16
|
+
inheritAttrs: false,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>()
|
|
20
|
+
const emits = defineEmits<DialogContentEmits>()
|
|
21
|
+
|
|
22
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
23
|
+
|
|
24
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<DialogPortal>
|
|
29
|
+
<DialogOverlay
|
|
30
|
+
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
|
31
|
+
>
|
|
32
|
+
<DialogContent
|
|
33
|
+
:class="
|
|
34
|
+
cn(
|
|
35
|
+
'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
|
|
36
|
+
props.class,
|
|
37
|
+
)
|
|
38
|
+
"
|
|
39
|
+
v-bind="{ ...$attrs, ...forwarded }"
|
|
40
|
+
@pointer-down-outside="(event) => {
|
|
41
|
+
const originalEvent = event.detail.originalEvent;
|
|
42
|
+
const target = originalEvent.target as HTMLElement;
|
|
43
|
+
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
}
|
|
46
|
+
}"
|
|
47
|
+
>
|
|
48
|
+
<slot />
|
|
49
|
+
|
|
50
|
+
<DialogClose
|
|
51
|
+
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
|
|
52
|
+
>
|
|
53
|
+
<X class="w-4 h-4" />
|
|
54
|
+
<span class="sr-only">Close</span>
|
|
55
|
+
</DialogClose>
|
|
56
|
+
</DialogContent>
|
|
57
|
+
</DialogOverlay>
|
|
58
|
+
</DialogPortal>
|
|
59
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DialogTitleProps } from "reka-ui"
|
|
3
|
+
import type { HTMLAttributes } from "vue"
|
|
4
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
5
|
+
import { DialogTitle, useForwardProps } from "reka-ui"
|
|
6
|
+
import { cn } from '~/lib/utils'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>()
|
|
9
|
+
|
|
10
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
11
|
+
|
|
12
|
+
const forwardedProps = useForwardProps(delegatedProps)
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<DialogTitle
|
|
17
|
+
data-slot="dialog-title"
|
|
18
|
+
v-bind="forwardedProps"
|
|
19
|
+
:class="cn('text-lg leading-none font-semibold', props.class)"
|
|
20
|
+
>
|
|
21
|
+
<slot />
|
|
22
|
+
</DialogTitle>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DialogTriggerProps } from "reka-ui"
|
|
3
|
+
import { DialogTrigger } from "reka-ui"
|
|
4
|
+
|
|
5
|
+
const props = defineProps<DialogTriggerProps>()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<DialogTrigger
|
|
10
|
+
data-slot="dialog-trigger"
|
|
11
|
+
v-bind="props"
|
|
12
|
+
>
|
|
13
|
+
<slot />
|
|
14
|
+
</DialogTrigger>
|
|
15
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as Dialog } from "./Dialog.vue"
|
|
2
|
+
export { default as DialogClose } from "./DialogClose.vue"
|
|
3
|
+
export { default as DialogContent } from "./DialogContent.vue"
|
|
4
|
+
export { default as DialogDescription } from "./DialogDescription.vue"
|
|
5
|
+
export { default as DialogFooter } from "./DialogFooter.vue"
|
|
6
|
+
export { default as DialogHeader } from "./DialogHeader.vue"
|
|
7
|
+
export { default as DialogOverlay } from "./DialogOverlay.vue"
|
|
8
|
+
export { default as DialogScrollContent } from "./DialogScrollContent.vue"
|
|
9
|
+
export { default as DialogTitle } from "./DialogTitle.vue"
|
|
10
|
+
export { default as DialogTrigger } from "./DialogTrigger.vue"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { cn } from '~/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes["class"]
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<kbd
|
|
12
|
+
:class="cn(
|
|
13
|
+
'bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none',
|
|
14
|
+
'[&_svg:not([class*=\'size-\'])]:size-3',
|
|
15
|
+
'[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10',
|
|
16
|
+
props.class,
|
|
17
|
+
)"
|
|
18
|
+
>
|
|
19
|
+
<slot />
|
|
20
|
+
</kbd>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { cn } from '~/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes["class"]
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<kbd
|
|
12
|
+
data-slot="kbd-group"
|
|
13
|
+
:class="cn('inline-flex items-center gap-1', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</kbd>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { NavigationItem } from './useNavigation'
|
|
2
|
+
|
|
3
|
+
const open = ref(false)
|
|
4
|
+
|
|
5
|
+
export interface SearchItem {
|
|
6
|
+
id: string
|
|
7
|
+
label: string
|
|
8
|
+
icon?: Component
|
|
9
|
+
to?: string
|
|
10
|
+
onSelect?: () => void
|
|
11
|
+
prefix?: string
|
|
12
|
+
suffix?: string
|
|
13
|
+
active?: boolean
|
|
14
|
+
disabled?: boolean
|
|
15
|
+
isDoc?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SearchGroup {
|
|
19
|
+
id: string
|
|
20
|
+
label: string
|
|
21
|
+
items: SearchItem[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useContentSearch() {
|
|
25
|
+
return {
|
|
26
|
+
open,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function flattenNavigation(items: NavigationItem[], prefix = '', isDocSection = false): SearchItem[] {
|
|
31
|
+
return items.flatMap((item) => {
|
|
32
|
+
const result: SearchItem[] = []
|
|
33
|
+
// Determine if this item is a doc page based on parent context or path
|
|
34
|
+
const itemIsDoc = isDocSection || item.path?.startsWith('/docs')
|
|
35
|
+
|
|
36
|
+
if (item.type === 'page' && item.path) {
|
|
37
|
+
result.push({
|
|
38
|
+
id: item.path,
|
|
39
|
+
label: item.title,
|
|
40
|
+
to: item.path,
|
|
41
|
+
prefix: prefix || undefined,
|
|
42
|
+
isDoc: itemIsDoc,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (item.children?.length) {
|
|
47
|
+
result.push(...flattenNavigation(item.children, item.title, itemIsDoc))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result
|
|
51
|
+
})
|
|
52
|
+
}
|
package/app/layouts/default.vue
CHANGED
package/app/layouts/docs.vue
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const addPrerenderPath = (path: string) => {
|
|
2
|
+
const event = useRequestEvent()
|
|
3
|
+
if (event) {
|
|
4
|
+
event.node.res.setHeader(
|
|
5
|
+
'x-nitro-prerender',
|
|
6
|
+
[
|
|
7
|
+
event.node.res.getHeader('x-nitro-prerender'),
|
|
8
|
+
path,
|
|
9
|
+
].filter(Boolean).join(','),
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
}
|
package/nuxt.config.ts
CHANGED
|
@@ -13,6 +13,9 @@ export default defineNuxtConfig({
|
|
|
13
13
|
'@nuxt/image',
|
|
14
14
|
'@nuxt/icon',
|
|
15
15
|
'nuxt-og-image',
|
|
16
|
+
'@nuxtjs/robots',
|
|
17
|
+
'nuxt-og-image',
|
|
18
|
+
'nuxt-llms',
|
|
16
19
|
],
|
|
17
20
|
devtools: { enabled: true },
|
|
18
21
|
css: [resolve('./app/assets/css/main.css')],
|
|
@@ -61,6 +64,7 @@ export default defineNuxtConfig({
|
|
|
61
64
|
'tabs': 'Tabs',
|
|
62
65
|
'tabs-item': 'TabsItem',
|
|
63
66
|
'u-color-mode-image': 'UColorModeImage',
|
|
67
|
+
'kbd': 'ProseKbd',
|
|
64
68
|
},
|
|
65
69
|
},
|
|
66
70
|
},
|
package/nuxt.schema.ts
CHANGED
|
@@ -29,6 +29,50 @@ export default defineNuxtSchema({
|
|
|
29
29
|
type: 'string',
|
|
30
30
|
},
|
|
31
31
|
},
|
|
32
|
+
header: {
|
|
33
|
+
$schema: {
|
|
34
|
+
title: 'Header Configuration',
|
|
35
|
+
description: 'Header configuration options',
|
|
36
|
+
},
|
|
37
|
+
title: {
|
|
38
|
+
$default: '',
|
|
39
|
+
$schema: {
|
|
40
|
+
title: 'Header Title',
|
|
41
|
+
description: 'Title displayed in the header (defaults to site title)',
|
|
42
|
+
type: 'string',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
logo: {
|
|
46
|
+
$schema: {
|
|
47
|
+
title: 'Logo Configuration',
|
|
48
|
+
description: 'Logo images for light and dark modes',
|
|
49
|
+
},
|
|
50
|
+
light: {
|
|
51
|
+
$default: '',
|
|
52
|
+
$schema: {
|
|
53
|
+
title: 'Light Mode Logo',
|
|
54
|
+
description: 'Logo image for light mode',
|
|
55
|
+
type: 'string',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
dark: {
|
|
59
|
+
$default: '',
|
|
60
|
+
$schema: {
|
|
61
|
+
title: 'Dark Mode Logo',
|
|
62
|
+
description: 'Logo image for dark mode',
|
|
63
|
+
type: 'string',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
alt: {
|
|
67
|
+
$default: '',
|
|
68
|
+
$schema: {
|
|
69
|
+
title: 'Logo Alt Text',
|
|
70
|
+
description: 'Alt text for the logo image',
|
|
71
|
+
type: 'string',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
32
76
|
github: {
|
|
33
77
|
$schema: {
|
|
34
78
|
title: 'GitHub Configuration',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docs-please",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2-beta.0",
|
|
5
5
|
"description": "Nuxt layer for documentation sites using shadcn-vue",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"modules",
|
|
19
19
|
"nuxt.config.ts",
|
|
20
20
|
"nuxt.schema.ts",
|
|
21
|
-
"server"
|
|
21
|
+
"server",
|
|
22
|
+
"utils"
|
|
22
23
|
],
|
|
23
24
|
"scripts": {
|
|
24
25
|
"dev": "nuxt dev",
|
|
@@ -29,7 +30,8 @@
|
|
|
29
30
|
"typecheck": "vue-tsc -b"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
|
-
"
|
|
33
|
+
"better-sqlite3": "12.x",
|
|
34
|
+
"nuxt": "4.x"
|
|
33
35
|
},
|
|
34
36
|
"dependencies": {
|
|
35
37
|
"@iconify-json/lucide": "^1.2.75",
|
|
@@ -42,12 +44,14 @@
|
|
|
42
44
|
"@nuxtjs/mdc": "^0.18.4",
|
|
43
45
|
"@tabler/icons-vue": "^3.35.0",
|
|
44
46
|
"@vueuse/core": "^14.1.0",
|
|
47
|
+
"@vueuse/integrations": "^14.1.0",
|
|
45
48
|
"class-variance-authority": "^0.7.1",
|
|
46
49
|
"clsx": "^2.1.1",
|
|
47
50
|
"debug": "^4.4.1",
|
|
48
51
|
"defu": "^6.1.4",
|
|
49
52
|
"eslint": "^9.39.1",
|
|
50
53
|
"exsolve": "^1.0.8",
|
|
54
|
+
"fuse.js": "^7.1.0",
|
|
51
55
|
"lucide-vue-next": "^0.555.0",
|
|
52
56
|
"nuxt-og-image": "^5.1.12",
|
|
53
57
|
"oxc-parser": "^0.99.0",
|
|
@@ -70,8 +74,10 @@
|
|
|
70
74
|
"zod-to-json-schema": "^3.25.0"
|
|
71
75
|
},
|
|
72
76
|
"devDependencies": {
|
|
77
|
+
"@nuxtjs/robots": "^5.6.1",
|
|
73
78
|
"@tailwindcss/vite": "^4.1.17",
|
|
74
79
|
"better-sqlite3": "^11.9.1",
|
|
80
|
+
"nuxt-llms": "^0.1.3",
|
|
75
81
|
"typescript": "^5.9.3",
|
|
76
82
|
"vue-tsc": "^2.0.0"
|
|
77
83
|
},
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { withLeadingSlash } from 'ufo'
|
|
2
|
+
import { stringify } from 'minimark/stringify'
|
|
3
|
+
import { queryCollection } from '@nuxt/content/nitro'
|
|
4
|
+
import type { Collections } from '@nuxt/content'
|
|
5
|
+
|
|
6
|
+
export default eventHandler(async (event) => {
|
|
7
|
+
const slug = getRouterParams(event)['slug.md']
|
|
8
|
+
if (!slug?.endsWith('.md')) {
|
|
9
|
+
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const path = withLeadingSlash(slug.replace('.md', ''))
|
|
13
|
+
const config = useRuntimeConfig(event).public
|
|
14
|
+
|
|
15
|
+
let collectionName = 'docs'
|
|
16
|
+
if (config.i18n?.locales) {
|
|
17
|
+
const pathSegments = path.split('/').filter(Boolean)
|
|
18
|
+
const firstSegment = pathSegments[0]
|
|
19
|
+
|
|
20
|
+
const availableLocales = config.i18n.locales.map((locale: string | { code: string }) =>
|
|
21
|
+
typeof locale === 'string' ? locale : locale.code,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if (firstSegment && availableLocales.includes(firstSegment)) {
|
|
25
|
+
collectionName = `docs_${firstSegment}`
|
|
26
|
+
}
|
|
27
|
+
else if (config.i18n.defaultLocale) {
|
|
28
|
+
collectionName = `docs_${config.i18n.defaultLocale}`
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const page = await queryCollection(event, collectionName as keyof Collections).path(path).first()
|
|
33
|
+
if (!page) {
|
|
34
|
+
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Add title and description to the top of the page if missing
|
|
38
|
+
if (page.body.value[0]?.[0] !== 'h1') {
|
|
39
|
+
page.body.value.unshift(['blockquote', {}, page.description])
|
|
40
|
+
page.body.value.unshift(['h1', {}, page.title])
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
|
|
44
|
+
return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
|
|
45
|
+
})
|
package/utils/git.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
|
|
4
|
+
export interface GitInfo {
|
|
5
|
+
name: string
|
|
6
|
+
owner: string
|
|
7
|
+
url: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getGitBranch(): string {
|
|
11
|
+
const envName
|
|
12
|
+
= process.env.CF_PAGES_BRANCH
|
|
13
|
+
|| process.env.CI_COMMIT_BRANCH
|
|
14
|
+
|| process.env.VERCEL_GIT_COMMIT_REF
|
|
15
|
+
|| process.env.BRANCH
|
|
16
|
+
|| process.env.GITHUB_REF_NAME
|
|
17
|
+
|
|
18
|
+
if (envName && envName !== 'HEAD') {
|
|
19
|
+
return envName
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
|
|
23
|
+
if (branch && branch !== 'HEAD') {
|
|
24
|
+
return branch
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Ignore error
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return 'main'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getGitEnv(): GitInfo {
|
|
35
|
+
const envInfo = {
|
|
36
|
+
provider: process.env.VERCEL_GIT_PROVIDER
|
|
37
|
+
|| (process.env.GITHUB_SERVER_URL ? 'github' : undefined)
|
|
38
|
+
|| '',
|
|
39
|
+
owner: process.env.VERCEL_GIT_REPO_OWNER
|
|
40
|
+
|| process.env.GITHUB_REPOSITORY_OWNER
|
|
41
|
+
|| process.env.CI_PROJECT_PATH?.split('/').shift()
|
|
42
|
+
|| '',
|
|
43
|
+
name: process.env.VERCEL_GIT_REPO_SLUG
|
|
44
|
+
|| process.env.GITHUB_REPOSITORY?.split('/').pop()
|
|
45
|
+
|| process.env.CI_PROJECT_PATH?.split('/').splice(1).join('/')
|
|
46
|
+
|| '',
|
|
47
|
+
url: process.env.REPOSITORY_URL || '',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!envInfo.url && envInfo.provider && envInfo.owner && envInfo.name) {
|
|
51
|
+
envInfo.url = `https://${envInfo.provider}.com/${envInfo.owner}/${envInfo.name}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
name: envInfo.name,
|
|
56
|
+
owner: envInfo.owner,
|
|
57
|
+
url: envInfo.url,
|
|
58
|
+
}
|
|
59
|
+
}
|
package/utils/meta.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
|
|
5
|
+
export function inferSiteURL(): string | undefined {
|
|
6
|
+
return (
|
|
7
|
+
process.env.NUXT_SITE_URL
|
|
8
|
+
|| (process.env.NEXT_PUBLIC_VERCEL_URL && `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`)
|
|
9
|
+
|| process.env.URL // Netlify
|
|
10
|
+
|| process.env.CI_PAGES_URL // Gitlab Pages
|
|
11
|
+
|| process.env.CF_PAGES_URL // Cloudflare Pages
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function getPackageJsonMetadata(dir: string): Promise<{ name: string, description?: string }> {
|
|
16
|
+
try {
|
|
17
|
+
const packageJson = await readFile(resolve(dir, 'package.json'), 'utf-8')
|
|
18
|
+
const parsed = JSON.parse(packageJson)
|
|
19
|
+
return {
|
|
20
|
+
name: parsed.name,
|
|
21
|
+
description: parsed.description,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return {
|
|
26
|
+
name: 'docs',
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|