@capawesome/cli
Version:
The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.
182 lines (181 loc) • 8.31 kB
JavaScript
import appCertificatesService from '../../../services/app-certificates.js';
import appProvisioningProfilesService from '../../../services/app-provisioning-profiles.js';
import { withAuth } from '../../../utils/auth.js';
import { isInteractive } from '../../../utils/environment.js';
import { isReadable } from '../../../utils/file.js';
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
import { defineCommand, defineOptions } from '@robingenz/zli';
import consola from 'consola';
import fs from 'fs';
import path from 'path';
import { z } from 'zod';
export default defineCommand({
description: 'Create a new app certificate.',
options: defineOptions(z.object({
appId: z.string().optional().describe('ID of the app.'),
file: z.string().optional().describe('Path to the certificate file.'),
keyAlias: z.string().optional().describe('Key alias for the certificate.'),
keyPassword: z.string().optional().describe('Key password for the certificate.'),
name: z.string().optional().describe('Name of the certificate.'),
password: z.string().optional().describe('Password for the certificate.'),
platform: z
.enum(['android', 'ios', 'web'])
.optional()
.describe('Platform of the certificate (android, ios, web).'),
provisioningProfile: z
.array(z.string())
.optional()
.describe('Paths to provisioning profile files to upload and link.'),
type: z
.enum(['development', 'production'])
.optional()
.describe('Type of the certificate (development, production).'),
yes: z.boolean().optional().describe('Skip confirmation prompts.'),
}), { y: 'yes' }),
action: withAuth(async (options, args) => {
let { appId, file, keyAlias, keyPassword, name, password, platform, provisioningProfile, type } = options;
// 1. Select organization and app
if (!appId) {
if (!isInteractive()) {
consola.error('You must provide an app ID when running in non-interactive environment.');
process.exit(1);
}
const organizationId = await promptOrganizationSelection();
appId = await promptAppSelection(organizationId);
}
// 2. Enter certificate name
if (!name) {
if (!isInteractive()) {
consola.error('You must provide the certificate name when running in non-interactive environment.');
process.exit(1);
}
name = await prompt('Enter the name of the certificate:', { type: 'text' });
if (!name) {
consola.error('You must provide a certificate name.');
process.exit(1);
}
}
// 3. Select platform
if (!platform) {
if (!isInteractive()) {
consola.error('You must provide the platform when running in non-interactive environment.');
process.exit(1);
}
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
platform = await prompt('Select the platform:', {
type: 'select',
options: [
{ label: 'Android', value: 'android' },
{ label: 'iOS', value: 'ios' },
{ label: 'Web', value: 'web' },
],
});
if (!platform) {
consola.error('You must select a platform.');
process.exit(1);
}
}
// 4. Warn if deprecated --type option is used
if (type) {
consola.warn('The --type option is deprecated and will be removed in a future version. The certificate type is now detected automatically.');
}
// 5. Enter certificate file path
if (!file) {
if (!isInteractive()) {
consola.error('You must provide a certificate file path when running in non-interactive environment.');
process.exit(1);
}
file = await prompt('Enter the path to the certificate file:', { type: 'text' });
if (!file) {
consola.error('You must provide a certificate file path.');
process.exit(1);
}
}
// 6. Enter certificate password (required for android/ios)
if (!password && (platform === 'android' || platform === 'ios')) {
if (!isInteractive()) {
consola.error('You must provide the certificate password when running in non-interactive environment.');
process.exit(1);
}
password = await prompt('Enter the certificate password:', { type: 'text' });
if (!password) {
consola.error('You must provide a certificate password.');
process.exit(1);
}
}
// 7. If Android, ask for key alias (required)
if (!keyAlias && platform === 'android') {
if (!isInteractive()) {
consola.error('You must provide the key alias when running in non-interactive environment.');
process.exit(1);
}
keyAlias = await prompt('Enter the key alias:', { type: 'text' });
if (!keyAlias) {
consola.error('You must provide a key alias.');
process.exit(1);
}
}
// 8. If Android, ask for key password (optional, skip with --yes)
if (!keyPassword && platform === 'android' && !options.yes && isInteractive()) {
keyPassword = await prompt('Enter the key password (leave empty if none):', { type: 'text' });
if (!keyPassword) {
keyPassword = undefined;
}
}
// 9. If iOS, ask for provisioning profile file path (optional, skip with --yes)
if (!provisioningProfile && platform === 'ios' && !options.yes && isInteractive()) {
const profilePath = await prompt('Enter the path to the provisioning profile file (leave empty to skip):', {
type: 'text',
});
if (profilePath) {
provisioningProfile = [profilePath];
}
}
const fileReadable = await isReadable(file);
if (!fileReadable) {
consola.error(`The certificate file does not exist or is not accessible: ${file}`);
process.exit(1);
}
const buffer = fs.readFileSync(file);
const fileName = path.basename(file);
// Upload provisioning profiles first
const provisioningProfileIds = [];
if (provisioningProfile && provisioningProfile.length > 0) {
for (const profilePath of provisioningProfile) {
const profileReadable = await isReadable(profilePath);
if (!profileReadable) {
consola.error(`The provisioning profile file does not exist or is not accessible: ${profilePath}`);
process.exit(1);
}
const profileBuffer = fs.readFileSync(profilePath);
const profileFileName = path.basename(profilePath);
const profile = await appProvisioningProfilesService.create({
appId,
buffer: profileBuffer,
fileName: profileFileName,
});
provisioningProfileIds.push(profile.id);
}
}
const certificate = await appCertificatesService.create({
appId,
buffer,
fileName,
name,
platform: platform,
password,
keyAlias,
keyPassword,
});
// Link provisioning profiles to the certificate
if (provisioningProfileIds.length > 0) {
await appProvisioningProfilesService.updateMany({
appId,
ids: provisioningProfileIds,
appCertificateId: certificate.id,
});
}
consola.info(`Certificate ID: ${certificate.id}`);
consola.success('Certificate created successfully.');
}),
});