nuxtseo-shared 0.1.1 → 0.1.3
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/dist/content.d.mts +39 -39
- package/dist/content.mjs +56 -29
- package/dist/devtools.d.mts +10 -10
- package/dist/devtools.mjs +37 -43
- package/dist/i18n.d.mts +29 -17
- package/dist/i18n.mjs +109 -108
- package/dist/index.d.mts +3 -7
- package/dist/index.mjs +3 -7
- package/dist/kit.d.mts +22 -12
- package/dist/kit.mjs +79 -73
- package/dist/layer-devtools/app.config.ts +27 -0
- package/dist/layer-devtools/assets/css/global.css +377 -0
- package/dist/layer-devtools/components/DevtoolsError.vue +78 -0
- package/dist/layer-devtools/components/DevtoolsLoading.vue +8 -0
- package/dist/layer-devtools/components/DevtoolsProductionError.vue +41 -0
- package/dist/layer-devtools/components/OCodeBlock.vue +23 -0
- package/{src/client → dist/layer-devtools}/components/OSectionBlock.vue +13 -22
- package/dist/layer-devtools/composables/rpc.ts +39 -0
- package/dist/layer-devtools/composables/shiki.ts +46 -0
- package/dist/layer-devtools/composables/state.ts +33 -0
- package/dist/layer-devtools/nuxt.config.ts +22 -0
- package/dist/module.d.mts +14 -0
- package/dist/module.mjs +33 -0
- package/dist/pro.d.mts +6 -7
- package/dist/pro.mjs +64 -64
- package/dist/telemetry.d.mts +11 -0
- package/dist/telemetry.mjs +49 -0
- package/package.json +22 -47
- package/dist/client/composables/rpc.d.mts +0 -21
- package/dist/client/composables/rpc.d.ts +0 -21
- package/dist/client/composables/rpc.mjs +0 -25
- package/dist/client/composables/shiki.d.mts +0 -13
- package/dist/client/composables/shiki.d.ts +0 -13
- package/dist/client/composables/shiki.mjs +0 -39
- package/dist/client/index.d.mts +0 -7
- package/dist/client/index.d.ts +0 -7
- package/dist/client/index.mjs +0 -6
- package/dist/content.d.ts +0 -82
- package/dist/devtools.d.ts +0 -14
- package/dist/i18n.d.ts +0 -32
- package/dist/index.d.ts +0 -7
- package/dist/kit.d.ts +0 -32
- package/dist/pro.d.ts +0 -18
- package/dist/runtime/server/kit.d.mts +0 -7
- package/dist/runtime/server/kit.d.ts +0 -7
- package/dist/runtime/server/kit.mjs +0 -27
- package/src/client/components/OCodeBlock.vue +0 -28
- /package/{src/client → dist/layer-devtools}/components/NuxtSeoLogo.vue +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { icon = 'carbon:warning', title = 'Something went wrong' } = defineProps<{
|
|
3
|
+
icon?: string
|
|
4
|
+
title?: string
|
|
5
|
+
error?: string | Error | null
|
|
6
|
+
}>()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<div class="devtools-error">
|
|
11
|
+
<div class="devtools-error-icon-wrap">
|
|
12
|
+
<UIcon :name="icon" class="devtools-error-icon" />
|
|
13
|
+
</div>
|
|
14
|
+
<p class="devtools-error-title">
|
|
15
|
+
{{ title }}
|
|
16
|
+
</p>
|
|
17
|
+
<p v-if="error" class="devtools-error-message">
|
|
18
|
+
{{ typeof error === 'string' ? error : error.message }}
|
|
19
|
+
</p>
|
|
20
|
+
<div v-if="$slots.default" class="devtools-error-actions">
|
|
21
|
+
<slot />
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<style scoped>
|
|
27
|
+
.devtools-error {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
gap: 0.75rem;
|
|
33
|
+
padding: 3rem 1.5rem;
|
|
34
|
+
text-align: center;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.devtools-error-icon-wrap {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
width: 3rem;
|
|
42
|
+
height: 3rem;
|
|
43
|
+
border-radius: var(--radius-lg);
|
|
44
|
+
background: oklch(65% 0.15 25 / 0.1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.dark .devtools-error-icon-wrap {
|
|
48
|
+
background: oklch(45% 0.1 25 / 0.15);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.devtools-error-icon {
|
|
52
|
+
font-size: 1.5rem;
|
|
53
|
+
color: oklch(60% 0.18 25);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.dark .devtools-error-icon {
|
|
57
|
+
color: oklch(70% 0.15 25);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.devtools-error-title {
|
|
61
|
+
font-size: 0.875rem;
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
color: var(--color-text);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.devtools-error-message {
|
|
67
|
+
font-size: 0.75rem;
|
|
68
|
+
color: var(--color-text-muted);
|
|
69
|
+
max-width: 20rem;
|
|
70
|
+
line-height: 1.5;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.devtools-error-actions {
|
|
74
|
+
display: flex;
|
|
75
|
+
gap: 0.5rem;
|
|
76
|
+
margin-top: 0.25rem;
|
|
77
|
+
}
|
|
78
|
+
</style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { previewSource, productionUrl } from '../composables/state'
|
|
3
|
+
|
|
4
|
+
const { error } = defineProps<{
|
|
5
|
+
error?: string | Error | null
|
|
6
|
+
}>()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<DevtoolsError
|
|
11
|
+
icon="carbon:cloud-offline"
|
|
12
|
+
title="Production site unreachable"
|
|
13
|
+
:error="error"
|
|
14
|
+
>
|
|
15
|
+
<template v-if="!error">
|
|
16
|
+
<p class="text-xs text-[var(--color-text-muted)] max-w-xs leading-relaxed">
|
|
17
|
+
Could not connect to <code class="prod-url">{{ productionUrl }}</code>.
|
|
18
|
+
Check that the site is deployed and accessible.
|
|
19
|
+
</p>
|
|
20
|
+
</template>
|
|
21
|
+
<UButton
|
|
22
|
+
variant="soft"
|
|
23
|
+
size="xs"
|
|
24
|
+
icon="carbon:laptop"
|
|
25
|
+
@click="previewSource = 'local'"
|
|
26
|
+
>
|
|
27
|
+
Switch to local
|
|
28
|
+
</UButton>
|
|
29
|
+
</DevtoolsError>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<style scoped>
|
|
33
|
+
.prod-url {
|
|
34
|
+
font-family: var(--font-mono);
|
|
35
|
+
font-size: 0.6875rem;
|
|
36
|
+
padding: 0.125rem 0.375rem;
|
|
37
|
+
border-radius: var(--radius-sm);
|
|
38
|
+
background: var(--color-surface-sunken);
|
|
39
|
+
border: 1px solid var(--color-border-subtle);
|
|
40
|
+
}
|
|
41
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useRenderCodeHighlight } from '../composables/shiki'
|
|
3
|
+
|
|
4
|
+
const { code, lang, lines = false, transformRendered } = defineProps<{
|
|
5
|
+
code: string
|
|
6
|
+
lang: 'json' | 'xml' | 'js'
|
|
7
|
+
lines?: boolean
|
|
8
|
+
transformRendered?: (code: string) => string
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const rendered = computed(() => {
|
|
12
|
+
const highlight = useRenderCodeHighlight(code, lang)
|
|
13
|
+
return transformRendered ? transformRendered(highlight.value || '') : highlight.value
|
|
14
|
+
})
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<pre
|
|
19
|
+
class="code-block p-5"
|
|
20
|
+
:class="lines ? 'code-block-lines' : ''"
|
|
21
|
+
v-html="rendered"
|
|
22
|
+
/>
|
|
23
|
+
</template>
|
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
containerClass: '',
|
|
17
|
-
open: true,
|
|
18
|
-
padding: true,
|
|
19
|
-
collapse: true,
|
|
20
|
-
},
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
const open = useVModel(props, 'open')
|
|
2
|
+
const { icon, text, description, containerClass = '', collapse = true, padding = true, headerClass } = defineProps<{
|
|
3
|
+
icon?: string
|
|
4
|
+
text?: string
|
|
5
|
+
description?: string
|
|
6
|
+
containerClass?: string
|
|
7
|
+
headerClass?: string
|
|
8
|
+
collapse?: boolean
|
|
9
|
+
open?: boolean
|
|
10
|
+
padding?: boolean | string
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
const open = defineModel('open', { default: true })
|
|
14
|
+
|
|
24
15
|
function onToggle(e: any) {
|
|
25
16
|
open.value = e.target.open
|
|
26
17
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NuxtDevtoolsClient } from '@nuxt/devtools-kit/types'
|
|
2
|
+
import type { $Fetch } from 'nitropack/types'
|
|
3
|
+
import type { Ref } from 'vue'
|
|
4
|
+
import { onDevtoolsClientConnected } from '@nuxt/devtools-kit/iframe-client'
|
|
5
|
+
import { ref, watchEffect } from 'vue'
|
|
6
|
+
|
|
7
|
+
export const appFetch: Ref<$Fetch | undefined> = ref()
|
|
8
|
+
export const devtools: Ref<NuxtDevtoolsClient | undefined> = ref()
|
|
9
|
+
export const colorMode: Ref<'dark' | 'light'> = ref('dark')
|
|
10
|
+
|
|
11
|
+
export interface DevtoolsConnectionOptions {
|
|
12
|
+
onConnected?: (client: any) => void
|
|
13
|
+
onRouteChange?: (route: any) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initialize the base devtools connection.
|
|
18
|
+
* Call this in your module's devtools client setup.
|
|
19
|
+
* Returns a cleanup function.
|
|
20
|
+
*/
|
|
21
|
+
export function useDevtoolsConnection(options: DevtoolsConnectionOptions = {}): void {
|
|
22
|
+
onDevtoolsClientConnected(async (client) => {
|
|
23
|
+
// @ts-expect-error untyped
|
|
24
|
+
appFetch.value = client.host.app.$fetch
|
|
25
|
+
watchEffect(() => {
|
|
26
|
+
colorMode.value = client.host.app.colorMode.value
|
|
27
|
+
})
|
|
28
|
+
devtools.value = client.devtools
|
|
29
|
+
options.onConnected?.(client)
|
|
30
|
+
|
|
31
|
+
if (options.onRouteChange) {
|
|
32
|
+
const $route = client.host.nuxt.vueApp.config.globalProperties?.$route
|
|
33
|
+
options.onRouteChange($route)
|
|
34
|
+
client.host.nuxt.$router.afterEach((route: any) => {
|
|
35
|
+
options.onRouteChange!(route)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { HighlighterCore, LanguageRegistration } from 'shiki'
|
|
2
|
+
import type { ComputedRef, MaybeRef, Ref } from 'vue'
|
|
3
|
+
import { createHighlighterCore } from 'shiki/core'
|
|
4
|
+
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
|
|
5
|
+
import { computed, ref, toValue } from 'vue'
|
|
6
|
+
import { colorMode } from './rpc'
|
|
7
|
+
|
|
8
|
+
export const shiki: Ref<HighlighterCore | undefined> = ref()
|
|
9
|
+
|
|
10
|
+
export interface LoadShikiOptions {
|
|
11
|
+
extraLangs?: (LanguageRegistration | Promise<LanguageRegistration>)[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function loadShiki(options: LoadShikiOptions = {}): Promise<HighlighterCore> {
|
|
15
|
+
const langs: any[] = [
|
|
16
|
+
import('@shikijs/langs/xml'),
|
|
17
|
+
import('@shikijs/langs/json'),
|
|
18
|
+
import('@shikijs/langs/js'),
|
|
19
|
+
]
|
|
20
|
+
if (options.extraLangs) {
|
|
21
|
+
langs.push(...options.extraLangs)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
shiki.value = await createHighlighterCore({
|
|
25
|
+
themes: [
|
|
26
|
+
import('@shikijs/themes/vitesse-light'),
|
|
27
|
+
import('@shikijs/themes/vitesse-dark'),
|
|
28
|
+
],
|
|
29
|
+
langs,
|
|
30
|
+
engine: createJavaScriptRegexEngine(),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return shiki.value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useRenderCodeHighlight(code: MaybeRef<string>, lang: string): ComputedRef<string> {
|
|
37
|
+
return computed(() => {
|
|
38
|
+
if (!shiki.value)
|
|
39
|
+
return ''
|
|
40
|
+
const theme = colorMode.value === 'dark' ? 'vitesse-dark' : 'vitesse-light'
|
|
41
|
+
return shiki.value.codeToHtml(toValue(code) || '', {
|
|
42
|
+
lang,
|
|
43
|
+
theme,
|
|
44
|
+
}) || ''
|
|
45
|
+
})
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useDebounceFn, useLocalStorage } from '@vueuse/core'
|
|
2
|
+
import { hasProtocol, withBase } from 'ufo'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
export const refreshTime = ref(Date.now())
|
|
6
|
+
|
|
7
|
+
export const hostname = window.location.host
|
|
8
|
+
export const path = ref('/')
|
|
9
|
+
export const query = ref()
|
|
10
|
+
export const base = ref('/')
|
|
11
|
+
|
|
12
|
+
export const host = computed(() => withBase(base.value, `${window.location.protocol}//${hostname}`))
|
|
13
|
+
|
|
14
|
+
export const refreshSources = useDebounceFn(() => {
|
|
15
|
+
refreshTime.value = Date.now()
|
|
16
|
+
}, 200)
|
|
17
|
+
|
|
18
|
+
export const slowRefreshSources = useDebounceFn(() => {
|
|
19
|
+
refreshTime.value = Date.now()
|
|
20
|
+
}, 1000)
|
|
21
|
+
|
|
22
|
+
// Production preview state
|
|
23
|
+
export const previewSource = useLocalStorage<'local' | 'production'>('nuxt-seo:preview-source', 'local')
|
|
24
|
+
export const productionUrl = ref<string>('')
|
|
25
|
+
|
|
26
|
+
export const hasProductionUrl = computed(() => {
|
|
27
|
+
const url = productionUrl.value
|
|
28
|
+
if (!url || !hasProtocol(url))
|
|
29
|
+
return false
|
|
30
|
+
return !url.includes('localhost') && !url.includes('127.0.0.1')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export const isProductionMode = computed(() => previewSource.value === 'production' && hasProductionUrl.value)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default defineNuxtConfig({
|
|
2
|
+
ssr: false,
|
|
3
|
+
|
|
4
|
+
modules: [
|
|
5
|
+
'@nuxt/fonts',
|
|
6
|
+
'@nuxt/ui',
|
|
7
|
+
],
|
|
8
|
+
|
|
9
|
+
css: ['./assets/css/global.css'],
|
|
10
|
+
|
|
11
|
+
fonts: {
|
|
12
|
+
families: [
|
|
13
|
+
{ name: 'Hubot Sans' },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
devtools: {
|
|
18
|
+
enabled: false,
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
compatibilityDate: '2026-03-13',
|
|
22
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ModuleRegistration } from "./pro.mjs";
|
|
2
|
+
import * as _nuxt_schema0 from "@nuxt/schema";
|
|
3
|
+
|
|
4
|
+
//#region src/module.d.ts
|
|
5
|
+
declare const _default: _nuxt_schema0.NuxtModule<_nuxt_schema0.ModuleOptions, _nuxt_schema0.ModuleOptions, false>;
|
|
6
|
+
/**
|
|
7
|
+
* Register a Nuxt SEO module with the shared instance.
|
|
8
|
+
* Pro modules are detected by name and trigger license verification at build time.
|
|
9
|
+
*
|
|
10
|
+
* Call this from your module's setup().
|
|
11
|
+
*/
|
|
12
|
+
declare function registerNuxtSeoModule(registration: ModuleRegistration): void;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { _default as default, registerNuxtSeoModule };
|
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { hookNuxtSeoProLicense } from "./pro.mjs";
|
|
2
|
+
import { hookNuxtSeoTelemetry } from "./telemetry.mjs";
|
|
3
|
+
import { defineNuxtModule, useNuxt } from "@nuxt/kit";
|
|
4
|
+
//#region src/module.ts
|
|
5
|
+
var module_default = defineNuxtModule({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "nuxtseo-shared",
|
|
8
|
+
configKey: "nuxtSeoShared",
|
|
9
|
+
compatibility: { nuxt: ">=3.16.0" }
|
|
10
|
+
},
|
|
11
|
+
setup() {
|
|
12
|
+
hookNuxtSeoProLicense();
|
|
13
|
+
hookNuxtSeoTelemetry();
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const PRO_MODULES = new Set(["nuxt-skew-protection", "nuxt-ai-ready"]);
|
|
17
|
+
/**
|
|
18
|
+
* Register a Nuxt SEO module with the shared instance.
|
|
19
|
+
* Pro modules are detected by name and trigger license verification at build time.
|
|
20
|
+
*
|
|
21
|
+
* Call this from your module's setup().
|
|
22
|
+
*/
|
|
23
|
+
function registerNuxtSeoModule(registration) {
|
|
24
|
+
const nuxt = useNuxt();
|
|
25
|
+
nuxt._nuxtSeoModules = nuxt._nuxtSeoModules || [];
|
|
26
|
+
nuxt._nuxtSeoModules.push(registration);
|
|
27
|
+
if (PRO_MODULES.has(registration.name)) {
|
|
28
|
+
nuxt._nuxtSeoProModules = nuxt._nuxtSeoProModules || [];
|
|
29
|
+
nuxt._nuxtSeoProModules.push(registration);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { module_default as default, registerNuxtSeoModule };
|
package/dist/pro.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
//#region src/pro.d.ts
|
|
1
2
|
interface ModuleRegistration {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
features?: Record<string, boolean | string | number>;
|
|
3
|
+
name: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
features?: Record<string, boolean | string | number>;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Register a Nuxt SEO Pro module for license verification.
|
|
@@ -13,6 +13,5 @@ interface ModuleRegistration {
|
|
|
13
13
|
*/
|
|
14
14
|
declare function registerNuxtSeoProModule(registration: ModuleRegistration): void;
|
|
15
15
|
declare function hookNuxtSeoProLicense(): void;
|
|
16
|
-
|
|
17
|
-
export { hookNuxtSeoProLicense, registerNuxtSeoProModule };
|
|
18
|
-
export type { ModuleRegistration };
|
|
16
|
+
//#endregion
|
|
17
|
+
export { ModuleRegistration, hookNuxtSeoProLicense, registerNuxtSeoProModule };
|
package/dist/pro.mjs
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
1
|
+
import { useLogger, useNuxt } from "@nuxt/kit";
|
|
2
|
+
import { isCI, isTest } from "std-env";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import { useSiteConfig } from "nuxt-site-config/kit";
|
|
5
|
+
import { $fetch } from "ofetch";
|
|
6
|
+
//#region src/pro.ts
|
|
7
7
|
const logger = useLogger("nuxt-seo-pro");
|
|
8
|
+
/**
|
|
9
|
+
* Register a Nuxt SEO Pro module for license verification.
|
|
10
|
+
* Uses Nuxt hook so modules don't need to import from each other.
|
|
11
|
+
*
|
|
12
|
+
* Call this in your module setup. Registrations are collected
|
|
13
|
+
* before the single license verification fetch.
|
|
14
|
+
*/
|
|
8
15
|
function registerNuxtSeoProModule(registration) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
const nuxt = useNuxt();
|
|
17
|
+
nuxt._nuxtSeoProModules = nuxt._nuxtSeoProModules || [];
|
|
18
|
+
nuxt._nuxtSeoProModules.push(registration);
|
|
12
19
|
}
|
|
13
20
|
function hookNuxtSeoProLicense() {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (!res) {
|
|
62
|
-
spinner.cancel("License verification skipped (network issue)");
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
spinner.stop("License verified \u2713");
|
|
66
|
-
});
|
|
67
|
-
}
|
|
21
|
+
const nuxt = useNuxt();
|
|
22
|
+
if (!nuxt.options.dev && !nuxt.options._prepare && !nuxt._isNuxtSeoProVerifying) {
|
|
23
|
+
const license = nuxt.options.runtimeConfig.seoProKey || process.env.NUXT_SEO_PRO_KEY;
|
|
24
|
+
if (isTest || process.env.VITEST) return;
|
|
25
|
+
if (!isCI && !license) {
|
|
26
|
+
p.log.warn("⚠️ Building without license in non-CI environment. A license is required for production builds.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!license) {
|
|
30
|
+
p.log.error("🔐 Nuxt SEO Pro license required");
|
|
31
|
+
p.note("Set NUXT_SEO_PRO_KEY or configure via module options.\n\nhttps://nuxtseo.com/pro/dashboard", "Get your license");
|
|
32
|
+
throw new Error("Missing Nuxt SEO Pro license key.");
|
|
33
|
+
}
|
|
34
|
+
nuxt._isNuxtSeoProVerifying = true;
|
|
35
|
+
nuxt.hooks.hook("build:before", async () => {
|
|
36
|
+
p.intro("Nuxt SEO Pro: License Verification");
|
|
37
|
+
const siteConfig = useSiteConfig();
|
|
38
|
+
const spinner = p.spinner();
|
|
39
|
+
spinner.start("🔑 Verifying Nuxt SEO Pro license...");
|
|
40
|
+
if (!await $fetch("https://nuxtseo.com/api/pro/verify", {
|
|
41
|
+
method: "POST",
|
|
42
|
+
body: {
|
|
43
|
+
apiKey: license,
|
|
44
|
+
siteUrl: siteConfig.url?.startsWith("http") ? siteConfig.url : void 0,
|
|
45
|
+
siteName: siteConfig.name || void 0,
|
|
46
|
+
modules: nuxt._nuxtSeoProModules?.length > 0 ? nuxt._nuxtSeoProModules : void 0
|
|
47
|
+
}
|
|
48
|
+
}).catch((err) => {
|
|
49
|
+
if (err?.response?.status === 401) {
|
|
50
|
+
spinner.error("Invalid API key");
|
|
51
|
+
p.note("Your API key is invalid.\n\nhttps://nuxtseo.com/pro/dashboard", "License Issue");
|
|
52
|
+
throw new Error("Invalid Nuxt SEO Pro API key.");
|
|
53
|
+
}
|
|
54
|
+
if (err?.response?.status === 403) {
|
|
55
|
+
spinner.error("No active subscription");
|
|
56
|
+
p.note("Your subscription has expired or is inactive.\n\nhttps://nuxtseo.com/pro/dashboard", "License Issue");
|
|
57
|
+
throw new Error("No active Nuxt SEO Pro subscription.");
|
|
58
|
+
}
|
|
59
|
+
logger.error(err);
|
|
60
|
+
return null;
|
|
61
|
+
})) {
|
|
62
|
+
spinner.cancel("License verification skipped (network issue)");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
spinner.stop("License verified ✓");
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
//#endregion
|
|
70
70
|
export { hookNuxtSeoProLicense, registerNuxtSeoProModule };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/telemetry.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Hook into build to send telemetry. Call once from the shared module.
|
|
4
|
+
* Reads from the existing _nuxtSeoModules registration list.
|
|
5
|
+
* Only fires in CI, never in dev/test, fully fire-and-forget.
|
|
6
|
+
*
|
|
7
|
+
* Disable with NUXT_SEO_TELEMETRY_DISABLED=1
|
|
8
|
+
*/
|
|
9
|
+
declare function hookNuxtSeoTelemetry(): void;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { hookNuxtSeoTelemetry };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useNuxt } from "@nuxt/kit";
|
|
2
|
+
import { isCI, isTest, provider } from "std-env";
|
|
3
|
+
import { $fetch } from "ofetch";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { detectPackageManager } from "pkg-types";
|
|
6
|
+
//#region src/telemetry.ts
|
|
7
|
+
const TELEMETRY_ENDPOINT = "https://nuxtseo.com/api/telemetry/collect";
|
|
8
|
+
/**
|
|
9
|
+
* Hook into build to send telemetry. Call once from the shared module.
|
|
10
|
+
* Reads from the existing _nuxtSeoModules registration list.
|
|
11
|
+
* Only fires in CI, never in dev/test, fully fire-and-forget.
|
|
12
|
+
*
|
|
13
|
+
* Disable with NUXT_SEO_TELEMETRY_DISABLED=1
|
|
14
|
+
*/
|
|
15
|
+
function hookNuxtSeoTelemetry() {
|
|
16
|
+
const nuxt = useNuxt();
|
|
17
|
+
if (!isCI || isTest || process.env.VITEST || process.env.NUXT_SEO_TELEMETRY_DISABLED) return;
|
|
18
|
+
if (nuxt._isNuxtSeoTelemetryHooked) return;
|
|
19
|
+
nuxt._isNuxtSeoTelemetryHooked = true;
|
|
20
|
+
nuxt.hooks.hook("build:done", async () => {
|
|
21
|
+
const modules = nuxt._nuxtSeoModules || [];
|
|
22
|
+
if (modules.length === 0) return;
|
|
23
|
+
const projectHash = createHash("sha256").update(nuxt.options.rootDir).digest("hex").slice(0, 16);
|
|
24
|
+
const config = {};
|
|
25
|
+
for (const mod of modules) {
|
|
26
|
+
if (!mod.features) continue;
|
|
27
|
+
const safe = {};
|
|
28
|
+
for (const [k, v] of Object.entries(mod.features)) if (typeof v === "boolean" || typeof v === "number") safe[k] = v;
|
|
29
|
+
if (Object.keys(safe).length > 0) config[mod.name] = safe;
|
|
30
|
+
}
|
|
31
|
+
$fetch(TELEMETRY_ENDPOINT, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
body: {
|
|
34
|
+
projectHash,
|
|
35
|
+
modules: modules.map((m) => m.name),
|
|
36
|
+
moduleVersions: Object.fromEntries(modules.filter((m) => m.version).map((m) => [m.name, m.version])),
|
|
37
|
+
config: Object.keys(config).length > 0 ? config : void 0,
|
|
38
|
+
nuxtVersion: nuxt._version,
|
|
39
|
+
nodeVersion: process.version,
|
|
40
|
+
os: process.platform,
|
|
41
|
+
packageManager: await detectPackageManager(nuxt.options.rootDir).then((r) => r?.name).catch(() => void 0),
|
|
42
|
+
ci: provider || "true"
|
|
43
|
+
},
|
|
44
|
+
timeout: 5e3
|
|
45
|
+
}).catch(() => {});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { hookNuxtSeoTelemetry };
|