@xsynaptic/astro-image-pipeline 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Synaptic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,43 @@
1
- # Temporary Holding Version
1
+ # @xsynaptic/astro-image-pipeline
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Image renditions for Astro sites with more images than the bundler can handle. The build records each image a page asks for, then a separate process renders them with sharp into a file cache. File names stay stable between builds. This is an early release, so expect the API to change.
4
+
5
+ ```sh
6
+ pnpm add @xsynaptic/astro-image-pipeline sharp
7
+ ```
8
+
9
+ ## Entry points
10
+
11
+ - `@xsynaptic/astro-image-pipeline`: the Astro integration. It records names during builds, writes the manifest, and renders images on request in dev.
12
+ - `@xsynaptic/astro-image-pipeline/presets`: `defineImagePipeline`, `defineOpenGraphCards` and the name helpers. It doesn't import sharp or `node:fs`, so page code can use it.
13
+ - `@xsynaptic/astro-image-pipeline/node`: `renderImages`, `renderOpenGraphCards`, `openImageSource` and the cache helpers. Run the render in its own process after the build.
14
+
15
+ ## Setup
16
+
17
+ ```ts
18
+ import { defineConfig } from 'astro/config';
19
+ import imagePipeline from '@xsynaptic/astro-image-pipeline';
20
+
21
+ import { pipeline } from './src/images';
22
+
23
+ export default defineConfig({
24
+ integrations: [
25
+ imagePipeline({
26
+ baseUrl: 'https://img.example.com',
27
+ cachePath: '.cache/images',
28
+ mediaPath: 'media',
29
+ pipeline,
30
+ }),
31
+ ],
32
+ });
33
+ ```
34
+
35
+ The pipeline owns everything in `cachePath`, so keep other files out of it.
36
+
37
+ ## Rules
38
+
39
+ Only names made with `getName` (or `getCardName` for OpenGraph cards) during a build get rendered.
40
+
41
+ - Make image URLs while pages render, or in prerendered endpoints.
42
+ - Don't make them in content loaders (a cached run records nothing) or in on-demand routes.
43
+ - `renderImages` renders what the last build recorded. It refuses a manifest from another version, so rebuild first.
@@ -0,0 +1,13 @@
1
+ import { JpegOptions, WebpOptions } from "sharp";
2
+ //#region src/image-encoding.d.ts
3
+ interface ImageEncoding {
4
+ jpg?: JpegOptions;
5
+ webp?: WebpOptions;
6
+ }
7
+ interface ResolvedImageEncoding {
8
+ jpg: JpegOptions;
9
+ webp: WebpOptions;
10
+ }
11
+ //#endregion
12
+ export { ResolvedImageEncoding as n, ImageEncoding as t };
13
+ //# sourceMappingURL=image-encoding-CPYBEK0T.d.mts.map
@@ -0,0 +1,83 @@
1
+ import { n as ResolvedImageEncoding, t as ImageEncoding } from "./image-encoding-CPYBEK0T.mjs";
2
+ //#region src/image-name.d.ts
3
+ declare const imageFormats: readonly ["jpg", "webp"];
4
+ type ImageFormat = (typeof imageFormats)[number];
5
+ interface ImageSize {
6
+ height?: number;
7
+ width: number;
8
+ }
9
+ interface ImageName extends ImageSize {
10
+ format: ImageFormat;
11
+ stem: string;
12
+ }
13
+ declare function isImageSource(id: string): boolean;
14
+ declare function getImageStem(id: string): string;
15
+ declare function findStemCollisions(ids: Iterable<string>): string[][];
16
+ declare function parseImageName(name: string): ImageName | undefined;
17
+ //#endregion
18
+ //#region src/open-graph-cards.d.ts
19
+ type OpenGraphCardValue = Array<OpenGraphCardValue> | boolean | number | OpenGraphCardObject | string;
20
+ interface OpenGraphCardObject {
21
+ [key: string]: OpenGraphCardValue | undefined;
22
+ }
23
+ type OpenGraphCardInput = OpenGraphCardObject;
24
+ interface OpenGraphCardImage {
25
+ id: string;
26
+ treatment?: string;
27
+ }
28
+ interface OpenGraphCard {
29
+ image?: OpenGraphCardImage;
30
+ input: OpenGraphCardInput;
31
+ }
32
+ declare function defineOpenGraphCards({ format }: {
33
+ format: ImageFormat;
34
+ }): {
35
+ getCardName: ({ id, ...card }: OpenGraphCard & {
36
+ id: string;
37
+ }) => string;
38
+ };
39
+ //#endregion
40
+ //#region src/image-presets.d.ts
41
+ interface ImagePresetCover {
42
+ aspectRatio: number;
43
+ fit: 'cover';
44
+ format: ImageFormat;
45
+ widths: ReadonlyArray<number>;
46
+ }
47
+ interface ImagePresetNative {
48
+ fit: 'native';
49
+ format: ImageFormat;
50
+ widths: ReadonlyArray<number>;
51
+ }
52
+ interface ImagePresetWidth {
53
+ fit: 'width';
54
+ format: ImageFormat;
55
+ widths: ReadonlyArray<number>;
56
+ }
57
+ type ImagePreset = ImagePresetCover | ImagePresetNative | ImagePresetWidth;
58
+ type ImagePresetTable = Record<string, ImagePreset>;
59
+ type CoverPresetName<Table extends ImagePresetTable> = { [Name in keyof Table & string]: Table[Name] extends ImagePresetCover ? Name : never; }[keyof Table & string];
60
+ type HeightPresetName<Table extends ImagePresetTable> = { [Name in keyof Table & string]: Table[Name] extends ImagePresetWidth ? never : Name; }[keyof Table & string];
61
+ interface ImageDimensions {
62
+ height: number;
63
+ width: number;
64
+ }
65
+ interface ImageSource extends ImageDimensions {
66
+ id: string;
67
+ path?: string;
68
+ }
69
+ declare function defineImagePipeline<const Table extends Record<keyof Table, ImagePreset>>({ encoding, presets }: {
70
+ encoding?: ImageEncoding;
71
+ presets: Table;
72
+ }): {
73
+ encoding: ResolvedImageEncoding;
74
+ getName: (source: ImageSource, presetName: keyof Table & string, width: number | undefined) => string;
75
+ getSize: {
76
+ (presetName: HeightPresetName<Table>, width: number, source: ImageDimensions): Required<ImageSize>;
77
+ (presetName: keyof Table & string, width: number, source: ImageDimensions): ImageSize;
78
+ };
79
+ getWidths: (presetName: keyof Table & string, source: ImageDimensions) => number[];
80
+ };
81
+ //#endregion
82
+ export { defineImagePipeline as a, OpenGraphCardInput as c, ImageFormat as d, findStemCollisions as f, parseImageName as g, isImageSource as h, ImageSource as i, OpenGraphCardValue as l, imageFormats as m, ImageDimensions as n, OpenGraphCard as o, getImageStem as p, ImagePreset as r, OpenGraphCardImage as s, CoverPresetName as t, defineOpenGraphCards as u };
83
+ //# sourceMappingURL=image-presets-CS7TnTsE.d.mts.map
@@ -0,0 +1,184 @@
1
+ //#region src/image-name.ts
2
+ const imageFormats = ["jpg", "webp"];
3
+ const imageNameTerminators = String.raw`\s"'&<>),`;
4
+ const imageNamePatternSource = String.raw`(?<stem>[^${imageNameTerminators}]+?)-(?<width>[1-9]\d*)(?:x(?<height>[1-9]\d*))?\.(?<format>${imageFormats.join("|")})`;
5
+ const imageNamePattern = new RegExp(`^${imageNamePatternSource}$`);
6
+ const imageStemPattern = new RegExp(`^[^${imageNameTerminators}]+$`);
7
+ const unsafeSegments = /* @__PURE__ */ new Set([
8
+ "",
9
+ ".",
10
+ ".."
11
+ ]);
12
+ const sourcePattern = /\.(?:avif|gif|jpe?g|png|webp)$/i;
13
+ function isFormat(value) {
14
+ return imageFormats.includes(value);
15
+ }
16
+ function isSafeStem(stem) {
17
+ return stem.split("/").every((segment) => !unsafeSegments.has(segment));
18
+ }
19
+ function isImageSource(id) {
20
+ return sourcePattern.test(id);
21
+ }
22
+ function getImageStem(id) {
23
+ return id.replace(/\.[^./]+$/, "");
24
+ }
25
+ function isImageStem(stem) {
26
+ return imageStemPattern.test(stem) && isSafeStem(stem);
27
+ }
28
+ function findStemCollisions(ids) {
29
+ return [...Map.groupBy(ids, (id) => getImageStem(id).toLowerCase()).values()].filter((stemIds) => stemIds.length > 1).map((stemIds) => stemIds.toSorted((first, second) => first.localeCompare(second)));
30
+ }
31
+ function formatImageName({ format, height, stem, width }) {
32
+ return `${stem}-${height === void 0 ? String(width) : `${String(width)}x${String(height)}`}.${format}`;
33
+ }
34
+ function parseImageName(name) {
35
+ const { format, height, stem, width } = imageNamePattern.exec(name)?.groups ?? {};
36
+ if (stem === void 0 || width === void 0 || !isFormat(format) || !isSafeStem(stem)) return;
37
+ const parsed = {
38
+ format,
39
+ stem,
40
+ width: Number(width)
41
+ };
42
+ if (height !== void 0) parsed.height = Number(height);
43
+ return parsed;
44
+ }
45
+ //#endregion
46
+ //#region src/image-encoding.ts
47
+ const encodingDefaults = {
48
+ jpg: {
49
+ mozjpeg: true,
50
+ quality: 80
51
+ },
52
+ webp: {
53
+ effort: 6,
54
+ quality: 80
55
+ }
56
+ };
57
+ function resolveImageEncoding(encoding = {}) {
58
+ return {
59
+ jpg: {
60
+ ...encodingDefaults.jpg,
61
+ ...encoding.jpg
62
+ },
63
+ webp: {
64
+ ...encodingDefaults.webp,
65
+ ...encoding.webp
66
+ }
67
+ };
68
+ }
69
+ function serializeEncoding(options) {
70
+ const entries = Object.entries(options).toSorted(([first], [second]) => first.localeCompare(second));
71
+ return JSON.stringify(Object.fromEntries(entries));
72
+ }
73
+ //#endregion
74
+ //#region src/image-recorder.ts
75
+ const imageRecordsVariable = "XSYNAPTIC_IMAGE_RECORDS";
76
+ let recorder;
77
+ function openRecorder(directory) {
78
+ const fs = process.getBuiltinModule("node:fs");
79
+ const { threadId } = process.getBuiltinModule("node:worker_threads");
80
+ return {
81
+ descriptor: fs.openSync(`${directory}/${String(process.pid)}-${String(threadId)}.ndjson`, "a"),
82
+ directory,
83
+ fs,
84
+ lines: /* @__PURE__ */ new Set()
85
+ };
86
+ }
87
+ function writeRecord(record) {
88
+ if (typeof process === "undefined") return;
89
+ const directory = process.env[imageRecordsVariable];
90
+ if (!directory) return;
91
+ if (recorder?.directory !== directory) {
92
+ if (recorder) recorder.fs.closeSync(recorder.descriptor);
93
+ recorder = openRecorder(directory);
94
+ }
95
+ const line = JSON.stringify(record);
96
+ if (recorder.lines.has(line)) return;
97
+ recorder.lines.add(line);
98
+ recorder.fs.writeSync(recorder.descriptor, `${line}\n`);
99
+ }
100
+ function recordImageName(name, sourcePath) {
101
+ writeRecord({
102
+ name,
103
+ sourcePath
104
+ });
105
+ }
106
+ function recordOpenGraphCard(name, card) {
107
+ writeRecord({
108
+ card,
109
+ kind: "open-graph-card",
110
+ name
111
+ });
112
+ }
113
+ //#endregion
114
+ //#region src/image-presets.ts
115
+ function getPresetSize(preset, width, source) {
116
+ switch (preset.fit) {
117
+ case "cover": return {
118
+ height: Math.round(width / preset.aspectRatio),
119
+ width
120
+ };
121
+ case "native": return {
122
+ height: Math.round(width * source.height / source.width),
123
+ width
124
+ };
125
+ case "width": return { width };
126
+ }
127
+ }
128
+ function isWithinSource(size, source) {
129
+ return size.width <= source.width && (size.height ?? 0) <= source.height;
130
+ }
131
+ function capImageSize(size, source) {
132
+ if (isWithinSource(size, source)) return size;
133
+ if (size.height === void 0) return { width: source.width };
134
+ const width = source.width * size.height <= source.height * size.width ? source.width : Math.floor(size.width * source.height / size.height);
135
+ return {
136
+ height: Math.min(Math.round(width * size.height / size.width), source.height),
137
+ width
138
+ };
139
+ }
140
+ function resolvePresetSize(preset, width, source) {
141
+ for (const presetWidth of preset.widths) {
142
+ const size = capImageSize(getPresetSize(preset, presetWidth, source), source);
143
+ if (size.width === width) return size;
144
+ }
145
+ }
146
+ function createImagePresets(table) {
147
+ function getWidths(presetName, source) {
148
+ const preset = table[presetName];
149
+ return preset.widths.filter((width) => isWithinSource(getPresetSize(preset, width, source), source));
150
+ }
151
+ function getSize(presetName, width, source) {
152
+ return capImageSize(getPresetSize(table[presetName], width, source), source);
153
+ }
154
+ function getName(source, presetName, width) {
155
+ if (width === void 0) throw new Error(`Image ${source.id} needs a width for "${presetName}"`);
156
+ const preset = table[presetName];
157
+ const size = resolvePresetSize(preset, width, source);
158
+ if (!size) throw new Error(`Image ${source.id} (${String(source.width)}x${String(source.height)}) has no "${presetName}" rendition ${String(width)} wide`);
159
+ const stem = getImageStem(source.id);
160
+ if (!isImageStem(stem)) throw new Error(`Image ${source.id} can't be named: whitespace, quotes, &, <, >, ), commas and "." or ".." segments don't fit in an image URL; rename the file`);
161
+ const name = formatImageName({
162
+ format: preset.format,
163
+ stem,
164
+ ...size
165
+ });
166
+ recordImageName(name, source.path ?? source.id);
167
+ return name;
168
+ }
169
+ return {
170
+ getName,
171
+ getSize,
172
+ getWidths
173
+ };
174
+ }
175
+ function defineImagePipeline({ encoding, presets }) {
176
+ return {
177
+ ...createImagePresets(presets),
178
+ encoding: resolveImageEncoding(encoding)
179
+ };
180
+ }
181
+ //#endregion
182
+ export { serializeEncoding as a, imageFormats as c, parseImageName as d, recordOpenGraphCard as i, isImageSource as l, isWithinSource as n, findStemCollisions as o, imageRecordsVariable as r, getImageStem as s, defineImagePipeline as t, isImageStem as u };
183
+
184
+ //# sourceMappingURL=image-presets-ZN7_n2p8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-presets-ZN7_n2p8.mjs","names":[],"sources":["../src/image-name.ts","../src/image-encoding.ts","../src/image-recorder.ts","../src/image-presets.ts"],"sourcesContent":["export const imageFormats = ['jpg', 'webp'] as const;\n\nexport type ImageFormat = (typeof imageFormats)[number];\n\nexport interface ImageSize {\n\theight?: number;\n\twidth: number;\n}\n\nexport interface ImageName extends ImageSize {\n\tformat: ImageFormat;\n\tstem: string;\n}\n\nconst imageNameTerminators = String.raw`\\s\"'&<>),`;\n\nconst imageNamePatternSource = String.raw`(?<stem>[^${imageNameTerminators}]+?)-(?<width>[1-9]\\d*)(?:x(?<height>[1-9]\\d*))?\\.(?<format>${imageFormats.join('|')})`;\n\nconst imageNamePattern = new RegExp(`^${imageNamePatternSource}$`);\n\nconst imageStemPattern = new RegExp(`^[^${imageNameTerminators}]+$`);\n\nconst unsafeSegments = new Set(['', '.', '..']);\n\nconst sourcePattern = /\\.(?:avif|gif|jpe?g|png|webp)$/i;\n\nfunction isFormat(value: string | undefined): value is ImageFormat {\n\treturn (imageFormats as ReadonlyArray<string | undefined>).includes(value);\n}\n\nfunction isSafeStem(stem: string) {\n\treturn stem.split('/').every((segment) => !unsafeSegments.has(segment));\n}\n\nexport function isImageSource(id: string) {\n\treturn sourcePattern.test(id);\n}\n\nexport function getImageStem(id: string) {\n\treturn id.replace(/\\.[^./]+$/, '');\n}\n\nexport function isImageStem(stem: string) {\n\treturn imageStemPattern.test(stem) && isSafeStem(stem);\n}\n\n// Names drop the extension, so a.jpg and a.png (or A.png on a case-insensitive disk) would collide\nexport function findStemCollisions(ids: Iterable<string>) {\n\tconst idsByStem = Map.groupBy(ids, (id) => getImageStem(id).toLowerCase());\n\n\treturn [...idsByStem.values()]\n\t\t.filter((stemIds) => stemIds.length > 1)\n\t\t.map((stemIds) => stemIds.toSorted((first, second) => first.localeCompare(second)));\n}\n\nexport function formatImageName({ format, height, stem, width }: ImageName) {\n\tconst size = height === undefined ? String(width) : `${String(width)}x${String(height)}`;\n\n\treturn `${stem}-${size}.${format}`;\n}\n\nexport function parseImageName(name: string): ImageName | undefined {\n\tconst { format, height, stem, width } = imageNamePattern.exec(name)?.groups ?? {};\n\n\tif (stem === undefined || width === undefined || !isFormat(format) || !isSafeStem(stem)) return;\n\n\tconst parsed: ImageName = { format, stem, width: Number(width) };\n\n\tif (height !== undefined) parsed.height = Number(height);\n\n\treturn parsed;\n}\n","import type { JpegOptions, WebpOptions } from 'sharp';\n\n// Part of every cache key; bump it when a render change alters the output bytes\nexport const imageRenderRevision = 3;\n\nexport interface ImageEncoding {\n\tjpg?: JpegOptions;\n\twebp?: WebpOptions;\n}\n\nexport interface ResolvedImageEncoding {\n\tjpg: JpegOptions;\n\twebp: WebpOptions;\n}\n\nconst encodingDefaults = {\n\tjpg: { mozjpeg: true, quality: 80 },\n\twebp: { effort: 6, quality: 80 },\n} as const satisfies ResolvedImageEncoding;\n\nexport function resolveImageEncoding(encoding: ImageEncoding = {}): ResolvedImageEncoding {\n\treturn {\n\t\tjpg: { ...encodingDefaults.jpg, ...encoding.jpg },\n\t\twebp: { ...encodingDefaults.webp, ...encoding.webp },\n\t};\n}\n\nexport function serializeEncoding(options: JpegOptions | WebpOptions) {\n\tconst entries = Object.entries(options).toSorted(([first], [second]) =>\n\t\tfirst.localeCompare(second),\n\t);\n\n\treturn JSON.stringify(Object.fromEntries(entries));\n}\n","import type * as NodeFs from 'node:fs';\n\nimport type { OpenGraphCard } from './open-graph-cards.js';\n\nexport const imageRecordsVariable = 'XSYNAPTIC_IMAGE_RECORDS';\n\ninterface ImageRecorder {\n\tdescriptor: number;\n\tdirectory: string;\n\tfs: typeof NodeFs;\n\tlines: Set<string>;\n}\n\nlet recorder: ImageRecorder | undefined;\n\n// node:fs is looked up lazily so the presets entry still loads where it doesn't exist\nfunction openRecorder(directory: string): ImageRecorder {\n\tconst fs = process.getBuiltinModule('node:fs');\n\tconst { threadId } = process.getBuiltinModule('node:worker_threads');\n\tconst descriptor = fs.openSync(\n\t\t`${directory}/${String(process.pid)}-${String(threadId)}.ndjson`,\n\t\t'a',\n\t);\n\n\treturn { descriptor, directory, fs, lines: new Set() };\n}\n\nfunction writeRecord(record: object) {\n\tif (typeof process === 'undefined') return;\n\n\tconst directory = process.env[imageRecordsVariable];\n\n\tif (!directory) return;\n\n\tif (recorder?.directory !== directory) {\n\t\tif (recorder) recorder.fs.closeSync(recorder.descriptor);\n\n\t\trecorder = openRecorder(directory);\n\t}\n\n\tconst line = JSON.stringify(record);\n\n\tif (recorder.lines.has(line)) return;\n\n\trecorder.lines.add(line);\n\trecorder.fs.writeSync(recorder.descriptor, `${line}\\n`);\n}\n\nexport function recordImageName(name: string, sourcePath: string) {\n\twriteRecord({ name, sourcePath });\n}\n\nexport function recordOpenGraphCard(name: string, card: OpenGraphCard) {\n\twriteRecord({ card, kind: 'open-graph-card', name });\n}\n","import type { ImageEncoding } from './image-encoding.js';\nimport type { ImageFormat, ImageSize } from './image-name.js';\n\nimport { resolveImageEncoding } from './image-encoding.js';\nimport { formatImageName, getImageStem, isImageStem } from './image-name.js';\nimport { recordImageName } from './image-recorder.js';\n\ninterface ImagePresetCover {\n\taspectRatio: number;\n\tfit: 'cover';\n\tformat: ImageFormat;\n\twidths: ReadonlyArray<number>;\n}\n\ninterface ImagePresetNative {\n\tfit: 'native';\n\tformat: ImageFormat;\n\twidths: ReadonlyArray<number>;\n}\n\ninterface ImagePresetWidth {\n\tfit: 'width';\n\tformat: ImageFormat;\n\twidths: ReadonlyArray<number>;\n}\n\nexport type ImagePreset = ImagePresetCover | ImagePresetNative | ImagePresetWidth;\n\ntype ImagePresetTable = Record<string, ImagePreset>;\n\nexport type CoverPresetName<Table extends ImagePresetTable> = {\n\t[Name in keyof Table & string]: Table[Name] extends ImagePresetCover ? Name : never;\n}[keyof Table & string];\n\ntype HeightPresetName<Table extends ImagePresetTable> = {\n\t[Name in keyof Table & string]: Table[Name] extends ImagePresetWidth ? never : Name;\n}[keyof Table & string];\n\nexport interface ImageDimensions {\n\theight: number;\n\twidth: number;\n}\n\nexport interface ImageSource extends ImageDimensions {\n\tid: string;\n\tpath?: string;\n}\n\nfunction getPresetSize(preset: ImagePreset, width: number, source: ImageDimensions): ImageSize {\n\tswitch (preset.fit) {\n\t\tcase 'cover': {\n\t\t\treturn { height: Math.round(width / preset.aspectRatio), width };\n\t\t}\n\t\tcase 'native': {\n\t\t\treturn { height: Math.round((width * source.height) / source.width), width };\n\t\t}\n\t\tcase 'width': {\n\t\t\treturn { width };\n\t\t}\n\t}\n}\n\nexport function isWithinSource(size: ImageSize, source: ImageDimensions) {\n\treturn size.width <= source.width && (size.height ?? 0) <= source.height;\n}\n\nfunction capImageSize(size: ImageSize, source: ImageDimensions): ImageSize {\n\tif (isWithinSource(size, source)) return size;\n\n\tif (size.height === undefined) return { width: source.width };\n\n\tconst width =\n\t\tsource.width * size.height <= source.height * size.width\n\t\t\t? source.width\n\t\t\t: Math.floor((size.width * source.height) / size.height);\n\n\treturn {\n\t\theight: Math.min(Math.round((width * size.height) / size.width), source.height),\n\t\twidth,\n\t};\n}\n\n// A width outside the preset is fine when one of the preset's widths shrinks to it on this source\nfunction resolvePresetSize(preset: ImagePreset, width: number, source: ImageDimensions) {\n\tfor (const presetWidth of preset.widths) {\n\t\tconst size = capImageSize(getPresetSize(preset, presetWidth, source), source);\n\n\t\tif (size.width === width) return size;\n\t}\n\n\treturn;\n}\n\nexport function createImagePresets<const Table extends Record<keyof Table, ImagePreset>>(\n\ttable: Table,\n) {\n\ttype PresetName = keyof Table & string;\n\n\tfunction getWidths(presetName: PresetName, source: ImageDimensions) {\n\t\tconst preset: ImagePreset = table[presetName];\n\n\t\treturn preset.widths.filter((width) =>\n\t\t\tisWithinSource(getPresetSize(preset, width, source), source),\n\t\t);\n\t}\n\n\tfunction getSize(\n\t\tpresetName: HeightPresetName<Table>,\n\t\twidth: number,\n\t\tsource: ImageDimensions,\n\t): Required<ImageSize>;\n\tfunction getSize(presetName: PresetName, width: number, source: ImageDimensions): ImageSize;\n\tfunction getSize(presetName: PresetName, width: number, source: ImageDimensions) {\n\t\treturn capImageSize(getPresetSize(table[presetName], width, source), source);\n\t}\n\n\tfunction getName(source: ImageSource, presetName: PresetName, width: number | undefined) {\n\t\tif (width === undefined)\n\t\t\tthrow new Error(`Image ${source.id} needs a width for \"${presetName}\"`);\n\n\t\tconst preset: ImagePreset = table[presetName];\n\t\tconst size = resolvePresetSize(preset, width, source);\n\n\t\tif (!size) {\n\t\t\tthrow new Error(\n\t\t\t\t`Image ${source.id} (${String(source.width)}x${String(source.height)}) has no \"${presetName}\" rendition ${String(width)} wide`,\n\t\t\t);\n\t\t}\n\n\t\tconst stem = getImageStem(source.id);\n\n\t\tif (!isImageStem(stem)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Image ${source.id} can't be named: whitespace, quotes, &, <, >, ), commas and \".\" or \"..\" segments don't fit in an image URL; rename the file`,\n\t\t\t);\n\t\t}\n\n\t\tconst name = formatImageName({ format: preset.format, stem, ...size });\n\n\t\trecordImageName(name, source.path ?? source.id);\n\n\t\treturn name;\n\t}\n\n\treturn { getName, getSize, getWidths };\n}\n\nexport function defineImagePipeline<const Table extends Record<keyof Table, ImagePreset>>({\n\tencoding,\n\tpresets,\n}: {\n\tencoding?: ImageEncoding;\n\tpresets: Table;\n}) {\n\treturn { ...createImagePresets(presets), encoding: resolveImageEncoding(encoding) };\n}\n"],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ import { createHash } from "node:crypto";
2
+ import { promises } from "node:fs";
3
+ import path from "node:path";
4
+ import sharp from "sharp";
5
+ //#region src/image-render.ts
6
+ const engineVersions = sharp.versions;
7
+ const engineId = createHash("sha256").update(JSON.stringify({
8
+ ...engineVersions,
9
+ arch: process.arch,
10
+ platform: process.platform
11
+ })).digest("hex").slice(0, 8);
12
+ function openImageSource(input, options) {
13
+ return sharp(input, {
14
+ ...options,
15
+ autoOrient: true
16
+ });
17
+ }
18
+ async function renderImage(source, { encoding, format, height, limitInputPixels, width }) {
19
+ const stage = openImageSource(source, { limitInputPixels }).resize(height === void 0 ? {
20
+ fastShrinkOnLoad: false,
21
+ width,
22
+ withoutEnlargement: true
23
+ } : {
24
+ fastShrinkOnLoad: false,
25
+ fit: "cover",
26
+ height,
27
+ width
28
+ });
29
+ if (format === "jpg") return stage.flatten({ background: "#ffffff" }).jpeg(encoding.jpg).toBuffer({ resolveWithObject: true });
30
+ return stage.webp(encoding.webp).toBuffer({ resolveWithObject: true });
31
+ }
32
+ async function readSourceSize(sourcePath) {
33
+ const { autoOrient } = await openImageSource(sourcePath).metadata();
34
+ return {
35
+ height: autoOrient.height,
36
+ width: autoOrient.width
37
+ };
38
+ }
39
+ //#endregion
40
+ //#region src/format.ts
41
+ function formatList(items) {
42
+ return items.slice(0, 10).map((item) => ` ${item}`).join("\n");
43
+ }
44
+ //#endregion
45
+ //#region src/files.ts
46
+ async function readFileIfExists(filePath) {
47
+ try {
48
+ return await promises.readFile(filePath);
49
+ } catch (error) {
50
+ if (error.code === "ENOENT") return;
51
+ throw error;
52
+ }
53
+ }
54
+ async function readJsonFile(filePath) {
55
+ const data = await readFileIfExists(filePath);
56
+ return data ? JSON.parse(data.toString("utf8")) : void 0;
57
+ }
58
+ async function writeJsonFile(filePath, value) {
59
+ const temporary = `${filePath}.tmp`;
60
+ await promises.mkdir(path.dirname(filePath), { recursive: true });
61
+ await promises.writeFile(temporary, `${JSON.stringify(value)}\n`);
62
+ await promises.rename(temporary, filePath);
63
+ }
64
+ //#endregion
65
+ //#region src/user-error.ts
66
+ var UserError = class extends Error {
67
+ name = "UserError";
68
+ };
69
+ //#endregion
70
+ //#region src/image-state.ts
71
+ const imageManifestFile = "manifest.json";
72
+ const imageBuildIdFile = "image-build-id.txt";
73
+ function getImageCachePaths(cachePath) {
74
+ return {
75
+ devPath: path.join(cachePath, "dev"),
76
+ openGraphCardsPath: path.join(cachePath, "cards"),
77
+ recordsPath: path.join(cachePath, "records"),
78
+ renditionsPath: path.join(cachePath, "renditions")
79
+ };
80
+ }
81
+ function isCurrentManifest(manifest) {
82
+ return manifest.version === 4;
83
+ }
84
+ async function readManifest(cachePath) {
85
+ const manifestPath = path.join(cachePath, imageManifestFile);
86
+ const manifest = await readJsonFile(manifestPath);
87
+ if (!manifest) return;
88
+ if (!isCurrentManifest(manifest)) throw new UserError(`The image manifest ${manifestPath} is version ${String(manifest.version ?? 1)}, not ${String(4)}; rebuild first`);
89
+ return manifest;
90
+ }
91
+ async function readCurrentManifest(cachePath) {
92
+ const manifest = await readManifest(cachePath);
93
+ if (!manifest) throw new UserError(`No image manifest in ${cachePath}; build first`);
94
+ const distBuildId = await readBuildId(manifest.distPath);
95
+ if (distBuildId !== manifest.buildId) throw new UserError(`Manifest build ${manifest.buildId} does not match ${manifest.distPath} (${distBuildId ?? "no build id"}); rebuild first`);
96
+ return manifest;
97
+ }
98
+ async function writeManifest(cachePath, manifest) {
99
+ await writeJsonFile(path.join(cachePath, imageManifestFile), manifest);
100
+ }
101
+ async function readBuildId(outputPath) {
102
+ return (await readFileIfExists(path.join(outputPath, imageBuildIdFile)))?.toString("utf8").trim();
103
+ }
104
+ async function writeBuildId(outputPath, buildId) {
105
+ await promises.writeFile(path.join(outputPath, imageBuildIdFile), buildId);
106
+ }
107
+ //#endregion
108
+ export { writeManifest as a, readJsonFile as c, engineId as d, engineVersions as f, renderImage as h, writeBuildId as i, writeJsonFile as l, readSourceSize as m, readCurrentManifest as n, UserError as o, openImageSource as p, readManifest as r, readFileIfExists as s, getImageCachePaths as t, formatList as u };
109
+
110
+ //# sourceMappingURL=image-state-Bdi_-yWQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-state-Bdi_-yWQ.mjs","names":["fs","fs"],"sources":["../src/image-render.ts","../src/format.ts","../src/files.ts","../src/user-error.ts","../src/image-state.ts"],"sourcesContent":["import type { SharpInput, SharpOptions } from 'sharp';\n\nimport { createHash } from 'node:crypto';\nimport sharp from 'sharp';\n\nimport type { ResolvedImageEncoding } from './image-encoding.js';\nimport type { ImageFormat } from './image-name.js';\nimport type { ImageDimensions } from './image-presets.js';\n\nexport const engineVersions = sharp.versions;\n\n// mozjpeg output differs slightly between CPU architectures\nexport const engineId = createHash('sha256')\n\t.update(JSON.stringify({ ...engineVersions, arch: process.arch, platform: process.platform }))\n\t.digest('hex')\n\t.slice(0, 8);\n\nexport function openImageSource(input: SharpInput, options?: Omit<SharpOptions, 'autoOrient'>) {\n\treturn sharp(input, { ...options, autoOrient: true });\n}\n\ninterface ImageRender {\n\tencoding: ResolvedImageEncoding;\n\tformat: ImageFormat;\n\theight?: number | undefined;\n\tlimitInputPixels: number;\n\twidth: number;\n}\n\nexport async function renderImage(\n\tsource: string,\n\t{ encoding, format, height, limitInputPixels, width }: ImageRender,\n) {\n\tconst stage = openImageSource(source, { limitInputPixels }).resize(\n\t\theight === undefined\n\t\t\t? { fastShrinkOnLoad: false, width, withoutEnlargement: true }\n\t\t\t: { fastShrinkOnLoad: false, fit: 'cover', height, width },\n\t);\n\n\t// A no-op on opaque sources, so the background stays out of the cache key\n\tif (format === 'jpg') {\n\t\treturn stage\n\t\t\t.flatten({ background: '#ffffff' })\n\t\t\t.jpeg(encoding.jpg)\n\t\t\t.toBuffer({ resolveWithObject: true });\n\t}\n\n\treturn stage.webp(encoding.webp).toBuffer({ resolveWithObject: true });\n}\n\nexport async function readSourceSize(sourcePath: string) {\n\tconst { autoOrient } = await openImageSource(sourcePath).metadata();\n\n\treturn { height: autoOrient.height, width: autoOrient.width } satisfies ImageDimensions;\n}\n","export function formatList(items: ReadonlyArray<string>) {\n\treturn items\n\t\t.slice(0, 10)\n\t\t.map((item) => ` ${item}`)\n\t\t.join('\\n');\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nexport async function readFileIfExists(filePath: string) {\n\ttry {\n\t\treturn await fs.readFile(filePath);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') return;\n\n\t\tthrow error;\n\t}\n}\n\nexport async function readJsonFile<Value>(filePath: string) {\n\tconst data = await readFileIfExists(filePath);\n\n\treturn data ? (JSON.parse(data.toString('utf8')) as Value) : undefined;\n}\n\nexport async function writeJsonFile(filePath: string, value: unknown) {\n\tconst temporary = `${filePath}.tmp`;\n\n\tawait fs.mkdir(path.dirname(filePath), { recursive: true });\n\tawait fs.writeFile(temporary, `${JSON.stringify(value)}\\n`);\n\tawait fs.rename(temporary, filePath);\n}\n","export class UserError extends Error {\n\toverride name = 'UserError';\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport type { ResolvedImageEncoding } from './image-encoding.js';\nimport type { OpenGraphCard } from './open-graph-cards.js';\n\nimport { readFileIfExists, readJsonFile, writeJsonFile } from './files.js';\nimport { UserError } from './user-error.js';\n\nconst imageManifestFile = 'manifest.json';\nconst imageBuildIdFile = 'image-build-id.txt';\n\nexport const imageManifestVersion = 4;\n\nexport interface ImageManifest {\n\tbuildId: string;\n\tdistPath: string;\n\tencoding: ResolvedImageEncoding;\n\tlimitInputPixels: number;\n\tmediaPath: string;\n\topenGraphCards: Record<string, OpenGraphCard>;\n\trenditions: Record<string, string>;\n\tversion: typeof imageManifestVersion;\n}\n\nexport function getImageCachePaths(cachePath: string) {\n\treturn {\n\t\tdevPath: path.join(cachePath, 'dev'),\n\t\topenGraphCardsPath: path.join(cachePath, 'cards'),\n\t\trecordsPath: path.join(cachePath, 'records'),\n\t\trenditionsPath: path.join(cachePath, 'renditions'),\n\t};\n}\n\nfunction isCurrentManifest(manifest: { version?: number }): manifest is ImageManifest {\n\treturn manifest.version === imageManifestVersion;\n}\n\nexport async function readManifest(cachePath: string) {\n\tconst manifestPath = path.join(cachePath, imageManifestFile);\n\tconst manifest = await readJsonFile<{ version?: number }>(manifestPath);\n\n\tif (!manifest) return;\n\n\tif (!isCurrentManifest(manifest)) {\n\t\tthrow new UserError(\n\t\t\t`The image manifest ${manifestPath} is version ${String(manifest.version ?? 1)}, not ${String(imageManifestVersion)}; rebuild first`,\n\t\t);\n\t}\n\n\treturn manifest;\n}\n\nexport async function readCurrentManifest(cachePath: string) {\n\tconst manifest = await readManifest(cachePath);\n\n\tif (!manifest) throw new UserError(`No image manifest in ${cachePath}; build first`);\n\n\tconst distBuildId = await readBuildId(manifest.distPath);\n\n\tif (distBuildId !== manifest.buildId) {\n\t\tthrow new UserError(\n\t\t\t`Manifest build ${manifest.buildId} does not match ${manifest.distPath} (${distBuildId ?? 'no build id'}); rebuild first`,\n\t\t);\n\t}\n\n\treturn manifest;\n}\n\nexport async function writeManifest(cachePath: string, manifest: ImageManifest) {\n\tawait writeJsonFile(path.join(cachePath, imageManifestFile), manifest);\n}\n\nasync function readBuildId(outputPath: string) {\n\tconst data = await readFileIfExists(path.join(outputPath, imageBuildIdFile));\n\n\treturn data?.toString('utf8').trim();\n}\n\nexport async function writeBuildId(outputPath: string, buildId: string) {\n\tawait fs.writeFile(path.join(outputPath, imageBuildIdFile), buildId);\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { n as ResolvedImageEncoding } from "./image-encoding-CPYBEK0T.mjs";
2
+ import { AstroIntegration } from "astro";
3
+ //#region src/image-sources.d.ts
4
+ interface ImageSourceResolver {
5
+ resolve(stem: string): Promise<string | undefined>;
6
+ }
7
+ //#endregion
8
+ //#region src/astro-integration.d.ts
9
+ export interface ImagePipelineOptions {
10
+ baseUrl: string;
11
+ cachePath: string;
12
+ limitInputPixels?: number;
13
+ mediaPath: string;
14
+ pipeline: {
15
+ encoding: ResolvedImageEncoding;
16
+ };
17
+ sources?: ImageSourceResolver;
18
+ }
19
+ export default function imagePipeline({ baseUrl, cachePath, limitInputPixels, mediaPath, pipeline, sources }: ImagePipelineOptions): AstroIntegration;
20
+ //#endregion
21
+ export type { ImageSourceResolver };
22
+ //# sourceMappingURL=index.d.mts.map