diff --git a/eleventy.config.js b/eleventy.config.js index 60b2a424e..37d1f1d04 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -2,7 +2,6 @@ import rss from '@11ty/eleventy-plugin-rss'; import syntaxHighlight from '@11ty/eleventy-plugin-syntaxhighlight'; -import fs from 'fs-extra'; import { load } from 'js-yaml'; import { concat, groupBy, merge } from 'lodash-es'; @@ -181,18 +180,20 @@ export default (eleventyConfig) => { eleventyConfig.addDataExtension('yml, yaml', load); eleventyConfig.setQuietMode(true); + // eleventy-img is async-only as of v7, but the `image` shortcode is called + // from inside (synchronous) Nunjucks macros -- so gather image metadata + // up front, and generate the markup synchronously from that. + eleventyConfig.on('eleventy.before', images.cacheImageMetadata); + + // image generation is started during render but not awaited, so wait for + // it here. This also writes the local image cache, since that may only + // happen once every image is known to have generated successfully. + eleventyConfig.on('eleventy.after', images.finishImages); + if (!process.env.NETLIFY) { eleventyConfig.on('eleventy.before', () => { delete process.env.IMAGE_CACHE_CHANGED; }); - - eleventyConfig.on('eleventy.after', () => { - if (process.env.IMAGE_CACHE_CHANGED) { - // If the image cache has been updated, emit the new JSON file - // eslint-disable-next-line no-sync - fs.outputJsonSync(images.CACHE_FILE, images.imageCache, { spaces: 2 }); - } - }); } // settings diff --git a/eslint.config.js b/eslint.config.js index 6ed3724d4..f666c63ba 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -35,7 +35,6 @@ export default [ sourceType: 'module', ecmaVersion: 2022, globals: { - ...globals.es6, ...globals.node, }, }, @@ -157,7 +156,6 @@ export default [ files: ['src/js/**/*.js', 'test/js/**/*.js'], languageOptions: { globals: { - ...globals.es6, ...globals.browser, }, }, diff --git a/package.json b/package.json index 8dcf52649..fd0460826 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "devDependencies": { "@11ty/eleventy": "^3.1.6", "@11ty/eleventy-fetch": "^5.1.3", - "@11ty/eleventy-img": "^6.0.4", + "@11ty/eleventy-img": "^7.0.0", "@11ty/eleventy-plugin-rss": "^3.0.0", "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2", "@11ty/is-land": "^5.0.1", diff --git a/src/filters/images.js b/src/filters/images.js index 5fabcb4d4..2cad91cb4 100644 --- a/src/filters/images.js +++ b/src/filters/images.js @@ -1,6 +1,8 @@ /* eslint-disable no-sync, no-process-env */ -import { basename, dirname, extname, join } from 'node:path'; +import { globSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { basename, dirname, extname, join, normalize } from 'node:path'; import { fileURLToPath } from 'node:url'; import eleventyImg from '@11ty/eleventy-img'; @@ -11,6 +13,9 @@ import { merge } from 'lodash-es'; import { fromTaxonomy } from '#filters/taxonomy.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); +const IMG_VERSION = createRequire(import.meta.url)( + '@11ty/eleventy-img/package.json', +).version; /* @docs label: Responsive Images @@ -45,13 +50,115 @@ const rebuildCache = Boolean( ); let cacheChanged = false; -export const CACHE_FILE = join(__dirname, 'image_cache.json'); -export let imageCache = { html: {}, src: {} }; +const CACHE_FILE = join(__dirname, 'image_cache.json'); +let imageCache = { version: IMG_VERSION, html: {}, src: {} }; /* istanbul ignore next */ if (useCache && !rebuildCache && fs.existsSync(CACHE_FILE)) { - imageCache = fs.readJsonSync(CACHE_FILE); + const cached = fs.readJsonSync(CACHE_FILE); + // eleventy-img can change its generated markup between versions, so a + // cache written by a different version must not be reused -- otherwise + // stale markup survives an upgrade locally, and only differs once the + // production build (which never uses this cache) regenerates it. + if (cached.version === IMG_VERSION) { + imageCache = cached; + } } +// Options are derived entirely from the source path, +// so image metadata can be cached by source path alone. +const imgOptionsFor = (src) => { + let outputDir = './_site/assets/images/'; + let urlPath = '/assets/images/'; + if (src.startsWith(IMG_SRC)) { + const dir = dirname(src.slice(IMG_SRC.length)); + outputDir = `${outputDir}${dir}`; + urlPath = `${urlPath}${dir}`; + } else { + // eslint-disable-next-line no-console + console.warn(`Unexpected image source path: "${src}"`); + } + return { ...imgOptions, outputDir, urlPath }; +}; + +// As of v7, eleventy-img is async-only (`statsSync` was removed). +// The `image` shortcode is called from inside Nunjucks macros, +// which can only be rendered synchronously, so we pre-compute the +// metadata for every source image before the build starts. This only +// reads image headers (no image processing), and takes under a second. +export const imageMetadata = new Map(); +// Images that exist, but could not be read (e.g. corrupt files). +// Tracked so that `image()` can report the underlying cause. +export const imageErrors = new Map(); + +// Content refers to the same file in several ways +// (e.g. `./src/images//projects/w3c.jpg`), so paths are normalized +// to a single canonical form before being used as keys or options. +const metadataKey = (src) => normalize(src); +const canonicalSrc = (src) => `./${metadataKey(src)}`; + +// Matched case-insensitively: macOS would match an uppercase `.JPG` +// in a case-sensitive glob, but Netlify (Linux) would not. +const IMG_EXTENSIONS = new Set([ + '.avif', + '.gif', + '.jpeg', + '.jpg', + '.png', + '.svg', + '.webp', +]); + +// Image generation is started during render but never awaited, since +// templates are synchronous. Track the work so that failures can be +// reported (with the source that caused them) once the build is done, +// instead of surfacing as a bare unhandled rejection. +const pendingImages = []; +const failedImages = []; + +export const finishImages = async () => { + await Promise.all(pendingImages.splice(0)); + const failures = failedImages.splice(0); + + // Persist the cache only for a build where every image generated. The + // cached markup is returned before an image is ever re-requested, so a + // cache written after a failure would serve markup for a file that was + // never written -- and the next build would report nothing at all. + // This has to happen here, rather than in a separate `eleventy.after` + // handler: those run in parallel, and would race this function. + /* istanbul ignore next */ + if (useCache && process.env.IMAGE_CACHE_CHANGED && !failures.length) { + fs.outputJsonSync(CACHE_FILE, imageCache, { spaces: 2 }); + } + + if (failures.length) { + throw new Error(`Unable to generate images:\n ${failures.join('\n ')}`); + } +}; + +export const cacheImageMetadata = async () => { + imageMetadata.clear(); + imageErrors.clear(); + const files = globSync(`${IMG_SRC}**/*`).filter((file) => + IMG_EXTENSIONS.has(extname(file).toLowerCase()), + ); + await Promise.all( + files.map(async (file) => { + const src = canonicalSrc(file); + try { + const metadata = await eleventyImg(src, { + ...imgOptionsFor(src), + statsOnly: true, + }); + imageMetadata.set(metadataKey(src), metadata); + } catch (error) { + imageErrors.set(metadataKey(src), error); + // eslint-disable-next-line no-console + console.warn(`Unable to read image metadata for "${src}": ${error}`); + } + }), + ); +}; + /* @docs label: image category: responsive images @@ -80,22 +187,11 @@ params: note: | Returns url to largest jpeg image instead of full HTML */ -export const image = (src, alt, attrs, sizes, getUrl) => { - let outputDir = './_site/assets/images/'; - let urlPath = '/assets/images/'; - if (src.startsWith(IMG_SRC)) { - const dir = dirname(src.slice(IMG_SRC.length)); - outputDir = `${outputDir}${dir}`; - urlPath = `${urlPath}${dir}`; - } else { - // eslint-disable-next-line no-console - console.warn(`Unexpected image source path: "${src}"`); - } - const opts = { - ...imgOptions, - outputDir, - urlPath, - }; +export const image = (rawSrc, alt, attrs, sizes, getUrl) => { + // Normalize once, so that the options used to generate an image always + // match the options its cached metadata was computed with. + const src = canonicalSrc(rawSrc); + const opts = imgOptionsFor(src); const imgSizes = sizes && imgConfig.sizes[sizes] ? imgConfig.sizes[sizes] @@ -136,10 +232,24 @@ export const image = (src, alt, attrs, sizes, getUrl) => { } } - // generate images; this is async but we don’t wait - eleventyImg(src, opts); + const metadata = imageMetadata.get(metadataKey(src)); + if (!metadata) { + const error = imageErrors.get(metadataKey(src)); + throw new Error( + error + ? `Unable to process image "${src}": ${error}` + : `Missing image metadata for "${src}". ` + + `Images must live in "${IMG_SRC}", ` + + `and \`cacheImageMetadata()\` must run before the build.`, + ); + } - const metadata = eleventyImg.statsSync(src, opts); + // generate images; this is async but we don’t wait + pendingImages.push( + eleventyImg(src, opts).catch((error) => { + failedImages.push(`${src}: ${error.message}`); + }), + ); if (getUrl) { const data = metadata.jpeg[metadata.jpeg.length - 1]; diff --git a/test/js/images.test.js b/test/js/images.test.js index 40f576a66..1f442c20c 100644 --- a/test/js/images.test.js +++ b/test/js/images.test.js @@ -1,4 +1,5 @@ -/* eslint-disable no-sync */ +import { globSync } from 'node:fs'; +import { extname, normalize } from 'node:path'; import { jest } from '@jest/globals'; @@ -7,12 +8,12 @@ jest.unstable_mockModule('@11ty/eleventy-img', () => ({ })); const eleventyImg = await import('@11ty/eleventy-img'); -const { image } = await import('#filters/images'); +const { cacheImageMetadata, finishImages, image, imageErrors, imageMetadata } = + await import('#filters/images'); eleventyImg.default.generateHTML = jest.fn(); -eleventyImg.default.statsSync = jest.fn().mockReturnValue({ - jpeg: [{ url: '/assets/images/img-960w.webp' }], -}); + +const metadata = { jpeg: [{ url: '/assets/images/img-960w.webp' }] }; describe('image filters', () => { describe('image', () => { @@ -24,8 +25,21 @@ describe('image filters', () => { global.console.warn = jest.fn(); }); + beforeEach(() => { + imageMetadata.clear(); + imageErrors.clear(); + // the only key shape `cacheImageMetadata` can produce + imageMetadata.set('src/images/foo/img.jpg', metadata); + // eleventy-img always returns a promise + eleventyImg.default.mockResolvedValue(metadata); + }); + + // drain the generation queue, so nothing leaks into a later block + afterEach(() => finishImages()); + afterAll(() => { global.console.warn = warn; + imageMetadata.clear(); }); test('calls eleventy-img plugin with options', () => { @@ -38,11 +52,16 @@ describe('image filters', () => { expect(options.widths).toEqual([480, 960, 1600]); expect(options.formats).toEqual(['webp', 'jpeg']); + // filenames are not hashed, so output paths must mirror the source dirs + expect(options.outputDir).toBe('./_site/assets/images/foo'); + expect(options.urlPath).toBe('/assets/images/foo'); expect(options.filenameFormat('hash', src, 480, 'webp')).toBe( 'img-480w.webp', ); - expect(eleventyImg.default.statsSync).toHaveBeenCalledTimes(1); expect(eleventyImg.default.generateHTML).toHaveBeenCalledTimes(1); + expect(eleventyImg.default.generateHTML.mock.calls[0][0]).toEqual( + metadata, + ); expect(eleventyImg.default.generateHTML.mock.calls[0][1]).toEqual({ alt: 'alt text', sizes: '(min-width: 45em) 50vw, 100vw', @@ -70,10 +89,183 @@ describe('image filters', () => { expect(url).toBe('/assets/images/img-960w.webp'); }); - test('warns if unexpected src prefix', () => { - image('foo/img.jpg'); + // `content/blog/2025/custom-functions.md` sets `image.src: /projects/w3c.jpg`, + // and `embed.macros.njk` prepends `./src/images/` + test('normalizes a doubled slash in the source path', () => { + image('./src/images//foo/img.jpg', 'alt text'); + + expect(eleventyImg.default.generateHTML).toHaveBeenCalledTimes(1); + expect(eleventyImg.default.generateHTML.mock.calls[0][0]).toBe(metadata); + // generation must target the same paths the metadata was computed with + expect(eleventyImg.default.mock.calls[0][1].outputDir).toBe( + './_site/assets/images/foo', + ); + }); + + test('normalizes a doubled slash when returning a url', () => { + expect(image('./src/images//foo/img.jpg', null, null, null, true)).toBe( + '/assets/images/img-960w.webp', + ); + }); + + test('warns and throws if src is outside the image directory', () => { + // `cacheImageMetadata` can only ever key paths under `./src/images/` + expect(() => image('foo/img.jpg')).toThrow('Missing image metadata'); + expect(global.console.warn).toHaveBeenCalledWith( + 'Unexpected image source path: "./foo/img.jpg"', + ); + }); + + test('throws if metadata was not pre-computed', () => { + imageMetadata.clear(); + + expect(() => image(src)).toThrow('Missing image metadata'); + }); + + test('reports the underlying cause for an unreadable image', () => { + imageMetadata.clear(); + imageErrors.set('src/images/foo/img.jpg', new Error('bad header')); + + expect(() => image(src)).toThrow('Unable to process image'); + expect(() => image(src)).toThrow('bad header'); + }); + }); + + describe('finishImages', () => { + const src = './src/images/foo/img.jpg'; + + beforeEach(() => { + imageMetadata.clear(); + imageErrors.clear(); + imageMetadata.set('src/images/foo/img.jpg', metadata); + }); + + afterEach(() => { + eleventyImg.default.mockReset(); + }); + + test('resolves when every image generated', async () => { + eleventyImg.default.mockResolvedValue(metadata); + image(src, 'alt text'); + + await expect(finishImages()).resolves.toBeUndefined(); + }); + + test('reports the source of any image that failed', async () => { + eleventyImg.default.mockRejectedValue(new Error('sharp exploded')); + image(src, 'alt text'); + + await expect(finishImages()).rejects.toThrow( + `Unable to generate images:\n ${src}: sharp exploded`, + ); + }); + + // The cache may only be written once generation has settled, so + // `finishImages` must await in-flight work rather than sampling it. + test('waits for in-flight generation before reporting', async () => { + let fail; + eleventyImg.default.mockReturnValue( + new Promise((resolve, reject) => { + fail = reject; + }), + ); + image(src, 'alt text'); + + let settled = false; + const finished = finishImages().catch((error) => { + settled = true; + throw error; + }); + + await new Promise((resolve) => { + setTimeout(resolve, 10); + }); + + expect(settled).toBe(false); + + fail(new Error('late failure')); + + await expect(finished).rejects.toThrow('late failure'); + }); + + test('does not re-report failures on a later build', async () => { + eleventyImg.default.mockRejectedValue(new Error('sharp exploded')); + image(src, 'alt text'); + + await expect(finishImages()).rejects.toThrow('sharp exploded'); + await expect(finishImages()).resolves.toBeUndefined(); + }); + }); + + describe('cacheImageMetadata', () => { + let warn; + + beforeAll(() => { + warn = global.console.warn; + global.console.warn = jest.fn(); + }); + + beforeEach(() => { + imageMetadata.clear(); + imageErrors.clear(); + }); + + // `clearMocks` resets calls but not implementations, which would + // otherwise leak into `image()`'s un-awaited `eleventyImg()` call + afterEach(() => { + eleventyImg.default.mockReset(); + }); + + afterAll(() => { + global.console.warn = warn; + }); + + test('stores metadata for every source image', async () => { + eleventyImg.default.mockResolvedValue(metadata); + + await cacheImageMetadata(); + + // The extension list is intentionally duplicated rather than imported + // from the filter: sharing the set would make this assertion + // tautological, and it would no longer catch a dropped extension. + const expected = globSync('./src/images/**/*') + .filter((file) => + ['.avif', '.gif', '.jpeg', '.jpg', '.png', '.svg', '.webp'].includes( + extname(file).toLowerCase(), + ), + ) + .map((file) => normalize(file)); + + expect(expected.length).toBeGreaterThan(0); + expect([...imageMetadata.keys()].sort()).toEqual(expected.sort()); + expect(imageMetadata.values().next().value).toEqual(metadata); + + for (const [src, opts] of eleventyImg.default.mock.calls) { + expect(src.startsWith('./src/images/')).toBe(true); + expect(opts.statsOnly).toBe(true); + } + }); + + test('warns (and skips) images it cannot read', async () => { + eleventyImg.default.mockRejectedValue(new Error('nope')); + + await cacheImageMetadata(); + + expect(imageMetadata.size).toBe(0); + expect(imageErrors.size).toBeGreaterThan(0); + const [message] = global.console.warn.mock.calls[0]; + + expect(message).toMatch(/^Unable to read image metadata for/u); + expect(message).toMatch(/"\.\/src\/images\/.+": Error: nope$/u); + }); + + test('clears stale entries, so removed images do not linger', async () => { + imageMetadata.set('src/images/gone.jpg', metadata); + eleventyImg.default.mockResolvedValue(metadata); + + await cacheImageMetadata(); - expect(global.console.warn).toHaveBeenCalledTimes(1); + expect(imageMetadata.has('src/images/gone.jpg')).toBe(false); }); }); }); diff --git a/yarn.lock b/yarn.lock index 9ba6f938a..e56cc4e1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -48,7 +48,7 @@ __metadata: languageName: node linkType: hard -"@11ty/eleventy-fetch@npm:^5.1.0, @11ty/eleventy-fetch@npm:^5.1.3": +"@11ty/eleventy-fetch@npm:^5.1.3": version: 5.1.3 resolution: "@11ty/eleventy-fetch@npm:5.1.3" dependencies: @@ -61,19 +61,18 @@ __metadata: languageName: node linkType: hard -"@11ty/eleventy-img@npm:^6.0.4": - version: 6.0.4 - resolution: "@11ty/eleventy-img@npm:6.0.4" +"@11ty/eleventy-img@npm:^7.0.0": + version: 7.0.0 + resolution: "@11ty/eleventy-img@npm:7.0.0" dependencies: - "@11ty/eleventy-fetch": "npm:^5.1.0" + "@11ty/eleventy-fetch": "npm:^5.1.3" "@11ty/eleventy-utils": "npm:^2.0.7" brotli-size: "npm:^4.0.0" - debug: "npm:^4.4.0" - entities: "npm:^6.0.0" - image-size: "npm:^1.2.1" - p-queue: "npm:^6.6.2" - sharp: "npm:^0.33.5" - checksum: 10/1304453a7ef3141d23aa12d50ff0ab307d5903116d24838027939672b3abd31c6133984b9691eaeda31dfc7687d5afe9c37c008872e423c6e796b163da8b4fe6 + entities: "npm:^8.0.0" + obug: "npm:^2.1.4" + p-queue: "npm:^9.3.3" + sharp: "npm:^0.35.3" + checksum: 10/4dbf0c687dadbd75e14abc3a963755772c9d2e0ed6892f71ea3bcd610b4e17ebca15ea3808477422acb2f3c02b79f779f88c2099715b50fadfc6b272231e45d4 languageName: node linkType: hard @@ -1678,7 +1677,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:^1.2.0": +"@emnapi/runtime@npm:^1.11.1": version: 1.11.3 resolution: "@emnapi/runtime@npm:1.11.3" dependencies: @@ -1848,11 +1847,18 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-arm64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-darwin-arm64@npm:0.33.5" +"@img/colour@npm:^1.1.0": + version: 1.1.0 + resolution: "@img/colour@npm:1.1.0" + checksum: 10/2a29be7b06b046bd33c80ffa0f3493b7535b0841a69f54af81bf5e2d8867f21704fab42e9cf24ec30c089de34f790bae4dad1fc617c3163fbf264998dc316f0a + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-darwin-arm64@npm:0.35.3" dependencies: - "@img/sharp-libvips-darwin-arm64": "npm:1.0.4" + "@img/sharp-libvips-darwin-arm64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-darwin-arm64": optional: true @@ -1860,11 +1866,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-x64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-darwin-x64@npm:0.33.5" +"@img/sharp-darwin-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-darwin-x64@npm:0.35.3" dependencies: - "@img/sharp-libvips-darwin-x64": "npm:1.0.4" + "@img/sharp-libvips-darwin-x64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-darwin-x64": optional: true @@ -1872,67 +1878,90 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-darwin-arm64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.4" +"@img/sharp-freebsd-wasm32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-freebsd-wasm32@npm:0.35.3" + dependencies: + "@img/sharp-wasm32": "npm:0.35.3" + conditions: os=freebsd + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.3.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@img/sharp-libvips-darwin-x64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.4" +"@img/sharp-libvips-darwin-x64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.3.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@img/sharp-libvips-linux-arm64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.4" +"@img/sharp-libvips-linux-arm64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.3.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-arm@npm:1.0.5": - version: 1.0.5 - resolution: "@img/sharp-libvips-linux-arm@npm:1.0.5" +"@img/sharp-libvips-linux-arm@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-arm@npm:1.3.2" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-s390x@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4" +"@img/sharp-libvips-linux-ppc64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.3.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.3.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.3.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-x64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linux-x64@npm:1.0.4" +"@img/sharp-libvips-linux-x64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-x64@npm:1.3.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4" +"@img/sharp-libvips-linuxmusl-arm64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.3.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-x64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.4" +"@img/sharp-libvips-linuxmusl-x64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.3.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@img/sharp-linux-arm64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-arm64@npm:0.33.5" +"@img/sharp-linux-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-arm64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-arm64": "npm:1.0.4" + "@img/sharp-libvips-linux-arm64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-arm64": optional: true @@ -1940,11 +1969,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-arm@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-arm@npm:0.33.5" +"@img/sharp-linux-arm@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-arm@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-arm": "npm:1.0.5" + "@img/sharp-libvips-linux-arm": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-arm": optional: true @@ -1952,11 +1981,35 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-s390x@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-s390x@npm:0.33.5" +"@img/sharp-linux-ppc64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-ppc64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-s390x": "npm:1.0.4" + "@img/sharp-libvips-linux-ppc64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-riscv64@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linux-riscv64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-s390x@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-s390x": optional: true @@ -1964,11 +2017,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-x64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-x64@npm:0.33.5" +"@img/sharp-linux-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-x64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linux-x64": "npm:1.0.4" + "@img/sharp-libvips-linux-x64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linux-x64": optional: true @@ -1976,11 +2029,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-arm64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.5" +"@img/sharp-linuxmusl-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linuxmusl-arm64": optional: true @@ -1988,11 +2041,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-x64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linuxmusl-x64@npm:0.33.5" +"@img/sharp-linuxmusl-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linuxmusl-x64@npm:0.35.3" dependencies: - "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.3.2" dependenciesMeta: "@img/sharp-libvips-linuxmusl-x64": optional: true @@ -2000,25 +2053,41 @@ __metadata: languageName: node linkType: hard -"@img/sharp-wasm32@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-wasm32@npm:0.33.5" +"@img/sharp-wasm32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-wasm32@npm:0.35.3" dependencies: - "@emnapi/runtime": "npm:^1.2.0" + "@emnapi/runtime": "npm:^1.11.1" + checksum: 10/9cad3671879be2448c6252a978af2dc39727e4464d335a3eea5aab6751596b8af68bba9487f5fd2600671807f7d4ec34abfbd78b01ededb48843d904c6a1387d + languageName: node + linkType: hard + +"@img/sharp-webcontainers-wasm32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-webcontainers-wasm32@npm:0.35.3" + dependencies: + "@img/sharp-wasm32": "npm:0.35.3" conditions: cpu=wasm32 languageName: node linkType: hard -"@img/sharp-win32-ia32@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-win32-ia32@npm:0.33.5" +"@img/sharp-win32-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-win32-arm64@npm:0.35.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-win32-ia32@npm:0.35.3" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@img/sharp-win32-x64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-win32-x64@npm:0.33.5" +"@img/sharp-win32-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-win32-x64@npm:0.35.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -4539,23 +4608,13 @@ __metadata: languageName: node linkType: hard -"color-name@npm:^1.0.0, color-name@npm:~1.1.4": +"color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard -"color-string@npm:^1.9.0": - version: 1.9.1 - resolution: "color-string@npm:1.9.1" - dependencies: - color-name: "npm:^1.0.0" - simple-swizzle: "npm:^0.2.2" - checksum: 10/72aa0b81ee71b3f4fb1ac9cd839cdbd7a011a7d318ef58e6cb13b3708dca75c7e45029697260488709f1b1c7ac4e35489a87e528156c1e365917d1c4ccb9b9cd - languageName: node - linkType: hard - "color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" @@ -4565,16 +4624,6 @@ __metadata: languageName: node linkType: hard -"color@npm:^4.2.3": - version: 4.2.3 - resolution: "color@npm:4.2.3" - dependencies: - color-convert: "npm:^2.0.1" - color-string: "npm:^1.9.0" - checksum: 10/b23f5e500a79ea22428db43d1a70642d983405c0dd1f95ef59dbdb9ba66afbb4773b334fa0b75bb10b0552fd7534c6b28d4db0a8b528f91975976e70973c0152 - languageName: node - linkType: hard - "colord@npm:^2.9.3": version: 2.9.3 resolution: "colord@npm:2.9.3" @@ -5049,7 +5098,7 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.3": +"detect-libc@npm:^2.0.3, detect-libc@npm:^2.1.2": version: 2.1.2 resolution: "detect-libc@npm:2.1.2" checksum: 10/b736c8d97d5d46164c0d1bed53eb4e6a3b1d8530d460211e2d52f1c552875e706c58a5376854e4e54f8b828c9cada58c855288c968522eb93ac7696d65970766 @@ -5802,6 +5851,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^5.0.4": + version: 5.0.4 + resolution: "eventemitter3@npm:5.0.4" + checksum: 10/54f5c8c543650d65f92d03dbef1bb73a682a920490c44699ad8f863a6b19bbca42fb7409aa09ca09cb98a44149d9a7bc1dffd55ca88a740bd928c7be0ad666a0 + languageName: node + linkType: hard + "events-universal@npm:^1.0.0": version: 1.0.1 resolution: "events-universal@npm:1.0.1" @@ -6917,17 +6973,6 @@ __metadata: languageName: node linkType: hard -"image-size@npm:^1.2.1": - version: 1.2.1 - resolution: "image-size@npm:1.2.1" - dependencies: - queue: "npm:6.0.2" - bin: - image-size: bin/image-size.js - checksum: 10/b290c6cc5635565b1da51991472eb6522808430dbe3415823649723dc5f5fd8263f0f98f9bdec46184274ea24fe4f3f7a297c84b647b412e14d2208703dd8a19 - languageName: node - linkType: hard - "immutable@npm:^5.1.5": version: 5.1.9 resolution: "immutable@npm:5.1.9" @@ -7065,13 +7110,6 @@ __metadata: languageName: node linkType: hard -"is-arrayish@npm:^0.3.1": - version: 0.3.4 - resolution: "is-arrayish@npm:0.3.4" - checksum: 10/bf31677cee9fa4086f660b1920c22cf924872e6853cc4021f37ca9ca9d8ac7f098ab75b3c7bf4900e2058c83526a9ead3bf8bc352a657156eba5b4b0792b6dae - languageName: node - linkType: hard - "is-async-function@npm:^2.0.0": version: 2.1.1 resolution: "is-async-function@npm:2.1.1" @@ -9404,13 +9442,20 @@ __metadata: languageName: node linkType: hard +"obug@npm:^2.1.4": + version: 2.1.4 + resolution: "obug@npm:2.1.4" + checksum: 10/05e3ac83f60ef18edb935d67703bada2cbc0feb1a1cef575240b1e48e6e877df95b74048e69bbe549759df18c57ad1808e1e60a9fc4f785525c8549bf7fe81db + languageName: node + linkType: hard + "oddsite@workspace:.": version: 0.0.0-use.local resolution: "oddsite@workspace:." dependencies: "@11ty/eleventy": "npm:^3.1.6" "@11ty/eleventy-fetch": "npm:^5.1.3" - "@11ty/eleventy-img": "npm:^6.0.4" + "@11ty/eleventy-img": "npm:^7.0.0" "@11ty/eleventy-plugin-rss": "npm:^3.0.0" "@11ty/eleventy-plugin-syntaxhighlight": "npm:^5.0.2" "@11ty/is-land": "npm:^5.0.1" @@ -9596,7 +9641,7 @@ __metadata: languageName: node linkType: hard -"p-queue@npm:6.6.2, p-queue@npm:^6.6.2": +"p-queue@npm:6.6.2": version: 6.6.2 resolution: "p-queue@npm:6.6.2" dependencies: @@ -9606,6 +9651,16 @@ __metadata: languageName: node linkType: hard +"p-queue@npm:^9.3.3": + version: 9.3.3 + resolution: "p-queue@npm:9.3.3" + dependencies: + eventemitter3: "npm:^5.0.4" + p-timeout: "npm:^7.0.0" + checksum: 10/2bab74e87ed806ae2dd7ec10f1e0d0423eb4c07419e35947dc66f8b6d3f2d89e978b76b3940d5a41e1b54fda82b716eb2c1bacb63bd95286f0852413628d43b3 + languageName: node + linkType: hard + "p-timeout@npm:^3.2.0": version: 3.2.0 resolution: "p-timeout@npm:3.2.0" @@ -9615,6 +9670,13 @@ __metadata: languageName: node linkType: hard +"p-timeout@npm:^7.0.0": + version: 7.0.1 + resolution: "p-timeout@npm:7.0.1" + checksum: 10/1d65827a07fd10eae5bc1fa493ef5c40522acda21cbbb04131cdb0f63f703c91e5fac2701431e28e308992284a86807d7138dbc2be694e2b8342bee20be652f6 + languageName: node + linkType: hard + "p-try@npm:^2.0.0": version: 2.2.0 resolution: "p-try@npm:2.2.0" @@ -10140,15 +10202,6 @@ __metadata: languageName: node linkType: hard -"queue@npm:6.0.2": - version: 6.0.2 - resolution: "queue@npm:6.0.2" - dependencies: - inherits: "npm:~2.0.3" - checksum: 10/3437954ef1442c86ff01a0fbe3dc6222838823b1ca97f37eff651bc20b868c0c2904424ef2c0d44cba46055f54b578f92866e573125dc9a5e8823d751e4d1585 - languageName: node - linkType: hard - "range-parser@npm:^1.2.1": version: 1.3.0 resolution: "range-parser@npm:1.3.0" @@ -11162,7 +11215,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.3, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.8.1": +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.8.1, semver@npm:^7.8.5": version: 7.8.5 resolution: "semver@npm:7.8.5" bin: @@ -11248,37 +11301,45 @@ __metadata: languageName: node linkType: hard -"sharp@npm:^0.33.5": - version: 0.33.5 - resolution: "sharp@npm:0.33.5" - dependencies: - "@img/sharp-darwin-arm64": "npm:0.33.5" - "@img/sharp-darwin-x64": "npm:0.33.5" - "@img/sharp-libvips-darwin-arm64": "npm:1.0.4" - "@img/sharp-libvips-darwin-x64": "npm:1.0.4" - "@img/sharp-libvips-linux-arm": "npm:1.0.5" - "@img/sharp-libvips-linux-arm64": "npm:1.0.4" - "@img/sharp-libvips-linux-s390x": "npm:1.0.4" - "@img/sharp-libvips-linux-x64": "npm:1.0.4" - "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4" - "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4" - "@img/sharp-linux-arm": "npm:0.33.5" - "@img/sharp-linux-arm64": "npm:0.33.5" - "@img/sharp-linux-s390x": "npm:0.33.5" - "@img/sharp-linux-x64": "npm:0.33.5" - "@img/sharp-linuxmusl-arm64": "npm:0.33.5" - "@img/sharp-linuxmusl-x64": "npm:0.33.5" - "@img/sharp-wasm32": "npm:0.33.5" - "@img/sharp-win32-ia32": "npm:0.33.5" - "@img/sharp-win32-x64": "npm:0.33.5" - color: "npm:^4.2.3" - detect-libc: "npm:^2.0.3" - semver: "npm:^7.6.3" +"sharp@npm:^0.35.3": + version: 0.35.3 + resolution: "sharp@npm:0.35.3" + dependencies: + "@img/colour": "npm:^1.1.0" + "@img/sharp-darwin-arm64": "npm:0.35.3" + "@img/sharp-darwin-x64": "npm:0.35.3" + "@img/sharp-freebsd-wasm32": "npm:0.35.3" + "@img/sharp-libvips-darwin-arm64": "npm:1.3.2" + "@img/sharp-libvips-darwin-x64": "npm:1.3.2" + "@img/sharp-libvips-linux-arm": "npm:1.3.2" + "@img/sharp-libvips-linux-arm64": "npm:1.3.2" + "@img/sharp-libvips-linux-ppc64": "npm:1.3.2" + "@img/sharp-libvips-linux-riscv64": "npm:1.3.2" + "@img/sharp-libvips-linux-s390x": "npm:1.3.2" + "@img/sharp-libvips-linux-x64": "npm:1.3.2" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.3.2" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.3.2" + "@img/sharp-linux-arm": "npm:0.35.3" + "@img/sharp-linux-arm64": "npm:0.35.3" + "@img/sharp-linux-ppc64": "npm:0.35.3" + "@img/sharp-linux-riscv64": "npm:0.35.3" + "@img/sharp-linux-s390x": "npm:0.35.3" + "@img/sharp-linux-x64": "npm:0.35.3" + "@img/sharp-linuxmusl-arm64": "npm:0.35.3" + "@img/sharp-linuxmusl-x64": "npm:0.35.3" + "@img/sharp-webcontainers-wasm32": "npm:0.35.3" + "@img/sharp-win32-arm64": "npm:0.35.3" + "@img/sharp-win32-ia32": "npm:0.35.3" + "@img/sharp-win32-x64": "npm:0.35.3" + detect-libc: "npm:^2.1.2" + semver: "npm:^7.8.5" dependenciesMeta: "@img/sharp-darwin-arm64": optional: true "@img/sharp-darwin-x64": optional: true + "@img/sharp-freebsd-wasm32": + optional: true "@img/sharp-libvips-darwin-arm64": optional: true "@img/sharp-libvips-darwin-x64": @@ -11287,6 +11348,10 @@ __metadata: optional: true "@img/sharp-libvips-linux-arm64": optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true "@img/sharp-libvips-linux-s390x": optional: true "@img/sharp-libvips-linux-x64": @@ -11299,6 +11364,10 @@ __metadata: optional: true "@img/sharp-linux-arm64": optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true "@img/sharp-linux-s390x": optional: true "@img/sharp-linux-x64": @@ -11307,13 +11376,18 @@ __metadata: optional: true "@img/sharp-linuxmusl-x64": optional: true - "@img/sharp-wasm32": + "@img/sharp-webcontainers-wasm32": + optional: true + "@img/sharp-win32-arm64": optional: true "@img/sharp-win32-ia32": optional: true "@img/sharp-win32-x64": optional: true - checksum: 10/9f153578cb02735359cbcc874f52b56b8074ed997498c35255c7099d4f4f506f6ddf83a437a55242c7ad4f979336660504b6c78e29d6933f4981dedbdae5ce09 + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/5f5c7739421f470d18e1b0d8160342103530724f59e3ef11d9cf15d5ec22e36fd2226d3c8f15ac72b3da947605c4076faf9ab8902e08575ed950c6f48a36ffa8 languageName: node linkType: hard @@ -11418,15 +11492,6 @@ __metadata: languageName: node linkType: hard -"simple-swizzle@npm:^0.2.2": - version: 0.2.4 - resolution: "simple-swizzle@npm:0.2.4" - dependencies: - is-arrayish: "npm:^0.3.1" - checksum: 10/f114785cc1b57cd79d8463af04b20f53483be5f22e66ac775218e5587f4591790da500126cd0434f1d523d81015c3c87835f99c8fee8a657c90a875c25e88f76 - languageName: node - linkType: hard - "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0"