diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index a85f46a4..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "extends": "next/core-web-vitals", - "plugins": ["@typescript-eslint"], - "rules": { - "@next/next/no-img-element": "off", - "@typescript-eslint/no-unused-expressions": ["warn"], - "@typescript-eslint/no-unused-vars": [ - "warn", { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_" - } - ], - "comma-dangle": [ - "warn", - "always-multiline" - ], - "indent": [ - "warn", - 2 - ], - "linebreak-style": [ - "warn", - "unix" - ], - "quotes": [ - "warn", - "single" - ], - "semi": [ - "warn", - "always" - ], - "max-len": [ - "warn", - { "code": 80 } - ] - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..927fb05f --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,51 @@ +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { FlatCompat } from '@eslint/eslintrc'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends('next/core-web-vitals', 'next/typescript'), { + rules: { + '@next/next/no-img-element': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'no-unused-expressions': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'warn', { + 'argsIgnorePattern': '^_', + 'varsIgnorePattern': '^_', + }, + ], + 'comma-dangle': [ + 'warn', + 'always-multiline', + ], + 'indent': [ + 'warn', + 2, + ], + 'linebreak-style': [ + 'warn', + 'unix', + ], + 'quotes': [ + 'warn', + 'single', + ], + 'semi': [ + 'warn', + 'always', + ], + 'max-len': [ + 'warn', + { 'code': 80 }, + ], + }, + }]; + +export default eslintConfig; diff --git a/package.json b/package.json index ca160fa2..0712f10e 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,6 @@ "@types/react": "19.0.2", "@types/react-dom": "19.0.2", "@types/sanitize-html": "^2.13.0", - "@typescript-eslint/eslint-plugin": "^8.19.0", - "@typescript-eslint/parser": "^8.19.0", "@upstash/ratelimit": "^2.0.5", "@vercel/analytics": "^1.4.1", "@vercel/blob": "^0.27.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 347524a5..e518f014 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,12 +59,6 @@ importers: '@types/sanitize-html': specifier: ^2.13.0 version: 2.13.0 - '@typescript-eslint/eslint-plugin': - specifier: ^8.19.0 - version: 8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2))(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2) - '@typescript-eslint/parser': - specifier: ^8.19.0 - version: 8.19.0(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2) '@upstash/ratelimit': specifier: ^2.0.5 version: 2.0.5(@upstash/redis@1.34.3) diff --git a/src/admin/AdminAddAllUploads.tsx b/src/admin/AdminAddAllUploads.tsx index 28a07bb6..c91aea05 100644 --- a/src/admin/AdminAddAllUploads.tsx +++ b/src/admin/AdminAddAllUploads.tsx @@ -58,7 +58,7 @@ export default function AdminAddAllUploads({ for await (const data of readStreamableValue(stream)) { setButtonText(addedUploadCount.current === 0 ? `Adding 1 of ${storageUrls.length}` - : `Adding ${addedUploadCount.current + 1} of ${storageUrls.length}` + : `Adding ${addedUploadCount.current + 1} of ${storageUrls.length}`, ); setUrlAddStatuses(current => { const update = current.map(status => @@ -70,7 +70,7 @@ export default function AdminAddAllUploads({ statusMessage: data.statusMessage, progress: data.progress, } - : status + : status, ); addedUploadCount.current = update .filter(({ status }) => status === 'added') diff --git a/src/admin/AdminOutdatedClient.tsx b/src/admin/AdminOutdatedClient.tsx index 46c3aea7..b09653ac 100644 --- a/src/admin/AdminOutdatedClient.tsx +++ b/src/admin/AdminOutdatedClient.tsx @@ -48,7 +48,7 @@ export default function AdminOutdatedClient({ onClick={async () => { if (window.confirm( // eslint-disable-next-line max-len - `Are you sure you want to sync the oldest ${updateBatchSize} photos? This action cannot be undone.` + `Are you sure you want to sync the oldest ${updateBatchSize} photos? This action cannot be undone.`, )) { const photosToSync = photos .slice(0, updateBatchSize) diff --git a/src/admin/DeleteFormButton.tsx b/src/admin/DeleteFormButton.tsx index 40ee4f2a..936cec25 100644 --- a/src/admin/DeleteFormButton.tsx +++ b/src/admin/DeleteFormButton.tsx @@ -9,7 +9,7 @@ import { BiTrash } from 'react-icons/bi'; export default function DeleteFormButton ( props: ComponentProps & { clearLocalState?: boolean - } + }, ) { const { onFormSubmit: onFormSubmitProps, diff --git a/src/admin/actions.ts b/src/admin/actions.ts index 8fe7665a..e2ddd1ae 100644 --- a/src/admin/actions.ts +++ b/src/admin/actions.ts @@ -9,7 +9,7 @@ import { CONFIG_CHECKLIST_STATUS } from '@/site/config'; const scanForError = ( shouldCheck: boolean, - promise: () => Promise + promise: () => Promise, ): Promise => shouldCheck ? promise() diff --git a/src/app/admin/baseline/page.tsx b/src/app/admin/baseline/page.tsx index 3739c416..6ef3b204 100644 --- a/src/app/admin/baseline/page.tsx +++ b/src/app/admin/baseline/page.tsx @@ -184,7 +184,7 @@ export default function ComponentsPage() { {DEBUG_LINES.map((_, i) =>
Line {(i + 1).toString().padStart(2, '0')} -
+ , )} diff --git a/src/app/admin/photos/[photoId]/edit/page.tsx b/src/app/admin/photos/[photoId]/edit/page.tsx index ebf0d73c..863695cb 100644 --- a/src/app/admin/photos/[photoId]/edit/page.tsx +++ b/src/app/admin/photos/[photoId]/edit/page.tsx @@ -28,13 +28,13 @@ export default async function PhotoEditPage({ // Only generate image thumbnails when AI generation is enabled const imageThumbnailBase64 = AI_TEXT_GENERATION_ENABLED ? await resizeImageFromUrl( - getNextImageUrlForManipulation(photo.url, IS_PREVIEW) + getNextImageUrlForManipulation(photo.url, IS_PREVIEW), ) : ''; const blurData = BLUR_ENABLED ? await blurImageFromUrl( - getNextImageUrlForManipulation(photo.url, IS_PREVIEW) + getNextImageUrlForManipulation(photo.url, IS_PREVIEW), ) : ''; diff --git a/src/app/api/storage/presigned-url/[key]/route.ts b/src/app/api/storage/presigned-url/[key]/route.ts index 098b4f72..9eb0db38 100644 --- a/src/app/api/storage/presigned-url/[key]/route.ts +++ b/src/app/api/storage/presigned-url/[key]/route.ts @@ -25,7 +25,7 @@ export async function GET( CURRENT_STORAGE === 'cloudflare-r2' ? cloudflareR2PutObjectCommandForKey(key) : awsS3PutObjectCommandForKey(key), - { expiresIn: 3600 } + { expiresIn: 3600 }, ); return new Response( url, diff --git a/src/camera/index.ts b/src/camera/index.ts index 42e332d3..08dadb67 100644 --- a/src/camera/index.ts +++ b/src/camera/index.ts @@ -58,7 +58,7 @@ export const cameraFromPhoto = ( export const formatCameraText = ( { make: makeRaw, model: modelRaw }: Camera, includeMake: 'always' | 'never' | 'if-not-apple' = 'if-not-apple', - removeIPhoneOnLongModels?: boolean + removeIPhoneOnLongModels?: boolean, ) => { // Remove 'Corporation' from makes like 'Nikon Corporation' const make = makeRaw.replace(/ Corporation/i, ''); diff --git a/src/components/FieldSetWithStatus.tsx b/src/components/FieldSetWithStatus.tsx index ef763d50..5b0eda42 100644 --- a/src/components/FieldSetWithStatus.tsx +++ b/src/components/FieldSetWithStatus.tsx @@ -70,7 +70,7 @@ export default function FieldSetWithStatus({ } {isModified && !error && * } diff --git a/src/components/ImageInput.tsx b/src/components/ImageInput.tsx index 8b046f86..745c8dfc 100644 --- a/src/components/ImageInput.tsx +++ b/src/components/ImageInput.tsx @@ -104,7 +104,7 @@ export default function ImageInput({ // Specify wide gamut to avoid data loss while resizing const ctx = canvas?.getContext( - '2d', { colorSpace: 'display-p3' } + '2d', { colorSpace: 'display-p3' }, ); if ((shouldResize || isPng) && canvas && ctx) { diff --git a/src/components/cmdk/CommandKClient.tsx b/src/components/cmdk/CommandKClient.tsx index f95ec9c1..e73ac731 100644 --- a/src/components/cmdk/CommandKClient.tsx +++ b/src/components/cmdk/CommandKClient.tsx @@ -47,7 +47,7 @@ import { formatCount, formatCountDescriptive } from '@/utility/string'; import CommandKItem from './CommandKItem'; import { GRID_HOMEPAGE_ENABLED } from '@/site/config'; import { DialogDescription, DialogTitle } from '@radix-ui/react-dialog'; -import * as VisuallyHidden from "@radix-ui/react-visually-hidden"; +import * as VisuallyHidden from '@radix-ui/react-visually-hidden'; const DIALOG_TITLE = 'Global Command-K Menu'; const DIALOG_DESCRIPTION = 'For searching photos, views, and settings'; diff --git a/src/components/more/MoreMenu.tsx b/src/components/more/MoreMenu.tsx index d076b3da..dd919069 100644 --- a/src/components/more/MoreMenu.tsx +++ b/src/components/more/MoreMenu.tsx @@ -46,7 +46,7 @@ export default function MoreMenu({ )} > {items.map(props => - + , )} diff --git a/src/components/primitives/LabeledIcon.tsx b/src/components/primitives/LabeledIcon.tsx index ca249680..66aac337 100644 --- a/src/components/primitives/LabeledIcon.tsx +++ b/src/components/primitives/LabeledIcon.tsx @@ -44,7 +44,7 @@ export default function LabeledIcon({ {children && type !== 'icon-only' && {children} } diff --git a/src/image-response/components/ImagePhotoGrid.tsx b/src/image-response/components/ImagePhotoGrid.tsx index cdec9da2..57c570bd 100644 --- a/src/image-response/components/ImagePhotoGrid.tsx +++ b/src/image-response/components/ImagePhotoGrid.tsx @@ -80,7 +80,7 @@ export default function ImagePhotoGrid({ objectFit: 'cover', }, }} /> - + , )} ); diff --git a/src/photo/PhotoGridPage.tsx b/src/photo/PhotoGridPage.tsx index 79447493..22640fcc 100644 --- a/src/photo/PhotoGridPage.tsx +++ b/src/photo/PhotoGridPage.tsx @@ -27,7 +27,7 @@ export default function PhotoGridPage({ useEffect( () => () => setSelectedPhotoIds?.(undefined), - [setSelectedPhotoIds] + [setSelectedPhotoIds], ); return ( diff --git a/src/photo/actions.ts b/src/photo/actions.ts index c3c7862c..9a0176b5 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -176,7 +176,7 @@ export const addAllUploadsAction = async ({ })(); if (shouldRevalidateAllKeysAndPaths) { - after(revalidateAllKeysAndPaths) + after(revalidateAllKeysAndPaths); } return stream.value; @@ -355,7 +355,7 @@ export const syncPhotoAction = async (photoId: string) => semanticDescription: aiSemanticDescription, } = await generateAiImageQueries( imageResizedBase64, - AI_TEXT_AUTO_GENERATED_FIELDS + AI_TEXT_AUTO_GENERATED_FIELDS, ); const photoFormDbInsert = convertFormDataToPhotoDbInsert({ diff --git a/src/photo/cache.ts b/src/photo/cache.ts index 714ccb51..6452d8f6 100644 --- a/src/photo/cache.ts +++ b/src/photo/cache.ts @@ -81,7 +81,7 @@ const getPhotosCacheKeys = (options: GetPhotosOptions = {}) => { Object.keys(options).forEach(key => { const tag = getPhotosCacheKeyForOption( options, - key as keyof GetPhotosOptions + key as keyof GetPhotosOptions, ); if (tag) { tags.push(tag); } }); @@ -181,7 +181,7 @@ export const getPhotosMostRecentUpdateCached = export const getPhotoCached = (...args: Parameters) => unstable_cache( getPhoto, - [KEY_PHOTOS, KEY_PHOTO] + [KEY_PHOTOS, KEY_PHOTO], )(...args).then(photo => photo ? parseCachedPhotoDates(photo) : undefined); export const getUniqueTagsCached = @@ -193,19 +193,19 @@ export const getUniqueTagsCached = export const getUniqueTagsHiddenCached = unstable_cache( getUniqueTagsHidden, - [KEY_PHOTOS, KEY_TAGS, KEY_HIDDEN] + [KEY_PHOTOS, KEY_TAGS, KEY_HIDDEN], ); export const getUniqueCamerasCached = unstable_cache( getUniqueCameras, - [KEY_PHOTOS, KEY_CAMERAS] + [KEY_PHOTOS, KEY_CAMERAS], ); export const getUniqueLensesCached = unstable_cache( getUniqueLenses, - [KEY_PHOTOS, KEY_LENSES] + [KEY_PHOTOS, KEY_LENSES], ); export const getUniqueFilmSimulationsCached = diff --git a/src/photo/db/index.ts b/src/photo/db/index.ts index cf2b1ec7..63613d8a 100644 --- a/src/photo/db/index.ts +++ b/src/photo/db/index.ts @@ -21,7 +21,7 @@ export const areOptionsSensitive = (options: GetPhotosOptions) => export const getWheresFromOptions = ( options: GetPhotosOptions, - initialValuesIndex = 1 + initialValuesIndex = 1, ) => { const { hidden = 'exclude', diff --git a/src/photo/db/query.ts b/src/photo/db/query.ts index e97e633b..82059919 100644 --- a/src/photo/db/query.ts +++ b/src/photo/db/query.ts @@ -80,7 +80,7 @@ const runMigration02 = () => // Wrapper for most queries for JIT table creation/migration running const safelyQueryPhotos = async ( callback: () => Promise, - debugMessage: string + debugMessage: string, ): Promise => { let result: T; @@ -369,8 +369,6 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => wheresValues, lastValuesIndex, } = getWheresFromOptions(options); - - let valuesIndex = lastValuesIndex; if (wheres) { sql.push(wheres); @@ -382,7 +380,7 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => const { limitAndOffset, limitAndOffsetValues, - } = getLimitAndOffsetFromOptions(options, valuesIndex); + } = getLimitAndOffsetFromOptions(options, lastValuesIndex); // LIMIT + OFFSET sql.push(limitAndOffset); @@ -421,7 +419,7 @@ export const getPhotosNearId = async ( WHERE twi.row_number >= current.row_number - 1 LIMIT $${valuesIndex++} `, - [...wheresValues, photoId, limit] + [...wheresValues, photoId, limit], ) .then(({ rows }) => { const photo = rows.find(({ id }) => id === photoId); diff --git a/src/photo/form/index.ts b/src/photo/form/index.ts index f6e1ddfd..d65bbd24 100644 --- a/src/photo/form/index.ts +++ b/src/photo/form/index.ts @@ -133,7 +133,7 @@ export const convertFormKeysToLabels = (keys: (keyof PhotoFormData)[]) => keys.map(key => FORM_METADATA()[key].label.toUpperCase()); export const getFormErrors = ( - formData: Partial + formData: Partial, ): Partial> => Object.keys(formData).reduce((acc, key) => ({ ...acc, @@ -147,7 +147,7 @@ export const isFormValid = (formData: Partial) => (!required || Boolean(formData[key])) && (!validate?.(formData[key])) && // eslint-disable-next-line max-len - (!validateStringMaxLength || (formData[key]?.length ?? 0) <= validateStringMaxLength) + (!validateStringMaxLength || (formData[key]?.length ?? 0) <= validateStringMaxLength), ); export const formHasTextContent = ({ @@ -302,12 +302,12 @@ export const getChangedFormFields = ( .keys(current) .filter(key => (original[key as keyof PhotoFormData] ?? '') !== - (current[key as keyof PhotoFormData] ?? '') + (current[key as keyof PhotoFormData] ?? ''), ) as (keyof PhotoFormData)[]; }; export const generateTakenAtFields = ( - form?: Partial + form?: Partial, ): { takenAt: string, takenAtNaive: string } => ({ takenAt: form?.takenAt || generateLocalPostgresString(), takenAtNaive: form?.takenAtNaive || generateLocalNaivePostgresString(), diff --git a/src/photo/index.ts b/src/photo/index.ts index 17f8c381..b96abc11 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -112,7 +112,7 @@ export interface PhotoSetAttributes { export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => { const photoDb = camelcaseKeys( - photoDbRaw as unknown as Record + photoDbRaw as unknown as Record, ) as unknown as PhotoDb; return { ...photoDb, @@ -193,7 +193,7 @@ export const generateOgImageMetaForPhotos = (photos: Photo[]): Metadata => { }; const PHOTO_ID_FORWARDING_TABLE: Record = JSON.parse( - process.env.PHOTO_ID_FORWARDING_TABLE || '{}' + process.env.PHOTO_ID_FORWARDING_TABLE || '{}', ); export const translatePhotoId = (id: string) => @@ -251,7 +251,7 @@ export const descriptionForPhotoSet = ( const sortPhotosByDate = ( photos: Photo[], - order: 'ASC' | 'DESC' = 'DESC' + order: 'ASC' | 'DESC' = 'DESC', ) => [...photos].sort((a, b) => order === 'DESC' ? b.takenAt.getTime() - a.takenAt.getTime() diff --git a/src/photo/server.ts b/src/photo/server.ts index 5078b13c..d5b9232e 100644 --- a/src/photo/server.ts +++ b/src/photo/server.ts @@ -117,14 +117,14 @@ const generateBase64 = async ( const resizeImage = async (image: ArrayBuffer) => generateBase64(image, sharp => sharp - .resize(IMAGE_WIDTH_RESIZE) + .resize(IMAGE_WIDTH_RESIZE), ); const blurImage = async (image: ArrayBuffer) => generateBase64(image, sharp => sharp .resize(IMAGE_WIDTH_BLUR) .modulate({ saturation: 1.15 }) - .blur(4) + .blur(4), ); export const resizeImageFromUrl = async (url: string) => diff --git a/src/photo/storage.ts b/src/photo/storage.ts index 1999f6f1..a28e4428 100644 --- a/src/photo/storage.ts +++ b/src/photo/storage.ts @@ -25,7 +25,7 @@ export const convertUploadToPhoto = async ({ if (shouldStripGpsData) { const fileWithoutGps = await removeGpsData( fileBytes ?? await fetch(urlOrigin, { cache: 'no-store' }) - .then(res => res.arrayBuffer()) + .then(res => res.arrayBuffer()), ); return putFile(fileWithoutGps, photoPath).then(async url => { if (url && shouldDeleteOrigin) { await deleteFile(urlOrigin); } diff --git a/src/site/IconSearch.tsx b/src/site/IconSearch.tsx index 102f672f..b310d916 100644 --- a/src/site/IconSearch.tsx +++ b/src/site/IconSearch.tsx @@ -1,5 +1,3 @@ -/* eslint-disable max-len */ - const INTRINSIC_WIDTH = 28; const INTRINSIC_HEIGHT = 24; diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx index 8824bb82..7cadb46a 100644 --- a/src/site/SiteChecklistClient.tsx +++ b/src/site/SiteChecklistClient.tsx @@ -262,7 +262,7 @@ export default function SiteChecklistClient({ )} {' '} and connect to project - + , )} {hasCloudflareR2Storage ? renderSubStatus('checked', 'Cloudflare R2: connected') diff --git a/src/site/font.ts b/src/site/font.ts index a122ffdc..17b02ad8 100644 --- a/src/site/font.ts +++ b/src/site/font.ts @@ -14,7 +14,7 @@ const getFontData = async () => { } else { data = await fetch(new URL( '/public/fonts/IBMPlexMono-Medium.ttf', - import.meta.url + import.meta.url, )).then(res => res.arrayBuffer()); } return data; diff --git a/src/utility/exif.ts b/src/utility/exif.ts index adeb3d65..e33bde1c 100644 --- a/src/utility/exif.ts +++ b/src/utility/exif.ts @@ -7,7 +7,7 @@ export const getOffsetFromExif = (data: ExifData) => Object.values(data.tags as any) .find((value: any) => typeof value === 'string' && - OFFSET_REGEX.test(value) + OFFSET_REGEX.test(value), ) as string | undefined; export const getAspectRatioFromExif = (data: ExifData): number => { @@ -32,7 +32,7 @@ export const getAspectRatioFromExif = (data: ExifData): number => { }; export const convertApertureValueToFNumber = ( - apertureValue?: string + apertureValue?: string, ): string | undefined => { if (apertureValue) { const aperture = parseInt(apertureValue); diff --git a/src/utility/useOnVisible.ts b/src/utility/useOnVisible.ts index b3948420..017a17bc 100644 --- a/src/utility/useOnVisible.ts +++ b/src/utility/useOnVisible.ts @@ -2,7 +2,7 @@ import { useEffect } from 'react'; export default function useOnVisible( ref: React.RefObject, - onVisible?: () => void + onVisible?: () => void, ) { useEffect(() => { if (onVisible && ref.current) { diff --git a/src/utility/usePrefersReducedMotion.ts b/src/utility/usePrefersReducedMotion.ts index 65a7b30f..6443c2e8 100644 --- a/src/utility/usePrefersReducedMotion.ts +++ b/src/utility/usePrefersReducedMotion.ts @@ -9,7 +9,7 @@ const safelyGetMediaQuery = () => typeof window !== 'undefined' const usePrefersReducedMotion = () => { const [prefersReducedMotion, setPrefersReducedMotion] = useState( - safelyGetMediaQuery()?.matches ?? false + safelyGetMediaQuery()?.matches ?? false, ); useEffect(() => { diff --git a/src/vendors/fujifilm/index.ts b/src/vendors/fujifilm/index.ts index 2089753a..6411765c 100644 --- a/src/vendors/fujifilm/index.ts +++ b/src/vendors/fujifilm/index.ts @@ -233,7 +233,7 @@ export const labelForFilmSimulation = (simulation: FujifilmSimulation) => const parseFujifilmMakerNote = ( bytes: Buffer, - valueForTagUInt: (tagId: number, value: number) => void + valueForTagUInt: (tagId: number, value: number) => void, ) => { const tagCount = bytes.readUint16LE(BYTE_INDEX_TAG_COUNT); for (let i = 0; i < tagCount; i++) {