Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/commands/apps/builds/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,75 @@ describe('apps-builds-create', () => {
expect(mockConsola.error).not.toHaveBeenCalled();
});

it('should send the channels as appChannelNames', async () => {
const options = { appId, platform: 'web' as const, gitRef: 'main', channel: ['beta', 'alpha'], detached: true };

const buildScope = nock(DEFAULT_API_BASE_URL)
.post(
`/v1/apps/${appId}/builds`,
(body) => JSON.stringify(body.appChannelNames) === JSON.stringify(['beta', 'alpha']),
)
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(201, { id: buildId, jobId: 'job-1', numberAsString: '42' });

await createCommand.action(options, undefined);

expect(buildScope.isDone()).toBe(true);
expect(mockConsola.error).not.toHaveBeenCalled();
});

it('should split comma-separated channels into separate appChannelNames', async () => {
const options = { appId, platform: 'web' as const, gitRef: 'main', channel: ['beta, alpha'], detached: true };

const buildScope = nock(DEFAULT_API_BASE_URL)
.post(
`/v1/apps/${appId}/builds`,
(body) => JSON.stringify(body.appChannelNames) === JSON.stringify(['beta', 'alpha']),
)
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(201, { id: buildId, jobId: 'job-1', numberAsString: '42' });

await createCommand.action(options, undefined);

expect(buildScope.isDone()).toBe(true);
expect(mockConsola.error).not.toHaveBeenCalled();
});

it('should accept --channel combined with --detached', async () => {
const options = { appId, platform: 'web' as const, gitRef: 'main', channel: ['beta'], detached: true };

const buildScope = nock(DEFAULT_API_BASE_URL)
.post(`/v1/apps/${appId}/builds`)
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(201, { id: buildId, jobId: 'job-1', numberAsString: '42' });

await createCommand.action(options, undefined);

expect(buildScope.isDone()).toBe(true);
expect(mockConsola.error).not.toHaveBeenCalled();
});

it('should reject --detached combined with --destination', async () => {
const options = { appId, platform: 'ios' as const, gitRef: 'main', destination: 'testflight', detached: true };

await expect(createCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');

expect(mockConsola.error).toHaveBeenCalledWith('The --detached flag cannot be used with the --destination flag.');
});

it('should parse a single channel into an array', () => {
const schema = createCommand.options?.schema;

const result = schema?.safeParse({
appId: validAppId,
platform: 'web',
gitRef: 'main',
channel: ['beta'],
});
expect(result?.success).toBe(true);
expect(result?.data?.channel).toEqual(['beta']);
});

it('should reject a non-positive shareExpiresInDays value', () => {
const schema = createCommand.options?.schema;

Expand Down
35 changes: 23 additions & 12 deletions src/commands/apps/builds/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ export default defineCommand({
.optional()
.describe('App ID to create the build for.'),
certificate: z.string().optional().describe('The name of the certificate to use for the build.'),
channel: z.string().optional().describe('The name of the channel to deploy to (Web only).'),
channel: z
.array(z.string())
.optional()
.describe('The name of a channel to deploy to (Web only). Can be specified multiple times or comma-separated.'),
configuration: z.string().optional().describe('The name of the native configuration (Android/iOS only).'),
destination: z.string().optional().describe('The name of the destination to deploy to (Android/iOS only).'),
detached: z
Expand Down Expand Up @@ -126,15 +129,20 @@ export default defineCommand({
url,
} = options;

const channels = options.channel
?.flatMap((value) => value.split(','))
.map((value) => value.trim())
.filter((value) => value.length > 0);
Comment thread
robingenz marked this conversation as resolved.

// Validate that detached flag cannot be used with artifact flags
if (options.detached && (options.apk || options.aab || options.ipa || options.zip)) {
consola.error('The --detached flag cannot be used with --apk, --aab, --ipa, or --zip flags.');
process.exit(1);
}

// Validate that detached flag cannot be used with channel or destination
if (options.detached && (options.channel || options.destination)) {
consola.error('The --detached flag cannot be used with --channel or --destination flags.');
// Validate that detached flag cannot be used with destination
if (options.detached && options.destination) {
consola.error('The --detached flag cannot be used with the --destination flag.');
process.exit(1);
}

Expand All @@ -157,7 +165,7 @@ export default defineCommand({
}

// Validate that channel and destination cannot be used together
if (options.channel && options.destination) {
if (channels?.length && options.destination) {
consola.error('The --channel and --destination flags cannot be used together.');
process.exit(1);
}
Expand Down Expand Up @@ -282,7 +290,7 @@ export default defineCommand({
}

// Validate that channel is only used with web platform
if (options.channel && platform !== 'web') {
if (channels?.length && platform !== 'web') {
consola.error('The --channel flag can only be used with the web platform.');
process.exit(1);
}
Expand Down Expand Up @@ -423,6 +431,7 @@ export default defineCommand({
adHocEnvironmentVariables,
appBuildSourceId,
appCertificateName: certificate,
appChannelNames: channels,
appConfigurationName: configuration,
appEnvironmentName: environment,
appId,
Expand All @@ -435,6 +444,11 @@ export default defineCommand({
consola.info(`Build Number: ${response.numberAsString}`);
consola.info(`Build URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/builds/${response.id}`);
consola.success('Build created successfully.');
if (channels?.length) {
consola.info(
`The build will be deployed to the following ${channels.length === 1 ? 'channel' : 'channels'} once it succeeds: ${channels.join(', ')}.`,
);
}

// Wait for build job to complete by default, unless --detached flag is set
const shouldWait = !options.detached;
Expand Down Expand Up @@ -523,14 +537,11 @@ export default defineCommand({
}
}

// Create deployment if channel or destination is set
if (options.channel || options.destination) {
// Create deployment if destination is set
if (options.destination) {
await (
await import('@/commands/apps/deployments/create.js').then((mod) => mod.default)
).action(
{ appId, buildId: response.id, channel: options.channel, destination: options.destination },
undefined,
);
).action({ appId, buildId: response.id, destination: options.destination }, undefined);
}

// Output JSON if json flag is set
Expand Down
1 change: 1 addition & 0 deletions src/types/app-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface CreateAppBuildDto {
adHocEnvironmentVariables?: Record<string, string>;
appBuildSourceId?: string;
appCertificateName?: string;
appChannelNames?: string[];
appConfigurationName?: string;
appEnvironmentName?: string;
appId: string;
Expand Down
Loading