@liria24/og-image 1.0.0 → 1.0.2
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/client.d.mts +30 -0
- package/dist/client.mjs +14 -0
- package/package.json +4 -1
- package/bump.config.ts +0 -9
- package/bun.lock +0 -634
- package/nitro.config.ts +0 -104
- package/oxfmt.config.ts +0 -15
- package/oxlint.config.ts +0 -11
- package/server/assets/avatio.svg +0 -5
- package/server/middleware/log.ts +0 -9
- package/server/presets/avatio.ts +0 -97
- package/server/routes/health.get.ts +0 -3
- package/server/routes/images/[imageId].delete.ts +0 -49
- package/server/routes/images/[imageId].get.ts +0 -69
- package/server/routes/images/[slug]/[version].post.ts +0 -136
- package/server/routes/images/index.delete.ts +0 -40
- package/server/routes/index.get.ts +0 -5
- package/server/utils/definePreset.ts +0 -160
- package/server/utils/error.ts +0 -75
- package/server/utils/preset.ts +0 -11
- package/server/utils/render.ts +0 -83
- package/server/utils/schema.ts +0 -9
- package/server/utils/sha256Hex.ts +0 -4
- package/server/utils/validateRequest.ts +0 -29
- package/src/client.ts +0 -42
- package/taze.config.ts +0 -15
- package/tsconfig.json +0 -3
- package/tsdown.config.ts +0 -8
- package/types/assets.d.ts +0 -25
- package/types/presets.d.ts +0 -3
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ImageNode,
|
|
3
|
-
ImageSource,
|
|
4
|
-
ConstructRendererOptions,
|
|
5
|
-
Node,
|
|
6
|
-
RenderOptions,
|
|
7
|
-
} from 'takumi-js/wasm'
|
|
8
|
-
import type { GenericSchema, InferOutput } from 'valibot'
|
|
9
|
-
|
|
10
|
-
type PresetRenderOptions = Omit<RenderOptions, 'width' | 'height' | 'format' | 'devicePixelRatio'>
|
|
11
|
-
type PresetPropsSchema = GenericSchema
|
|
12
|
-
type FontTextValue = string | null | undefined | false
|
|
13
|
-
type FontText = string | readonly FontTextValue[]
|
|
14
|
-
|
|
15
|
-
export interface GoogleFontConfig {
|
|
16
|
-
family: string
|
|
17
|
-
options?: Omit<import('takumi-js/helpers').GoogleFontOptions, 'text'>
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface DefinePresetOptions<TPropsSchema extends PresetPropsSchema> {
|
|
21
|
-
version: string
|
|
22
|
-
props: TPropsSchema
|
|
23
|
-
fonts: readonly GoogleFontConfig[]
|
|
24
|
-
fontText: (props: InferOutput<TPropsSchema>) => FontText
|
|
25
|
-
content: (props: InferOutput<TPropsSchema>) => Node
|
|
26
|
-
width?: number
|
|
27
|
-
height?: number
|
|
28
|
-
format?: RenderOptions['format']
|
|
29
|
-
devicePixelRatio?: number
|
|
30
|
-
persistentImages?: ConstructRendererOptions['persistentImages']
|
|
31
|
-
renderOptions?: PresetRenderOptions
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface PresetRenderConfig {
|
|
35
|
-
width: number
|
|
36
|
-
height: number
|
|
37
|
-
format: RenderOptions['format']
|
|
38
|
-
devicePixelRatio: number
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface OgImagePreset {
|
|
42
|
-
slug: string
|
|
43
|
-
version: string
|
|
44
|
-
props: PresetPropsSchema
|
|
45
|
-
fonts: readonly GoogleFontConfig[]
|
|
46
|
-
fontText: (props: unknown) => string
|
|
47
|
-
persistentImages?: ConstructRendererOptions['persistentImages']
|
|
48
|
-
renderOptions: PresetRenderConfig
|
|
49
|
-
content: (props: unknown) => Node
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
type DefinedOgImagePreset<TPropsSchema extends PresetPropsSchema> = Omit<
|
|
53
|
-
OgImagePreset,
|
|
54
|
-
'slug' | 'props' | 'fontText' | 'content'
|
|
55
|
-
> & {
|
|
56
|
-
props: TPropsSchema
|
|
57
|
-
fontText: (props: InferOutput<TPropsSchema>) => string
|
|
58
|
-
content: (props: InferOutput<TPropsSchema>) => Node
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const normalizeFontText = (value: FontText) =>
|
|
62
|
-
typeof value === 'string'
|
|
63
|
-
? value
|
|
64
|
-
: value.filter((text): text is string => Boolean(text)).join('\n')
|
|
65
|
-
|
|
66
|
-
export const definePreset = <const TPropsSchema extends PresetPropsSchema>(
|
|
67
|
-
options: DefinePresetOptions<TPropsSchema>,
|
|
68
|
-
): DefinedOgImagePreset<TPropsSchema> => {
|
|
69
|
-
const {
|
|
70
|
-
fontText,
|
|
71
|
-
width = 1200,
|
|
72
|
-
height = 630,
|
|
73
|
-
format = 'png',
|
|
74
|
-
devicePixelRatio = 1,
|
|
75
|
-
renderOptions,
|
|
76
|
-
...preset
|
|
77
|
-
} = options
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
...preset,
|
|
81
|
-
fontText: (props) => normalizeFontText(fontText(props)),
|
|
82
|
-
renderOptions: {
|
|
83
|
-
width,
|
|
84
|
-
height,
|
|
85
|
-
format,
|
|
86
|
-
devicePixelRatio,
|
|
87
|
-
...renderOptions,
|
|
88
|
-
},
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface SvgImageAsset {
|
|
93
|
-
src: string
|
|
94
|
-
svg: string
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
interface DefineSvgImageOptions {
|
|
98
|
-
color: string
|
|
99
|
-
width: number
|
|
100
|
-
height: number
|
|
101
|
-
src?: string
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const setStyleColor = (style: string, color: string) => {
|
|
105
|
-
const normalizedStyle = style.trim().replace(/;+$/, '')
|
|
106
|
-
const nextStyle = normalizedStyle.match(/(^|;)\s*color\s*:/i)
|
|
107
|
-
? normalizedStyle.replace(/(^|;)\s*color\s*:[^;]*/i, `$1 color: ${color}`)
|
|
108
|
-
: [normalizedStyle, `color: ${color}`].filter(Boolean).join('; ')
|
|
109
|
-
|
|
110
|
-
return nextStyle.trim().replace(/;?$/, ';')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const escapeAttribute = (value: string) =>
|
|
114
|
-
value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<')
|
|
115
|
-
|
|
116
|
-
const withSvgRootColor = (svg: string, color: string) => {
|
|
117
|
-
let foundSvgRoot = false
|
|
118
|
-
const replaced = svg.replace(/<svg\b([^>]*)>/i, (_tag, attributes: string) => {
|
|
119
|
-
foundSvgRoot = true
|
|
120
|
-
const nextAttributes = attributes.match(/\sstyle=(["'])(.*?)\1/i)
|
|
121
|
-
? attributes.replace(
|
|
122
|
-
/\sstyle=(["'])(.*?)\1/i,
|
|
123
|
-
(_style, quote: string, style: string) =>
|
|
124
|
-
` style=${quote}${escapeAttribute(setStyleColor(style, color))}${quote}`,
|
|
125
|
-
)
|
|
126
|
-
: `${attributes} style="color: ${escapeAttribute(color)};"`
|
|
127
|
-
|
|
128
|
-
return `<svg${nextAttributes}>`
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
if (!foundSvgRoot) throw new Error('SVG root element was not found.')
|
|
132
|
-
|
|
133
|
-
return replaced
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export const defineSvgImage = (
|
|
137
|
-
asset: SvgImageAsset,
|
|
138
|
-
{ color, height, src = asset.src, width }: DefineSvgImageOptions,
|
|
139
|
-
): { image: ImageSource; node: ImageNode } => ({
|
|
140
|
-
image: {
|
|
141
|
-
src,
|
|
142
|
-
data: new TextEncoder().encode(withSvgRootColor(asset.svg, color)),
|
|
143
|
-
},
|
|
144
|
-
node: {
|
|
145
|
-
type: 'image',
|
|
146
|
-
src,
|
|
147
|
-
width,
|
|
148
|
-
height,
|
|
149
|
-
},
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
export const withPresetId = <const TPropsSchema extends PresetPropsSchema>(
|
|
153
|
-
preset: DefinedOgImagePreset<TPropsSchema>,
|
|
154
|
-
slug: string,
|
|
155
|
-
): OgImagePreset => ({
|
|
156
|
-
...preset,
|
|
157
|
-
slug,
|
|
158
|
-
fontText: preset.fontText as (props: unknown) => string,
|
|
159
|
-
content: preset.content as (props: unknown) => Node,
|
|
160
|
-
})
|
package/server/utils/error.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { getReasonPhrase, StatusCodes } from 'http-status-codes'
|
|
2
|
-
import { HTTPError } from 'nitro/h3'
|
|
3
|
-
|
|
4
|
-
interface ServerErrorOptions {
|
|
5
|
-
log?: {
|
|
6
|
-
tag?: string
|
|
7
|
-
message: string
|
|
8
|
-
}
|
|
9
|
-
responseMessage?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const FAILURE_CACHE_CONTROL = 'no-store'
|
|
13
|
-
|
|
14
|
-
export const serverError = {
|
|
15
|
-
/** 400 */
|
|
16
|
-
badRequest(options?: ServerErrorOptions): never {
|
|
17
|
-
if (options?.log) console.error(options.log.message)
|
|
18
|
-
throw new HTTPError({
|
|
19
|
-
status: StatusCodes.BAD_REQUEST,
|
|
20
|
-
statusText: getReasonPhrase(StatusCodes.BAD_REQUEST),
|
|
21
|
-
message: options?.responseMessage,
|
|
22
|
-
headers: {
|
|
23
|
-
'cache-control': FAILURE_CACHE_CONTROL,
|
|
24
|
-
},
|
|
25
|
-
})
|
|
26
|
-
},
|
|
27
|
-
/** 401 */
|
|
28
|
-
unauthorized(options?: ServerErrorOptions): never {
|
|
29
|
-
if (options?.log) console.error(options.log.message)
|
|
30
|
-
throw new HTTPError({
|
|
31
|
-
status: StatusCodes.UNAUTHORIZED,
|
|
32
|
-
statusText: getReasonPhrase(StatusCodes.UNAUTHORIZED),
|
|
33
|
-
message: options?.responseMessage,
|
|
34
|
-
headers: {
|
|
35
|
-
'cache-control': FAILURE_CACHE_CONTROL,
|
|
36
|
-
},
|
|
37
|
-
})
|
|
38
|
-
},
|
|
39
|
-
/** 403 */
|
|
40
|
-
forbidden(options?: ServerErrorOptions): never {
|
|
41
|
-
if (options?.log) console.error(options.log.message)
|
|
42
|
-
throw new HTTPError({
|
|
43
|
-
status: StatusCodes.FORBIDDEN,
|
|
44
|
-
statusText: getReasonPhrase(StatusCodes.FORBIDDEN),
|
|
45
|
-
message: options?.responseMessage,
|
|
46
|
-
headers: {
|
|
47
|
-
'cache-control': FAILURE_CACHE_CONTROL,
|
|
48
|
-
},
|
|
49
|
-
})
|
|
50
|
-
},
|
|
51
|
-
/** 404 */
|
|
52
|
-
notFound(options?: ServerErrorOptions): never {
|
|
53
|
-
if (options?.log) console.error(options.log.message)
|
|
54
|
-
throw new HTTPError({
|
|
55
|
-
status: StatusCodes.NOT_FOUND,
|
|
56
|
-
statusText: getReasonPhrase(StatusCodes.NOT_FOUND),
|
|
57
|
-
message: options?.responseMessage,
|
|
58
|
-
headers: {
|
|
59
|
-
'cache-control': FAILURE_CACHE_CONTROL,
|
|
60
|
-
},
|
|
61
|
-
})
|
|
62
|
-
},
|
|
63
|
-
/** 500 */
|
|
64
|
-
internalServerError(options?: ServerErrorOptions): never {
|
|
65
|
-
if (options?.log) console.error(options.log.message)
|
|
66
|
-
throw new HTTPError({
|
|
67
|
-
status: StatusCodes.INTERNAL_SERVER_ERROR,
|
|
68
|
-
statusText: getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
|
|
69
|
-
message: options?.responseMessage,
|
|
70
|
-
headers: {
|
|
71
|
-
'cache-control': FAILURE_CACHE_CONTROL,
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
},
|
|
75
|
-
}
|
package/server/utils/preset.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { allPresets } from '#presets'
|
|
2
|
-
|
|
3
|
-
const presetMap: Record<string, OgImagePreset> = Object.fromEntries(
|
|
4
|
-
allPresets.map((preset) => [preset.slug, preset]),
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
export const getPreset = (descriptor: OgImageDescriptor): OgImagePreset => {
|
|
8
|
-
const preset = presetMap[descriptor.slug]
|
|
9
|
-
if (!preset || preset.version !== descriptor.version) throw new Error('Preset not found')
|
|
10
|
-
return preset
|
|
11
|
-
}
|
package/server/utils/render.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import init, { Renderer } from '@takumi-rs/wasm'
|
|
2
|
-
import wasmModule from '@takumi-rs/wasm/next'
|
|
3
|
-
import { googleFont } from 'takumi-js/helpers'
|
|
4
|
-
|
|
5
|
-
const RENDER_TIMEOUT_MS = 15_000 // 15 seconds
|
|
6
|
-
|
|
7
|
-
const resolved = await wasmModule
|
|
8
|
-
const module =
|
|
9
|
-
resolved && typeof resolved === 'object' && 'default' in resolved ? resolved.default : resolved
|
|
10
|
-
await init({ module_or_path: module })
|
|
11
|
-
|
|
12
|
-
type RenderPng = (descriptor: OgImageDescriptor, context?: RenderContext) => Promise<Uint8Array>
|
|
13
|
-
|
|
14
|
-
export interface RenderContext {
|
|
15
|
-
signal?: AbortSignal
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const renderDescriptor = async (
|
|
19
|
-
descriptor: OgImageDescriptor,
|
|
20
|
-
context: RenderContext = {},
|
|
21
|
-
): Promise<Uint8Array> => {
|
|
22
|
-
if (context.signal?.aborted) throw new Error('Render aborted')
|
|
23
|
-
|
|
24
|
-
const preset = getPreset(descriptor)
|
|
25
|
-
if (!preset) throw new Error('Unknown renderer')
|
|
26
|
-
|
|
27
|
-
if (context.signal?.aborted) throw new Error('Render aborted')
|
|
28
|
-
|
|
29
|
-
const renderer = new Renderer({
|
|
30
|
-
loadDefaultFonts: false,
|
|
31
|
-
persistentImages: preset.persistentImages,
|
|
32
|
-
})
|
|
33
|
-
try {
|
|
34
|
-
const descriptors = (
|
|
35
|
-
await Promise.all(
|
|
36
|
-
preset.fonts.map((config) =>
|
|
37
|
-
googleFont(config.family, {
|
|
38
|
-
...config.options,
|
|
39
|
-
text: preset.fontText(descriptor.props),
|
|
40
|
-
}),
|
|
41
|
-
),
|
|
42
|
-
)
|
|
43
|
-
).flat()
|
|
44
|
-
|
|
45
|
-
await renderer.loadFonts(descriptors, context.signal)
|
|
46
|
-
return renderer.render(preset.content(descriptor.props), preset.renderOptions)
|
|
47
|
-
} finally {
|
|
48
|
-
renderer.free()
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const pngBytes = (png: ArrayBuffer | ArrayBufferView) =>
|
|
53
|
-
png instanceof ArrayBuffer
|
|
54
|
-
? new Uint8Array(png)
|
|
55
|
-
: new Uint8Array(png.buffer, png.byteOffset, png.byteLength)
|
|
56
|
-
|
|
57
|
-
export const withRenderTimeout = async (
|
|
58
|
-
descriptor: OgImageDescriptor,
|
|
59
|
-
renderPng: RenderPng,
|
|
60
|
-
context: RenderContext = {},
|
|
61
|
-
) => {
|
|
62
|
-
const controller = new AbortController()
|
|
63
|
-
let timeout: ReturnType<typeof setTimeout> | undefined
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
67
|
-
timeout = setTimeout(() => {
|
|
68
|
-
controller.abort()
|
|
69
|
-
reject(new Error('OG image render timed out'))
|
|
70
|
-
}, RENDER_TIMEOUT_MS)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
return await Promise.race([
|
|
74
|
-
renderPng(descriptor, {
|
|
75
|
-
...context,
|
|
76
|
-
signal: controller.signal,
|
|
77
|
-
}),
|
|
78
|
-
timeoutPromise,
|
|
79
|
-
])
|
|
80
|
-
} finally {
|
|
81
|
-
if (timeout) clearTimeout(timeout)
|
|
82
|
-
}
|
|
83
|
-
}
|
package/server/utils/schema.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { H3Event } from 'nitro'
|
|
2
|
-
import { getValidatedQuery, getValidatedRouterParams, readValidatedBody } from 'nitro/h3'
|
|
3
|
-
import * as v from 'valibot'
|
|
4
|
-
|
|
5
|
-
const throwIfFailed = <T extends v.GenericSchema>(
|
|
6
|
-
result: v.SafeParseResult<T>,
|
|
7
|
-
): v.InferOutput<T> => {
|
|
8
|
-
if (!result.success) {
|
|
9
|
-
if (import.meta.dev) console.error(result.issues)
|
|
10
|
-
throw serverError.badRequest({ responseMessage: 'Validation Error' })
|
|
11
|
-
}
|
|
12
|
-
return result.output
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const validateBody = async <const T extends v.GenericSchema>(
|
|
16
|
-
event: H3Event,
|
|
17
|
-
s: T,
|
|
18
|
-
): Promise<v.InferOutput<T>> => throwIfFailed(await readValidatedBody(event, v.safeParser(s)))
|
|
19
|
-
|
|
20
|
-
export const validateParams = async <T extends v.GenericSchema>(
|
|
21
|
-
event: H3Event,
|
|
22
|
-
s: T,
|
|
23
|
-
): Promise<v.InferOutput<T>> =>
|
|
24
|
-
throwIfFailed(await getValidatedRouterParams(event, v.safeParser(s)))
|
|
25
|
-
|
|
26
|
-
export const validateQuery = async <T extends v.GenericSchema>(
|
|
27
|
-
event: H3Event,
|
|
28
|
-
s: T,
|
|
29
|
-
): Promise<v.InferOutput<T>> => throwIfFailed(await getValidatedQuery(event, v.safeParser(s)))
|
package/src/client.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { ofetch } from 'ofetch'
|
|
2
|
-
|
|
3
|
-
type Presets = 'avatio'
|
|
4
|
-
interface PresetVersions {
|
|
5
|
-
avatio: 'v1'
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface RequestOgImageOptions<TProps = unknown> {
|
|
9
|
-
/**
|
|
10
|
-
* @default https://og.liria.me
|
|
11
|
-
*/
|
|
12
|
-
endpoint?: string
|
|
13
|
-
/**
|
|
14
|
-
* @default process.env.OG_IMAGE_SECRET
|
|
15
|
-
*/
|
|
16
|
-
secret?: string
|
|
17
|
-
preset: Presets
|
|
18
|
-
version: PresetVersions[Presets]
|
|
19
|
-
props: TProps
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface IssueImageResponse {
|
|
23
|
-
url: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const requestOgImage = async ({
|
|
27
|
-
preset,
|
|
28
|
-
version,
|
|
29
|
-
props,
|
|
30
|
-
endpoint = 'https://og.liria.me',
|
|
31
|
-
secret = process.env.OG_IMAGE_SECRET || '',
|
|
32
|
-
}: RequestOgImageOptions) =>
|
|
33
|
-
ofetch<IssueImageResponse>(
|
|
34
|
-
`/images/${encodeURIComponent(preset)}/${encodeURIComponent(version)}`,
|
|
35
|
-
{
|
|
36
|
-
baseURL: endpoint,
|
|
37
|
-
method: 'POST',
|
|
38
|
-
headers: { 'content-type': 'application/json' },
|
|
39
|
-
body: { secret, props },
|
|
40
|
-
retry: 3,
|
|
41
|
-
},
|
|
42
|
-
)
|
package/taze.config.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'taze'
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
force: true,
|
|
5
|
-
write: true,
|
|
6
|
-
install: false,
|
|
7
|
-
interactive: true,
|
|
8
|
-
recursive: false,
|
|
9
|
-
includeLocked: true,
|
|
10
|
-
ignorePaths: ['**/node_modules/**'],
|
|
11
|
-
ignoreOtherWorkspaces: true,
|
|
12
|
-
depFields: {
|
|
13
|
-
overrides: false,
|
|
14
|
-
},
|
|
15
|
-
})
|
package/tsconfig.json
DELETED
package/tsdown.config.ts
DELETED
package/types/assets.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
declare module '*.woff2' {
|
|
2
|
-
const value: ArrayBuffer
|
|
3
|
-
export default value
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
declare module '#fonts/*' {
|
|
7
|
-
interface FontAssetDefinition {
|
|
8
|
-
key: string
|
|
9
|
-
name: string
|
|
10
|
-
path: string
|
|
11
|
-
ranges: readonly (readonly [number, number])[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const fontFamily: string
|
|
15
|
-
export const fonts: readonly FontAssetDefinition[]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
declare module '#images' {
|
|
19
|
-
interface OgImageAsset {
|
|
20
|
-
src: string
|
|
21
|
-
svg: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const images: Record<string, OgImageAsset>
|
|
25
|
-
}
|
package/types/presets.d.ts
DELETED