appstore-cli
Version:
A command-line interface (CLI) to interact with the Apple App Store Connect API.
201 lines (192 loc) • 8.59 kB
text/typescript
const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');
import * as listApps from './commands/list-apps.js';
import * as getAppDetails from './commands/get-app-details.js';
import * as listBuilds from './commands/list-builds.js';
import * as getBuildDetails from './commands/get-build-details.js';
import * as uploadBuild from './commands/upload-build.js';
import * as listBetaTesters from './commands/list-beta-testers.js';
import * as addBetaTester from './commands/add-beta-tester.js';
import * as removeBetaTester from './commands/remove-beta-tester.js';
import * as listBetaGroups from './commands/list-beta-groups.js';
import * as addTesterToGroup from './commands/add-tester-to-group.js';
import * as downloadSalesReports from './commands/download-sales-reports.js';
import * as downloadFinancialReports from './commands/download-financial-reports.js';
import * as createApp from './commands/create-app.js';
import * as createBetaGroup from './commands/create-beta-group.js';
import * as submitBuild from './commands/submit-build.js';
const { buildConfigureCommand } = require('./cli/build-configure-command.js');
const { buildRunCommand } = require('./cli/build-run-command.js');
const { exportConfigureCommand } = require('./cli/export-configure-command.js');
const { exportRunCommand } = require('./cli/export-run-command.js');
const { certificateConfigureCommand } = require('./cli/certificate-configure-command.js');
const { saveConfig, loadConfig } = require('./config.js');
const fs = require('fs');
yargs(hideBin(process.argv))
.command('hello', 'prints hello world', () => {
console.log('hello world');
})
.command('config <subcommand>', 'Manage configuration', (yargs: any) => {
yargs
.command('set', 'Set API credentials', (yargs: any) => {
return yargs
.option('keyId', {
alias: 'k',
describe: 'Your App Store Connect API Key ID',
type: 'string',
demandOption: false
})
.option('issuerId', {
alias: 'i',
describe: 'Your App Store Connect API Issuer ID',
type: 'string',
demandOption: false
})
.option('privateKey', {
alias: 'p',
describe: 'Your App Store Connect API Private Key',
type: 'string',
demandOption: false
})
.option('privateKeyPath', {
alias: 'P',
describe: 'Path to your App Store Connect API Private Key file',
type: 'string',
demandOption: false
})
.option('fastlaneToken', {
alias: 'f',
describe: 'Fastlane spaceauth session token',
type: 'string',
demandOption: false
})
.option('username', {
alias: 'u',
describe: 'Apple ID username for authentication',
type: 'string',
demandOption: false
})
.check((argv: any) => {
// Ensure we have either API credentials or Fastlane token
const hasApiCredentials = argv.keyId && argv.issuerId && (argv.privateKey || argv.privateKeyPath);
const hasFastlaneToken = argv.fastlaneToken;
if (!hasApiCredentials && !hasFastlaneToken) {
throw new Error('You must provide either API credentials (--keyId, --issuerId, and --privateKey/--privateKeyPath) or a Fastlane token (--fastlaneToken)');
}
// If we have API credentials, ensure we have all required parts
if (argv.keyId || argv.issuerId || argv.privateKey || argv.privateKeyPath) {
if (!(argv.keyId && argv.issuerId && (argv.privateKey || argv.privateKeyPath))) {
throw new Error('If providing API credentials, you must provide --keyId, --issuerId, and either --privateKey or --privateKeyPath');
}
}
// Ensure exactly one of privateKey or privateKeyPath is provided if we have API credentials
if (argv.privateKey && argv.privateKeyPath) {
throw new Error('You must provide exactly one of --privateKey or --privateKeyPath');
}
return true;
});
}, async (argv: any) => {
const { keyId, issuerId, privateKey, privateKeyPath, fastlaneToken, username } = argv;
// If privateKeyPath is provided, read the private key from the file
let actualPrivateKey: string | undefined;
if (privateKeyPath) {
if (!fs.existsSync(privateKeyPath)) {
throw new Error(`Private key file not found: ${privateKeyPath}`);
}
actualPrivateKey = fs.readFileSync(privateKeyPath, 'utf8');
if (!actualPrivateKey || !actualPrivateKey.trim()) {
throw new Error(`Private key file is empty: ${privateKeyPath}`);
}
} else if (privateKey) {
actualPrivateKey = privateKey as string;
}
await saveConfig({
keyId,
issuerId,
...(actualPrivateKey && { privateKey: actualPrivateKey }),
...(fastlaneToken && { fastlaneToken }),
...(username && { username })
});
console.log('Configuration saved successfully.');
})
.command('get', 'Get current configuration', () => {},
() => {
try {
const config = loadConfig();
console.log('Current configuration:');
console.log(` Key ID: ${config.keyId}`);
console.log(` Issuer ID: ${config.issuerId}`);
console.log('');
console.log('Note: Private key is stored securely and not displayed.');
console.log('You can also configure the CLI using environment variables:');
console.log(' APPSTORE_KEY_ID: Your App Store Connect API Key ID');
console.log(' APPSTORE_ISSUER_ID: Your App Store Connect API Issuer ID');
console.log(' APPSTORE_PRIVATE_KEY: Your App Store Connect API Private Key');
} catch (error: any) {
console.error(error.message);
console.log('');
console.log('You can configure the CLI using environment variables:');
console.log(' APPSTORE_KEY_ID: Your App Store Connect API Key ID');
console.log(' APPSTORE_ISSUER_ID: Your App Store Connect API Issuer ID');
console.log(' APPSTORE_PRIVATE_KEY: Your App Store Connect API Private Key');
}
}
)
.demandCommand(1, 'You need to specify a subcommand (set or get).');
})
.command('apps', 'Manage apps', (yargs: any) => {
yargs
.command(listApps)
.command(getAppDetails)
.command(createApp)
.demandCommand(1, 'You need to specify a subcommand.');
})
.command('builds', 'Manage builds', (yargs: any) => {
yargs
.command(listBuilds)
.command(getBuildDetails)
.command(uploadBuild)
.command(submitBuild)
.demandCommand(1, 'You need to specify a subcommand.');
})
.command('build', 'Build and export iOS apps', (yargs: any) => {
yargs
.command(buildConfigureCommand)
.command(buildRunCommand)
.command('export', 'Export IPA file', (yargs: any) => {
yargs
.command(exportConfigureCommand)
.command(exportRunCommand)
.demandCommand(1, 'You need to specify a subcommand.');
})
.command('certificate', 'Configure certificates and provisioning profiles', (yargs: any) => {
yargs
.command(certificateConfigureCommand)
.demandCommand(1, 'You need to specify a subcommand.');
})
.demandCommand(1, 'You need to specify a subcommand.');
})
.command('testers', 'Manage beta testers', (yargs: any) => {
yargs
.command(listBetaTesters)
.command(addBetaTester)
.command(removeBetaTester)
.demandCommand(1, 'You need to specify a subcommand.');
})
.command('groups', 'Manage beta groups', (yargs: any) => {
yargs
.command(listBetaGroups)
.command(addTesterToGroup)
.command(createBetaGroup)
.demandCommand(1, 'You need to specify a subcommand.');
})
.command('reports', 'Download reports', (yargs: any) => {
yargs
.command(downloadSalesReports)
.command(downloadFinancialReports)
.demandCommand(1, 'You need to specify a subcommand.');
})
.version()
.demandCommand(1)
.parse();