diff --git a/packages/migrations/src/actions/2025.11.25T00-00-00.members-search.ts b/packages/migrations/src/actions/2025.11.25T00-00-00.members-search.ts new file mode 100644 index 00000000000..4528ebc8526 --- /dev/null +++ b/packages/migrations/src/actions/2025.11.25T00-00-00.members-search.ts @@ -0,0 +1,9 @@ +import { type MigrationExecutor } from '../pg-migrator'; + +export default { + name: '2025.11.25T00-00-00.members-search.ts', + run: ({ sql }) => sql` + CREATE EXTENSION IF NOT EXISTS pg_trgm; + CREATE INDEX CONCURRENTLY IF NOT EXISTS "users_search_by_email_and_display_name" on users using gin(LOWER(email|| ' ' || display_name) gin_trgm_ops); + `, +} satisfies MigrationExecutor; diff --git a/packages/services/api/src/modules/organization/module.graphql.ts b/packages/services/api/src/modules/organization/module.graphql.ts index 208d749bacf..3bf9415d24c 100644 --- a/packages/services/api/src/modules/organization/module.graphql.ts +++ b/packages/services/api/src/modules/organization/module.graphql.ts @@ -868,6 +868,14 @@ export default gql` appDeployments: [String!] } + input MembersFilter { + """ + Part of a user's email or username that is used to filter the list of + members. + """ + searchTerm: String + } + type Organization { """ Unique UUID of the organization @@ -881,8 +889,11 @@ export default gql` name: String! @deprecated(reason: "Use the 'slug' field instead.") owner: Member! @tag(name: "public") me: Member! - members(first: Int @tag(name: "public"), after: String @tag(name: "public")): MemberConnection! - @tag(name: "public") + members( + first: Int @tag(name: "public") + after: String @tag(name: "public") + filters: MembersFilter + ): MemberConnection! @tag(name: "public") invitations( first: Int @tag(name: "public") after: String @tag(name: "public") diff --git a/packages/services/api/src/modules/organization/providers/organization-manager.ts b/packages/services/api/src/modules/organization/providers/organization-manager.ts index 6fe4226b7f0..a252a46c738 100644 --- a/packages/services/api/src/modules/organization/providers/organization-manager.ts +++ b/packages/services/api/src/modules/organization/providers/organization-manager.ts @@ -1221,7 +1221,7 @@ export class OrganizationManager { async getPaginatedOrganizationMembersForOrganization( organization: Organization, - args: { first: number | null; after: string | null }, + args: { first: number | null; after: string | null; searchTerm: string | null }, ) { await this.session.assertPerformAction({ action: 'member:describe', diff --git a/packages/services/api/src/modules/organization/providers/organization-members.ts b/packages/services/api/src/modules/organization/providers/organization-members.ts index abd3de1b17f..533c1f31577 100644 --- a/packages/services/api/src/modules/organization/providers/organization-members.ts +++ b/packages/services/api/src/modules/organization/providers/organization-members.ts @@ -148,21 +148,31 @@ export class OrganizationMembers { async getPaginatedOrganizationMembersForOrganization( organization: Organization, - args: { first: number | null; after: string | null }, + args: { first: number | null; after: string | null; searchTerm: string | null }, ) { this.logger.debug( 'Find paginated organization members for organization. (organizationId=%s)', organization.id, ); - const first = args.first; + const first = args.first ? Math.min(args.first, 50) : 50; const cursor = args.after ? decodeCreatedAtAndUUIDIdBasedCursor(args.after) : null; + const searchTerm = args.searchTerm?.trim().toLowerCase() ?? ''; + const searching = searchTerm.length > 0; const query = sql` SELECT ${organizationMemberFields(sql`"om"`)} FROM "organization_member" AS "om" + ${ + searching + ? sql` + JOIN "users" as "u" + ON "om"."user_id" = "u"."id" + ` + : sql`` + } WHERE "om"."organization_id" = ${organization.id} ${ @@ -178,11 +188,12 @@ export class OrganizationMembers { ` : sql`` } + ${searching ? sql`AND (LOWER("u"."display_name" || ' ' || "u"."email") LIKE ${'%' + searchTerm + '%'})` : sql``} ORDER BY "om"."organization_id" DESC + , "om"."created_at" DESC , "om"."user_id" DESC - , "om"."user_id" DESC - ${first ? sql`LIMIT ${first + 1}` : sql``} + LIMIT ${first + 1} `; const result = await this.pool.any(query); diff --git a/packages/services/api/src/modules/organization/resolvers/Organization.ts b/packages/services/api/src/modules/organization/resolvers/Organization.ts index ed85c305022..8a2ff2be066 100644 --- a/packages/services/api/src/modules/organization/resolvers/Organization.ts +++ b/packages/services/api/src/modules/organization/resolvers/Organization.ts @@ -67,6 +67,7 @@ export const Organization: Pick< .getPaginatedOrganizationMembersForOrganization(organization, { first: args.first ?? null, after: args.after ?? null, + searchTerm: args.filters?.searchTerm ?? null, }); }, invitations: async (organization, args, { injector }) => { diff --git a/packages/web/app/src/components/organization/members/list.tsx b/packages/web/app/src/components/organization/members/list.tsx index fc6be5973c7..828a9e57d94 100644 --- a/packages/web/app/src/components/organization/members/list.tsx +++ b/packages/web/app/src/components/organization/members/list.tsx @@ -1,8 +1,9 @@ -import { useCallback, useMemo, useState } from 'react'; -import { MoreHorizontalIcon, MoveDownIcon, MoveUpIcon } from 'lucide-react'; +import { memo, useEffect, useState } from 'react'; +import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react'; import type { IconType } from 'react-icons'; import { FaGithub, FaGoogle, FaOpenid, FaUserLock } from 'react-icons/fa'; -import { useMutation } from 'urql'; +import { useMutation, type UseQueryExecute } from 'urql'; +import { useDebouncedCallback } from 'use-debounce'; import { AlertDialog, AlertDialogAction, @@ -20,13 +21,15 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { Link } from '@/components/ui/link'; +import { Input } from '@/components/ui/input'; import { SubPageLayout, SubPageLayoutHeader } from '@/components/ui/page-content-layout'; import * as Sheet from '@/components/ui/sheet'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { useToast } from '@/components/ui/use-toast'; import { FragmentType, graphql, useFragment } from '@/gql'; import * as GraphQLSchema from '@/gql/graphql'; +import { useSearchParamsFilter } from '@/lib/hooks/use-search-params-filters'; +import { organizationMembersRoute } from '../../../router'; import { MemberInvitationButton } from './invitations'; import { MemberRolePicker } from './member-role-picker'; @@ -83,7 +86,6 @@ const OrganizationMemberRow_MemberFragment = graphql(` } role { id - name } isOwner viewerCanRemove @@ -91,10 +93,9 @@ const OrganizationMemberRow_MemberFragment = graphql(` } `); -function OrganizationMemberRow(props: { +const OrganizationMemberRow = memo(function OrganizationMemberRow(props: { organization: FragmentType; member: FragmentType; - refetchMembers(): void; }) { const organization = useFragment(OrganizationMembers_OrganizationFragment, props.organization); const member = useFragment(OrganizationMemberRow_MemberFragment, props.member); @@ -212,7 +213,7 @@ function OrganizationMemberRow(props: { ); -} +}); const MemberRole_OrganizationFragment = graphql(` fragment MemberRole_OrganizationFragment on Organization { @@ -234,7 +235,6 @@ const MemberRole_MemberFragment = graphql(` projects { project { id - slug } } } @@ -264,7 +264,9 @@ function MemberRole(props: { {organization.viewerCanAssignUserRoles && ( setIsOpen(isOpen)}> - change + {isOpen && ( ; - refetchMembers(): void; + refetchMembers: UseQueryExecute; + /** + * The setter for the reactive "after" variable required by urql + */ + setAfter: (after: string | null) => void; }) { + // Pagination state + const [cursorHistory, setCursorHistory] = useState>([null]); + const [currentPage, setCurrentPage] = useState(0); + + const search = organizationMembersRoute.useSearch(); + const organization = useFragment(OrganizationMembers_OrganizationFragment, props.organization); const members = organization.members?.edges?.map(edge => edge.node); - const [orderDirection, setOrderDirection] = useState<'asc' | 'desc' | null>(null); - const [sortByKey, setSortByKey] = useState<'name' | 'role'>('name'); + const pageInfo = organization.members?.pageInfo; - const sortedMembers = useMemo(() => { - if (!members) { - return []; - } + // Reset pagination when search changes + useEffect(() => { + setCursorHistory([null]); + setCurrentPage(0); + props.setAfter(null); + }, [search.search]); - if (!orderDirection) { - return members ?? []; - } + useEffect(() => { + // Update the cursor in parent, which will trigger query refetch + props.setAfter(cursorHistory[currentPage]); + }, [currentPage]); - const sorted = [...members].sort((a, b) => { - if (sortByKey === 'name') { - return a.user.displayName.localeCompare(b.user.displayName); - } - - if (sortByKey === 'role') { - return (a.role?.name ?? 'Select role').localeCompare(b.role?.name ?? 'Select role') ?? 0; - } + const [searchValue, setSearchValue] = useSearchParamsFilter('search', ''); - return 0; - }); + const handleSearchChange = useDebouncedCallback((e: React.ChangeEvent) => { + setSearchValue(e.target.value); + }, 300); - return orderDirection === 'asc' ? sorted : sorted.reverse(); - }, [members, orderDirection, sortByKey]); + const handleNextPage = (endCursor: string) => { + setCursorHistory(prev => [...prev, endCursor]); + setCurrentPage(prev => prev + 1); + }; - const updateSorting = useCallback( - (newSortBy: 'name' | 'role') => { - if (newSortBy === sortByKey) { - setOrderDirection( - orderDirection === 'asc' ? 'desc' : orderDirection === 'desc' ? null : 'asc', - ); - } else { - setSortByKey(newSortBy); - setOrderDirection('asc'); - } - }, - [sortByKey, orderDirection], - ); + const handlePreviousPage = () => { + if (currentPage > 0) { + setCurrentPage(prev => prev - 1); + } + }; return ( @@ -360,61 +362,96 @@ export function OrganizationMembers(props: { subPageTitle="List of organization members" description="Manage the members of your organization and their permissions." > - {organization.viewerCanManageInvitations && ( - + - )} + {organization.viewerCanManageInvitations && ( + + )} + - - - {sortedMembers.map(node => ( - - ))} + {members.length === 0 ? ( + + + + ) : ( + members.map(node => ( + + )) + )}
updateSorting('name')} - > + Member - - {sortByKey === 'name' ? ( - orderDirection === 'asc' ? ( - - ) : orderDirection === 'desc' ? ( - - ) : null - ) : null} - updateSorting('role')} - > + Assigned Role - - {sortByKey === 'role' ? ( - orderDirection === 'asc' ? ( - - ) : orderDirection === 'desc' ? ( - - ) : null - ) : null} -
+
+

No members found

+ +

+ {`No results for "${searchValue}". Try adjusting your search term.`} +

+
+
+ {/* Pagination Controls */} +
+
+ Page {currentPage + 1} + {searchValue && members.length > 0 && ` - showing results for "${searchValue}"`} +
+
+ + +
+
); } diff --git a/packages/web/app/src/lib/hooks/use-search-params-filters.ts b/packages/web/app/src/lib/hooks/use-search-params-filters.ts index 2bd230605a9..f20344d4bb3 100644 --- a/packages/web/app/src/lib/hooks/use-search-params-filters.ts +++ b/packages/web/app/src/lib/hooks/use-search-params-filters.ts @@ -11,14 +11,24 @@ export function useSearchParamsFilter( const rawSearchValue = ((name as string) in searchParams && (searchParams[name] as string)) || null; - const searchValue = (deserializeSearchValue(rawSearchValue) ?? defaultState) as TValue; + + /** + * If our extracted search params (rawSearchValue) is an array, we deserialize. + * Otherwise, it's just a simple string. + */ + const searchValue = ( + rawSearchValue + ? Array.isArray(defaultState) + ? deserializeSearchValue(rawSearchValue) + : rawSearchValue + : defaultState + ) as TValue; const setSearchValue = (value: TValue) => { void router.navigate({ search: { ...searchParams, - [name]: - Array.isArray(value) && value.length === 0 ? undefined : serializeSearchValue(value), + [name]: value.length === 0 ? undefined : serializeSearchValue(value), }, replace: true, }); diff --git a/packages/web/app/src/pages/organization-members.tsx b/packages/web/app/src/pages/organization-members.tsx index feadb9ebebc..6c165136239 100644 --- a/packages/web/app/src/pages/organization-members.tsx +++ b/packages/web/app/src/pages/organization-members.tsx @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; -import { useQuery } from 'urql'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useQuery, UseQueryExecute } from 'urql'; import { OrganizationLayout, Page } from '@/components/layouts/organization'; import { OrganizationInvitations } from '@/components/organization/members/invitations'; import { OrganizationMembers } from '@/components/organization/members/list'; @@ -11,12 +11,14 @@ import { QueryError } from '@/components/ui/query-error'; import { FragmentType, graphql, useFragment } from '@/gql'; import { useRedirect } from '@/lib/access/common'; import { cn } from '@/lib/utils'; +import { organizationMembersRoute } from '../router'; const OrganizationMembersPage_OrganizationFragment = graphql(` fragment OrganizationMembersPage_OrganizationFragment on Organization { ...OrganizationInvitations_OrganizationFragment ...OrganizationMemberRoles_OrganizationFragment ...OrganizationMembers_OrganizationFragment + viewerCanManageInvitations viewerCanManageRoles } @@ -43,7 +45,8 @@ function PageContent(props: { page: SubPage; onPageChange(page: SubPage): void; organization: FragmentType; - refetchQuery(): void; + refetchQuery: UseQueryExecute; + setAfter: (after: string | null) => void; }) { const organization = useFragment( OrganizationMembersPage_OrganizationFragment, @@ -89,7 +92,11 @@ function PageContent(props: { {props.page === 'list' ? ( - + ) : null} {props.page === 'roles' && organization.viewerCanManageRoles ? ( @@ -106,7 +113,12 @@ function PageContent(props: { } const OrganizationMembersPageQuery = graphql(` - query OrganizationMembersPageQuery($organizationSlug: String!) { + query OrganizationMembersPageQuery( + $organizationSlug: String! + $searchTerm: String + $first: Int + $after: String + ) { organization: organizationBySlug(organizationSlug: $organizationSlug) { ...OrganizationMembersPage_OrganizationFragment viewerCanSeeMembers @@ -119,17 +131,26 @@ function OrganizationMembersPageContent(props: { page: SubPage; onPageChange(page: SubPage): void; }) { + const search = organizationMembersRoute.useSearch(); + const [after, setAfter] = useState(null); + + // Reset cursor when search changes + useEffect(() => { + setAfter(null); + }, [search.search]); + const [query, refetch] = useQuery({ query: OrganizationMembersPageQuery, variables: { organizationSlug: props.organizationSlug, + searchTerm: search.search || undefined, + first: 20, + after, }, }); - const currentOrganization = query.data?.organization; - useRedirect({ - canAccess: currentOrganization?.viewerCanSeeMembers === true, + canAccess: query.data?.organization?.viewerCanSeeMembers === true, redirectTo: router => { void router.navigate({ to: '/$organizationSlug', @@ -138,10 +159,14 @@ function OrganizationMembersPageContent(props: { }, }); }, - entity: currentOrganization, + entity: query.data?.organization, }); - if (currentOrganization?.viewerCanSeeMembers === false) { + const refetchQuery = useCallback(() => { + refetch({ requestPolicy: 'network-only' }); + }, [refetch]); + + if (query.data?.organization?.viewerCanSeeMembers === false) { return null; } @@ -155,14 +180,13 @@ function OrganizationMembersPageContent(props: { page={Page.Members} className="flex flex-col gap-y-10" > - {currentOrganization ? ( + {query.data?.organization ? ( { - refetch({ requestPolicy: 'network-only' }); - }} - organization={currentOrganization} + page={props.page} + refetchQuery={refetchQuery} + setAfter={setAfter} /> ) : null} diff --git a/packages/web/app/src/router.tsx b/packages/web/app/src/router.tsx index a0ed86fa531..849bf6ae58c 100644 --- a/packages/web/app/src/router.tsx +++ b/packages/web/app/src/router.tsx @@ -431,9 +431,10 @@ const organizationSettingsRoute = createRoute({ const OrganizationMembersRouteSearch = z.object({ page: z.enum(['list', 'roles', 'invitations']).catch('list').default('list'), + search: z.string().optional(), }); -const organizationMembersRoute = createRoute({ +export const organizationMembersRoute = createRoute({ getParentRoute: () => organizationRoute, path: 'view/members', validateSearch(search) { @@ -445,7 +446,7 @@ const organizationMembersRoute = createRoute({ const navigate = useNavigate({ from: organizationMembersRoute.fullPath }); const onPageChange = useCallback( (newPage: z.infer['page']) => { - void navigate({ search: { page: newPage } }); + void navigate({ search: { page: newPage, search: undefined } }); }, [navigate], ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4442c983071..40c5d9fe4c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1714,21 +1714,6 @@ importers: specifier: 3.25.76 version: 3.25.76 - packages/services/workflows: - dependencies: - '@graphql-hive/logger': - specifier: 1.0.9 - version: 1.0.9 - '@openworkflow/backend-postgres': - specifier: 0.3.0 - version: 0.3.0(openworkflow@0.3.0) - openworkflow: - specifier: 0.3.0 - version: 0.3.0 - zod: - specifier: 3.25.76 - version: 3.25.76 - packages/web/app: devDependencies: '@date-fns/utc': @@ -7265,11 +7250,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 - '@openworkflow/backend-postgres@0.3.0': - resolution: {integrity: sha512-h7uE/+xrQpGpXeI0IaAy1Q+FN2SILIYX166R5kk47TleEYhRBF1JZ8jmZkmkqUapODxFp+BUZmTVmg3SctIIFg==} - peerDependencies: - openworkflow: ^0.3.0 - '@pagefind/darwin-arm64@1.3.0': resolution: {integrity: sha512-365BEGl6ChOsauRjyVpBjXybflXAOvoMROw3TucAROHIcdBvXk9/2AmEvGFU0r75+vdQI4LJdJdpH4Y6Yqaj4A==} cpu: [arm64] @@ -15735,10 +15715,6 @@ packages: resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==} engines: {node: '>=0.10'} - openworkflow@0.3.0: - resolution: {integrity: sha512-eP3W7bvmcdllRZp3Xawh0iB2VKR4eyUML5D2yi87f2GDyFcrKMHCddM1tVxUgjaXBYa6zpTeJasbcSgrVTRsAQ==} - engines: {node: '>=20'} - optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -16277,10 +16253,6 @@ packages: postgres-range@1.1.3: resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==} - postgres@3.4.7: - resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} - engines: {node: '>=12'} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -19398,8 +19370,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.596.0 - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -19551,11 +19523,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.596.0': + '@aws-sdk/client-sso-oidc@3.596.0(@aws-sdk/client-sts@3.596.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -19594,6 +19566,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 transitivePeerDependencies: + - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso-oidc@3.723.0(@aws-sdk/client-sts@3.723.0)': @@ -19813,11 +19786,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sts@3.596.0(@aws-sdk/client-sso-oidc@3.596.0)': + '@aws-sdk/client-sts@3.596.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.596.0 + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -19856,7 +19829,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/client-sts@3.723.0': @@ -20088,7 +20060,7 @@ snapshots: '@aws-sdk/credential-provider-ini@3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0)': dependencies: - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/credential-provider-env': 3.587.0 '@aws-sdk/credential-provider-http': 3.596.0 '@aws-sdk/credential-provider-process': 3.587.0 @@ -20335,7 +20307,7 @@ snapshots: '@aws-sdk/credential-provider-web-identity@3.587.0(@aws-sdk/client-sts@3.596.0)': dependencies: - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.1.11 '@smithy/types': 3.7.2 @@ -20724,7 +20696,7 @@ snapshots: '@aws-sdk/token-providers@3.587.0(@aws-sdk/client-sso-oidc@3.596.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.596.0 + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.1.11 '@smithy/shared-ini-file-loader': 3.1.12 @@ -27398,11 +27370,6 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@openworkflow/backend-postgres@0.3.0(openworkflow@0.3.0)': - dependencies: - openworkflow: 0.3.0 - postgres: 3.4.7 - '@pagefind/darwin-arm64@1.3.0': optional: true @@ -38175,8 +38142,6 @@ snapshots: opentracing@0.14.7: {} - openworkflow@0.3.0: {} - optionator@0.9.3: dependencies: '@aashutoshrathi/word-wrap': 1.2.6 @@ -38751,8 +38716,6 @@ snapshots: postgres-range@1.1.3: {} - postgres@3.4.7: {} - prelude-ls@1.2.1: {} prettier-plugin-pkg@0.18.0(prettier@3.4.2):