appstore-cli
Version:
A command-line interface (CLI) to interact with the Apple App Store Connect API.
161 lines (148 loc) • 6.24 kB
text/typescript
import yargs from 'yargs';
import { ConfigurationValidationMiddleware } from '../middleware/configuration-validation-middleware.js';
import fastlaneService from '../services/fastlane-service.js';
import { FastlaneServiceError } from '../services/fastlane-service.js';
import { safeLogger } from '../security/dataHandler.js';
import configurationService from '../services/configuration-service.js';
import migrateBuildAuthConfig from '../scripts/migrate-build-auth.js';
/**
* CLI command for running the build process.
*/
export const buildRunCommand: yargs.CommandModule<{}, {
clean?: boolean;
'provisioning-profile-path'?: string;
'certificate-path'?: string;
'certificate-password'?: string;
'use-automatic-signing'?: boolean;
'log-output'?: string;
}> = {
command: 'run',
describe: 'Execute the build process',
builder: (yargs) => {
return yargs
.option('clean', {
alias: 'c',
describe: 'Clean the build before building',
type: 'boolean',
default: false,
})
.option('provisioning-profile-path', {
describe: 'Path to the provisioning profile file (.mobileprovision)',
type: 'string',
})
.option('certificate-path', {
describe: 'Path to the signing certificate file (.p12 or .pem)',
type: 'string',
})
.option('certificate-password', {
describe: 'Password for the signing certificate (if encrypted)',
type: 'string',
})
.option('use-automatic-signing', {
describe: 'Whether to use automatic code signing',
type: 'boolean',
})
.option('log-output', {
alias: 'l',
describe: 'Path to save the build log output to a file',
type: 'string',
})
.check((argv) => {
// Validate certificate and provisioning profile paths for runtime overrides
if (argv['provisioning-profile-path']) {
if (!require('fs').existsSync(argv['provisioning-profile-path'])) {
throw new Error(`Provisioning profile file not found: ${argv['provisioning-profile-path']}`);
}
if (!argv['provisioning-profile-path'].endsWith('.mobileprovision')) {
throw new Error('Provisioning profile file must have .mobileprovision extension');
}
}
if (argv['certificate-path']) {
if (!require('fs').existsSync(argv['certificate-path'])) {
throw new Error(`Certificate file not found: ${argv['certificate-path']}`);
}
if (!argv['certificate-path'].endsWith('.p12') && !argv['certificate-path'].endsWith('.pem')) {
throw new Error('Certificate file must have .p12 or .pem extension');
}
}
if (argv['certificate-path'] && argv['certificate-path'].endsWith('.p12') &&
argv['certificate-password'] === undefined) {
throw new Error('Certificate password is required for .p12 certificate files');
}
if (argv['use-automatic-signing'] &&
(argv['provisioning-profile-path'] || argv['certificate-path'])) {
throw new Error('Cannot use automatic signing with manual provisioning profile or certificate paths');
}
// Validate log output path
if (argv['log-output']) {
const path = require('path');
const dir = path.dirname(argv['log-output']);
if (!require('fs').existsSync(dir)) {
throw new Error(`Log output directory does not exist: ${dir}`);
}
}
return true;
});
},
handler: async (argv) => {
try {
// Check if we have an old build auth configuration and migrate it
const authConfig = configurationService.loadAuthenticationConfiguration();
if (authConfig) {
console.warn('⚠️ WARNING: Found deprecated build auth configuration.');
console.warn('Migrating to unified appstore-cli config...');
await migrateBuildAuthConfig();
console.warn('Migration completed. You can now remove the separate build auth configuration.');
console.warn('');
}
// Check if we have certificate configuration
let certConfig = configurationService.loadCertificateConfiguration() || {};
// Validate configurations using middleware
const { buildConfig, authConfig: validatedAuthConfig } = ConfigurationValidationMiddleware.validateAllConfigurations();
if (!buildConfig) {
console.error('Build configuration not found. Please run "appstore-cli build configure" first.');
process.exit(1);
}
// Merge runtime certificate overrides (do not persist)
const runtimeCertOverrides: any = {};
if (argv['provisioning-profile-path']) {
runtimeCertOverrides.provisioningProfilePath = argv['provisioning-profile-path'];
}
if (argv['certificate-path']) {
runtimeCertOverrides.certificatePath = argv['certificate-path'];
}
if (argv['certificate-password']) {
runtimeCertOverrides.certificatePassword = argv['certificate-password'];
}
if (argv['use-automatic-signing'] !== undefined) {
runtimeCertOverrides.useAutomaticSigning = argv['use-automatic-signing'];
}
certConfig = { ...certConfig, ...runtimeCertOverrides };
// Execute the build
console.log('Starting build process...');
const archivePath = await fastlaneService.executeBuild(
buildConfig,
validatedAuthConfig || {},
certConfig,
argv.clean || false,
argv['log-output']
);
// Output success message
console.log(`Build completed successfully. Archive saved to: ${archivePath}`);
} catch (error) {
if (error instanceof FastlaneServiceError) {
console.error(`Build failed: ${error.message}`);
safeLogger.error('Build failed with FastlaneServiceError', {
error: error.message,
code: error.code
});
} else {
console.error(`Build failed: ${(error as Error).message}`);
safeLogger.error('Build failed with unexpected error', {
error: (error as Error).message
});
}
process.exit(1);
}
},
};