diff --git a/apps/dev/package.json b/apps/dev/package.json index d633065..0abd720 100644 --- a/apps/dev/package.json +++ b/apps/dev/package.json @@ -16,9 +16,11 @@ "start": "cross-env NODE_OPTIONS=--no-deprecation next start" }, "dependencies": { + "@eslint/eslintrc": "^3.3.5", "@focus-reactive/payload-plugin-ab": "workspace:*", - "@focus-reactive/payload-plugin-presets": "workspace:*", "@focus-reactive/payload-plugin-comments": "workspace:*", + "@focus-reactive/payload-plugin-content-releases": "workspace:*", + "@focus-reactive/payload-plugin-presets": "workspace:*", "@focus-reactive/payload-plugin-scheduling": "workspace:*", "@payloadcms/db-sqlite": "3.79.0", "@payloadcms/live-preview-react": "3.79.0", @@ -28,6 +30,7 @@ "cross-env": "7.0.3", "dotenv": "16.4.7", "graphql": "16.8.1", + "libsql": "^0.5.29", "next": "15.4.11", "payload": "3.79.0", "react": "19.2.1", diff --git a/apps/dev/src/app/(payload)/admin/importMap.js b/apps/dev/src/app/(payload)/admin/importMap.js index 9794fc8..e4c4d5e 100644 --- a/apps/dev/src/app/(payload)/admin/importMap.js +++ b/apps/dev/src/app/(payload)/admin/importMap.js @@ -2,8 +2,14 @@ import { FieldCommentLabel as FieldCommentLabel_d1361f235be9df6e08bfd730fe68e7c8 import { BlockLabelWithPresets as BlockLabelWithPresets_f0a4a6f21f15d606fa328a5e35f17d11 } from '@focus-reactive/payload-plugin-presets/client' import { BlocksFieldWithPresets as BlocksFieldWithPresets_f0a4a6f21f15d606fa328a5e35f17d11 } from '@focus-reactive/payload-plugin-presets/client' import { VariantsField as VariantsField_1e5bf2338fb8c7d4f6284f3e67c93951 } from '@focus-reactive/payload-plugin-ab/admin/VariantsField' +import { ReleaseSidebarField as ReleaseSidebarField_01ef71babab0d155f9b7e71fc3fd8245 } from '@focus-reactive/payload-plugin-content-releases/client' import { PresetAdminComponentPreview as PresetAdminComponentPreview_f0a4a6f21f15d606fa328a5e35f17d11 } from '@focus-reactive/payload-plugin-presets/client' import { PresetAdminComponentCell as PresetAdminComponentCell_f0a4a6f21f15d606fa328a5e35f17d11 } from '@focus-reactive/payload-plugin-presets/client' +import { ReleaseStatusField as ReleaseStatusField_01ef71babab0d155f9b7e71fc3fd8245 } from '@focus-reactive/payload-plugin-content-releases/client' +import { ReleaseActionsField as ReleaseActionsField_01ef71babab0d155f9b7e71fc3fd8245 } from '@focus-reactive/payload-plugin-content-releases/client' +import { TargetDocCell as TargetDocCell_01ef71babab0d155f9b7e71fc3fd8245 } from '@focus-reactive/payload-plugin-content-releases/client' +import { ReleaseActionCell as ReleaseActionCell_01ef71babab0d155f9b7e71fc3fd8245 } from '@focus-reactive/payload-plugin-content-releases/client' +import { ReleaseItemStatusField as ReleaseItemStatusField_01ef71babab0d155f9b7e71fc3fd8245 } from '@focus-reactive/payload-plugin-content-releases/client' import { CommentsHeaderButton as CommentsHeaderButton_30d38dd40c31eff500900a16a2792204 } from '@focus-reactive/payload-plugin-comments/components/CommentsHeaderButton' import { CommentsProviderWrapper as CommentsProviderWrapper_bc62ec20ac2037360812e296d7662f4a } from '@focus-reactive/payload-plugin-comments/providers/CommentsProviderWrapper' import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc' @@ -13,8 +19,14 @@ export const importMap = { "@focus-reactive/payload-plugin-presets/client#BlockLabelWithPresets": BlockLabelWithPresets_f0a4a6f21f15d606fa328a5e35f17d11, "@focus-reactive/payload-plugin-presets/client#BlocksFieldWithPresets": BlocksFieldWithPresets_f0a4a6f21f15d606fa328a5e35f17d11, "@focus-reactive/payload-plugin-ab/admin/VariantsField#VariantsField": VariantsField_1e5bf2338fb8c7d4f6284f3e67c93951, + "@focus-reactive/payload-plugin-content-releases/client#ReleaseSidebarField": ReleaseSidebarField_01ef71babab0d155f9b7e71fc3fd8245, "@focus-reactive/payload-plugin-presets/client#PresetAdminComponentPreview": PresetAdminComponentPreview_f0a4a6f21f15d606fa328a5e35f17d11, "@focus-reactive/payload-plugin-presets/client#PresetAdminComponentCell": PresetAdminComponentCell_f0a4a6f21f15d606fa328a5e35f17d11, + "@focus-reactive/payload-plugin-content-releases/client#ReleaseStatusField": ReleaseStatusField_01ef71babab0d155f9b7e71fc3fd8245, + "@focus-reactive/payload-plugin-content-releases/client#ReleaseActionsField": ReleaseActionsField_01ef71babab0d155f9b7e71fc3fd8245, + "@focus-reactive/payload-plugin-content-releases/client#TargetDocCell": TargetDocCell_01ef71babab0d155f9b7e71fc3fd8245, + "@focus-reactive/payload-plugin-content-releases/client#ReleaseActionCell": ReleaseActionCell_01ef71babab0d155f9b7e71fc3fd8245, + "@focus-reactive/payload-plugin-content-releases/client#ReleaseItemStatusField": ReleaseItemStatusField_01ef71babab0d155f9b7e71fc3fd8245, "@focus-reactive/payload-plugin-comments/components/CommentsHeaderButton#CommentsHeaderButton": CommentsHeaderButton_30d38dd40c31eff500900a16a2792204, "@focus-reactive/payload-plugin-comments/providers/CommentsProviderWrapper#CommentsProviderWrapper": CommentsProviderWrapper_bc62ec20ac2037360812e296d7662f4a, "@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 diff --git a/apps/dev/src/payload-types.ts b/apps/dev/src/payload-types.ts index d48d58c..77174bb 100644 --- a/apps/dev/src/payload-types.ts +++ b/apps/dev/src/payload-types.ts @@ -72,19 +72,27 @@ export interface Config { pages: Page; presets: Preset; comments: Comment; + releases: Release; + 'release-items': ReleaseItem; 'payload-kv': PayloadKv; 'payload-jobs': PayloadJob; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; }; - collectionsJoins: {}; + collectionsJoins: { + releases: { + items: 'release-items'; + }; + }; collectionsSelect: { users: UsersSelect | UsersSelect; media: MediaSelect | MediaSelect; pages: PagesSelect | PagesSelect; presets: PresetsSelect | PresetsSelect; comments: CommentsSelect | CommentsSelect; + releases: ReleasesSelect | ReleasesSelect; + 'release-items': ReleaseItemsSelect | ReleaseItemsSelect; 'payload-kv': PayloadKvSelect | PayloadKvSelect; 'payload-jobs': PayloadJobsSelect | PayloadJobsSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; @@ -292,6 +300,70 @@ export interface Comment { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "releases". + */ +export interface Release { + id: number; + name: string; + description?: string | null; + status: 'draft' | 'scheduled' | 'publishing' | 'published' | 'reverting' | 'reverted' | 'failed' | 'cancelled'; + scheduledAt?: string | null; + publishedAt?: string | null; + /** + * Resources are added from the sidebar of any document — open a page and use 'Add Current State to Release' or 'Add Version to Release'. + */ + items?: { + docs?: (number | ReleaseItem)[]; + hasNextPage?: boolean; + totalDocs?: number; + }; + rollbackSnapshot?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + rollbackSkipped?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "release-items". + */ +export interface ReleaseItem { + id: number; + release: number | Release; + targetCollection: 'pages'; + targetDoc: string; + action: 'publish' | 'unpublish'; + status: 'pending' | 'published' | 'failed' | 'skipped' | 'reverted'; + snapshot: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + baseVersion?: string | null; + updatedAt: string; + createdAt: string; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-kv". @@ -427,6 +499,14 @@ export interface PayloadLockedDocument { | ({ relationTo: 'comments'; value: number | Comment; + } | null) + | ({ + relationTo: 'releases'; + value: number | Release; + } | null) + | ({ + relationTo: 'release-items'; + value: number | ReleaseItem; } | null); globalSlug?: string | null; user: { @@ -599,6 +679,37 @@ export interface CommentsSelect { updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "releases_select". + */ +export interface ReleasesSelect { + name?: T; + description?: T; + status?: T; + scheduledAt?: T; + publishedAt?: T; + items?: T; + rollbackSnapshot?: T; + rollbackSkipped?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "release-items_select". + */ +export interface ReleaseItemsSelect { + release?: T; + targetCollection?: T; + targetDoc?: T; + action?: T; + status?: T; + snapshot?: T; + baseVersion?: T; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-kv_select". diff --git a/bun.lock b/bun.lock index b1a01e6..344f328 100644 --- a/bun.lock +++ b/bun.lock @@ -19,8 +19,10 @@ "name": "dev", "version": "1.0.0", "dependencies": { + "@eslint/eslintrc": "^3.3.5", "@focus-reactive/payload-plugin-ab": "workspace:*", "@focus-reactive/payload-plugin-comments": "workspace:*", + "@focus-reactive/payload-plugin-content-releases": "workspace:*", "@focus-reactive/payload-plugin-presets": "workspace:*", "@focus-reactive/payload-plugin-scheduling": "workspace:*", "@payloadcms/db-sqlite": "3.79.0", @@ -31,6 +33,7 @@ "cross-env": "7.0.3", "dotenv": "16.4.7", "graphql": "16.8.1", + "libsql": "^0.5.29", "next": "15.4.11", "payload": "3.79.0", "react": "19.2.1", @@ -139,6 +142,28 @@ "react-dom", ], }, + "packages/payload-plugin-content-releases": { + "name": "@focus-reactive/payload-plugin-content-releases", + "version": "0.0.0-development", + "devDependencies": { + "@payloadcms/ui": "3.79.0", + "@types/node": "^20.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "next": "15.4.10", + "payload": "3.79.0", + "react": "^19.0.0", + "tsup": "^8.0.0", + "typescript": "^5.0.0", + "vitest": "^3.0.0", + }, + "peerDependencies": { + "@payloadcms/ui": "^3.0.0", + "next": "^14.0.0 || ^15.0.0", + "payload": "^3.0.0", + "react": "^18.0.0 || ^19.0.0", + }, + }, "packages/payload-plugin-presets": { "name": "@focus-reactive/payload-plugin-presets", "version": "0.10.5", @@ -359,6 +384,8 @@ "@focus-reactive/payload-plugin-comments": ["@focus-reactive/payload-plugin-comments@workspace:packages/payload-plugin-comments"], + "@focus-reactive/payload-plugin-content-releases": ["@focus-reactive/payload-plugin-content-releases@workspace:packages/payload-plugin-content-releases"], + "@focus-reactive/payload-plugin-presets": ["@focus-reactive/payload-plugin-presets@workspace:packages/payload-plugin-presets"], "@focus-reactive/payload-plugin-scheduling": ["@focus-reactive/payload-plugin-scheduling@workspace:packages/payload-plugin-scheduling"], @@ -489,9 +516,9 @@ "@libsql/core": ["@libsql/core@0.14.0", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q=="], - "@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.4.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-yOL742IfWUlUevnI5PdnIT4fryY3LYTdLm56bnY0wXBw7dhFcnjuA7jrH3oSVz2mjZTHujxoITgAE7V6Z+eAbg=="], + "@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.5.29", "", { "os": "darwin", "cpu": "arm64" }, "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A=="], - "@libsql/darwin-x64": ["@libsql/darwin-x64@0.4.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-ezc7V75+eoyyH07BO9tIyJdqXXcRfZMbKcLCeF8+qWK5nP8wWuMcfOVywecsXGRbT99zc5eNra4NEx6z5PkSsA=="], + "@libsql/darwin-x64": ["@libsql/darwin-x64@0.5.29", "", { "os": "darwin", "cpu": "x64" }, "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ=="], "@libsql/hrana-client": ["@libsql/hrana-client@0.7.0", "", { "dependencies": { "@libsql/isomorphic-fetch": "^0.3.1", "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw=="], @@ -499,15 +526,19 @@ "@libsql/isomorphic-ws": ["@libsql/isomorphic-ws@0.1.5", "", { "dependencies": { "@types/ws": "^8.5.4", "ws": "^8.13.0" } }, "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg=="], - "@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.4.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-WlX2VYB5diM4kFfNaYcyhw5y+UJAI3xcMkEUJZPtRDEIu85SsSFrQ+gvoKfcVh76B//ztSeEX2wl9yrjF7BBCA=="], + "@libsql/linux-arm-gnueabihf": ["@libsql/linux-arm-gnueabihf@0.5.29", "", { "os": "linux", "cpu": "arm" }, "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ=="], + + "@libsql/linux-arm-musleabihf": ["@libsql/linux-arm-musleabihf@0.5.29", "", { "os": "linux", "cpu": "arm" }, "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg=="], + + "@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.5.29", "", { "os": "linux", "cpu": "arm64" }, "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w=="], - "@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.4.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-6kK9xAArVRlTCpWeqnNMCoXW1pe7WITI378n4NpvU5EJ0Ok3aNTIC2nRPRjhro90QcnmLL1jPcrVwO4WD1U0xw=="], + "@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.5.29", "", { "os": "linux", "cpu": "arm64" }, "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg=="], - "@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.4.7", "", { "os": "linux", "cpu": "x64" }, "sha512-CMnNRCmlWQqqzlTw6NeaZXzLWI8bydaXDke63JTUCvu8R+fj/ENsLrVBtPDlxQ0wGsYdXGlrUCH8Qi9gJep0yQ=="], + "@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.5.29", "", { "os": "linux", "cpu": "x64" }, "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg=="], - "@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.4.7", "", { "os": "linux", "cpu": "x64" }, "sha512-nI6tpS1t6WzGAt1Kx1n1HsvtBbZ+jHn0m7ogNNT6pQHZQj7AFFTIMeDQw/i/Nt5H38np1GVRNsFe99eSIMs9XA=="], + "@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.5.29", "", { "os": "linux", "cpu": "x64" }, "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w=="], - "@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.4.7", "", { "os": "win32", "cpu": "x64" }, "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw=="], + "@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.5.29", "", { "os": "win32", "cpu": "x64" }, "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg=="], "@manypkg/find-root": ["@manypkg/find-root@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@types/node": "^12.7.1", "find-up": "^4.1.0", "fs-extra": "^8.1.0" } }, "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA=="], @@ -887,8 +918,12 @@ "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -997,6 +1032,20 @@ "@vercel/edge-config-fs": ["@vercel/edge-config-fs@0.1.0", "", {}, "sha512-NRIBwfcS0bUoUbRWlNGetqjvLSwgYH/BqKqDN7vK1g32p7dN96k0712COgaz6VFizAm9b0g6IG6hR6+hc0KCPg=="], + "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="], + + "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="], + + "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + "abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], @@ -1053,6 +1102,8 @@ "arrify": ["arrify@1.0.1", "", {}, "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], @@ -1123,6 +1174,8 @@ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], @@ -1137,6 +1190,8 @@ "charenc": ["charenc@0.0.2", "", {}, "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="], + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], @@ -1173,8 +1228,12 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + "config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="], + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "console-table-printer": ["console-table-printer@2.12.1", "", { "dependencies": { "simple-wcswidth": "^1.0.1" } }, "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ=="], "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], @@ -1237,6 +1296,8 @@ "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], @@ -1253,7 +1314,7 @@ "detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="], - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], @@ -1321,6 +1382,8 @@ "es-iterator-helpers": ["es-iterator-helpers@1.3.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" } }, "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], @@ -1381,6 +1444,8 @@ "estree-util-visit": ["estree-util-visit@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" } }, "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], @@ -1391,6 +1456,8 @@ "executable": ["executable@4.1.1", "", { "dependencies": { "pify": "^2.2.0" } }, "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "ext-list": ["ext-list@2.2.2", "", { "dependencies": { "mime-db": "^1.28.0" } }, "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA=="], "ext-name": ["ext-name@5.0.0", "", { "dependencies": { "ext-list": "^2.0.0", "sort-keys-length": "^1.0.0" } }, "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ=="], @@ -1437,6 +1504,8 @@ "find-versions": ["find-versions@6.0.0", "", { "dependencies": { "semver-regex": "^4.0.5", "super-regex": "^1.0.0" } }, "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA=="], + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], "flatted": ["flatted@3.4.1", "", {}, "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ=="], @@ -1739,7 +1808,7 @@ "lib0": ["lib0@0.2.117", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0serve": "bin/0serve.js", "0gentesthtml": "bin/gentesthtml.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw=="], - "libsql": ["libsql@0.4.7", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.4.7", "@libsql/darwin-x64": "0.4.7", "@libsql/linux-arm64-gnu": "0.4.7", "@libsql/linux-arm64-musl": "0.4.7", "@libsql/linux-x64-gnu": "0.4.7", "@libsql/linux-x64-musl": "0.4.7", "@libsql/win32-x64-msvc": "0.4.7" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw=="], + "libsql": ["libsql@0.5.29", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.29", "@libsql/darwin-x64": "0.5.29", "@libsql/linux-arm-gnueabihf": "0.5.29", "@libsql/linux-arm-musleabihf": "0.5.29", "@libsql/linux-arm64-gnu": "0.5.29", "@libsql/linux-arm64-musl": "0.5.29", "@libsql/linux-x64-gnu": "0.5.29", "@libsql/linux-x64-musl": "0.5.29", "@libsql/win32-x64-msvc": "0.5.29" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg=="], "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], @@ -1797,6 +1866,8 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], @@ -1905,6 +1976,8 @@ "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "mlly": ["mlly@1.8.1", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ=="], + "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="], "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], @@ -2035,6 +2108,10 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + "payload": ["payload@3.79.0", "", { "dependencies": { "@next/env": "^15.1.5", "@payloadcms/translations": "3.79.0", "@types/busboy": "1.5.4", "ajv": "8.17.1", "bson-objectid": "2.0.4", "busboy": "^1.6.0", "ci-info": "^4.1.0", "console-table-printer": "2.12.1", "croner": "9.1.0", "dataloader": "2.2.3", "deepmerge": "4.3.1", "file-type": "19.3.0", "get-tsconfig": "4.8.1", "http-status": "2.1.0", "image-size": "2.0.2", "ipaddr.js": "2.2.0", "jose": "5.9.6", "json-schema-to-typescript": "15.0.3", "minimist": "1.2.8", "path-to-regexp": "6.3.0", "pino": "9.14.0", "pino-pretty": "13.1.2", "pluralize": "8.0.0", "qs-esm": "7.0.2", "range-parser": "1.2.1", "sanitize-filename": "1.6.3", "ts-essentials": "10.0.3", "tsx": "4.21.0", "undici": "7.18.2", "uuid": "10.0.0", "ws": "^8.16.0" }, "peerDependencies": { "graphql": "^16.8.1" }, "bin": { "payload": "bin.js" } }, "sha512-Pey2gBhFL5QkAmN2KMkzXdiBS4QOi5IiQF4Ji9hCNu7kaDcNYtgk75EeWRGP4hOcb22+dqYnw2TZm5Zs/0KBKw=="], "peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="], @@ -2043,7 +2120,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], @@ -2061,6 +2138,8 @@ "pkg-conf": ["pkg-conf@2.1.0", "", { "dependencies": { "find-up": "^2.0.0", "load-json-file": "^4.0.0" } }, "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g=="], + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], @@ -2111,7 +2190,7 @@ "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="], + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "react-datepicker": ["react-datepicker@7.6.0", "", { "dependencies": { "@floating-ui/react": "^0.27.0", "clsx": "^2.1.1", "date-fns": "^3.6.0" }, "peerDependencies": { "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-9cQH6Z/qa4LrGhzdc3XoHbhrxNcMi9MKjZmYgF/1MNNaJwvdSjv3Xd+jjvrEEbKEf71ZgCA3n7fQbdwd70qCRw=="], @@ -2231,6 +2310,8 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "signale": ["signale@1.4.0", "", { "dependencies": { "chalk": "^2.3.2", "figures": "^2.0.0", "pkg-conf": "^2.1.0" } }, "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w=="], @@ -2275,8 +2356,12 @@ "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + "state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="], + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "stream-buffers": ["stream-buffers@3.0.3", "", {}, "sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw=="], @@ -2317,7 +2402,9 @@ "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], - "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], "strip-outer": ["strip-outer@2.0.0", "", {}, "sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg=="], @@ -2363,8 +2450,18 @@ "time-span": ["time-span@5.1.0", "", { "dependencies": { "convert-hrtime": "^5.0.0" } }, "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], + + "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], + "to-no-case": ["to-no-case@1.0.2", "", {}, "sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -2433,6 +2530,8 @@ "typescript-eslint": ["typescript-eslint@8.0.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.0.0", "@typescript-eslint/parser": "8.0.0", "@typescript-eslint/utils": "8.0.0" } }, "sha512-yQWBJutWL1PmpmDddIOl9/Mi6vZjqNCjqSGBMQ4vsc2Aiodk0SnbQQWPXbSy0HNuKCuGkw1+u4aQ2mO40TdhDQ=="], + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], @@ -2491,6 +2590,12 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + + "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], + + "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], "web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="], @@ -2511,6 +2616,8 @@ "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], @@ -2557,8 +2664,6 @@ "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - "@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - "@focus-reactive/payload-plugin-ab/prettier": ["prettier@3.0.0", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g=="], "@focus-reactive/payload-plugin-ab/typescript": ["typescript@5.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw=="], @@ -2569,6 +2674,16 @@ "@focus-reactive/payload-plugin-comments/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], + "@focus-reactive/payload-plugin-content-releases/@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="], + + "@focus-reactive/payload-plugin-content-releases/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@focus-reactive/payload-plugin-content-releases/next": ["next@15.4.10", "", { "dependencies": { "@next/env": "15.4.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.8", "@next/swc-darwin-x64": "15.4.8", "@next/swc-linux-arm64-gnu": "15.4.8", "@next/swc-linux-arm64-musl": "15.4.8", "@next/swc-linux-x64-gnu": "15.4.8", "@next/swc-linux-x64-musl": "15.4.8", "@next/swc-win32-arm64-msvc": "15.4.8", "@next/swc-win32-x64-msvc": "15.4.8", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-itVlc79QjpKMFMRhP+kbGKaSG/gZM6RCvwhEbwmCNF06CdDiNaoHcbeg0PqkEa2GOcn8KJ0nnc7+yL7EjoYLHQ=="], + + "@focus-reactive/payload-plugin-content-releases/tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + + "@focus-reactive/payload-plugin-presets/react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="], + "@focus-reactive/payload-plugin-presets/typescript": ["typescript@5.5.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew=="], "@focus-reactive/payload-plugin-scheduling/typescript": ["typescript@5.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw=="], @@ -2583,6 +2698,8 @@ "@lexical/react/react-error-boundary": ["react-error-boundary@6.1.1", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w=="], + "@libsql/client/libsql": ["libsql@0.4.7", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.4.7", "@libsql/darwin-x64": "0.4.7", "@libsql/linux-arm64-gnu": "0.4.7", "@libsql/linux-arm64-musl": "0.4.7", "@libsql/linux-x64-gnu": "0.4.7", "@libsql/linux-x64-musl": "0.4.7", "@libsql/win32-x64-msvc": "0.4.7" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw=="], + "@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], "@manypkg/find-root/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -2595,30 +2712,22 @@ "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], - "@parcel/watcher/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "@parcel/watcher/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "@payloadcms/db-sqlite/uuid": ["uuid@9.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="], "@payloadcms/drizzle/uuid": ["uuid@9.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="], - "@payloadcms/live-preview-react/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "@payloadcms/live-preview-react/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "@payloadcms/richtext-lexical/csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "@payloadcms/richtext-lexical/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "@payloadcms/richtext-lexical/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], - "@payloadcms/ui/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "@payloadcms/ui/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="], - "@react-email/render/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "@react-email/render/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "@repo/eslint-config/eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], @@ -2657,8 +2766,6 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@tanstack/react-query/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.0.0", "", { "dependencies": { "@typescript-eslint/types": "8.0.0", "@typescript-eslint/visitor-keys": "8.0.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" } }, "sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg=="], "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.57.1", "", {}, "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ=="], @@ -2681,6 +2788,8 @@ "anymatch/normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "babel-plugin-macros/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], "bin-check/execa": ["execa@0.7.0", "", { "dependencies": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" } }, "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw=="], @@ -2757,8 +2866,6 @@ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "fdir/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "from2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], @@ -2775,7 +2882,7 @@ "json-schema-to-typescript/prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], - "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], + "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "load-json-file/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="], @@ -2791,6 +2898,8 @@ "meow/type-fest": ["type-fest@0.18.1", "", {}, "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw=="], + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "minimist-options/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="], "monaco-editor/marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], @@ -3115,6 +3224,8 @@ "postcss-load-config/yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "react-datepicker/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], "react-promise-suspense/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="], @@ -3129,6 +3240,8 @@ "readable-web-to-node-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], @@ -3145,6 +3258,8 @@ "semantic-release/yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], + "sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "signale/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], "signale/figures": ["figures@2.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA=="], @@ -3161,6 +3276,8 @@ "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], "tempy/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], @@ -3169,14 +3286,16 @@ "through2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "trim-repeated/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "tsx/esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], "tsx/get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], + "vite/esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], + + "vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], @@ -3229,10 +3348,38 @@ "@focus-reactive/payload-plugin-comments/next/sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + "@focus-reactive/payload-plugin-content-releases/next/@next/env": ["@next/env@15.4.10", "", {}, "sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "@libsql/client/libsql/@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.4.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-yOL742IfWUlUevnI5PdnIT4fryY3LYTdLm56bnY0wXBw7dhFcnjuA7jrH3oSVz2mjZTHujxoITgAE7V6Z+eAbg=="], + + "@libsql/client/libsql/@libsql/darwin-x64": ["@libsql/darwin-x64@0.4.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-ezc7V75+eoyyH07BO9tIyJdqXXcRfZMbKcLCeF8+qWK5nP8wWuMcfOVywecsXGRbT99zc5eNra4NEx6z5PkSsA=="], + + "@libsql/client/libsql/@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.4.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-WlX2VYB5diM4kFfNaYcyhw5y+UJAI3xcMkEUJZPtRDEIu85SsSFrQ+gvoKfcVh76B//ztSeEX2wl9yrjF7BBCA=="], + + "@libsql/client/libsql/@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.4.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-6kK9xAArVRlTCpWeqnNMCoXW1pe7WITI378n4NpvU5EJ0Ok3aNTIC2nRPRjhro90QcnmLL1jPcrVwO4WD1U0xw=="], + + "@libsql/client/libsql/@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.4.7", "", { "os": "linux", "cpu": "x64" }, "sha512-CMnNRCmlWQqqzlTw6NeaZXzLWI8bydaXDke63JTUCvu8R+fj/ENsLrVBtPDlxQ0wGsYdXGlrUCH8Qi9gJep0yQ=="], + + "@libsql/client/libsql/@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.4.7", "", { "os": "linux", "cpu": "x64" }, "sha512-nI6tpS1t6WzGAt1Kx1n1HsvtBbZ+jHn0m7ogNNT6pQHZQj7AFFTIMeDQw/i/Nt5H38np1GVRNsFe99eSIMs9XA=="], + + "@libsql/client/libsql/@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.4.7", "", { "os": "win32", "cpu": "x64" }, "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw=="], + "@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "@manypkg/find-root/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], @@ -3473,6 +3620,8 @@ "next/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + "next/sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "npm/minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "npm/minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -3585,6 +3734,52 @@ "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], + + "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], + + "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], + + "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], + + "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], + + "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], + + "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], + + "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], + + "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], + + "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], + + "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], + + "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], + + "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], + + "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], + + "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], + + "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], + + "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], + + "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], + + "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], + + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], + + "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], + + "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], + + "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], + "@focus-reactive/payload-plugin-comments/next/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], "@focus-reactive/payload-plugin-comments/next/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], @@ -3627,6 +3822,100 @@ "@focus-reactive/payload-plugin-comments/next/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + "@focus-reactive/payload-plugin-comments/next/sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + + "@focus-reactive/payload-plugin-content-releases/next/sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], + + "@focus-reactive/payload-plugin-content-releases/tsup/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], + "@manypkg/find-root/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "@repo/eslint-config/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.1", "", { "dependencies": { "@typescript-eslint/types": "8.57.1", "@typescript-eslint/visitor-keys": "8.57.1" } }, "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg=="], diff --git a/packages/payload-plugin-content-releases/.gitignore b/packages/payload-plugin-content-releases/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/packages/payload-plugin-content-releases/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/packages/payload-plugin-content-releases/README.md b/packages/payload-plugin-content-releases/README.md new file mode 100644 index 0000000..d4f3808 --- /dev/null +++ b/packages/payload-plugin-content-releases/README.md @@ -0,0 +1,229 @@ +# @focus-reactive/payload-plugin-content-releases + +Batch content publishing plugin for [Payload CMS](https://payloadcms.com/) v3. Group multiple document changes into a single release and publish them together -- manually or on a schedule. + +The plugin creates two collections (`releases` and `release-items`), exposes REST endpoints for publishing and conflict detection, and optionally supports scheduled releases via a cron-triggered endpoint. + +--- + +## Installation + +```bash +bun add @focus-reactive/payload-plugin-content-releases +# or +pnpm add @focus-reactive/payload-plugin-content-releases +# or +npm install @focus-reactive/payload-plugin-content-releases +``` + +**Peer dependencies:** `payload ^3.0.0` + +--- + +## Quick Start + +```ts +// payload.config.ts +import { buildConfig } from "payload"; +import { contentReleasesPlugin } from "@focus-reactive/payload-plugin-content-releases"; + +export default buildConfig({ + plugins: [ + contentReleasesPlugin({ + enabledCollections: ["pages", "posts"], + schedulerSecret: process.env.CRON_SECRET, + }), + ], +}); +``` + +--- + +## Configuration Reference + +| Option | Type | Required | Default | Description | +| -------------------- | -------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------ | +| `enabledCollections` | `string[]` | **Yes** | -- | Collection slugs that can be added as release items | +| `conflictStrategy` | `"fail" \| "force"` | No | `"fail"` | How to handle documents modified after they were staged (see [Conflict Strategies](#conflict-strategies)) | +| `publishBatchSize` | `number` | No | `20` | Number of items processed per batch during publish | +| `useTransactions` | `boolean` | No | `true` | Whether to use database transactions during publish | +| `schedulerSecret` | `string` | No | -- | Bearer token for the `run-scheduled` endpoint. Omit to disable scheduled releases | +| `access` | `{ releases?, releaseItems? }` | No | -- | Payload access control overrides for the generated collections (each accepts `create`, `read`, `update`, `delete`) | +| `hooks` | `{ afterPublish?, onPublishError? }` | No | -- | Lifecycle hooks called after a release is published or when errors occur | + +### Hooks + +```ts +hooks: { + afterPublish: async ({ releaseId, req }) => { + // Called after a successful publish + }, + onPublishError: async ({ releaseId, errors, req }) => { + // Called when one or more items fail to publish + // errors: Array<{ collection, docId, error }> + }, +} +``` + +--- + +## How It Works + +The plugin adds two collections to your Payload config: + +- **`releases`** -- groups of content changes with a name, description, status, and optional scheduled date. +- **`release-items`** -- individual document operations (publish or unpublish) linked to a release. Each item stores a snapshot of the document data and a `baseVersion` timestamp for conflict detection. + +When a release is published, the plugin iterates through its items in batches, applies each snapshot to the target document, and tracks results. A rollback snapshot of previous document states is stored on the release for reference. + +--- + +## Release Lifecycle + +``` +draft --> scheduled --> publishing --> published + \ \--> failed + \--> cancelled +``` + +| Status | Description | +| ------------ | ------------------------------------------------------------ | +| `draft` | Default state. Items can be added or removed. | +| `scheduled` | A `scheduledAt` date has been set. Awaiting cron trigger. | +| `publishing` | Publish is in progress. | +| `published` | All items were published successfully. | +| `failed` | One or more items failed. Check `errorLog` for details. | +| `cancelled` | Release was manually cancelled. | + +Only `draft` releases can be published via the publish endpoint. Scheduled releases transition automatically when the cron fires. + +--- + +## REST API Endpoints + +### Publish a release + +``` +POST /api/content-releases/:id/publish +``` + +Publishes all items in a `draft` release. Returns the result summary. + +**Success response:** + +```json +{ + "ok": true, + "status": "published", + "published": 5, + "failed": 0 +} +``` + +**Failure response (partial):** + +```json +{ + "ok": true, + "status": "failed", + "published": 3, + "failed": 2, + "errors": [ + { "itemId": "...", "collection": "pages", "docId": "...", "error": "Conflict: document modified since staging" } + ] +} +``` + +### Check for conflicts + +``` +GET /api/content-releases/:id/conflicts +``` + +Returns a list of items whose target documents have been modified since they were staged. + +```json +{ + "conflicts": [ + { + "itemId": "...", + "collection": "pages", + "docId": "...", + "reason": "Document was modified since staging. Expected version: ..., current: ..." + } + ], + "total": 5 +} +``` + +### Run scheduled releases + +``` +GET /api/content-releases/run-scheduled +``` + +Only registered when `schedulerSecret` is provided. Finds all releases with status `scheduled` and a `scheduledAt` date in the past, then publishes them. + +**Authentication:** `Authorization: Bearer ` + +**Response:** + +```json +{ + "ok": true, + "processed": 2, + "results": [ + { "releaseId": "...", "status": "published", "published": 3, "failed": 0 }, + { "releaseId": "...", "status": "failed", "published": 1, "failed": 1 } + ] +} +``` + +--- + +## Scheduled Publishing + +To enable scheduled releases, provide `schedulerSecret` in the plugin config and set up a cron to call the `run-scheduled` endpoint. + +### Vercel + +```json +{ + "crons": [ + { + "path": "/api/content-releases/run-scheduled", + "schedule": "* * * * *" + } + ] +} +``` + +Set `CRON_SECRET` in your Vercel project settings. Vercel automatically sends it as `Authorization: Bearer $CRON_SECRET`. + +### External cron / curl + +```bash +curl -X GET https://your-cms.com/api/content-releases/run-scheduled \ + -H "Authorization: Bearer YOUR_SECRET" +``` + +--- + +## Conflict Strategies + +When a release item is staged, the current `updatedAt` timestamp of the target document is stored as `baseVersion`. At publish time, the plugin compares this against the document's current `updatedAt`. + +| Strategy | Behaviour | +| -------- | ------------------------------------------------------------------------- | +| `fail` | If the document was modified since staging, the item is skipped and marked as failed. This is the default. | +| `force` | Conflicts are ignored. The staged snapshot overwrites the document regardless of intermediate changes. | + +Use the `/conflicts` endpoint to check for conflicts before publishing. + +--- + +## Exports Reference + +| Import path | Exports | +| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| `@focus-reactive/payload-plugin-content-releases` | `contentReleasesPlugin`, `ContentReleasesPluginConfig`, `ReleaseStatus`, `ReleaseItemAction`, `ReleaseItemStatus`, `ConflictStrategy` | diff --git a/packages/payload-plugin-content-releases/package.json b/packages/payload-plugin-content-releases/package.json new file mode 100644 index 0000000..9432c73 --- /dev/null +++ b/packages/payload-plugin-content-releases/package.json @@ -0,0 +1,57 @@ +{ + "name": "@focus-reactive/payload-plugin-content-releases", + "version": "0.0.0-development", + "description": "Payload CMS plugin for grouping and atomically publishing batches of content changes", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./client": { + "import": "./dist/client.js", + "types": "./dist/client.d.ts" + } + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "https://github.com/focusreactive/payload-plugins", + "directory": "packages/payload-plugin-content-releases" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "test": "vitest run", + "test:watch": "vitest", + "lint": "eslint src/", + "lint:fix": "eslint src/ --fix", + "format": "prettier --write src/" + }, + "peerDependencies": { + "payload": "^3.0.0", + "@payloadcms/ui": "^3.0.0", + "next": "^14.0.0 || ^15.0.0", + "react": "^18.0.0 || ^19.0.0" + }, + "devDependencies": { + "@payloadcms/ui": "3.79.0", + "@types/node": "^20.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "payload": "3.79.0", + "react": "^19.0.0", + "tsup": "^8.0.0", + "typescript": "^5.0.0", + "vitest": "^3.0.0", + "next": "15.4.10" + }, + "license": "MIT" +} diff --git a/packages/payload-plugin-content-releases/src/__tests__/admin/getButtonProps.test.ts b/packages/payload-plugin-content-releases/src/__tests__/admin/getButtonProps.test.ts new file mode 100644 index 0000000..d24df57 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/admin/getButtonProps.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from "vitest"; +import { getPublishButtonProps } from "../../admin/components/getPublishButtonProps"; +import type { ReleaseStatus } from "../../types"; + +describe("ReleaseActionsField — Publish Now button enablement", () => { + const enabledStates: ReleaseStatus[] = ["draft", "scheduled", "cancelled"]; + const disabledStates: ReleaseStatus[] = [ + "publishing", + "published", + "failed", + "reverting", + "reverted", + ]; + + it.each(enabledStates)("enables button for status '%s'", (status) => { + expect(getPublishButtonProps(status).disabled).toBe(false); + }); + + it.each(disabledStates)( + "disables button with a tooltip for status '%s'", + (status) => { + const props = getPublishButtonProps(status); + expect(props.disabled).toBe(true); + expect(props.tooltip).toBeTruthy(); + }, + ); + + it("enables button when status is undefined (initial render)", () => { + expect(getPublishButtonProps(undefined).disabled).toBe(false); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/collections/releaseItems.test.ts b/packages/payload-plugin-content-releases/src/__tests__/collections/releaseItems.test.ts new file mode 100644 index 0000000..dfa3f44 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/collections/releaseItems.test.ts @@ -0,0 +1,120 @@ +import { describe, it, expect } from "vitest"; +import { buildReleaseItemsCollection } from "../../collections/releaseItems"; +import { + RELEASE_ITEMS_SLUG, + RELEASES_SLUG, + RELEASE_ITEM_ACTIONS, + RELEASE_ITEM_STATUSES, +} from "../../constants"; + +describe("release-items collection", () => { + const enabledCollections = ["pages", "posts", "products"]; + const collection = buildReleaseItemsCollection(enabledCollections); + + it("should have the correct slug", () => { + expect(collection.slug).toBe(RELEASE_ITEMS_SLUG); + }); + + it("should have release relationship to releases collection", () => { + const field = collection.fields.find((f: any) => f.name === "release") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("relationship"); + expect(field.relationTo).toBe(RELEASES_SLUG); + expect(field.required).toBe(true); + }); + + it("should have targetCollection select with enabled collections", () => { + const field = collection.fields.find((f: any) => f.name === "targetCollection") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("select"); + const values = field.options.map((o: any) => o.value ?? o); + expect(values).toEqual(enabledCollections); + }); + + it("should have targetDoc text field", () => { + const field = collection.fields.find((f: any) => f.name === "targetDoc") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("text"); + expect(field.required).toBe(true); + expect(field.admin?.components?.Cell).toBe( + "@focus-reactive/payload-plugin-content-releases/client#TargetDocCell", + ); + }); + + it("should have action select field", () => { + const field = collection.fields.find((f: any) => f.name === "action") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("select"); + const values = field.options.map((o: any) => o.value ?? o); + for (const action of RELEASE_ITEM_ACTIONS) { + expect(values).toContain(action); + } + }); + + it("should have status select field", () => { + const field = collection.fields.find((f: any) => f.name === "status") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("select"); + const values = field.options.map((o: any) => o.value ?? o); + for (const status of RELEASE_ITEM_STATUSES) { + expect(values).toContain(status); + } + }); + + it("should have snapshot json field", () => { + const field = collection.fields.find((f: any) => f.name === "snapshot") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("json"); + }); + + it("should have baseVersion text field", () => { + const field = collection.fields.find((f: any) => f.name === "baseVersion") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("text"); + }); +}); + +describe("buildReleaseItemsCollection — admin.defaultColumns", () => { + it("does not include 'status' in defaultColumns", () => { + const config = buildReleaseItemsCollection(["pages"]); + expect(config.admin?.defaultColumns).not.toContain("status"); + }); +}); + +describe("buildReleaseItemsCollection — status field display", () => { + it("registers a custom Field component for status", () => { + const config = buildReleaseItemsCollection(["pages"]); + const statusField = config.fields.find( + (f) => "name" in f && f.name === "status", + ) as any; + expect(statusField.admin?.components?.Field).toContain("ReleaseItemStatusField"); + }); + + it("keeps status field type as select for validation", () => { + const config = buildReleaseItemsCollection(["pages"]); + const statusField = config.fields.find( + (f) => "name" in f && f.name === "status", + ) as any; + expect(statusField.type).toBe("select"); + }); +}); + +describe("buildReleaseItemsCollection — action field label", () => { + it("uses 'Release action' as the action field label", () => { + const config = buildReleaseItemsCollection(["pages"]); + const actionField = config.fields.find( + (f) => "name" in f && f.name === "action", + ) as any; + expect(actionField.label).toBe("Release action"); + }); +}); + +describe("buildReleaseItemsCollection — action field Cell", () => { + it("registers a custom Cell for the action field", () => { + const config = buildReleaseItemsCollection(["pages"]); + const actionField = config.fields.find( + (f) => "name" in f && f.name === "action", + ) as any; + expect(actionField.admin?.components?.Cell).toContain("ReleaseActionCell"); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/collections/releases.test.ts b/packages/payload-plugin-content-releases/src/__tests__/collections/releases.test.ts new file mode 100644 index 0000000..a127632 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/collections/releases.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect } from "vitest"; +import { buildReleasesCollection } from "../../collections/releases"; +import { RELEASES_SLUG, RELEASE_STATUSES } from "../../constants"; + +describe("releases collection", () => { + const collection = buildReleasesCollection(); + + it("should have the correct slug", () => { + expect(collection.slug).toBe(RELEASES_SLUG); + }); + + it("should have required name field", () => { + const nameField = collection.fields.find((f: any) => f.name === "name") as any; + expect(nameField).toBeDefined(); + expect(nameField.type).toBe("text"); + expect(nameField.required).toBe(true); + }); + + it("should have status field with all valid statuses", () => { + const statusField = collection.fields.find((f: any) => f.name === "status") as any; + expect(statusField).toBeDefined(); + expect(statusField.type).toBe("select"); + const optionValues = statusField.options.map((o: any) => o.value ?? o); + for (const status of RELEASE_STATUSES) { + expect(optionValues).toContain(status); + } + }); + + it("should have scheduledAt date field", () => { + const field = collection.fields.find((f: any) => f.name === "scheduledAt") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("date"); + }); + + it("should have publishedAt date field", () => { + const field = collection.fields.find((f: any) => f.name === "publishedAt") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("date"); + }); + + it("should have description textarea field", () => { + const field = collection.fields.find((f: any) => f.name === "description") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("textarea"); + }); + + it("should have rollbackSnapshot json field", () => { + const field = collection.fields.find((f: any) => f.name === "rollbackSnapshot") as any; + expect(field).toBeDefined(); + expect(field.type).toBe("json"); + }); + + it("should not have errorLog field (removed per single-release decision)", () => { + const field = collection.fields.find((f: any) => f.name === "errorLog"); + expect(field).toBeUndefined(); + }); + + it("should apply custom access if provided", () => { + const customAccess = { read: () => true }; + const col = buildReleasesCollection({ access: customAccess }); + expect(col.access?.read).toBe(customAccess.read); + }); +}); + +describe("buildReleasesCollection — items join defaultColumns", () => { + it("does not include 'status' in items join defaultColumns", () => { + const config = buildReleasesCollection(); + const itemsField = config.fields.find( + (f) => "name" in f && f.name === "items", + ) as any; + expect(itemsField.admin.defaultColumns).not.toContain("status"); + }); +}); + +describe("buildReleasesCollection — status field display", () => { + it("registers a custom Field component for status", () => { + const config = buildReleasesCollection(); + const statusField = config.fields.find( + (f) => "name" in f && f.name === "status", + ) as any; + expect(statusField.admin?.components?.Field).toContain("ReleaseStatusField"); + }); + + it("keeps status field type as select for validation", () => { + const config = buildReleasesCollection(); + const statusField = config.fields.find( + (f) => "name" in f && f.name === "status", + ) as any; + expect(statusField.type).toBe("select"); + expect(statusField.required).toBe(true); + }); +}); + +describe("buildReleasesCollection — items join field", () => { + it("uses 'Resources' as the items field label", () => { + const config = buildReleasesCollection(); + const itemsField = config.fields.find( + (f) => "name" in f && f.name === "items", + ) as any; + expect(itemsField.label).toBe("Resources"); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/endpoints/checkConflicts.test.ts b/packages/payload-plugin-content-releases/src/__tests__/endpoints/checkConflicts.test.ts new file mode 100644 index 0000000..fb4c63a --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/endpoints/checkConflicts.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect, vi } from "vitest"; +import { createCheckConflictsHandler } from "../../endpoints/checkConflicts"; + +describe("checkConflicts handler", () => { + const handler = createCheckConflictsHandler(); + + it("should reject unauthenticated requests", async () => { + const req = { + routeParams: { id: "rel-1" }, + payload: { + find: vi.fn().mockResolvedValue({ docs: [] }), + findByID: vi.fn(), + }, + }; + const response = await handler(req as any); + expect(response.status).toBe(401); + }); + + it("should return empty conflicts for items with matching versions", async () => { + const req = { + routeParams: { id: "rel-1" }, + user: { id: "user-1" }, + payload: { + find: vi.fn().mockResolvedValue({ + docs: [ + { id: "item-1", targetCollection: "pages", targetDoc: "doc-1", baseVersion: "2026-01-01T00:00:00Z", action: "publish" }, + ], + }), + findByID: vi.fn().mockResolvedValue({ id: "doc-1", updatedAt: "2026-01-01T00:00:00Z" }), + }, + }; + const response = await handler(req as any); + const body = await response.json(); + expect(body.conflicts).toHaveLength(0); + }); + + it("should return conflicts for modified documents", async () => { + const req = { + routeParams: { id: "rel-1" }, + user: { id: "user-1" }, + payload: { + find: vi.fn().mockResolvedValue({ + docs: [ + { id: "item-1", targetCollection: "pages", targetDoc: "doc-1", baseVersion: "2026-01-01T00:00:00Z", action: "publish" }, + ], + }), + findByID: vi.fn().mockResolvedValue({ id: "doc-1", updatedAt: "2026-01-02T00:00:00Z" }), + }, + }; + const response = await handler(req as any); + const body = await response.json(); + expect(body.conflicts).toHaveLength(1); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/endpoints/previewRollback.test.ts b/packages/payload-plugin-content-releases/src/__tests__/endpoints/previewRollback.test.ts new file mode 100644 index 0000000..56cbb18 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/endpoints/previewRollback.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect, vi } from "vitest"; +import { createPreviewRollbackHandler } from "../../endpoints/previewRollback"; + +function makeReq({ + releaseId = "rel-1", + releaseData = { status: "published" } as any, +} = {}) { + return { + routeParams: { id: releaseId }, + user: { id: "user-1" }, + payload: { + findByID: vi.fn().mockResolvedValue(releaseData), + findVersions: vi.fn().mockResolvedValue({ docs: [] }), + }, + }; +} + +describe("createPreviewRollbackHandler", () => { + it("unauthenticated request → 401", async () => { + const handler = createPreviewRollbackHandler(); + const req = makeReq(); + (req as any).user = undefined; + + const response = await handler(req as any); + + expect(response.status).toBe(401); + }); + + it("non-published release → 400", async () => { + const handler = createPreviewRollbackHandler(); + const req = makeReq({ releaseData: { status: "draft" } }); + + const response = await handler(req as any); + + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.error).toBeTruthy(); + }); + + it("valid published release → 200 with eligible and skipped", async () => { + const handler = createPreviewRollbackHandler(); + const req = makeReq({ + releaseData: { + status: "published", + rollbackSnapshot: [ + { + collection: "pages", + docId: "doc-1", + action: "publish", + previousState: { title: "Old" }, + }, + ], + publishedAt: "2026-01-10T00:00:00.000Z", + }, + }); + + const response = await handler(req as any); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty("eligible"); + expect(body).toHaveProperty("skipped"); + expect(Array.isArray(body.eligible)).toBe(true); + expect(Array.isArray(body.skipped)).toBe(true); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/endpoints/publishRelease.test.ts b/packages/payload-plugin-content-releases/src/__tests__/endpoints/publishRelease.test.ts new file mode 100644 index 0000000..dc24a54 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/endpoints/publishRelease.test.ts @@ -0,0 +1,80 @@ +import { describe, it, expect, vi } from "vitest"; +import { createPublishReleaseHandler } from "../../endpoints/publishRelease"; + +function makeReq({ + releaseId = "rel-1", + releaseData = { status: "draft", name: "Test" } as any, + releaseItems = [] as any[], + updateResult = {} as any, +} = {}) { + return { + routeParams: { id: releaseId }, + user: { id: "user-1" }, + payload: { + findByID: vi.fn().mockResolvedValue(releaseData), + find: vi.fn().mockResolvedValue({ docs: releaseItems }), + update: vi.fn().mockResolvedValue(updateResult), + }, + }; +} + +describe("publishRelease handler", () => { + const handler = createPublishReleaseHandler({ + conflictStrategy: "fail", + publishBatchSize: 20, + }); + + it("should reject unauthenticated requests", async () => { + const req = makeReq(); + (req as any).user = undefined; + const response = await handler(req as any); + expect(response.status).toBe(401); + }); + + it.each(["publishing", "published", "failed", "reverting", "reverted"])( + "should reject publishing a release with status %s", + async (status) => { + const req = makeReq({ releaseData: { status, name: "Test" } }); + const response = await handler(req as any); + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.error).toContain(status); + }, + ); + + it.each(["scheduled", "cancelled"])("should allow publishing a release with status %s", async (status) => { + const req = makeReq({ releaseData: { status, name: "Test" } }); + const response = await handler(req as any); + // 200 with failed status (empty items) is acceptable — the guard passed + expect(response.status).toBe(200); + }); + + it("should handle publishing an empty release as failed", async () => { + const req = makeReq({ releaseItems: [] }); + const response = await handler(req as any); + expect(response.status).toBe(200); + const body = await response.json(); + expect(body.status).toBe("failed"); + }); + + it("should return 200 on successful publish", async () => { + const items = [ + { + id: "item-1", + targetCollection: "pages", + targetDoc: "doc-1", + action: "publish", + status: "pending", + baseVersion: null, + snapshot: { title: "Hello", _status: "published" }, + }, + ]; + const req = makeReq({ releaseItems: items }); + req.payload.findByID + .mockResolvedValueOnce({ status: "draft", name: "Test" }) + .mockResolvedValue({ id: "doc-1", updatedAt: "2026-01-01" }); + + const response = await handler(req as any); + expect(response.status).toBe(200); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/endpoints/rollbackRelease.test.ts b/packages/payload-plugin-content-releases/src/__tests__/endpoints/rollbackRelease.test.ts new file mode 100644 index 0000000..c31ec23 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/endpoints/rollbackRelease.test.ts @@ -0,0 +1,127 @@ +import { describe, it, expect, vi } from "vitest"; +import { createRollbackReleaseHandler } from "../../endpoints/rollbackRelease"; + +const PUBLISHED_RELEASE_WITH_SNAPSHOT = { + status: "published", + rollbackSnapshot: [ + { + collection: "pages", + docId: "doc-1", + action: "publish", + previousState: { title: "Old", _status: "published" }, + }, + ], + publishedAt: "2026-01-01T00:00:00.000Z", +}; + +function makeReq({ + releaseId = "rel-1", + releaseData = PUBLISHED_RELEASE_WITH_SNAPSHOT as any, +} = {}) { + return { + routeParams: { id: releaseId }, + user: { id: "user-1" }, + payload: { + findByID: vi.fn().mockResolvedValue(releaseData), + findVersions: vi.fn().mockResolvedValue({ docs: [] }), + find: vi.fn().mockResolvedValue({ docs: [{ id: "item-1" }] }), + update: vi.fn().mockResolvedValue({}), + }, + }; +} + +describe("createRollbackReleaseHandler", () => { + it("unauthenticated request → 401", async () => { + const handler = createRollbackReleaseHandler({}); + const req = makeReq(); + (req as any).user = undefined; + + const response = await handler(req as any); + + expect(response.status).toBe(401); + }); + + it("non-published release → 400 with error mentioning the current status", async () => { + const handler = createRollbackReleaseHandler({}); + const req = makeReq({ releaseData: { status: "draft" } }); + + const response = await handler(req as any); + + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.error).toMatch(/draft/i); + }); + + it("all eligible, all succeed → 200, status reverted", async () => { + const handler = createRollbackReleaseHandler({}); + const req = makeReq(); + + const response = await handler(req as any); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body.status).toBe("reverted"); + }); + + it("all eligible, all fail → status failed", async () => { + const handler = createRollbackReleaseHandler({}); + const req = makeReq(); + + // orchestrateRollback calls: + // 1st findByID → release (in orchestrateRollback) + // 2nd findByID → release (in previewRollback) + // 1st update → set to "reverting" + // 2nd update → restore doc (this should fail) + // 3rd update → set final status + req.payload.update + .mockResolvedValueOnce({}) // set to "reverting" + .mockRejectedValueOnce(new Error("DB error")) // restore doc fails + .mockResolvedValue({}); // set final status + + const response = await handler(req as any); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body.status).toBe("failed"); + }); + + it("afterRollback hook fires on success", async () => { + const afterRollback = vi.fn().mockResolvedValue(undefined); + const handler = createRollbackReleaseHandler({ hooks: { afterRollback } }); + const req = makeReq(); + + const response = await handler(req as any); + const body = await response.json(); + + expect(body.status).toBe("reverted"); + expect(afterRollback).toHaveBeenCalledOnce(); + expect(afterRollback).toHaveBeenCalledWith( + expect.objectContaining({ releaseId: "rel-1" }), + ); + }); + + it("onRollbackError hook fires on failure", async () => { + const onRollbackError = vi.fn().mockResolvedValue(undefined); + const handler = createRollbackReleaseHandler({ hooks: { onRollbackError } }); + const req = makeReq(); + + req.payload.update + .mockResolvedValueOnce({}) // set to "reverting" + .mockRejectedValueOnce(new Error("DB error")) // restore doc fails + .mockResolvedValue({}); // set final status + + const response = await handler(req as any); + const body = await response.json(); + + expect(body.status).toBe("failed"); + expect(onRollbackError).toHaveBeenCalledOnce(); + expect(onRollbackError).toHaveBeenCalledWith( + expect.objectContaining({ + releaseId: "rel-1", + errors: expect.arrayContaining([ + expect.objectContaining({ docId: "doc-1", error: "DB error" }), + ]), + }), + ); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/endpoints/runScheduled.test.ts b/packages/payload-plugin-content-releases/src/__tests__/endpoints/runScheduled.test.ts new file mode 100644 index 0000000..3f17079 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/endpoints/runScheduled.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, vi } from "vitest"; +import { createRunScheduledHandler } from "../../endpoints/runScheduled"; + +describe("runScheduled handler", () => { + const handler = createRunScheduledHandler({ + secret: "test-secret", + conflictStrategy: "fail", + publishBatchSize: 20, + }); + + it("should reject unauthorized requests", async () => { + const req = { headers: { get: () => null }, payload: {} }; + const response = await handler(req as any); + expect(response.status).toBe(401); + }); + + it("should reject wrong bearer token", async () => { + const req = { headers: { get: () => "Bearer wrong-token" }, payload: {} }; + const response = await handler(req as any); + expect(response.status).toBe(401); + }); + + it("should process due scheduled releases", async () => { + const dueRelease = { + id: "rel-1", + name: "Test Release", + status: "scheduled", + scheduledAt: new Date(Date.now() - 60000).toISOString(), + }; + const req = { + headers: { get: () => "Bearer test-secret" }, + payload: { + find: vi.fn() + .mockResolvedValueOnce({ docs: [dueRelease] }) // find due releases + .mockResolvedValueOnce({ docs: [] }), // find items (empty -> failed) + findByID: vi.fn().mockResolvedValue(dueRelease), + update: vi.fn().mockResolvedValue({}), + }, + }; + + const response = await handler(req as any); + expect(response.status).toBe(200); + const body = await response.json(); + expect(body.ok).toBe(true); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/hooks/releaseItemsBeforeChange.test.ts b/packages/payload-plugin-content-releases/src/__tests__/hooks/releaseItemsBeforeChange.test.ts new file mode 100644 index 0000000..6d9925e --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/hooks/releaseItemsBeforeChange.test.ts @@ -0,0 +1,113 @@ +import { describe, it, expect, vi } from "vitest"; +import { buildReleaseItemsBeforeChange } from "../../hooks/releaseItemsBeforeChange"; + +function makePayload(releaseStatus: string, existingItems: any[] = []) { + return { + findByID: vi.fn().mockResolvedValue({ status: releaseStatus }), + find: vi.fn().mockResolvedValue({ docs: existingItems }), + }; +} + +function makeArgs( + data: Record, + payload: any, + operation: "create" | "update" = "create", + originalDoc?: Record, +) { + return { + data, + originalDoc, + operation, + req: { payload }, + collection: {} as any, + context: {} as any, + }; +} + +describe("releaseItemsBeforeChange", () => { + const hook = buildReleaseItemsBeforeChange(); + + it("should allow adding items to a draft release", async () => { + const payload = makePayload("draft"); + const data = { + release: "rel-1", + targetCollection: "pages", + targetDoc: "doc-1", + snapshot: { title: "Hello" }, + }; + const result = await hook(makeArgs(data, payload) as any); + expect(result).toEqual(data); + }); + + it("should allow adding items to a scheduled release", async () => { + const payload = makePayload("scheduled"); + const data = { + release: "rel-1", + targetCollection: "pages", + targetDoc: "doc-1", + snapshot: { title: "Hello" }, + }; + const result = await hook(makeArgs(data, payload) as any); + expect(result).toEqual(data); + }); + + it("should reject adding items to a published release", async () => { + const payload = makePayload("published"); + const data = { release: "rel-1", targetCollection: "pages", targetDoc: "doc-1" }; + await expect( + hook(makeArgs(data, payload) as any), + ).rejects.toThrow(/can only be modified/i); + }); + + it("should reject duplicate doc in same release on create", async () => { + const existing = [{ id: "item-99", targetCollection: "pages", targetDoc: "doc-1" }]; + const payload = makePayload("draft", existing); + const data = { + release: "rel-1", + targetCollection: "pages", + targetDoc: "doc-1", + }; + await expect( + hook(makeArgs(data, payload) as any), + ).rejects.toThrow(/already exists in this release/i); + }); + + it("should allow status-only updates when release is publishing", async () => { + const payload = makePayload("publishing", []); + const data = { + release: "rel-1", + status: "published", + }; + const result = await hook( + makeArgs(data, payload, "update", { id: "item-1", status: "pending" }) as any, + ); + expect(result.status).toBe("published"); + }); + + it("should reject content updates when release is publishing", async () => { + const payload = makePayload("publishing", []); + const data = { + release: "rel-1", + targetCollection: "pages", + targetDoc: "doc-1", + snapshot: { title: "Sneaky edit" }, + }; + await expect( + hook(makeArgs(data, payload, "update", { id: "item-1" }) as any), + ).rejects.toThrow(/can only be modified/i); + }); + + it("should allow updates to existing items in draft releases", async () => { + const payload = makePayload("draft", []); + const data = { + release: "rel-1", + targetCollection: "pages", + targetDoc: "doc-1", + snapshot: { title: "Updated" }, + }; + const result = await hook( + makeArgs(data, payload, "update", { id: "item-1" }) as any, + ); + expect(result.snapshot.title).toBe("Updated"); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/hooks/releasesBeforeChange.test.ts b/packages/payload-plugin-content-releases/src/__tests__/hooks/releasesBeforeChange.test.ts new file mode 100644 index 0000000..76c9bc5 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/hooks/releasesBeforeChange.test.ts @@ -0,0 +1,128 @@ +import { describe, it, expect } from "vitest"; +import { releasesBeforeChange } from "../../hooks/releasesBeforeChange"; + +function makeArgs(data: Record, originalDoc?: Record) { + return { + data, + originalDoc: originalDoc ?? {}, + req: {} as any, + operation: "update" as const, + collection: {} as any, + context: {} as any, + }; +} + +describe("releasesBeforeChange", () => { + it("should allow creating a release with draft status", () => { + const args = { + data: { name: "My Release", status: "draft" }, + req: {} as any, + operation: "create" as const, + collection: {} as any, + context: {} as any, + }; + const result = releasesBeforeChange(args as any); + expect(result.status).toBe("draft"); + }); + + it("should force draft status on create regardless of input", () => { + const args = { + data: { name: "My Release", status: "published" }, + req: {} as any, + operation: "create" as const, + collection: {} as any, + context: {} as any, + }; + const result = releasesBeforeChange(args as any); + expect(result.status).toBe("draft"); + }); + + it("should allow valid transition from draft to scheduled", () => { + const result = releasesBeforeChange( + makeArgs({ status: "scheduled" }, { status: "draft" }) as any, + ); + expect(result.status).toBe("scheduled"); + }); + + it("should throw on invalid transition from published to draft", () => { + expect(() => + releasesBeforeChange( + makeArgs({ status: "draft" }, { status: "published" }) as any, + ), + ).toThrow(/Invalid status transition/); + }); + + it("should throw on invalid transition from cancelled to draft", () => { + expect(() => + releasesBeforeChange( + makeArgs({ status: "draft" }, { status: "cancelled" }) as any, + ), + ).toThrow(/Invalid status transition/); + }); + + it("should pass through unchanged status", () => { + const result = releasesBeforeChange( + makeArgs({ status: "draft", name: "Updated" }, { status: "draft" }) as any, + ); + expect(result.name).toBe("Updated"); + }); + + it("should allow valid transition to published without setting publishedAt", () => { + const result = releasesBeforeChange( + makeArgs({ status: "published" }, { status: "publishing" }) as any, + ); + expect(result.publishedAt).toBeUndefined(); + }); + + it("create with scheduledAt becomes 'scheduled' instead of 'draft'", () => { + const args = { + data: { name: "My Release", scheduledAt: "2099-01-01T00:00:00Z" }, + req: {} as any, + operation: "create" as const, + collection: {} as any, + context: {} as any, + }; + const result = releasesBeforeChange(args as any); + expect(result.status).toBe("scheduled"); + }); + + it("update draft → adds scheduledAt → status auto-flips to 'scheduled'", () => { + const result = releasesBeforeChange( + makeArgs( + { scheduledAt: "2099-01-01T00:00:00Z" }, + { status: "draft", scheduledAt: null }, + ) as any, + ); + expect(result.status).toBe("scheduled"); + }); + + it("update scheduled → clears scheduledAt → status auto-flips to 'draft'", () => { + const result = releasesBeforeChange( + makeArgs( + { scheduledAt: null }, + { status: "scheduled", scheduledAt: "2099-01-01T00:00:00Z" }, + ) as any, + ); + expect(result.status).toBe("draft"); + }); + + it("update draft without touching scheduledAt keeps status as draft", () => { + const result = releasesBeforeChange( + makeArgs( + { description: "edit only" }, + { status: "draft", scheduledAt: null }, + ) as any, + ); + expect(result.status).toBeUndefined(); + }); + + it("update scheduled where scheduledAt remains keeps status as scheduled", () => { + const result = releasesBeforeChange( + makeArgs( + { description: "edit only" }, + { status: "scheduled", scheduledAt: "2099-01-01T00:00:00Z" }, + ) as any, + ); + expect(result.status).toBeUndefined(); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/plugin-endpoints.test.ts b/packages/payload-plugin-content-releases/src/__tests__/plugin-endpoints.test.ts new file mode 100644 index 0000000..14e3d5e --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/plugin-endpoints.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from "vitest"; +import { contentReleasesPlugin } from "../plugin"; +import type { Config } from "payload"; + +function makeBaseConfig(): Config { + return { + collections: [{ slug: "pages", fields: [] }], + globals: [], + } as unknown as Config; +} + +describe("plugin endpoints", () => { + it("should register publish endpoint", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + const config = plugin(makeBaseConfig()) as Config; + const endpoint = config.endpoints?.find((e: any) => e.path === "/content-releases/:id/publish"); + expect(endpoint).toBeDefined(); + expect(endpoint?.method).toBe("post"); + }); + + it("should register conflicts endpoint", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + const config = plugin(makeBaseConfig()) as Config; + const endpoint = config.endpoints?.find((e: any) => e.path === "/content-releases/:id/conflicts"); + expect(endpoint).toBeDefined(); + expect(endpoint?.method).toBe("get"); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/plugin-integration.test.ts b/packages/payload-plugin-content-releases/src/__tests__/plugin-integration.test.ts new file mode 100644 index 0000000..ca90c50 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/plugin-integration.test.ts @@ -0,0 +1,64 @@ +import { describe, it, expect, vi } from "vitest"; +import { contentReleasesPlugin } from "../plugin"; +import { RELEASES_SLUG, RELEASE_ITEMS_SLUG } from "../constants"; +import type { Config } from "payload"; + +function makeBaseConfig(overrides?: Partial): Config { + return { + collections: [ + { slug: "pages", fields: [] }, + { slug: "posts", fields: [] }, + ], + globals: [], + ...overrides, + } as unknown as Config; +} + +describe("contentReleasesPlugin", () => { + it("should return a function", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + expect(typeof plugin).toBe("function"); + }); + + it("should inject releases collection", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + const config = plugin(makeBaseConfig()) as Config; + const releases = config.collections?.find((c: any) => c.slug === RELEASES_SLUG); + expect(releases).toBeDefined(); + }); + + it("should inject release-items collection", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + const config = plugin(makeBaseConfig()) as Config; + const items = config.collections?.find((c: any) => c.slug === RELEASE_ITEMS_SLUG); + expect(items).toBeDefined(); + }); + + it("should preserve existing collections", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + const config = plugin(makeBaseConfig()) as Config; + const pages = config.collections?.find((c: any) => c.slug === "pages"); + const posts = config.collections?.find((c: any) => c.slug === "posts"); + expect(pages).toBeDefined(); + expect(posts).toBeDefined(); + }); + + it("should warn about unknown collection slugs", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const plugin = contentReleasesPlugin({ enabledCollections: ["nonexistent"] }); + plugin(makeBaseConfig()); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("nonexistent")); + warnSpy.mockRestore(); + }); + + it("should pass access config to releases collection", () => { + const readFn = () => true; + const plugin = contentReleasesPlugin({ + enabledCollections: ["pages"], + access: { releases: { read: readFn } }, + }); + const config = plugin(makeBaseConfig()) as Config; + const releases = config.collections?.find((c: any) => c.slug === RELEASES_SLUG); + expect((releases as any)?.access?.read).toBe(readFn); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/plugin-sidebar.test.ts b/packages/payload-plugin-content-releases/src/__tests__/plugin-sidebar.test.ts new file mode 100644 index 0000000..a04524a --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/plugin-sidebar.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from "vitest"; +import { contentReleasesPlugin } from "../plugin"; +import type { Config } from "payload"; + +function makeBaseConfig(): Config { + return { + collections: [ + { slug: "pages", fields: [{ name: "title", type: "text" }] }, + { slug: "posts", fields: [{ name: "title", type: "text" }] }, + ], + globals: [], + } as unknown as Config; +} + +describe("plugin sidebar injection", () => { + it("should inject _releases UI field into enabled collections", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + const config = plugin(makeBaseConfig()) as Config; + const pages = config.collections?.find((c: any) => c.slug === "pages"); + const releasesField = pages?.fields.find((f: any) => f.name === "_releases"); + expect(releasesField).toBeDefined(); + expect((releasesField as any).type).toBe("ui"); + expect((releasesField as any).admin?.position).toBe("sidebar"); + }); + + it("should NOT inject _releases field into non-enabled collections", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + const config = plugin(makeBaseConfig()) as Config; + const posts = config.collections?.find((c: any) => c.slug === "posts"); + const releasesField = posts?.fields.find((f: any) => f.name === "_releases"); + expect(releasesField).toBeUndefined(); + }); + + it("should pass enabledCollections via admin.custom", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + const config = plugin(makeBaseConfig()) as Config; + expect((config.admin as any)?.custom?.contentReleases?.enabledCollections).toEqual(["pages"]); + }); + + it("should preserve existing fields on enabled collections", () => { + const plugin = contentReleasesPlugin({ enabledCollections: ["pages"] }); + const config = plugin(makeBaseConfig()) as Config; + const pages = config.collections?.find((c: any) => c.slug === "pages"); + const titleField = pages?.fields.find((f: any) => f.name === "title"); + expect(titleField).toBeDefined(); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/plugin.test.ts b/packages/payload-plugin-content-releases/src/__tests__/plugin.test.ts new file mode 100644 index 0000000..630019d --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/plugin.test.ts @@ -0,0 +1,8 @@ +import { describe, it, expect } from "vitest"; + +describe("payload-plugin-content-releases", () => { + it("should be importable", async () => { + const mod = await import("../index"); + expect(mod).toBeDefined(); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/publish/detectConflicts.test.ts b/packages/payload-plugin-content-releases/src/__tests__/publish/detectConflicts.test.ts new file mode 100644 index 0000000..ee3556f --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/publish/detectConflicts.test.ts @@ -0,0 +1,58 @@ +import { describe, it, expect, vi } from "vitest"; +import { detectConflicts } from "../../publish/detectConflicts"; + +function makePayload(docs: Record) { + return { + findByID: vi.fn().mockImplementation(({ collection, id }) => { + const key = `${collection}:${id}`; + if (docs[key]) return Promise.resolve(docs[key]); + return Promise.reject(new Error("Not found")); + }), + }; +} + +describe("detectConflicts", () => { + it("should return no conflicts when baseVersion matches", async () => { + const payload = makePayload({ + "pages:doc-1": { id: "doc-1", updatedAt: "2026-01-01T00:00:00Z" }, + }); + const items = [ + { id: "item-1", targetCollection: "pages", targetDoc: "doc-1", baseVersion: "2026-01-01T00:00:00Z", action: "publish" }, + ]; + const result = await detectConflicts(items as any, payload as any); + expect(result).toHaveLength(0); + }); + + it("should detect conflict when baseVersion differs", async () => { + const payload = makePayload({ + "pages:doc-1": { id: "doc-1", updatedAt: "2026-01-02T00:00:00Z" }, + }); + const items = [ + { id: "item-1", targetCollection: "pages", targetDoc: "doc-1", baseVersion: "2026-01-01T00:00:00Z", action: "publish" }, + ]; + const result = await detectConflicts(items as any, payload as any); + expect(result).toHaveLength(1); + expect(result[0]!.itemId).toBe("item-1"); + }); + + it("should detect conflict when document is missing", async () => { + const payload = makePayload({}); + const items = [ + { id: "item-1", targetCollection: "pages", targetDoc: "doc-1", baseVersion: "2026-01-01T00:00:00Z", action: "publish" }, + ]; + const result = await detectConflicts(items as any, payload as any); + expect(result).toHaveLength(1); + expect(result[0]!.reason).toContain("not found"); + }); + + it("should skip conflict check for items without baseVersion", async () => { + const payload = makePayload({ + "pages:doc-1": { id: "doc-1", updatedAt: "2026-01-02T00:00:00Z" }, + }); + const items = [ + { id: "item-1", targetCollection: "pages", targetDoc: "doc-1", baseVersion: null, action: "publish" }, + ]; + const result = await detectConflicts(items as any, payload as any); + expect(result).toHaveLength(0); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/publish/executePublish.nested-snapshots.test.ts b/packages/payload-plugin-content-releases/src/__tests__/publish/executePublish.nested-snapshots.test.ts new file mode 100644 index 0000000..fffe8ed --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/publish/executePublish.nested-snapshots.test.ts @@ -0,0 +1,192 @@ +import { describe, it, expect, vi } from "vitest"; +import { executePublish } from "../../publish/executePublish"; + +function makePayload(findByIdResult: any = {}) { + return { + findByID: vi.fn().mockResolvedValue(findByIdResult), + update: vi.fn().mockResolvedValue({}), + }; +} + +describe("executePublish — nested snapshot depth", () => { + it("preserves deeply nested blocks array (form-builder-style)", async () => { + const payload = makePayload({ id: "doc-0", updatedAt: "2026-01-01" }); + + const deepSnapshot = { + title: "Form page", + sections: [ + { + blockType: "form", + fields: [ + { + blockType: "text", + name: "email", + required: true, + validation: { pattern: "^.+@.+$" }, + }, + { + blockType: "select", + name: "country", + options: [ + { label: "Germany", value: "de" }, + { label: "France", value: "fr" }, + ], + }, + ], + }, + ], + }; + + const items = [{ + id: "item-0", + targetCollection: "pages", + targetDoc: "doc-0", + action: "publish", + snapshot: deepSnapshot, + baseVersion: null, + }]; + + await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + expect(payload.update).toHaveBeenCalledTimes(1); + const updateArg = payload.update.mock.calls[0]![0]; + expect(updateArg.data).toEqual({ + ...deepSnapshot, + _status: "published", + }); + expect(updateArg.data.sections[0].fields[1].options[1].value).toBe("fr"); + }); + + it("preserves populated relationship objects in snapshot", async () => { + const payload = makePayload({ id: "doc-0", updatedAt: "2026-01-01" }); + + const snapshotWithRel = { + title: "Page with refs", + featuredMedia: { id: "media-1", filename: "hero.jpg", alt: "Hero" }, + relatedPosts: [ + { id: "post-1", title: "Post 1" }, + { id: "post-2", title: "Post 2" }, + ], + }; + + const items = [{ + id: "item-0", + targetCollection: "pages", + targetDoc: "doc-0", + action: "publish", + snapshot: snapshotWithRel, + baseVersion: null, + }]; + + await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + const updateArg = payload.update.mock.calls[0]![0]; + expect(updateArg.data.featuredMedia).toEqual({ + id: "media-1", + filename: "hero.jpg", + alt: "Hero", + }); + expect(updateArg.data.relatedPosts).toHaveLength(2); + expect(updateArg.data.relatedPosts[0].id).toBe("post-1"); + }); + + it("does not mutate the snapshot object passed in", async () => { + const payload = makePayload({ id: "doc-0", updatedAt: "2026-01-01" }); + + const original = { + title: "Immutable", + nested: { array: [{ leaf: "value" }] }, + }; + const snapshot = JSON.parse(JSON.stringify(original)); + + const items = [{ + id: "item-0", + targetCollection: "pages", + targetDoc: "doc-0", + action: "publish", + snapshot, + baseVersion: null, + }]; + + await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + expect(snapshot).toEqual(original); + }); + + it("captures previous state for deeply nested rollback snapshot", async () => { + const previousState = { + id: "doc-0", + updatedAt: "2026-01-01", + sections: [ + { blockType: "hero", title: "Old hero" }, + { blockType: "form", fields: [{ name: "old-field" }] }, + ], + }; + const payload = makePayload(previousState); + + const items = [{ + id: "item-0", + targetCollection: "pages", + targetDoc: "doc-0", + action: "publish", + snapshot: { sections: [{ blockType: "hero", title: "New hero" }] }, + baseVersion: null, + }]; + + const result = await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + expect(result.rollbackSnapshot).toHaveLength(1); + expect(result.rollbackSnapshot[0]!.previousState).toEqual(previousState); + const captured = result.rollbackSnapshot[0]!.previousState as any; + expect(captured.sections[1].fields[0].name).toBe("old-field"); + }); + + it("handles unpublish without leaking snapshot fields", async () => { + const payload = makePayload({ + id: "doc-0", + updatedAt: "2026-01-01", + _status: "published", + sections: [{ blockType: "form", fields: [{ name: "foo" }] }], + }); + + const items = [{ + id: "item-0", + targetCollection: "pages", + targetDoc: "doc-0", + action: "unpublish", + snapshot: { whatever: true }, + baseVersion: null, + }]; + + await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + const updateArg = payload.update.mock.calls[0]![0]; + expect(updateArg.data).toEqual({ _status: "draft" }); + expect(updateArg.data.whatever).toBeUndefined(); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/publish/executePublish.test.ts b/packages/payload-plugin-content-releases/src/__tests__/publish/executePublish.test.ts new file mode 100644 index 0000000..e808b34 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/publish/executePublish.test.ts @@ -0,0 +1,136 @@ +import { describe, it, expect, vi } from "vitest"; +import { executePublish } from "../../publish/executePublish"; + +function makePayload({ + findByIdResult = {} as any, + updateResult = {} as any, +} = {}) { + return { + findByID: vi.fn().mockResolvedValue(findByIdResult), + update: vi.fn().mockResolvedValue(updateResult), + }; +} + +function makeItems(overrides: any[] = []) { + return overrides.map((o, i) => ({ + id: `item-${i}`, + targetCollection: "pages", + targetDoc: `doc-${i}`, + action: "publish", + status: "pending", + baseVersion: null, + snapshot: { title: `Page ${i}`, _status: "published" }, + ...o, + })); +} + +describe("executePublish", () => { + it("should publish all items by calling payload.update", async () => { + const payload = makePayload({ findByIdResult: { id: "doc-0", updatedAt: "2026-01-01" } }); + const items = makeItems([{ targetDoc: "doc-0" }]); + + const result = await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + expect(payload.update).toHaveBeenCalledWith( + expect.objectContaining({ + collection: "pages", + id: "doc-0", + data: expect.objectContaining({ title: "Page 0" }), + }), + ); + expect(result.published).toHaveLength(1); + expect(result.failed).toHaveLength(0); + }); + + it("should capture rollback snapshot before publishing", async () => { + const originalDoc = { id: "doc-0", title: "Original", updatedAt: "2026-01-01" }; + const payload = makePayload({ findByIdResult: originalDoc }); + const items = makeItems([{ targetDoc: "doc-0" }]); + + const result = await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + expect(result.rollbackSnapshot).toHaveLength(1); + expect(result.rollbackSnapshot[0]!.previousState).toEqual(originalDoc); + }); + + it("should handle unpublish action by setting _status to draft", async () => { + const payload = makePayload({ + findByIdResult: { id: "doc-0", _status: "published", updatedAt: "2026-01-01" }, + }); + const items = makeItems([{ targetDoc: "doc-0", action: "unpublish" }]); + + await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + expect(payload.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ _status: "draft" }), + }), + ); + }); + + it("should skip items with conflicts when strategy is fail", async () => { + const payload = makePayload({ + findByIdResult: { id: "doc-0", updatedAt: "2026-01-02T00:00:00Z" }, + }); + const items = makeItems([{ targetDoc: "doc-0", baseVersion: "2026-01-01T00:00:00Z" }]); + + const result = await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + expect(result.failed).toHaveLength(1); + expect(result.published).toHaveLength(0); + }); + + it("should force-publish items with conflicts when strategy is force", async () => { + const payload = makePayload({ + findByIdResult: { id: "doc-0", updatedAt: "2026-01-02T00:00:00Z" }, + }); + const items = makeItems([{ targetDoc: "doc-0", baseVersion: "2026-01-01T00:00:00Z" }]); + + const result = await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "force", + batchSize: 20, + }); + + expect(result.published).toHaveLength(1); + }); + + it("should record errors for failed updates", async () => { + const payload = makePayload({ + findByIdResult: { id: "doc-0", updatedAt: "2026-01-01" }, + }); + payload.update.mockRejectedValue(new Error("DB write failed")); + const items = makeItems([{ targetDoc: "doc-0" }]); + + const result = await executePublish({ + items: items as any, + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + expect(result.failed).toHaveLength(1); + expect(result.failed[0]!.error).toContain("DB write failed"); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/rollback/executeRollback.test.ts b/packages/payload-plugin-content-releases/src/__tests__/rollback/executeRollback.test.ts new file mode 100644 index 0000000..b0c52f9 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/rollback/executeRollback.test.ts @@ -0,0 +1,113 @@ +import { describe, it, expect, vi } from "vitest"; +import { executeRollback } from "../../rollback/executeRollback"; +import type { RollbackEntry } from "../../rollback/previewRollback"; + +function makePayload({ updateResult = { updatedAt: "2026-02-01T00:00:00.000Z" } as any } = {}) { + return { update: vi.fn().mockResolvedValue(updateResult) }; +} + +function makeEntry(overrides: Partial = {}): RollbackEntry { + return { + collection: "pages", + docId: "doc-1", + action: "publish", + previousState: { + id: "doc-1", + title: "Old Title", + createdAt: "2026-01-01", + updatedAt: "2026-01-05", + _status: "published", + }, + ...overrides, + }; +} + +describe("executeRollback", () => { + it("all succeed → restored list populated, failed empty", async () => { + const payload = makePayload(); + const entry = makeEntry(); + + const result = await executeRollback({ eligible: [entry], payload: payload as any }); + + expect(result.restored).toHaveLength(1); + expect(result.restored[0]).toEqual({ + collection: "pages", + docId: "doc-1", + newUpdatedAt: "2026-02-01T00:00:00.000Z", + }); + expect(result.failed).toHaveLength(0); + }); + + it("previousState === null → entry in failed with correct error, loop continues for other entries", async () => { + const payload = makePayload(); + const nullEntry = makeEntry({ docId: "doc-null", previousState: null }); + const goodEntry = makeEntry({ docId: "doc-good" }); + + const result = await executeRollback({ + eligible: [nullEntry, goodEntry], + payload: payload as any, + }); + + expect(result.failed).toHaveLength(1); + expect(result.failed[0]!.docId).toBe("doc-null"); + expect(result.failed[0]!.error).toBe("No previous state to restore"); + + // Loop continued: good entry was still processed + expect(result.restored).toHaveLength(1); + expect(result.restored[0]!.docId).toBe("doc-good"); + }); + + it("payload.update throws → entry in failed, loop continues for other entries", async () => { + const payload = makePayload(); + payload.update + .mockRejectedValueOnce(new Error("DB error")) + .mockResolvedValue({}); + + const failEntry = makeEntry({ docId: "doc-fail" }); + const goodEntry = makeEntry({ docId: "doc-good" }); + + const result = await executeRollback({ + eligible: [failEntry, goodEntry], + payload: payload as any, + }); + + expect(result.failed).toHaveLength(1); + expect(result.failed[0]!.docId).toBe("doc-fail"); + expect(result.failed[0]!.error).toBe("DB error"); + + // Loop continued: second entry still processed + expect(result.restored).toHaveLength(1); + expect(result.restored[0]!.docId).toBe("doc-good"); + }); + + it("strips id, createdAt, updatedAt before calling payload.update", async () => { + const payload = makePayload(); + const entry = makeEntry(); + + await executeRollback({ eligible: [entry], payload: payload as any }); + + expect(payload.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.not.objectContaining({ id: expect.anything() }), + }), + ); + expect(payload.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.not.objectContaining({ createdAt: expect.anything() }), + }), + ); + expect(payload.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.not.objectContaining({ updatedAt: expect.anything() }), + }), + ); + // Verify remaining fields are still passed + expect(payload.update).toHaveBeenCalledWith( + expect.objectContaining({ + collection: "pages", + id: "doc-1", + data: expect.objectContaining({ title: "Old Title", _status: "published" }), + }), + ); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/rollback/previewRollback.test.ts b/packages/payload-plugin-content-releases/src/__tests__/rollback/previewRollback.test.ts new file mode 100644 index 0000000..65aa4b3 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/rollback/previewRollback.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect, vi } from "vitest"; +import { previewRollback } from "../../rollback/previewRollback"; + +function makePayload({ + findByIDResult = {} as any, + findVersionsResult = { docs: [] } as any, +} = {}) { + return { + findByID: vi.fn().mockResolvedValue(findByIDResult), + findVersions: vi.fn().mockResolvedValue(findVersionsResult), + }; +} + +const BASE_RELEASE = { + rollbackSnapshot: [ + { + collection: "pages", + docId: "doc-1", + action: "publish", + previousState: { title: "Old" }, + }, + ], + publishedAt: "2026-01-10T00:00:00.000Z", +}; + +describe("previewRollback", () => { + it("no newer published version → entry is eligible", async () => { + const payload = makePayload({ + findByIDResult: BASE_RELEASE, + findVersionsResult: { + docs: [{ updatedAt: "2026-01-05T00:00:00.000Z" }], + }, + }); + + const result = await previewRollback({ releaseId: "rel-1", payload: payload as any }); + + expect(result.eligible).toHaveLength(1); + expect(result.skipped).toHaveLength(0); + expect(result.eligible[0]!.docId).toBe("doc-1"); + }); + + it("published version with updatedAt after release.publishedAt → entry is skipped", async () => { + const payload = makePayload({ + findByIDResult: BASE_RELEASE, + findVersionsResult: { + docs: [{ updatedAt: "2026-01-15T00:00:00.000Z" }], + }, + }); + + const result = await previewRollback({ releaseId: "rel-1", payload: payload as any }); + + expect(result.eligible).toHaveLength(0); + expect(result.skipped).toHaveLength(1); + expect(result.skipped[0]!.docId).toBe("doc-1"); + }); + + it("no versions at all → entry is eligible", async () => { + const payload = makePayload({ + findByIDResult: BASE_RELEASE, + findVersionsResult: { docs: [] }, + }); + + const result = await previewRollback({ releaseId: "rel-1", payload: payload as any }); + + expect(result.eligible).toHaveLength(1); + expect(result.skipped).toHaveLength(0); + }); + + it("mixed entries → correct split between eligible and skipped", async () => { + const release = { + rollbackSnapshot: [ + { + collection: "pages", + docId: "doc-1", + action: "publish", + previousState: { title: "Old Page" }, + }, + { + collection: "pages", + docId: "doc-2", + action: "publish", + previousState: { title: "Old Page 2" }, + }, + ], + publishedAt: "2026-01-10T00:00:00.000Z", + }; + + const payload = makePayload({ findByIDResult: release }); + // First call: version before publishedAt → eligible + // Second call: version after publishedAt → skipped + payload.findVersions + .mockResolvedValueOnce({ docs: [{ updatedAt: "2026-01-05T00:00:00.000Z" }] }) + .mockResolvedValueOnce({ docs: [{ updatedAt: "2026-01-15T00:00:00.000Z" }] }); + + const result = await previewRollback({ releaseId: "rel-1", payload: payload as any }); + + expect(result.eligible).toHaveLength(1); + expect(result.skipped).toHaveLength(1); + expect(result.eligible[0]!.docId).toBe("doc-1"); + expect(result.skipped[0]!.docId).toBe("doc-2"); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/scheduler/checkScheduledReleases.test.ts b/packages/payload-plugin-content-releases/src/__tests__/scheduler/checkScheduledReleases.test.ts new file mode 100644 index 0000000..0c6ccfc --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/scheduler/checkScheduledReleases.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect, vi } from "vitest"; +import { checkScheduledReleases } from "../../scheduler/checkScheduledReleases"; + +function makePayload(dueReleases: any[] = [], items: any[] = []) { + return { + find: vi.fn() + .mockResolvedValueOnce({ docs: dueReleases }) // scheduled releases query + .mockResolvedValue({ docs: items }), // release items query + findByID: vi.fn().mockResolvedValue({ id: "doc-1", updatedAt: "2026-01-01" }), + update: vi.fn().mockResolvedValue({}), + logger: { + info: vi.fn(), + error: vi.fn(), + }, + }; +} + +describe("checkScheduledReleases", () => { + it("should do nothing when no releases are due", async () => { + const payload = makePayload([]); + + await checkScheduledReleases({ + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + // Only one find call (for scheduled releases), no updates + expect(payload.find).toHaveBeenCalledTimes(1); + expect(payload.update).not.toHaveBeenCalled(); + }); + + it("should process due releases", async () => { + const dueRelease = { + id: "rel-1", + name: "Spring Campaign", + status: "scheduled", + scheduledAt: new Date(Date.now() - 60000).toISOString(), + }; + const items = [ + { + id: "item-1", + targetCollection: "pages", + targetDoc: "doc-1", + action: "publish", + status: "pending", + baseVersion: null, + snapshot: { title: "Published", _status: "published" }, + }, + ]; + + const payload = makePayload([dueRelease], items); + + await checkScheduledReleases({ + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + // Should have called update multiple times (status transitions + item updates) + expect(payload.update).toHaveBeenCalled(); + expect(payload.logger.info).toHaveBeenCalledWith( + expect.stringContaining("Spring Campaign"), + ); + }); + + it("should log errors for failed releases", async () => { + const dueRelease = { + id: "rel-1", + name: "Bad Release", + status: "scheduled", + scheduledAt: new Date(Date.now() - 60000).toISOString(), + }; + + const payload = makePayload([dueRelease]); + // Make find fail on second call (items query) + payload.find + .mockReset() + .mockResolvedValueOnce({ docs: [dueRelease] }) + .mockRejectedValueOnce(new Error("DB connection lost")); + // orchestratePublish will throw + payload.update.mockRejectedValue(new Error("DB connection lost")); + + await checkScheduledReleases({ + payload: payload as any, + conflictStrategy: "fail", + batchSize: 20, + }); + + expect(payload.logger.error).toHaveBeenCalledWith( + expect.stringContaining("Bad Release"), + ); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/setup.ts b/packages/payload-plugin-content-releases/src/__tests__/setup.ts new file mode 100644 index 0000000..94b0528 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/setup.ts @@ -0,0 +1 @@ +// Global test setup — add shared mocks here as needed diff --git a/packages/payload-plugin-content-releases/src/__tests__/types.test.ts b/packages/payload-plugin-content-releases/src/__tests__/types.test.ts new file mode 100644 index 0000000..8517e7f --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/types.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from "vitest"; +import type { + ContentReleasesPluginConfig, + ReleaseStatus, + ReleaseItemAction, + ReleaseItemStatus, + ConflictStrategy, +} from "../types"; + +describe("types", () => { + it("should accept a valid minimal config", () => { + const config: ContentReleasesPluginConfig = { + enabledCollections: ["pages", "posts"], + }; + expect(config.enabledCollections).toHaveLength(2); + }); + + it("should accept a full config", () => { + const config: ContentReleasesPluginConfig = { + enabledCollections: ["pages"], + conflictStrategy: "fail", + publishBatchSize: 50, + useTransactions: true, + access: {}, + hooks: {}, + }; + expect(config.conflictStrategy).toBe("fail"); + }); + + it("should define all release statuses", () => { + const statuses: ReleaseStatus[] = [ + "draft", "scheduled", "publishing", "published", "failed", "cancelled", + ]; + expect(statuses).toHaveLength(6); + }); + + it("should define release item actions", () => { + const actions: ReleaseItemAction[] = ["publish", "unpublish"]; + expect(actions).toHaveLength(2); + }); + + it("should define release item statuses", () => { + const statuses: ReleaseItemStatus[] = ["pending", "published", "failed", "skipped"]; + expect(statuses).toHaveLength(4); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/__tests__/validation/statusTransitions.test.ts b/packages/payload-plugin-content-releases/src/__tests__/validation/statusTransitions.test.ts new file mode 100644 index 0000000..d2726ef --- /dev/null +++ b/packages/payload-plugin-content-releases/src/__tests__/validation/statusTransitions.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from "vitest"; +import { isValidTransition } from "../../validation/statusTransitions"; +import type { ReleaseStatus } from "../../types"; + +describe("isValidTransition", () => { + const validCases: Array<[ReleaseStatus, ReleaseStatus]> = [ + ["draft", "scheduled"], + ["draft", "publishing"], + ["draft", "cancelled"], + ["scheduled", "draft"], + ["scheduled", "publishing"], + ["publishing", "published"], + ["publishing", "failed"], + ["failed", "draft"], + ["cancelled", "publishing"], + ["reverted", "draft"], + ]; + + const invalidCases: Array<[ReleaseStatus, ReleaseStatus]> = [ + ["published", "draft"], + ["published", "cancelled"], + ["cancelled", "draft"], + ["publishing", "cancelled"], + ["draft", "published"], + ["draft", "failed"], + ["scheduled", "published"], + ]; + + it.each(validCases)("should allow transition from %s to %s", (from, to) => { + expect(isValidTransition(from, to)).toBe(true); + }); + + it.each(invalidCases)("should reject transition from %s to %s", (from, to) => { + expect(isValidTransition(from, to)).toBe(false); + }); + + it("should reject same-state transitions", () => { + expect(isValidTransition("draft", "draft")).toBe(false); + }); +}); diff --git a/packages/payload-plugin-content-releases/src/admin/components/ReleaseActionCell.tsx b/packages/payload-plugin-content-releases/src/admin/components/ReleaseActionCell.tsx new file mode 100644 index 0000000..2cca2d0 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/admin/components/ReleaseActionCell.tsx @@ -0,0 +1,14 @@ +"use client"; + +import type { DefaultCellComponentProps } from "payload"; +import { Pill } from "@payloadcms/ui"; + +export function ReleaseActionCell({ cellData }: DefaultCellComponentProps) { + const value = typeof cellData === "string" ? cellData : ""; + if (!value) return ; + + const label = value.charAt(0).toUpperCase() + value.slice(1); + const pillStyle = value === "unpublish" ? "warning" : "success"; + + return {label}; +} diff --git a/packages/payload-plugin-content-releases/src/admin/components/ReleaseActionsField.tsx b/packages/payload-plugin-content-releases/src/admin/components/ReleaseActionsField.tsx new file mode 100644 index 0000000..c00e389 --- /dev/null +++ b/packages/payload-plugin-content-releases/src/admin/components/ReleaseActionsField.tsx @@ -0,0 +1,201 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; +import { Banner, Button, toast, useDocumentInfo } from "@payloadcms/ui"; +import { useRouter } from "next/navigation"; +import { ReleaseStatus } from "../../types"; +import { isValidTransition } from "../../validation/statusTransitions"; +import { getPublishButtonProps } from "./getPublishButtonProps"; +import { RollbackButton } from "./RollbackButton"; + +interface FailedItem { + id: string; + targetCollection: string; + targetDoc: string; +} + +export function ReleaseActionsField() { + const { id, data } = useDocumentInfo(); + const router = useRouter(); + const [publishing, setPublishing] = useState(false); + const [resetting, setResetting] = useState(false); + const [failedItems, setFailedItems] = useState([]); + const [refreshingItem, setRefreshingItem] = useState(null); + + const status = data?.status as ReleaseStatus; + + const fetchFailedItems = useCallback(async () => { + if (!id) return; + if (status !== "failed") { + setFailedItems([]); + return; + } + try { + const res = await fetch( + `/api/release-items?where[release][equals]=${id}&where[status][equals]=failed&limit=100&depth=0`, + ); + if (!res.ok) return; + const json = await res.json(); + setFailedItems( + (json.docs ?? []).map((d: any) => ({ + id: String(d.id), + targetCollection: d.targetCollection, + targetDoc: String(d.targetDoc), + })), + ); + } catch { + // non-fatal + } + }, [id, status]); + + useEffect(() => { + void fetchFailedItems(); + }, [fetchFailedItems]); + + if (!id) return null; + + const { disabled, tooltip } = getPublishButtonProps(status); + const canResetToDraft = !!status && isValidTransition(status, "draft"); + + const handlePublish = async () => { + setPublishing(true); + + try { + const res = await fetch(`/api/content-releases/${id}/publish`, { + method: "POST", + }); + + const result = await res.json(); + if (!res.ok) { + toast.error(result?.error ?? "Publish failed"); + return; + } + + toast.success("Release published"); + router.refresh(); + } catch (err) { + toast.error(err instanceof Error ? err.message : "Publish failed"); + } finally { + setPublishing(false); + } + }; + + const handleRefreshItem = async (itemId: string) => { + setRefreshingItem(itemId); + try { + const res = await fetch( + `/api/content-releases/items/${itemId}/refresh-snapshot`, + { method: "POST" }, + ); + const result = await res.json(); + if (!res.ok) { + toast.error(result?.error ?? "Failed to refresh snapshot"); + return; + } + toast.success("Snapshot refreshed"); + router.refresh(); + void fetchFailedItems(); + } catch (err) { + toast.error(err instanceof Error ? err.message : "Failed to refresh snapshot"); + } finally { + setRefreshingItem(null); + } + }; + + const handleResetToDraft = async () => { + setResetting(true); + try { + const res = await fetch(`/api/releases/${id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ status: "draft" }), + }); + const result = await res.json(); + if (!res.ok) { + const errMsg = + result?.errors?.[0]?.message ?? result?.message ?? "Reset failed"; + toast.error(errMsg); + return; + } + toast.success("Release reset to draft"); + router.refresh(); + } catch (err) { + toast.error(err instanceof Error ? err.message : "Reset failed"); + } finally { + setResetting(false); + } + }; + + return ( +
+ {status === "failed" && failedItems.length > 0 && ( + +
+
+ + {failedItems.length} item{failedItems.length === 1 ? "" : "s"} failed to publish. + {" "} + Usually this means the document was modified after the snapshot + was staged. Refresh the snapshot to use the document’s + current state, then reset to draft and republish. +
+
    + {failedItems.map((item) => ( +
  • + + {item.targetCollection} / {item.targetDoc} + + +
  • + ))} +
+
+
+ )} + +
+ + {canResetToDraft && ( + + )} + {status === "published" && } +
+
+ ); +} diff --git a/packages/payload-plugin-content-releases/src/admin/components/ReleaseDrawer.tsx b/packages/payload-plugin-content-releases/src/admin/components/ReleaseDrawer.tsx new file mode 100644 index 0000000..3989f9a --- /dev/null +++ b/packages/payload-plugin-content-releases/src/admin/components/ReleaseDrawer.tsx @@ -0,0 +1,305 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; +import { createPortal } from "react-dom"; +import { Button, Drawer, Pill, toast, useModal, DatePicker } from "@payloadcms/ui"; + +interface Release { + id: string; + name: string; + status: string; + createdAt: string; + scheduledAt?: string; +} + +interface ReleaseDrawerProps { + slug: string; + snapshot: Record | null; + collectionSlug: string; + docId: string; + baseVersion?: string; + onSuccess: () => void; + onBack?: () => void; +} + +export function ReleaseDrawer({ + slug, + snapshot, + collectionSlug, + docId, + baseVersion, + onSuccess, + onBack, +}: ReleaseDrawerProps) { + const { closeModal, modalState, containerRef } = useModal(); + const isOpen = !!modalState[slug]?.isOpen; + const [releases, setReleases] = useState([]); + const [loading, setLoading] = useState(true); + const [creating, setCreating] = useState(false); + const [showCreateForm, setShowCreateForm] = useState(false); + const [newName, setNewName] = useState(""); + const [newDescription, setNewDescription] = useState(""); + const [newScheduledAt, setNewScheduledAt] = useState(""); + + const fetchDraftReleases = useCallback(async () => { + if (!isOpen) return; + setLoading(true); + try { + const res = await fetch( + "/api/releases?where[status][in]=draft,scheduled&sort=-createdAt&limit=100" + ); + if (!res.ok) return; + const data = await res.json(); + setReleases(data.docs ?? []); + } catch { + // non-fatal + } finally { + setLoading(false); + } + }, [isOpen]); + + useEffect(() => { + if (isOpen) { + fetchDraftReleases(); + setShowCreateForm(false); + setNewName(""); + setNewDescription(""); + setNewScheduledAt(""); + } + }, [isOpen, fetchDraftReleases]); + + const addToRelease = useCallback( + async (releaseId: string, releaseName: string) => { + if (!snapshot) return; + try { + const res = await fetch("/api/release-items", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + release: releaseId, + targetCollection: collectionSlug, + targetDoc: docId, + action: "publish", + snapshot, + baseVersion: baseVersion ?? null, + }), + }); + + if (!res.ok) { + const err = await res.json(); + const errMsg = err.errors?.[0]?.message ?? err.message ?? "Failed"; + + if (errMsg.toLowerCase().includes("already exists")) { + const confirmed = window.confirm( + "This document is already in this release. Replace snapshot?" + ); + if (confirmed) { + const existing = await fetch( + `/api/release-items?where[release][equals]=${releaseId}&where[targetDoc][equals]=${docId}&where[targetCollection][equals]=${collectionSlug}&limit=1` + ); + const existingData = await existing.json(); + const existingItem = existingData.docs?.[0]; + if (existingItem) { + await fetch(`/api/release-items/${existingItem.id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ snapshot, baseVersion: baseVersion ?? null }), + }); + toast.success(`Updated snapshot in "${releaseName}"`); + closeModal(slug); + onSuccess(); + return; + } + } + return; + } + toast.error(errMsg); + return; + } + + toast.success(`Added to "${releaseName}"`); + closeModal(slug); + onSuccess(); + } catch { + toast.error("Failed to add to release"); + } + }, + [collectionSlug, docId, snapshot, baseVersion, slug, closeModal, onSuccess] + ); + + const createAndAdd = useCallback(async () => { + if (!newName.trim()) return; + setCreating(true); + try { + const releaseData: Record = { + name: newName.trim(), + description: newDescription.trim() || undefined, + }; + if (newScheduledAt) { + releaseData.scheduledAt = new Date(newScheduledAt).toISOString(); + } + + const res = await fetch("/api/releases", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(releaseData), + }); + if (!res.ok) { + toast.error("Failed to create release"); + return; + } + const data = await res.json(); + await addToRelease(data.doc.id, data.doc.name); + + if (newScheduledAt) { + await fetch(`/api/releases/${data.doc.id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ status: "scheduled" }), + }); + } + } catch { + toast.error("Failed to create release"); + } finally { + setCreating(false); + } + }, [newName, newDescription, newScheduledAt, addToRelease]); + + const header = ( +
+ {onBack && ( + + )} +

Add to Release

+
+ ); + + return ( + <> + {isOpen && containerRef.current && createPortal( +
closeModal(slug)} + />, + containerRef.current, + )} + +
+ {/* Create New Release */} + {!showCreateForm ? ( +
+ +
+ ) : ( +
+
+ + setNewName(e.target.value)} + placeholder="Release name" + autoFocus + /> +
+
+ +