Skip to content

Commit c2e59bb

Browse files
committed
[eas-cli] Fix Convex invite output
1 parent 51311f1 commit c2e59bb

14 files changed

Lines changed: 107 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This is the log of notable changes to EAS CLI and related packages.
1010

1111
### 🐛 Bug fixes
1212

13+
- [eas-cli] Fix Convex team invite output after skipped or unnecessary invitations. ([#3672](https://github.com/expo/eas-cli/pull/3672) by [@fiberjw](https://github.com/fiberjw))
14+
1315
### 🧹 Chores
1416

1517
## [18.10.0](https://github.com/expo/eas-cli/releases/tag/v18.10.0) - 2026-05-04

packages/eas-cli/src/commandUtils/__tests__/convex-test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ describe('Convex command utilities', () => {
2222
convexTeamIdentifier: 'team-123',
2323
convexTeamName: 'Test Team',
2424
convexTeamSlug: 'test team',
25+
hasBeenClaimed: true,
2526
createdAt: '2024-01-01T00:00:00.000Z',
2627
updatedAt: '2024-01-01T00:00:00.000Z',
2728
invitedAt: '2024-01-02T00:00:00.000Z',
@@ -58,8 +59,10 @@ describe('Convex command utilities', () => {
5859
expect(formatConvexTeamConnection(mockConnection)).toContain('Test Team / test team');
5960
expect(formatConvexTeamConnection(mockConnection)).not.toContain('team-123');
6061
expect(formatConvexTeamConnection(mockConnection)).not.toContain('connection-1');
62+
expect(formatConvexTeamConnection(mockConnection)).toContain('Claimed');
63+
expect(formatConvexTeamConnection(mockConnection)).toContain('Yes');
6164
expect(formatConvexTeamConnection(mockConnection)).toContain('user@example.com');
62-
expect(formatConvexTeamConnection(mockConnection)).toContain('2024-01-02T00:00:00.000Z');
65+
expect(formatConvexTeamConnection(mockConnection)).toContain('Jan 2, 2024 00:00:00 UTC');
6366
expect(formatConvexProject(mockProject)).toContain('project-123');
6467
expect(formatConvexProject(mockProject)).not.toContain('convex-project-1');
6568
expect(formatConvexProject(mockProject)).toContain('Test Team / test team');

packages/eas-cli/src/commandUtils/convex.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import chalk from 'chalk';
2+
import dateFormat from 'dateformat';
23

34
import { ConvexProjectData, ConvexTeamConnectionData } from '../graphql/types/ConvexTeamConnection';
45
import Log, { link } from '../log';
@@ -7,6 +8,10 @@ import { confirmAsync } from '../prompts';
78
const CONVEX_DASHBOARD_HOST = 'https://dashboard.convex.dev';
89
const RECENT_INVITE_THRESHOLD_MS = 60 * 60 * 1000;
910

11+
export function formatConvexInviteTimestamp(timestamp: string): string {
12+
return `${dateFormat(timestamp, 'UTC:mmm d, yyyy HH:MM:ss')} UTC`;
13+
}
14+
1015
export function getConvexTeamDashboardUrl(connection: ConvexTeamConnectionData): string {
1116
return `${CONVEX_DASHBOARD_HOST}/t/${encodeURIComponent(connection.convexTeamSlug)}`;
1217
}
@@ -25,13 +30,14 @@ export function formatConvexTeamConnection(connection: ConvexTeamConnectionData)
2530
const lines = [
2631
`${chalk.bold('Team')}: ${formatConvexTeam(connection)}`,
2732
`${chalk.bold('Dashboard')}: ${link(getConvexTeamDashboardUrl(connection), { dim: false })}`,
33+
`${chalk.bold('Claimed')}: ${connection.hasBeenClaimed ? 'Yes' : 'No'}`,
2834
];
2935

3036
if (connection.invitedEmail) {
3137
lines.push(`${chalk.bold('Invited email')}: ${connection.invitedEmail}`);
3238
}
3339
if (connection.invitedAt) {
34-
lines.push(`${chalk.bold('Invited at')}: ${connection.invitedAt}`);
40+
lines.push(`${chalk.bold('Invited at')}: ${formatConvexInviteTimestamp(connection.invitedAt)}`);
3541
}
3642

3743
return lines.join('\n');
@@ -69,7 +75,7 @@ export async function confirmRecentConvexInviteAsync(
6975
return true;
7076
}
7177

72-
const previousInvite = `A Convex team invite was already sent${connection.invitedEmail ? ` to ${connection.invitedEmail}` : ''} at ${connection.invitedAt}.`;
78+
const previousInvite = `A Convex team invite was already sent${connection.invitedEmail ? ` to ${connection.invitedEmail}` : ''} at ${formatConvexInviteTimestamp(connection.invitedAt)}.`;
7379
if (nonInteractive) {
7480
Log.warn(
7581
`${previousInvite} Sending another invite because this command is running in non-interactive mode.`

packages/eas-cli/src/commands/integrations/convex/__tests__/connect.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ describe(IntegrationsConvexConnect, () => {
7575
convexTeamIdentifier: 'team-123',
7676
convexTeamName: 'Test Team',
7777
convexTeamSlug: 'test-team',
78+
hasBeenClaimed: false,
7879
createdAt: '2024-01-01T00:00:00.000Z',
7980
updatedAt: '2024-01-01T00:00:00.000Z',
8081
invitedAt: null,
@@ -153,6 +154,9 @@ describe(IntegrationsConvexConnect, () => {
153154
graphqlClient,
154155
{ convexTeamConnectionId: 'connection-1' }
155156
);
157+
expect(Log.log).toHaveBeenCalledWith(
158+
expect.stringContaining('Check your email for an invitation')
159+
);
156160
expect(spawnAsync).toHaveBeenCalledWith('npx', ['expo', 'install', 'convex'], {
157161
cwd: testProjectDir,
158162
stdio: 'inherit',
@@ -237,6 +241,32 @@ describe(IntegrationsConvexConnect, () => {
237241
});
238242
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).not.toHaveBeenCalled();
239243
expect(Log.warn).toHaveBeenCalledWith('Skipped sending Convex team invitation.');
244+
expect(Log.log).not.toHaveBeenCalledWith(
245+
expect.stringContaining('Check your email for an invitation')
246+
);
247+
});
248+
249+
it('skips sending an invite when the Convex team has already been claimed', async () => {
250+
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
251+
{
252+
...mockConnection,
253+
hasBeenClaimed: true,
254+
invitedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
255+
},
256+
]);
257+
258+
await createCommand([]).runAsync();
259+
260+
expect(confirmAsync).not.toHaveBeenCalledWith({
261+
message: expect.stringContaining('Are you sure you want to send another invite?'),
262+
});
263+
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).not.toHaveBeenCalled();
264+
expect(Log.warn).toHaveBeenCalledWith(
265+
'Convex team has already been claimed. Skipping Convex team invitation.'
266+
);
267+
expect(Log.log).not.toHaveBeenCalledWith(
268+
expect.stringContaining('Check your email for an invitation')
269+
);
240270
});
241271

242272
it('writes the deploy key and Convex URL to .env.local', async () => {

packages/eas-cli/src/commands/integrations/convex/__tests__/dashboard.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe(IntegrationsConvexDashboard, () => {
3232
convexTeamIdentifier: 'team-123',
3333
convexTeamName: 'Test Team',
3434
convexTeamSlug: 'test team',
35+
hasBeenClaimed: false,
3536
createdAt: '2024-01-01T00:00:00.000Z',
3637
updatedAt: '2024-01-01T00:00:00.000Z',
3738
invitedAt: null,

packages/eas-cli/src/commands/integrations/convex/__tests__/project-delete.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe(IntegrationsConvexProjectDelete, () => {
3232
convexTeamIdentifier: 'team-123',
3333
convexTeamName: 'Test Team',
3434
convexTeamSlug: 'test-team',
35+
hasBeenClaimed: false,
3536
createdAt: '2024-01-01T00:00:00.000Z',
3637
updatedAt: '2024-01-01T00:00:00.000Z',
3738
invitedAt: null,

packages/eas-cli/src/commands/integrations/convex/__tests__/project.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe(IntegrationsConvexProject, () => {
2121
convexTeamIdentifier: 'team-123',
2222
convexTeamName: 'Test Team',
2323
convexTeamSlug: 'test-team',
24+
hasBeenClaimed: false,
2425
createdAt: '2024-01-01T00:00:00.000Z',
2526
updatedAt: '2024-01-01T00:00:00.000Z',
2627
invitedAt: null,

packages/eas-cli/src/commands/integrations/convex/__tests__/team-delete.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe(IntegrationsConvexTeamDelete, () => {
4040
convexTeamIdentifier: 'team-123',
4141
convexTeamName: 'Test Team',
4242
convexTeamSlug: 'test-team',
43+
hasBeenClaimed: false,
4344
createdAt: '2024-01-01T00:00:00.000Z',
4445
updatedAt: '2024-01-01T00:00:00.000Z',
4546
invitedAt: '2024-01-02T00:00:00.000Z',

packages/eas-cli/src/commands/integrations/convex/__tests__/team-invite.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ describe(IntegrationsConvexTeamInvite, () => {
4747
convexTeamIdentifier: 'team-123',
4848
convexTeamName: 'Test Team',
4949
convexTeamSlug: 'test-team',
50+
hasBeenClaimed: false,
5051
createdAt: '2024-01-01T00:00:00.000Z',
5152
updatedAt: '2024-01-01T00:00:00.000Z',
5253
invitedAt: '2024-01-02T00:00:00.000Z',
@@ -98,8 +99,10 @@ describe(IntegrationsConvexTeamInvite, () => {
9899
expect(Log.log).not.toHaveBeenCalledWith(expect.stringContaining('team-123'));
99100
expect(Log.log).not.toHaveBeenCalledWith(expect.stringContaining('connection-1'));
100101
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Previous invite'));
102+
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Claimed'));
103+
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('No'));
101104
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('previous@example.com'));
102-
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('2024-01-02T00:00:00.000Z'));
105+
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Jan 2, 2024 00:00:00 UTC'));
103106
});
104107

105108
it('prompts before resending a recent invite', async () => {
@@ -133,6 +136,24 @@ describe(IntegrationsConvexTeamInvite, () => {
133136
expect(Log.warn).toHaveBeenCalledWith('Skipped sending Convex team invitation.');
134137
});
135138

139+
it('skips sending an invite when the Convex team has already been claimed', async () => {
140+
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
141+
{
142+
...mockConnection,
143+
hasBeenClaimed: true,
144+
invitedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
145+
},
146+
]);
147+
148+
await createCommand([]).runAsync();
149+
150+
expect(confirmAsync).not.toHaveBeenCalled();
151+
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).not.toHaveBeenCalled();
152+
expect(Log.warn).toHaveBeenCalledWith(
153+
'Convex team has already been claimed. Skipping Convex team invitation.'
154+
);
155+
});
156+
136157
it('resends recent invites in non-interactive mode with a warning', async () => {
137158
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
138159
{

packages/eas-cli/src/commands/integrations/convex/__tests__/team.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe(IntegrationsConvexTeam, () => {
2929
convexTeamIdentifier: 'team-123',
3030
convexTeamName: 'Test Team',
3131
convexTeamSlug: 'test-team',
32+
hasBeenClaimed: true,
3233
createdAt: '2024-01-01T00:00:00.000Z',
3334
updatedAt: '2024-01-01T00:00:00.000Z',
3435
invitedAt: null,
@@ -65,6 +66,8 @@ describe(IntegrationsConvexTeam, () => {
6566
expect.stringContaining('Convex teams linked to @testuser')
6667
);
6768
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Test Team / test-team'));
69+
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Claimed'));
70+
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Yes'));
6871
expect(Log.log).not.toHaveBeenCalledWith(expect.stringContaining('team-123'));
6972
});
7073

0 commit comments

Comments
 (0)