Skip to content

Commit f5c18bd

Browse files
committed
refactor(astro-sdk): tighten config resolution, types, helpers; drop PUBLIC_ env
Centralize ImageKit config resolution, harden the image service, and simplify the OG helper. Also remove PUBLIC_IMAGEKIT_URL_ENDPOINT since every SDK caller runs server-side. Resolution & integration: - getImageKitConfig is the single resolver: prop > integration config (virtual:@imagekit/astro/config) > IMAGEKIT_URL_ENDPOINT - Video.astro delegates to getImageKitConfig instead of resolving inline - Integration only carries forward known service config keys (urlEndpoint, transformationPosition); foreign keys from a previous service no longer leak through - Warn when urlEndpoint cannot be parsed as a URL - Mark virtual:@imagekit/astro/config external in tsup Image service: - Throw a clear error when no urlEndpoint can be resolved (was: silent '') - Add IKImageTransform type, remove all `as any` casts - Align quality presets with Astro sharp defaults (low/mid/high/max = 25/50/75/100, was 30/80/90/95) Helpers & types: - Drop `format` option from getOgImageTags; emit a single image URL for og:image and twitter:image (ImageKit auto-format negotiates WebP/AVIF/JPEG via the Accept header) - VideoProps extends Astro's HTMLAttributes<'video'> for full HTML video attribute autocomplete (replaces [key: string]: unknown) test-app: - Add /og page exercising getOgImageTags (default, minimal, custom dimensions + transformation) - Link /og from index page
1 parent 45d7b11 commit f5c18bd

9 files changed

Lines changed: 201 additions & 94 deletions

File tree

‎imagekit-astro/src/components/Video.astro‎

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
---
22
import { buildSrc } from '@imagekit/javascript';
3-
import {
4-
urlEndpoint as integrationUrlEndpoint,
5-
transformationPosition as integrationTransformationPosition,
6-
} from 'virtual:@imagekit/astro/config';
73
import { getImageKitConfig } from '../lib/imagekit';
84
import type { VideoProps } from '../types/index';
95
@@ -21,17 +17,15 @@ const {
2117
2218
// Resolve ImageKit configuration.
2319
// Priority: prop > integration config (astro.config.mjs) > env vars.
24-
const config = getImageKitConfig({
25-
urlEndpoint: urlEndpoint ?? (integrationUrlEndpoint || undefined),
26-
transformationPosition:
27-
transformationPosition ?? integrationTransformationPosition,
28-
});
20+
const config = getImageKitConfig({ urlEndpoint, transformationPosition });
2921
30-
// Build the video URL
22+
// Build the video URL.
23+
// Note: width/height props are HTML attributes only; they are NOT applied as
24+
// ImageKit transformations. To resize the video, pass them via `transformation`.
3125
const videoSrc = buildSrc({
3226
src,
3327
urlEndpoint: config.urlEndpoint,
34-
transformation: [...transformation],
28+
transformation,
3529
queryParameters,
3630
transformationPosition: config.transformationPosition,
3731
});
@@ -48,3 +42,4 @@ if (className) {
4842
class={videoClassName}
4943
{...restProps}
5044
/>
45+

‎imagekit-astro/src/helpers/index.ts‎

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export interface OgMetaTag {
1111
/**
1212
* Build OG/Twitter meta tags for a given image.
1313
*
14-
* Render these tags inside your page's <head>.
14+
* Render these tags inside your page's <head>. ImageKit serves the optimal
15+
* image format automatically based on the requesting client (no `format`
16+
* option needed).
1517
*/
1618
export function getOgImageTags(props: OgImageTagOptions): OgMetaTag[] {
1719
const {
@@ -28,7 +30,6 @@ export function getOgImageTags(props: OgImageTagOptions): OgMetaTag[] {
2830
transformation = [],
2931
queryParameters,
3032
transformationPosition,
31-
format,
3233
width = OG_IMAGE_WIDTH,
3334
height = OG_IMAGE_HEIGHT,
3435
} = props;
@@ -47,33 +48,19 @@ export function getOgImageTags(props: OgImageTagOptions): OgMetaTag[] {
4748

4849
const config = getImageKitConfig({ urlEndpoint, transformationPosition });
4950

50-
const ogImageUrl = buildSrc({
51+
const imageUrl = buildSrc({
5152
src,
5253
urlEndpoint: config.urlEndpoint,
53-
transformation: [
54-
...transformation,
55-
{ width, height },
56-
],
57-
queryParameters,
58-
transformationPosition: config.transformationPosition,
59-
});
60-
61-
const twitterImageUrl = buildSrc({
62-
src,
63-
urlEndpoint: config.urlEndpoint,
64-
transformation: [
65-
...transformation,
66-
{ width, height, format: format || 'webp' },
67-
],
54+
transformation: [...transformation, { width, height }],
6855
queryParameters,
6956
transformationPosition: config.transformationPosition,
7057
});
7158

7259
const tags: Array<OgMetaTag | undefined> = [
7360
resolvedOgTitle ? { property: 'og:title', content: resolvedOgTitle } : undefined,
7461
resolvedOgDescription ? { property: 'og:description', content: resolvedOgDescription } : undefined,
75-
{ property: 'og:image', content: ogImageUrl },
76-
{ property: 'og:image:secure_url', content: ogImageUrl },
62+
{ property: 'og:image', content: imageUrl },
63+
{ property: 'og:image:secure_url', content: imageUrl },
7764
{ property: 'og:image:width', content: String(width) },
7865
{ property: 'og:image:height', content: String(height) },
7966
alt ? { property: 'og:image:alt', content: alt } : undefined,
@@ -82,8 +69,9 @@ export function getOgImageTags(props: OgImageTagOptions): OgMetaTag[] {
8269
? { property: 'twitter:description', content: resolvedTwitterDescription }
8370
: undefined,
8471
{ property: 'twitter:card', content: twitterCard },
85-
{ property: 'twitter:image', content: twitterImageUrl },
72+
{ property: 'twitter:image', content: imageUrl },
8673
];
8774

8875
return tags.filter((tag): tag is OgMetaTag => Boolean(tag));
8976
}
77+

‎imagekit-astro/src/integration.ts‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ export default { urlEndpoint, transformationPosition };
3838
export interface ImageKitIntegrationOptions {
3939
/**
4040
* The ImageKit URL endpoint.
41-
* If omitted, PUBLIC_IMAGEKIT_URL_ENDPOINT is checked first,
42-
* then IMAGEKIT_URL_ENDPOINT.
41+
* If omitted, IMAGEKIT_URL_ENDPOINT is used.
4342
*/
4443
urlEndpoint?: string;
4544

@@ -197,7 +196,6 @@ export default function imagekit(
197196
const resolvedUrlEndpoint =
198197
options.urlEndpoint ??
199198
asString(existingServiceConfig.urlEndpoint) ??
200-
import.meta.env?.PUBLIC_IMAGEKIT_URL_ENDPOINT ??
201199
import.meta.env?.IMAGEKIT_URL_ENDPOINT;
202200

203201
const resolvedTransformationPosition =
@@ -211,6 +209,12 @@ export default function imagekit(
211209
? parseEndpoint(resolvedUrlEndpoint)
212210
: undefined;
213211

212+
if (resolvedUrlEndpoint && !endpoint) {
213+
logger.warn(
214+
`Could not parse urlEndpoint "${resolvedUrlEndpoint}" as a URL. Make sure it includes the protocol (e.g. "https://ik.imagekit.io/your_id"). The endpoint host will not be added to image.domains/remotePatterns.`,
215+
);
216+
}
217+
214218
const mergedDomains = uniqStrings([
215219
...normalizeDomainList(imageConfigRecord.domains),
216220
...normalizeDomainList(options.domains),
@@ -224,9 +228,10 @@ export default function imagekit(
224228
...getDefaultRemotePatterns(endpoint),
225229
]);
226230

227-
const nextServiceConfig: Record<string, unknown> = {
228-
...(typeof existingServiceConfig === 'object' ? existingServiceConfig : {}),
229-
};
231+
// Only carry forward keys we recognize. Any leftover keys from a
232+
// previous image service config (e.g. sharp's `limitInputPixels`)
233+
// would not make sense for this service.
234+
const nextServiceConfig: Record<string, unknown> = {};
230235

231236
if (resolvedUrlEndpoint) {
232237
nextServiceConfig.urlEndpoint = resolvedUrlEndpoint;
@@ -248,7 +253,7 @@ export default function imagekit(
248253

249254
if (!resolvedUrlEndpoint) {
250255
logger.warn(
251-
'No ImageKit urlEndpoint found. Set PUBLIC_IMAGEKIT_URL_ENDPOINT or IMAGEKIT_URL_ENDPOINT, or pass urlEndpoint to imagekit().',
256+
'No ImageKit urlEndpoint found. Set IMAGEKIT_URL_ENDPOINT, or pass urlEndpoint to imagekit().',
252257
);
253258
}
254259

‎imagekit-astro/src/lib/imagekit.ts‎

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11

2+
import {
3+
urlEndpoint as integrationUrlEndpoint,
4+
transformationPosition as integrationTransformationPosition,
5+
} from 'virtual:@imagekit/astro/config';
26

37
/**
48
* ImageKit configuration options.
5-
* Can be passed as props to override environment variable defaults.
9+
* Can be passed as props to override values resolved from the integration
10+
* config (`astro.config.mjs`) or environment variables.
611
*/
712
export interface ImageKitConfig {
8-
/**
9-
* The URL endpoint for your ImageKit account.
13+
/**
14+
* The URL endpoint for your ImageKit account.
1015
* Get it from https://imagekit.io/dashboard/url-endpoints
1116
*/
1217
urlEndpoint: string;
13-
18+
1419
/**
1520
* Position of the transformation string in the URL.
1621
* - `'query'` (default): appended as `?tr=...`
@@ -20,26 +25,31 @@ export interface ImageKitConfig {
2025
}
2126

2227
/**
23-
* Resolves ImageKit configuration from props and/or environment variables.
24-
*
25-
* Priority: prop overrides > environment variables
26-
*
27-
* @throws {Error} If no urlEndpoint is available from either source.
28+
* Resolves ImageKit configuration.
29+
*
30+
* Priority:
31+
* 1. Per-call overrides (props)
32+
* 2. Integration config from `astro.config.mjs` (via virtual module)
33+
* 3. `IMAGEKIT_URL_ENDPOINT` env var
34+
*
35+
* @throws {Error} If no urlEndpoint is available from any source.
2836
*/
2937
export function getImageKitConfig(overrides?: Partial<ImageKitConfig>): ImageKitConfig {
3038
const urlEndpoint =
31-
overrides?.urlEndpoint ??
32-
import.meta.env.PUBLIC_IMAGEKIT_URL_ENDPOINT ??
39+
overrides?.urlEndpoint ||
40+
integrationUrlEndpoint ||
3341
import.meta.env.IMAGEKIT_URL_ENDPOINT;
3442

3543
if (!urlEndpoint) {
3644
throw new Error(
37-
'An ImageKit URL endpoint is required. Set PUBLIC_IMAGEKIT_URL_ENDPOINT or IMAGEKIT_URL_ENDPOINT in your environment, or pass urlEndpoint as a prop.'
45+
'An ImageKit URL endpoint is required. Pass urlEndpoint to the imagekit() integration in astro.config.mjs, set IMAGEKIT_URL_ENDPOINT, or pass urlEndpoint as a prop.'
3846
);
3947
}
4048

4149
return {
4250
urlEndpoint,
43-
transformationPosition: overrides?.transformationPosition ?? 'query',
51+
transformationPosition:
52+
overrides?.transformationPosition ?? integrationTransformationPosition ?? 'query',
4453
};
4554
}
55+

‎imagekit-astro/src/services/imagekit-service.ts‎

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ type UnresolvedSrcSetValue = {
1313
attributes?: Record<string, any>;
1414
};
1515

16+
/**
17+
* Astro `ImageTransform` augmented with the ImageKit-specific props
18+
* declared on `Astro.CustomImageProps` via the integration's `injectTypes`.
19+
*/
20+
type IKImageTransform = ImageTransform & {
21+
urlEndpoint?: string;
22+
transformation?: Transformation[];
23+
queryParameters?: Record<string, string | number>;
24+
transformationPosition?: 'path' | 'query';
25+
densities?: Array<number | `${number}x`>;
26+
widths?: number[];
27+
};
28+
1629
export interface ImageKitServiceConfig {
1730
/**
1831
* The URL endpoint for your ImageKit account.
@@ -30,32 +43,45 @@ export interface ImageKitServiceConfig {
3043

3144
/**
3245
* Astro quality presets mapped to ImageKit quality (0-100).
33-
* https://imagekit.io/docs/image-resize-and-crop#quality-q
46+
*
47+
* Aligned with Astro's sharp service defaults
48+
* (https://docs.astro.build/en/reference/image-service-reference/).
3449
*/
3550
const QUALITY_PRESETS: Record<string, number> = {
36-
low: 30,
37-
mid: 80,
38-
high: 90,
39-
max: 95,
51+
low: 25,
52+
mid: 50,
53+
high: 75,
54+
max: 100,
4055
};
4156

4257
/**
4358
* Resolves the ImageKit config from the service config and per-image overrides.
59+
*
60+
* Priority: per-image prop > integration service config > env vars.
61+
*
62+
* @throws {Error} If no urlEndpoint can be resolved.
4463
*/
4564
function resolveConfig(
46-
options: ImageTransform,
65+
options: IKImageTransform,
4766
imageConfig: AstroConfig['image'],
4867
): { urlEndpoint: string; transformationPosition: 'path' | 'query' } {
49-
const config = (imageConfig.service.config ?? {}) as ImageKitServiceConfig;
68+
const config = (imageConfig.service.config ?? {}) as Partial<ImageKitServiceConfig>;
5069
const urlEndpoint =
51-
(options as any).urlEndpoint ??
52-
config.urlEndpoint ??
53-
import.meta.env?.PUBLIC_IMAGEKIT_URL_ENDPOINT ??
54-
import.meta.env?.IMAGEKIT_URL_ENDPOINT ??
55-
'';
56-
const transformationPosition =
57-
(options as any).transformationPosition ?? config.transformationPosition ?? 'query';
58-
return { urlEndpoint, transformationPosition };
70+
options.urlEndpoint ||
71+
config.urlEndpoint ||
72+
import.meta.env?.IMAGEKIT_URL_ENDPOINT;
73+
74+
if (!urlEndpoint) {
75+
throw new Error(
76+
'[@imagekit/astro] An ImageKit URL endpoint is required. Pass urlEndpoint to the imagekit() integration in astro.config.mjs, set IMAGEKIT_URL_ENDPOINT, or pass urlEndpoint as a prop on <Image>.',
77+
);
78+
}
79+
80+
return {
81+
urlEndpoint,
82+
transformationPosition:
83+
options.transformationPosition ?? config.transformationPosition ?? 'query',
84+
};
5985
}
6086

6187
/**
@@ -87,13 +113,12 @@ function resolveQuality(quality: ImageTransform['quality']): number | undefined
87113
* preserves aspect ratio and there's no crop/focus to apply on top of it.
88114
* Users who need cropping or focus should pass them via `transformation`.
89115
*/
90-
function buildIKTransformations(options: ImageTransform): Transformation[] {
116+
function buildIKTransformations(options: IKImageTransform): Transformation[] {
91117
const result: Transformation[] = [];
92118

93119
// 1. User-supplied transformations come first.
94-
const userTransformation = (options as any).transformation as Transformation[] | undefined;
95-
if (Array.isArray(userTransformation) && userTransformation.length > 0) {
96-
result.push(...userTransformation);
120+
if (Array.isArray(options.transformation) && options.transformation.length > 0) {
121+
result.push(...options.transformation);
97122
}
98123

99124
// 2. Final chain step: width / quality / at_max crop.
@@ -118,14 +143,15 @@ const service: ExternalImageService = {
118143
},
119144

120145
getURL(options: ImageTransform, imageConfig: AstroConfig['image']) {
121-
const { urlEndpoint, transformationPosition } = resolveConfig(options, imageConfig);
122-
const src = typeof options.src === 'string' ? options.src : options.src.src;
146+
const opts = options as IKImageTransform;
147+
const { urlEndpoint, transformationPosition } = resolveConfig(opts, imageConfig);
148+
const src = typeof opts.src === 'string' ? opts.src : opts.src.src;
123149

124150
return buildSrc({
125151
src,
126152
urlEndpoint,
127-
transformation: buildIKTransformations(options),
128-
queryParameters: (options as any).queryParameters,
153+
transformation: buildIKTransformations(opts),
154+
queryParameters: opts.queryParameters,
129155
transformationPosition,
130156
});
131157
},
@@ -156,7 +182,14 @@ const service: ExternalImageService = {
156182
queryParameters,
157183
transformationPosition,
158184
...nonIKAttributes
159-
} = options as any;
185+
} = options as IKImageTransform & {
186+
format?: unknown;
187+
fit?: unknown;
188+
position?: unknown;
189+
layout?: unknown;
190+
background?: unknown;
191+
inferSize?: unknown;
192+
};
160193

161194
return {
162195
...nonIKAttributes,
@@ -173,10 +206,8 @@ const service: ExternalImageService = {
173206
* need handling here.
174207
*/
175208
getSrcSet(options: ImageTransform): UnresolvedSrcSetValue[] {
176-
const { width, height, densities, widths } = options as ImageTransform & {
177-
densities?: Array<number | `${number}x`>;
178-
widths?: number[];
179-
};
209+
const opts = options as IKImageTransform;
210+
const { width, height, densities, widths } = opts;
180211

181212
const targets: Array<{ w: number; descriptor: string }> = [];
182213

@@ -193,7 +224,7 @@ const service: ExternalImageService = {
193224

194225
return targets.map(({ w, descriptor }) => ({
195226
transform: {
196-
...options,
227+
...opts,
197228
width: Math.round(w),
198229
height: aspectRatio ? Math.round(w / aspectRatio) : height,
199230
},
@@ -204,3 +235,4 @@ const service: ExternalImageService = {
204235
};
205236

206237
export default service;
238+

0 commit comments

Comments
 (0)