Skip to content

Commit 82af4e2

Browse files
authored
Merge pull request #229 from udecode/codex/225-migrate-aggregate-deploy-env
2 parents 8df3c74 + a93f264 commit 82af4e2

6 files changed

Lines changed: 268 additions & 3 deletions

File tree

.changeset/serious-pans-sparkle.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"kitcn": patch
3+
---
4+
5+
## Patches
6+
7+
- Fix `kitcn migrate` and `kitcn aggregate` so Convex prod-target runs keep
8+
ambient deployment auth env in CI.

packages/kitcn/src/cli/backend-core.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5594,8 +5594,9 @@ export async function runAggregatePruneFlow(params: {
55945594
execaFn: typeof execa;
55955595
backendAdapter: BackendAdapter;
55965596
targetArgs: string[];
5597+
env?: Record<string, string | undefined>;
55975598
}): Promise<number> {
5598-
const { execaFn, backendAdapter, targetArgs } = params;
5599+
const { execaFn, backendAdapter, targetArgs, env } = params;
55995600
const result = await runBackendFunction(
56005601
execaFn,
56015602
backendAdapter,
@@ -5606,6 +5607,7 @@ export async function runAggregatePruneFlow(params: {
56065607
targetArgs,
56075608
{
56085609
echoOutput: false,
5610+
env,
56095611
}
56105612
);
56115613

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { describe, expect, mock, test } from 'bun:test';
2+
import { createDefaultConfig } from '../test-utils';
3+
import { handleAggregateCommand } from './aggregate';
4+
5+
describe('cli/commands/aggregate', () => {
6+
const withConvexDeployKey = async (
7+
deployKey: string,
8+
run: () => Promise<void>
9+
) => {
10+
const originalDeployKey = process.env.CONVEX_DEPLOY_KEY;
11+
process.env.CONVEX_DEPLOY_KEY = deployKey;
12+
13+
try {
14+
await run();
15+
} finally {
16+
process.env.CONVEX_DEPLOY_KEY = originalDeployKey;
17+
}
18+
};
19+
20+
test('handleAggregateCommand(backfill) forwards ambient Convex deployment env for convex backend', async () => {
21+
const calls: Record<string, string | undefined>[] = [];
22+
const execaStub = mock(
23+
async (
24+
_cmd: string,
25+
_args: string[],
26+
options?: { env?: Record<string, string | undefined> }
27+
) => {
28+
calls.push(options?.env ?? {});
29+
return {
30+
exitCode: 0,
31+
stdout: `${JSON.stringify({ scheduled: 0, targets: 0 })}\n`,
32+
stderr: '',
33+
} as any;
34+
}
35+
);
36+
const loadConfigStub = mock(() => {
37+
const config = createDefaultConfig();
38+
config.deploy.aggregateBackfill.wait = false;
39+
return config;
40+
});
41+
42+
await withConvexDeployKey('prod:demo|secret', async () => {
43+
const exitCode = await handleAggregateCommand(
44+
['aggregate', 'backfill', '--prod'],
45+
{
46+
realConvex: '/fake/convex/main.js',
47+
execa: execaStub as any,
48+
loadCliConfig: loadConfigStub as any,
49+
}
50+
);
51+
52+
expect(exitCode).toBe(0);
53+
});
54+
55+
expect(calls).toHaveLength(1);
56+
expect(calls[0]).toEqual(
57+
expect.objectContaining({
58+
CONVEX_DEPLOY_KEY: 'prod:demo|secret',
59+
})
60+
);
61+
});
62+
63+
test('handleAggregateCommand(prune) forwards ambient Convex deployment env for convex backend', async () => {
64+
const calls: Record<string, string | undefined>[] = [];
65+
const execaStub = mock(
66+
async (
67+
_cmd: string,
68+
_args: string[],
69+
options?: { env?: Record<string, string | undefined> }
70+
) => {
71+
calls.push(options?.env ?? {});
72+
return {
73+
exitCode: 0,
74+
stdout: `${JSON.stringify({ pruned: 0 })}\n`,
75+
stderr: '',
76+
} as any;
77+
}
78+
);
79+
const loadConfigStub = mock(() => createDefaultConfig());
80+
81+
await withConvexDeployKey('prod:demo|secret', async () => {
82+
const exitCode = await handleAggregateCommand(
83+
['aggregate', 'prune', '--prod'],
84+
{
85+
realConvex: '/fake/convex/main.js',
86+
execa: execaStub as any,
87+
loadCliConfig: loadConfigStub as any,
88+
}
89+
);
90+
91+
expect(exitCode).toBe(0);
92+
});
93+
94+
expect(calls).toHaveLength(1);
95+
expect(calls[0]).toEqual(
96+
expect.objectContaining({
97+
CONVEX_DEPLOY_KEY: 'prod:demo|secret',
98+
})
99+
);
100+
});
101+
});

packages/kitcn/src/cli/commands/aggregate.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
createBackendAdapter,
33
extractBackendRunTargetArgs,
44
extractBackfillCliOptions,
5+
getConvexDeploymentCommandEnv,
56
parseArgs,
67
type RunDeps,
78
resolveBackfillConfig,
@@ -43,6 +44,8 @@ export const handleAggregateCommand = async (
4344
realConvexPath,
4445
realConcavePath,
4546
});
47+
const commandEnv =
48+
backend === 'convex' ? getConvexDeploymentCommandEnv() : undefined;
4649
const {
4750
remainingArgs: aggregateCommandArgs,
4851
overrides: aggregateBackfillOverrides,
@@ -62,6 +65,7 @@ export const handleAggregateCommand = async (
6265
execaFn,
6366
backendAdapter,
6467
targetArgs,
68+
env: commandEnv,
6569
});
6670
}
6771

@@ -71,6 +75,7 @@ export const handleAggregateCommand = async (
7175
backfillConfig,
7276
mode: subcommand === 'rebuild' ? 'rebuild' : 'resume',
7377
targetArgs,
78+
env: commandEnv,
7479
context: 'aggregate',
7580
});
7681
};

packages/kitcn/src/cli/commands/migrate.test.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ import {
1313
} from './migrate';
1414

1515
describe('cli/commands/migrate', () => {
16+
const withConvexDeployKey = async (
17+
deployKey: string,
18+
run: () => Promise<void>
19+
) => {
20+
const originalDeployKey = process.env.CONVEX_DEPLOY_KEY;
21+
process.env.CONVEX_DEPLOY_KEY = deployKey;
22+
23+
try {
24+
await run();
25+
} finally {
26+
process.env.CONVEX_DEPLOY_KEY = originalDeployKey;
27+
}
28+
};
29+
1630
test('parseMigrateCommandArgs parses create/list/up/down/status/cancel shapes', () => {
1731
expect(parseMigrateCommandArgs(['create', 'Add', 'field'])).toEqual({
1832
subcommand: 'create',
@@ -254,4 +268,128 @@ describe('cli/commands/migrate', () => {
254268
expect(calls).toHaveLength(2);
255269
expect(calls[1]?.args).toContain('generated/server:migrationStatus');
256270
});
271+
272+
test('handleMigrateCommand(up) forwards ambient Convex deployment env for convex backend', async () => {
273+
const calls: Array<{
274+
args: string[];
275+
env: Record<string, string | undefined>;
276+
}> = [];
277+
const execaStub = mock(
278+
async (_cmd: string, args: string[], options?: { env?: unknown }) => {
279+
calls.push({
280+
args,
281+
env: (options?.env ?? {}) as Record<string, string | undefined>,
282+
});
283+
return {
284+
exitCode: 0,
285+
stdout: `${JSON.stringify({ status: 'noop' })}\n`,
286+
stderr: '',
287+
} as any;
288+
}
289+
);
290+
const loadConfigStub = mock(() => {
291+
const config = createDefaultConfig();
292+
config.deploy.migrations.wait = false;
293+
return config;
294+
});
295+
296+
await withConvexDeployKey('prod:demo|secret', async () => {
297+
const exitCode = await handleMigrateCommand(
298+
['migrate', 'up', '--prod', '--yes'],
299+
{
300+
realConvex: '/fake/convex/main.js',
301+
execa: execaStub as any,
302+
loadCliConfig: loadConfigStub as any,
303+
}
304+
);
305+
306+
expect(exitCode).toBe(0);
307+
});
308+
309+
expect(calls).toHaveLength(1);
310+
expect(calls[0]?.args).toContain('--prod');
311+
expect(calls[0]?.env).toEqual(
312+
expect.objectContaining({
313+
CONVEX_DEPLOY_KEY: 'prod:demo|secret',
314+
})
315+
);
316+
});
317+
318+
test('handleMigrateCommand(status) forwards ambient Convex deployment env for convex backend', async () => {
319+
const calls: Record<string, string | undefined>[] = [];
320+
const execaStub = mock(
321+
async (
322+
_cmd: string,
323+
_args: string[],
324+
options?: { env?: Record<string, string | undefined> }
325+
) => {
326+
calls.push(options?.env ?? {});
327+
return {
328+
exitCode: 0,
329+
stdout: '{}\n',
330+
stderr: '',
331+
} as any;
332+
}
333+
);
334+
const loadConfigStub = mock(() => createDefaultConfig());
335+
336+
await withConvexDeployKey('prod:demo|secret', async () => {
337+
const exitCode = await handleMigrateCommand(
338+
['migrate', 'status', '--prod'],
339+
{
340+
realConvex: '/fake/convex/main.js',
341+
execa: execaStub as any,
342+
loadCliConfig: loadConfigStub as any,
343+
}
344+
);
345+
346+
expect(exitCode).toBe(0);
347+
});
348+
349+
expect(calls).toHaveLength(1);
350+
expect(calls[0]).toEqual(
351+
expect.objectContaining({
352+
CONVEX_DEPLOY_KEY: 'prod:demo|secret',
353+
})
354+
);
355+
});
356+
357+
test('handleMigrateCommand(cancel) forwards ambient Convex deployment env for convex backend', async () => {
358+
const calls: Record<string, string | undefined>[] = [];
359+
const execaStub = mock(
360+
async (
361+
_cmd: string,
362+
_args: string[],
363+
options?: { env?: Record<string, string | undefined> }
364+
) => {
365+
calls.push(options?.env ?? {});
366+
return {
367+
exitCode: 0,
368+
stdout: '{}\n',
369+
stderr: '',
370+
} as any;
371+
}
372+
);
373+
const loadConfigStub = mock(() => createDefaultConfig());
374+
375+
await withConvexDeployKey('prod:demo|secret', async () => {
376+
const exitCode = await handleMigrateCommand(
377+
['migrate', 'cancel', '--prod', '--run-id', 'mr_123'],
378+
{
379+
realConvex: '/fake/convex/main.js',
380+
execa: execaStub as any,
381+
loadCliConfig: loadConfigStub as any,
382+
}
383+
);
384+
385+
expect(exitCode).toBe(0);
386+
});
387+
388+
expect(calls).toHaveLength(1);
389+
expect(calls[0]).toEqual(
390+
expect.objectContaining({
391+
CONVEX_DEPLOY_KEY: 'prod:demo|secret',
392+
})
393+
);
394+
});
257395
});

packages/kitcn/src/cli/commands/migrate.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
extractBackendRunTargetArgs,
44
extractMigrationCliOptions,
55
extractMigrationDownOptions,
6+
getConvexDeploymentCommandEnv,
67
parseArgs,
78
type RunDeps,
89
resolveConfiguredBackend,
@@ -110,6 +111,8 @@ export const handleMigrateCommand = async (
110111
realConvexPath,
111112
realConcavePath,
112113
});
114+
const commandEnv =
115+
backend === 'convex' ? getConvexDeploymentCommandEnv() : undefined;
113116

114117
if (migrateArgs.subcommand === 'create') {
115118
const rawName = migrateArgs.restArgs.join(' ').trim();
@@ -142,6 +145,7 @@ export const handleMigrateCommand = async (
142145
backendAdapter,
143146
migrationConfig,
144147
targetArgs,
148+
env: commandEnv,
145149
context: 'migration',
146150
direction: 'up',
147151
});
@@ -156,6 +160,7 @@ export const handleMigrateCommand = async (
156160
backendAdapter,
157161
migrationConfig,
158162
targetArgs: downTargetArgs,
163+
env: commandEnv,
159164
context: 'migration',
160165
direction: 'down',
161166
steps,
@@ -169,7 +174,10 @@ export const handleMigrateCommand = async (
169174
backendAdapter,
170175
'generated/server:migrationStatus',
171176
{},
172-
targetArgs
177+
targetArgs,
178+
{
179+
env: commandEnv,
180+
}
173181
);
174182
return statusResult.exitCode;
175183
}
@@ -203,7 +211,10 @@ export const handleMigrateCommand = async (
203211
backendAdapter,
204212
'generated/server:migrationCancel',
205213
runId ? { runId } : {},
206-
cancelTargetArgs
214+
cancelTargetArgs,
215+
{
216+
env: commandEnv,
217+
}
207218
);
208219
return cancelResult.exitCode;
209220
};

0 commit comments

Comments
 (0)