@jhb.software/payload-alt-text-plugin 0.4.3 → 0.5.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.
Files changed (41) hide show
  1. package/README.md +62 -33
  2. package/dist/components/AltTextField.d.ts +1 -1
  3. package/dist/components/AltTextField.js +9 -0
  4. package/dist/components/AltTextField.js.map +1 -1
  5. package/dist/components/BulkGenerateAltTextsButton.js +5 -9
  6. package/dist/components/BulkGenerateAltTextsButton.js.map +1 -1
  7. package/dist/components/icons/ArrowRightIcon.js +6 -7
  8. package/dist/components/icons/ArrowRightIcon.js.map +1 -1
  9. package/dist/components/icons/CheckIcon.js +6 -7
  10. package/dist/components/icons/CheckIcon.js.map +1 -1
  11. package/dist/components/icons/ImageIcon.js +9 -26
  12. package/dist/components/icons/ImageIcon.js.map +1 -1
  13. package/dist/components/icons/Lightning.js +8 -4
  14. package/dist/components/icons/Lightning.js.map +1 -1
  15. package/dist/endpoints/bulkGenerateAltTexts.js +5 -0
  16. package/dist/endpoints/bulkGenerateAltTexts.js.map +1 -1
  17. package/dist/endpoints/generateAltText.js +9 -0
  18. package/dist/endpoints/generateAltText.js.map +1 -1
  19. package/dist/fields/altTextField.d.ts +7 -2
  20. package/dist/fields/altTextField.js +5 -19
  21. package/dist/fields/altTextField.js.map +1 -1
  22. package/dist/hooks/revalidateAltTextHealth.js +5 -0
  23. package/dist/hooks/revalidateAltTextHealth.js.map +1 -1
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.js +1 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/plugin.js +23 -47
  28. package/dist/plugin.js.map +1 -1
  29. package/dist/translations/de.js +4 -5
  30. package/dist/translations/de.js.map +1 -1
  31. package/dist/translations/en.js +3 -4
  32. package/dist/translations/en.js.map +1 -1
  33. package/dist/translations/translation-schema.json +4 -6
  34. package/dist/types/AltTextPluginConfig.d.ts +21 -5
  35. package/dist/types/AltTextPluginConfig.js.map +1 -1
  36. package/dist/utilities/altTextHealth.js +18 -10
  37. package/dist/utilities/altTextHealth.js.map +1 -1
  38. package/dist/utilities/mimeTypes.d.ts +70 -0
  39. package/dist/utilities/mimeTypes.js +115 -0
  40. package/dist/utilities/mimeTypes.js.map +1 -0
  41. package/package.json +14 -12
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/altTextHealth.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\nimport { unstable_cache } from 'next/cache.js'\n\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\nimport { createCachedAltTextHealthScan } from './altTextHealthCache.js'\nimport { localesFromConfig } from './localesFromConfig.js'\nimport { summarizeCollection } from './summarizeCollection.js'\n\nexport const ALT_TEXT_HEALTH_PLUGIN_SLUG = 'alt-text'\nexport const ALT_TEXT_HEALTH_CACHE_TTL = 3600\nexport const ALT_TEXT_HEALTH_GLOBAL_TAG = 'alt-text-health'\n\nexport type AltTextHealthErrorCode =\n | 'ALT_TEXT_COLLECTION_READ_FAILED'\n | 'ALT_TEXT_PLUGIN_CONFIG_MISSING'\n\nexport type AltTextHealthError = {\n code: AltTextHealthErrorCode\n collection?: string\n message: string\n operation?: 'find'\n}\n\nexport type AltTextHealthScanCollection = {\n collection: string\n completeDocs: number\n error?: AltTextHealthError\n invalidDocIds: (number | string)[] | undefined\n missingDocs: number\n partialDocs: number\n totalDocs: number\n}\n\nexport type AltTextHealthScan = {\n checkedAt: string\n collections: AltTextHealthScanCollection[]\n errors: AltTextHealthError[]\n isLocalized: boolean\n localeCodes: string[]\n}\n\nexport type AltTextHealthWidgetData = {\n collections: AltTextHealthScanCollection[]\n errors: AltTextHealthError[]\n isLocalized: boolean\n localeCount: number\n totalDocs: number\n}\n\ntype AltTextHealthComputationArgs = {\n collections: string[]\n isLocalized: boolean\n localeCodes: string[]\n payload: Payload\n}\n\nconst createUnknownScan = ({\n error,\n isLocalized,\n localeCodes,\n}: {\n error: AltTextHealthScan['errors'][number]\n isLocalized: boolean\n localeCodes: string[]\n}): AltTextHealthScan => ({\n checkedAt: new Date().toISOString(),\n collections: [],\n errors: [error],\n isLocalized,\n localeCodes,\n})\n\nconst createCollectionReadError = (collection: string, message: string) => ({\n code: 'ALT_TEXT_COLLECTION_READ_FAILED' as const,\n collection,\n message,\n operation: 'find' as const,\n})\n\nconst PAGE_SIZE = 500\n\nasync function fetchAllDocs(\n payload: Payload,\n collection: string,\n isLocalized: boolean,\n): Promise<{ alt: unknown; id: number | string }[]> {\n const docs: { alt: unknown; id: number | string }[] = []\n let page = 1\n let hasMore = true\n\n while (hasMore) {\n const result = await payload.find({\n collection,\n depth: 0,\n fallbackLocale: isLocalized ? false : undefined,\n limit: PAGE_SIZE,\n locale: isLocalized ? 'all' : undefined,\n overrideAccess: true,\n page,\n select: {\n alt: true,\n },\n })\n\n for (const doc of result.docs) {\n docs.push({\n id: doc.id,\n alt: 'alt' in doc ? doc.alt : undefined,\n })\n }\n\n hasMore = result.hasNextPage\n page++\n }\n\n return docs\n}\n\nasync function computeAltTextHealthScan({\n collections,\n isLocalized,\n localeCodes,\n payload,\n}: AltTextHealthComputationArgs): Promise<AltTextHealthScan> {\n const collectionSummaries = await Promise.all(\n collections.map(async (collection): Promise<AltTextHealthScanCollection> => {\n try {\n const docs = await fetchAllDocs(payload, collection, isLocalized)\n\n return summarizeCollection({\n collection,\n docs,\n isLocalized,\n localeCodes,\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n const collectionError = createCollectionReadError(collection, message)\n\n payload.logger.error({\n collection,\n err: error,\n msg: 'Alt text health check failed while reading a collection.',\n operation: 'find',\n plugin: ALT_TEXT_HEALTH_PLUGIN_SLUG,\n })\n\n return {\n collection,\n completeDocs: 0,\n error: collectionError,\n invalidDocIds: undefined,\n missingDocs: 0,\n partialDocs: 0,\n totalDocs: 0,\n }\n }\n }),\n )\n\n const errors = collectionSummaries\n .filter((summary) => summary.error)\n .map((summary) => summary.error!)\n\n return {\n checkedAt: new Date().toISOString(),\n collections: collectionSummaries,\n errors,\n isLocalized,\n localeCodes,\n }\n}\n\nexport const getAltTextHealthCollectionTag = (collectionSlug: string): string =>\n `${ALT_TEXT_HEALTH_GLOBAL_TAG}:${collectionSlug}`\n\nasync function getAltTextHealthScan(req: PayloadRequest): Promise<AltTextHealthScan> {\n const { payload } = req\n const pluginConfig = payload.config.custom?.altTextPluginConfig as AltTextPluginConfig | undefined\n const localeCodes =\n localesFromConfig(payload.config) ?? (pluginConfig?.locale ? [pluginConfig.locale] : [])\n const isLocalized = Boolean(payload.config.localization)\n\n if (!pluginConfig) {\n return createUnknownScan({\n error: {\n code: 'ALT_TEXT_PLUGIN_CONFIG_MISSING',\n message: 'Alt text plugin config not found',\n },\n isLocalized,\n localeCodes,\n })\n }\n\n const collections = pluginConfig.collections\n\n const cacheKeyParts = [\n ALT_TEXT_HEALTH_GLOBAL_TAG,\n [...collections].sort().join(','),\n localeCodes.join(','),\n ]\n\n const tags = [\n ALT_TEXT_HEALTH_GLOBAL_TAG,\n ...new Set(collections.map((collection) => getAltTextHealthCollectionTag(collection))),\n ]\n\n const getCachedHealthScan = createCachedAltTextHealthScan({\n cacheFactory: unstable_cache,\n cacheKeyParts,\n compute: async () =>\n computeAltTextHealthScan({\n collections,\n isLocalized,\n localeCodes,\n payload,\n }),\n revalidate: ALT_TEXT_HEALTH_CACHE_TTL,\n tags,\n })\n\n return getCachedHealthScan()\n}\n\nexport async function getAltTextHealth(req: PayloadRequest): Promise<AltTextHealthScan> {\n return getAltTextHealthScan(req)\n}\n\nexport async function getAltTextHealthWidgetData(\n req: PayloadRequest,\n): Promise<AltTextHealthWidgetData> {\n const scan = await getAltTextHealthScan(req)\n\n return {\n collections: scan.collections,\n errors: scan.errors,\n isLocalized: scan.isLocalized,\n localeCount: scan.localeCodes.length,\n totalDocs: scan.collections.reduce((total, c) => total + c.totalDocs, 0),\n }\n}\n"],"names":["unstable_cache","createCachedAltTextHealthScan","localesFromConfig","summarizeCollection","ALT_TEXT_HEALTH_PLUGIN_SLUG","ALT_TEXT_HEALTH_CACHE_TTL","ALT_TEXT_HEALTH_GLOBAL_TAG","createUnknownScan","error","isLocalized","localeCodes","checkedAt","Date","toISOString","collections","errors","createCollectionReadError","collection","message","code","operation","PAGE_SIZE","fetchAllDocs","payload","docs","page","hasMore","result","find","depth","fallbackLocale","undefined","limit","locale","overrideAccess","select","alt","doc","push","id","hasNextPage","computeAltTextHealthScan","collectionSummaries","Promise","all","map","Error","collectionError","logger","err","msg","plugin","completeDocs","invalidDocIds","missingDocs","partialDocs","totalDocs","filter","summary","getAltTextHealthCollectionTag","collectionSlug","getAltTextHealthScan","req","pluginConfig","config","custom","altTextPluginConfig","Boolean","localization","cacheKeyParts","sort","join","tags","Set","getCachedHealthScan","cacheFactory","compute","revalidate","getAltTextHealth","getAltTextHealthWidgetData","scan","localeCount","length","reduce","total","c"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/utilities/altTextHealth.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\nimport { unstable_cache } from 'next/cache.js'\n\nimport type {\n AltTextPluginConfig,\n NormalizedAltTextCollectionConfig,\n} from '../types/AltTextPluginConfig.js'\n\nimport { createCachedAltTextHealthScan } from './altTextHealthCache.js'\nimport { localesFromConfig } from './localesFromConfig.js'\nimport { buildMimeTypeWhere } from './mimeTypes.js'\nimport { summarizeCollection } from './summarizeCollection.js'\n\nexport const ALT_TEXT_HEALTH_PLUGIN_SLUG = 'alt-text'\nexport const ALT_TEXT_HEALTH_CACHE_TTL = 3600\nexport const ALT_TEXT_HEALTH_GLOBAL_TAG = 'alt-text-health'\n\nexport type AltTextHealthErrorCode =\n | 'ALT_TEXT_COLLECTION_READ_FAILED'\n | 'ALT_TEXT_PLUGIN_CONFIG_MISSING'\n\nexport type AltTextHealthError = {\n code: AltTextHealthErrorCode\n collection?: string\n message: string\n operation?: 'find'\n}\n\nexport type AltTextHealthScanCollection = {\n collection: string\n completeDocs: number\n error?: AltTextHealthError\n invalidDocIds: (number | string)[] | undefined\n missingDocs: number\n partialDocs: number\n totalDocs: number\n}\n\nexport type AltTextHealthScan = {\n checkedAt: string\n collections: AltTextHealthScanCollection[]\n errors: AltTextHealthError[]\n isLocalized: boolean\n localeCodes: string[]\n}\n\nexport type AltTextHealthWidgetData = {\n collections: AltTextHealthScanCollection[]\n errors: AltTextHealthError[]\n isLocalized: boolean\n localeCount: number\n totalDocs: number\n}\n\ntype AltTextHealthComputationArgs = {\n collections: NormalizedAltTextCollectionConfig[]\n isLocalized: boolean\n localeCodes: string[]\n payload: Payload\n}\n\nconst createUnknownScan = ({\n error,\n isLocalized,\n localeCodes,\n}: {\n error: AltTextHealthScan['errors'][number]\n isLocalized: boolean\n localeCodes: string[]\n}): AltTextHealthScan => ({\n checkedAt: new Date().toISOString(),\n collections: [],\n errors: [error],\n isLocalized,\n localeCodes,\n})\n\nconst createCollectionReadError = (collection: string, message: string) => ({\n code: 'ALT_TEXT_COLLECTION_READ_FAILED' as const,\n collection,\n message,\n operation: 'find' as const,\n})\n\nconst PAGE_SIZE = 500\n\nasync function fetchAllDocs(\n payload: Payload,\n collection: string,\n isLocalized: boolean,\n mimeTypes: readonly string[],\n): Promise<{ alt: unknown; id: number | string }[]> {\n const where = buildMimeTypeWhere(mimeTypes)\n if (!where) {\n return []\n }\n\n const docs: { alt: unknown; id: number | string }[] = []\n let page = 1\n let hasMore = true\n\n while (hasMore) {\n const result = await payload.find({\n collection,\n depth: 0,\n fallbackLocale: isLocalized ? false : undefined,\n limit: PAGE_SIZE,\n locale: isLocalized ? 'all' : undefined,\n overrideAccess: true,\n page,\n select: {\n alt: true,\n },\n where,\n })\n\n for (const doc of result.docs) {\n docs.push({\n id: doc.id,\n alt: 'alt' in doc ? doc.alt : undefined,\n })\n }\n\n hasMore = result.hasNextPage\n page++\n }\n\n return docs\n}\n\nasync function computeAltTextHealthScan({\n collections,\n isLocalized,\n localeCodes,\n payload,\n}: AltTextHealthComputationArgs): Promise<AltTextHealthScan> {\n const collectionSummaries = await Promise.all(\n collections.map(async ({ slug, mimeTypes }): Promise<AltTextHealthScanCollection> => {\n try {\n const docs = await fetchAllDocs(payload, slug, isLocalized, mimeTypes)\n\n return summarizeCollection({\n collection: slug,\n docs,\n isLocalized,\n localeCodes,\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n const collectionError = createCollectionReadError(slug, message)\n\n payload.logger.error({\n collection: slug,\n err: error,\n msg: 'Alt text health check failed while reading a collection.',\n operation: 'find',\n plugin: ALT_TEXT_HEALTH_PLUGIN_SLUG,\n })\n\n return {\n collection: slug,\n completeDocs: 0,\n error: collectionError,\n invalidDocIds: undefined,\n missingDocs: 0,\n partialDocs: 0,\n totalDocs: 0,\n }\n }\n }),\n )\n\n const errors = collectionSummaries\n .filter((summary) => summary.error)\n .map((summary) => summary.error!)\n\n return {\n checkedAt: new Date().toISOString(),\n collections: collectionSummaries,\n errors,\n isLocalized,\n localeCodes,\n }\n}\n\nexport const getAltTextHealthCollectionTag = (collectionSlug: string): string =>\n `${ALT_TEXT_HEALTH_GLOBAL_TAG}:${collectionSlug}`\n\nasync function getAltTextHealthScan(req: PayloadRequest): Promise<AltTextHealthScan> {\n const { payload } = req\n const pluginConfig = payload.config.custom?.altTextPluginConfig as AltTextPluginConfig | undefined\n const localeCodes =\n localesFromConfig(payload.config) ?? (pluginConfig?.locale ? [pluginConfig.locale] : [])\n const isLocalized = Boolean(payload.config.localization)\n\n if (!pluginConfig) {\n return createUnknownScan({\n error: {\n code: 'ALT_TEXT_PLUGIN_CONFIG_MISSING',\n message: 'Alt text plugin config not found',\n },\n isLocalized,\n localeCodes,\n })\n }\n\n const collections = pluginConfig.collections\n\n const cacheKeyParts = [\n ALT_TEXT_HEALTH_GLOBAL_TAG,\n [...collections]\n .map(({ slug, mimeTypes }) => `${slug}:${[...mimeTypes].sort().join('|')}`)\n .sort()\n .join(','),\n localeCodes.join(','),\n ]\n\n const tags = [\n ALT_TEXT_HEALTH_GLOBAL_TAG,\n ...new Set(collections.map(({ slug }) => getAltTextHealthCollectionTag(slug))),\n ]\n\n const getCachedHealthScan = createCachedAltTextHealthScan({\n cacheFactory: unstable_cache,\n cacheKeyParts,\n compute: async () =>\n computeAltTextHealthScan({\n collections,\n isLocalized,\n localeCodes,\n payload,\n }),\n revalidate: ALT_TEXT_HEALTH_CACHE_TTL,\n tags,\n })\n\n return getCachedHealthScan()\n}\n\nexport async function getAltTextHealth(req: PayloadRequest): Promise<AltTextHealthScan> {\n return getAltTextHealthScan(req)\n}\n\nexport async function getAltTextHealthWidgetData(\n req: PayloadRequest,\n): Promise<AltTextHealthWidgetData> {\n const scan = await getAltTextHealthScan(req)\n\n return {\n collections: scan.collections,\n errors: scan.errors,\n isLocalized: scan.isLocalized,\n localeCount: scan.localeCodes.length,\n totalDocs: scan.collections.reduce((total, c) => total + c.totalDocs, 0),\n }\n}\n"],"names":["unstable_cache","createCachedAltTextHealthScan","localesFromConfig","buildMimeTypeWhere","summarizeCollection","ALT_TEXT_HEALTH_PLUGIN_SLUG","ALT_TEXT_HEALTH_CACHE_TTL","ALT_TEXT_HEALTH_GLOBAL_TAG","createUnknownScan","error","isLocalized","localeCodes","checkedAt","Date","toISOString","collections","errors","createCollectionReadError","collection","message","code","operation","PAGE_SIZE","fetchAllDocs","payload","mimeTypes","where","docs","page","hasMore","result","find","depth","fallbackLocale","undefined","limit","locale","overrideAccess","select","alt","doc","push","id","hasNextPage","computeAltTextHealthScan","collectionSummaries","Promise","all","map","slug","Error","collectionError","logger","err","msg","plugin","completeDocs","invalidDocIds","missingDocs","partialDocs","totalDocs","filter","summary","getAltTextHealthCollectionTag","collectionSlug","getAltTextHealthScan","req","pluginConfig","config","custom","altTextPluginConfig","Boolean","localization","cacheKeyParts","sort","join","tags","Set","getCachedHealthScan","cacheFactory","compute","revalidate","getAltTextHealth","getAltTextHealthWidgetData","scan","localeCount","length","reduce","total","c"],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import type { CollectionSlug, TextareaFieldValidation, Where } from 'payload';
2
+ export declare const DEFAULT_TRACKED_MIME_TYPES: readonly string[];
3
+ export type AltTextCollectionConfig = {
4
+ /**
5
+ * MIME types for which alt text is tracked, validated, and generated in this collection.
6
+ *
7
+ * Accepts exact MIME types (e.g. `image/png`) or wildcards (e.g. `image/*`).
8
+ * For documents whose mime type does not match, the alt text field is hidden,
9
+ * its validation is skipped, and the document is excluded from the health widget.
10
+ *
11
+ * @default ['image/*']
12
+ */
13
+ mimeTypes?: string[];
14
+ /** Collection slug to enable the plugin for. */
15
+ slug: CollectionSlug;
16
+ /**
17
+ * Custom validate function for the alt text field on this collection.
18
+ * When provided, it fully replaces the default validator (`validateAltText`).
19
+ *
20
+ * Use this to relax or extend the default — for example, to skip the
21
+ * required-alt check when the request body does not touch `alt`
22
+ * (folder moves, partial API updates).
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { validateAltText } from '@jhb.software/payload-alt-text-plugin'
27
+ *
28
+ * collections: [
29
+ * {
30
+ * slug: 'media',
31
+ * validate: (value, args) => {
32
+ * const { req } = args
33
+ * if (!req.data || !('alt' in req.data)) return true
34
+ * return validateAltText(value, args)
35
+ * },
36
+ * },
37
+ * ]
38
+ * ```
39
+ */
40
+ validate?: TextareaFieldValidation;
41
+ };
42
+ export type NormalizedAltTextCollectionConfig = {
43
+ mimeTypes: string[];
44
+ slug: CollectionSlug;
45
+ validate?: TextareaFieldValidation;
46
+ };
47
+ export type IncomingCollectionsConfig = (AltTextCollectionConfig | CollectionSlug)[];
48
+ export declare function normalizeCollectionsConfig(incoming: IncomingCollectionsConfig): NormalizedAltTextCollectionConfig[];
49
+ export declare function matchesMimeType(mimeType: string, patterns: readonly string[]): boolean;
50
+ /**
51
+ * Builds a Payload `where` clause that matches documents whose `mimeType`
52
+ * is in the given list of patterns. Returns `null` when nothing should match
53
+ * (empty patterns), so callers can short-circuit the query.
54
+ *
55
+ * Wildcards like `image/*` are translated to a `like` (case-insensitive
56
+ * substring) match on the prefix (`image/`). For valid MIME types this is
57
+ * equivalent to a prefix match.
58
+ */
59
+ export declare function buildMimeTypeWhere(patterns: readonly string[]): null | Where;
60
+ /**
61
+ * Default validation logic for the alt text field.
62
+ *
63
+ * - Allows an empty value during the initial upload (no regular update has occurred yet).
64
+ * - Allows an empty value when the document's mime type is not tracked for alt text.
65
+ * - Otherwise requires a non-empty value.
66
+ *
67
+ * Projects with stricter or looser requirements can pass a custom function to
68
+ * a collection's `validate` option instead.
69
+ */
70
+ export declare function validateAltText(value: Parameters<TextareaFieldValidation>[0], args: Parameters<TextareaFieldValidation>[1], trackedMimeTypes?: readonly string[]): string | true;
@@ -0,0 +1,115 @@
1
+ export const DEFAULT_TRACKED_MIME_TYPES = [
2
+ 'image/*'
3
+ ];
4
+ export function normalizeCollectionsConfig(incoming) {
5
+ return incoming.map((entry)=>{
6
+ if (typeof entry === 'string') {
7
+ return {
8
+ slug: entry,
9
+ mimeTypes: [
10
+ ...DEFAULT_TRACKED_MIME_TYPES
11
+ ]
12
+ };
13
+ }
14
+ const normalized = {
15
+ slug: entry.slug,
16
+ mimeTypes: entry.mimeTypes ? [
17
+ ...entry.mimeTypes
18
+ ] : [
19
+ ...DEFAULT_TRACKED_MIME_TYPES
20
+ ]
21
+ };
22
+ if (entry.validate) {
23
+ normalized.validate = entry.validate;
24
+ }
25
+ return normalized;
26
+ });
27
+ }
28
+ // Payload stores upload mimeType values as the lowercase MIME string (e.g. `image/png`).
29
+ // Pattern comparisons here are case-sensitive; callers should pass lowercase patterns.
30
+ export function matchesMimeType(mimeType, patterns) {
31
+ return patterns.some((pattern)=>{
32
+ if (pattern === mimeType) {
33
+ return true;
34
+ }
35
+ if (pattern.endsWith('/*')) {
36
+ const prefix = pattern.slice(0, -1);
37
+ return mimeType.startsWith(prefix);
38
+ }
39
+ return false;
40
+ });
41
+ }
42
+ /**
43
+ * Builds a Payload `where` clause that matches documents whose `mimeType`
44
+ * is in the given list of patterns. Returns `null` when nothing should match
45
+ * (empty patterns), so callers can short-circuit the query.
46
+ *
47
+ * Wildcards like `image/*` are translated to a `like` (case-insensitive
48
+ * substring) match on the prefix (`image/`). For valid MIME types this is
49
+ * equivalent to a prefix match.
50
+ */ export function buildMimeTypeWhere(patterns) {
51
+ if (patterns.length === 0) {
52
+ return null;
53
+ }
54
+ const exacts = [];
55
+ const wildcardPrefixes = [];
56
+ for (const pattern of patterns){
57
+ if (pattern.endsWith('/*')) {
58
+ wildcardPrefixes.push(pattern.slice(0, -1));
59
+ } else {
60
+ exacts.push(pattern);
61
+ }
62
+ }
63
+ const clauses = [];
64
+ if (exacts.length > 0) {
65
+ clauses.push({
66
+ mimeType: {
67
+ in: exacts
68
+ }
69
+ });
70
+ }
71
+ for (const prefix of wildcardPrefixes){
72
+ clauses.push({
73
+ mimeType: {
74
+ like: prefix
75
+ }
76
+ });
77
+ }
78
+ return clauses.length === 1 ? clauses[0] : {
79
+ or: clauses
80
+ };
81
+ }
82
+ /**
83
+ * Default validation logic for the alt text field.
84
+ *
85
+ * - Allows an empty value during the initial upload (no regular update has occurred yet).
86
+ * - Allows an empty value when the document's mime type is not tracked for alt text.
87
+ * - Otherwise requires a non-empty value.
88
+ *
89
+ * Projects with stricter or looser requirements can pass a custom function to
90
+ * a collection's `validate` option instead.
91
+ */ export function validateAltText(value, args, trackedMimeTypes) {
92
+ const data = args.data ?? {};
93
+ const { operation, req } = args;
94
+ // Since https://github.com/payloadcms/payload/pull/14988, when using external storage (e.g., S3),
95
+ // it is no longer possible to detect whether this validation runs during the initial upload
96
+ // or a regular update by checking the existence of the ID.
97
+ // Instead, compare the timestamps of the createdAt and updatedAt fields.
98
+ const isInitialUpload = operation === 'create' || 'createdAt' in data && 'updatedAt' in data && data.createdAt === data.updatedAt;
99
+ if (isInitialUpload) {
100
+ return true;
101
+ }
102
+ if (trackedMimeTypes && trackedMimeTypes.length > 0) {
103
+ const mimeType = typeof data.mimeType === 'string' ? data.mimeType : undefined;
104
+ if (!mimeType || !matchesMimeType(mimeType, trackedMimeTypes)) {
105
+ return true;
106
+ }
107
+ }
108
+ if (typeof value !== 'string' || value.trim().length === 0) {
109
+ // @ts-expect-error - the translation key type does not include the custom key
110
+ return req.t('@jhb.software/payload-alt-text-plugin:theAlternateTextIsRequired');
111
+ }
112
+ return true;
113
+ }
114
+
115
+ //# sourceMappingURL=mimeTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/mimeTypes.ts"],"sourcesContent":["import type { CollectionSlug, TextareaFieldValidation, Where } from 'payload'\n\nexport const DEFAULT_TRACKED_MIME_TYPES: readonly string[] = ['image/*']\n\nexport type AltTextCollectionConfig = {\n /**\n * MIME types for which alt text is tracked, validated, and generated in this collection.\n *\n * Accepts exact MIME types (e.g. `image/png`) or wildcards (e.g. `image/*`).\n * For documents whose mime type does not match, the alt text field is hidden,\n * its validation is skipped, and the document is excluded from the health widget.\n *\n * @default ['image/*']\n */\n mimeTypes?: string[]\n /** Collection slug to enable the plugin for. */\n slug: CollectionSlug\n /**\n * Custom validate function for the alt text field on this collection.\n * When provided, it fully replaces the default validator (`validateAltText`).\n *\n * Use this to relax or extend the default — for example, to skip the\n * required-alt check when the request body does not touch `alt`\n * (folder moves, partial API updates).\n *\n * @example\n * ```typescript\n * import { validateAltText } from '@jhb.software/payload-alt-text-plugin'\n *\n * collections: [\n * {\n * slug: 'media',\n * validate: (value, args) => {\n * const { req } = args\n * if (!req.data || !('alt' in req.data)) return true\n * return validateAltText(value, args)\n * },\n * },\n * ]\n * ```\n */\n validate?: TextareaFieldValidation\n}\n\nexport type NormalizedAltTextCollectionConfig = {\n mimeTypes: string[]\n slug: CollectionSlug\n validate?: TextareaFieldValidation\n}\n\nexport type IncomingCollectionsConfig = (AltTextCollectionConfig | CollectionSlug)[]\n\nexport function normalizeCollectionsConfig(\n incoming: IncomingCollectionsConfig,\n): NormalizedAltTextCollectionConfig[] {\n return incoming.map((entry) => {\n if (typeof entry === 'string') {\n return { slug: entry, mimeTypes: [...DEFAULT_TRACKED_MIME_TYPES] }\n }\n\n const normalized: NormalizedAltTextCollectionConfig = {\n slug: entry.slug,\n mimeTypes: entry.mimeTypes ? [...entry.mimeTypes] : [...DEFAULT_TRACKED_MIME_TYPES],\n }\n if (entry.validate) {\n normalized.validate = entry.validate\n }\n return normalized\n })\n}\n\n// Payload stores upload mimeType values as the lowercase MIME string (e.g. `image/png`).\n// Pattern comparisons here are case-sensitive; callers should pass lowercase patterns.\nexport function matchesMimeType(mimeType: string, patterns: readonly string[]): boolean {\n return patterns.some((pattern) => {\n if (pattern === mimeType) {\n return true\n }\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1)\n return mimeType.startsWith(prefix)\n }\n return false\n })\n}\n\n/**\n * Builds a Payload `where` clause that matches documents whose `mimeType`\n * is in the given list of patterns. Returns `null` when nothing should match\n * (empty patterns), so callers can short-circuit the query.\n *\n * Wildcards like `image/*` are translated to a `like` (case-insensitive\n * substring) match on the prefix (`image/`). For valid MIME types this is\n * equivalent to a prefix match.\n */\nexport function buildMimeTypeWhere(patterns: readonly string[]): null | Where {\n if (patterns.length === 0) {\n return null\n }\n\n const exacts: string[] = []\n const wildcardPrefixes: string[] = []\n\n for (const pattern of patterns) {\n if (pattern.endsWith('/*')) {\n wildcardPrefixes.push(pattern.slice(0, -1))\n } else {\n exacts.push(pattern)\n }\n }\n\n const clauses: Where[] = []\n if (exacts.length > 0) {\n clauses.push({ mimeType: { in: exacts } })\n }\n for (const prefix of wildcardPrefixes) {\n clauses.push({ mimeType: { like: prefix } })\n }\n\n return clauses.length === 1 ? clauses[0] : { or: clauses }\n}\n\n/**\n * Default validation logic for the alt text field.\n *\n * - Allows an empty value during the initial upload (no regular update has occurred yet).\n * - Allows an empty value when the document's mime type is not tracked for alt text.\n * - Otherwise requires a non-empty value.\n *\n * Projects with stricter or looser requirements can pass a custom function to\n * a collection's `validate` option instead.\n */\nexport function validateAltText(\n value: Parameters<TextareaFieldValidation>[0],\n args: Parameters<TextareaFieldValidation>[1],\n trackedMimeTypes?: readonly string[],\n): string | true {\n const data = (args.data ?? {}) as Record<string, unknown>\n const { operation, req } = args\n\n // Since https://github.com/payloadcms/payload/pull/14988, when using external storage (e.g., S3),\n // it is no longer possible to detect whether this validation runs during the initial upload\n // or a regular update by checking the existence of the ID.\n // Instead, compare the timestamps of the createdAt and updatedAt fields.\n const isInitialUpload =\n operation === 'create' ||\n ('createdAt' in data && 'updatedAt' in data && data.createdAt === data.updatedAt)\n\n if (isInitialUpload) {\n return true\n }\n\n if (trackedMimeTypes && trackedMimeTypes.length > 0) {\n const mimeType = typeof data.mimeType === 'string' ? data.mimeType : undefined\n if (!mimeType || !matchesMimeType(mimeType, trackedMimeTypes)) {\n return true\n }\n }\n\n if (typeof value !== 'string' || value.trim().length === 0) {\n // @ts-expect-error - the translation key type does not include the custom key\n return req.t('@jhb.software/payload-alt-text-plugin:theAlternateTextIsRequired')\n }\n\n return true\n}\n"],"names":["DEFAULT_TRACKED_MIME_TYPES","normalizeCollectionsConfig","incoming","map","entry","slug","mimeTypes","normalized","validate","matchesMimeType","mimeType","patterns","some","pattern","endsWith","prefix","slice","startsWith","buildMimeTypeWhere","length","exacts","wildcardPrefixes","push","clauses","in","like","or","validateAltText","value","args","trackedMimeTypes","data","operation","req","isInitialUpload","createdAt","updatedAt","undefined","trim","t"],"mappings":"AAEA,OAAO,MAAMA,6BAAgD;IAAC;CAAU,CAAA;AAkDxE,OAAO,SAASC,2BACdC,QAAmC;IAEnC,OAAOA,SAASC,GAAG,CAAC,CAACC;QACnB,IAAI,OAAOA,UAAU,UAAU;YAC7B,OAAO;gBAAEC,MAAMD;gBAAOE,WAAW;uBAAIN;iBAA2B;YAAC;QACnE;QAEA,MAAMO,aAAgD;YACpDF,MAAMD,MAAMC,IAAI;YAChBC,WAAWF,MAAME,SAAS,GAAG;mBAAIF,MAAME,SAAS;aAAC,GAAG;mBAAIN;aAA2B;QACrF;QACA,IAAII,MAAMI,QAAQ,EAAE;YAClBD,WAAWC,QAAQ,GAAGJ,MAAMI,QAAQ;QACtC;QACA,OAAOD;IACT;AACF;AAEA,yFAAyF;AACzF,uFAAuF;AACvF,OAAO,SAASE,gBAAgBC,QAAgB,EAAEC,QAA2B;IAC3E,OAAOA,SAASC,IAAI,CAAC,CAACC;QACpB,IAAIA,YAAYH,UAAU;YACxB,OAAO;QACT;QACA,IAAIG,QAAQC,QAAQ,CAAC,OAAO;YAC1B,MAAMC,SAASF,QAAQG,KAAK,CAAC,GAAG,CAAC;YACjC,OAAON,SAASO,UAAU,CAACF;QAC7B;QACA,OAAO;IACT;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASG,mBAAmBP,QAA2B;IAC5D,IAAIA,SAASQ,MAAM,KAAK,GAAG;QACzB,OAAO;IACT;IAEA,MAAMC,SAAmB,EAAE;IAC3B,MAAMC,mBAA6B,EAAE;IAErC,KAAK,MAAMR,WAAWF,SAAU;QAC9B,IAAIE,QAAQC,QAAQ,CAAC,OAAO;YAC1BO,iBAAiBC,IAAI,CAACT,QAAQG,KAAK,CAAC,GAAG,CAAC;QAC1C,OAAO;YACLI,OAAOE,IAAI,CAACT;QACd;IACF;IAEA,MAAMU,UAAmB,EAAE;IAC3B,IAAIH,OAAOD,MAAM,GAAG,GAAG;QACrBI,QAAQD,IAAI,CAAC;YAAEZ,UAAU;gBAAEc,IAAIJ;YAAO;QAAE;IAC1C;IACA,KAAK,MAAML,UAAUM,iBAAkB;QACrCE,QAAQD,IAAI,CAAC;YAAEZ,UAAU;gBAAEe,MAAMV;YAAO;QAAE;IAC5C;IAEA,OAAOQ,QAAQJ,MAAM,KAAK,IAAII,OAAO,CAAC,EAAE,GAAG;QAAEG,IAAIH;IAAQ;AAC3D;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASI,gBACdC,KAA6C,EAC7CC,IAA4C,EAC5CC,gBAAoC;IAEpC,MAAMC,OAAQF,KAAKE,IAAI,IAAI,CAAC;IAC5B,MAAM,EAAEC,SAAS,EAAEC,GAAG,EAAE,GAAGJ;IAE3B,kGAAkG;IAClG,4FAA4F;IAC5F,2DAA2D;IAC3D,yEAAyE;IACzE,MAAMK,kBACJF,cAAc,YACb,eAAeD,QAAQ,eAAeA,QAAQA,KAAKI,SAAS,KAAKJ,KAAKK,SAAS;IAElF,IAAIF,iBAAiB;QACnB,OAAO;IACT;IAEA,IAAIJ,oBAAoBA,iBAAiBX,MAAM,GAAG,GAAG;QACnD,MAAMT,WAAW,OAAOqB,KAAKrB,QAAQ,KAAK,WAAWqB,KAAKrB,QAAQ,GAAG2B;QACrE,IAAI,CAAC3B,YAAY,CAACD,gBAAgBC,UAAUoB,mBAAmB;YAC7D,OAAO;QACT;IACF;IAEA,IAAI,OAAOF,UAAU,YAAYA,MAAMU,IAAI,GAAGnB,MAAM,KAAK,GAAG;QAC1D,8EAA8E;QAC9E,OAAOc,IAAIM,CAAC,CAAC;IACf;IAEA,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhb.software/payload-alt-text-plugin",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "A Payload CMS plugin that adds AI-powered alt text generation for images.",
5
5
  "bugs": "https://github.com/jhb-software/payload-plugins/issues",
6
6
  "repository": "https://github.com/jhb-software/payload-plugins",
@@ -17,28 +17,28 @@
17
17
  "main": "./dist/index.js",
18
18
  "types": "./dist/index.d.ts",
19
19
  "dependencies": {
20
- "openai": "^6.33.0",
20
+ "openai": "^6.34.0",
21
21
  "p-map": "^7.0.4",
22
22
  "zod": "^4.3.6"
23
23
  },
24
24
  "peerDependencies": {
25
- "@payloadcms/translations": "^3.81.0",
26
- "@payloadcms/ui": "^3.81.0",
25
+ "@payloadcms/translations": "^3.83.0",
26
+ "@payloadcms/ui": "^3.83.0",
27
27
  "next": "15.4.11",
28
- "payload": "^3.81.0",
29
- "react": "19.2.4",
30
- "react-dom": "19.2.4"
28
+ "payload": "^3.83.0",
29
+ "react": "19.2.5",
30
+ "react-dom": "19.2.5"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@payloadcms/eslint-config": "^3.28.0",
34
- "@payloadcms/translations": "^3.81.0",
34
+ "@payloadcms/translations": "^3.83.0",
35
35
  "@swc/cli": "^0.8.1",
36
- "@swc/core": "^1.15.24",
36
+ "@swc/core": "^1.15.26",
37
37
  "@types/react": "19.2.14",
38
38
  "@types/react-dom": "19.2.3",
39
39
  "copyfiles": "2.4.1",
40
40
  "eslint": "^9.0.0",
41
- "prettier": "^3.8.1",
41
+ "prettier": "^3.8.3",
42
42
  "rimraf": "6.1.3",
43
43
  "typescript": "5.9.3"
44
44
  },
@@ -72,9 +72,11 @@
72
72
  "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
73
73
  "clean": "rimraf --glob {dist,*.tsbuildinfo}",
74
74
  "dev": "tsc -w",
75
- "format": "prettier --write src",
75
+ "format": "prettier --write src \"*.{json,md,js,mjs,cjs}\"",
76
76
  "lint": "eslint src",
77
77
  "lint:fix": "eslint src --fix",
78
- "test:health": "node --experimental-strip-types --test test/altTextHealth.test.ts"
78
+ "test": "node --experimental-strip-types --test test/*.test.ts",
79
+ "test:health": "node --experimental-strip-types --test test/altTextHealth.test.ts",
80
+ "typecheck": "tsc --noEmit"
79
81
  }
80
82
  }