Skip to content

Commit e7e2935

Browse files
do some checks
1 parent 849c238 commit e7e2935

5 files changed

Lines changed: 108 additions & 42 deletions

File tree

dist/index.js

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57464,7 +57464,7 @@ exports.UnauthorizedError = void 0;
5746457464
exports.GetAppId = GetAppId;
5746557465
exports.GetLatestBundleVersion = GetLatestBundleVersion;
5746657466
exports.UpdateTestDetails = UpdateTestDetails;
57467-
exports.ListCertificates = ListCertificates;
57467+
exports.GetCertificate = GetCertificate;
5746857468
const app_store_connect_api_1 = __nccwpck_require__(9073);
5746957469
const utilities_1 = __nccwpck_require__(5739);
5747057470
const core = __nccwpck_require__(2186);
@@ -57749,13 +57749,14 @@ async function UpdateTestDetails(project, whatsNew) {
5774957749
function normalizeVersion(version) {
5775057750
return version.split('.').map(part => parseInt(part, 10).toString()).join('.');
5775157751
}
57752-
async function ListCertificates(project, certificateType) {
57752+
async function GetCertificate(project, certificateType = null) {
5775357753
await getOrCreateClient(project);
57754-
const certificateQuery = {
57755-
query: {
57756-
"filter[certificateType]": certificateType,
57757-
}
57758-
};
57754+
const certificateQuery = {};
57755+
if (certificateType) {
57756+
certificateQuery.query = {
57757+
'filter[certificateType]': [certificateType],
57758+
};
57759+
}
5775957760
(0, utilities_1.log)(`GET /certificates?${JSON.stringify(certificateQuery.query)}`);
5776057761
const { data: response, error: responseError } = await appStoreConnectClient.api.CertificatesService.certificatesGetCollection(certificateQuery);
5776157762
if (responseError) {
@@ -57764,10 +57765,17 @@ async function ListCertificates(project, certificateType) {
5776457765
}
5776557766
const responseJson = JSON.stringify(response, null, 2);
5776657767
if (!response || !response.data || response.data.length === 0) {
57767-
throw new Error(`No certificates found! ${responseJson}`);
57768+
return null;
5776857769
}
5776957770
(0, utilities_1.log)(responseJson);
57770-
return response.data;
57771+
const validCerts = response.data.filter(certificate => {
57772+
if (!certificate.attributes) {
57773+
return false;
57774+
}
57775+
const isExpired = new Date(certificate.attributes.expirationDate) < new Date();
57776+
return certificate.attributes.activated && !isExpired;
57777+
});
57778+
return validCerts.length === 0 ? null : validCerts[0];
5777157779
}
5777257780

5777357781

@@ -57782,6 +57790,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
5778257790
exports.AppleCredential = void 0;
5778357791
exports.ImportCredentials = ImportCredentials;
5778457792
exports.RemoveCredentials = RemoveCredentials;
57793+
exports.ImportCertificate = ImportCertificate;
5778557794
const core = __nccwpck_require__(2186);
5778657795
const exec = __nccwpck_require__(1514);
5778757796
const uuid = __nccwpck_require__(5840);
@@ -57933,6 +57942,15 @@ async function RemoveCredentials() {
5793357942
core.error(`Failed to remove app store connect key!\n${error.stack}`);
5793457943
}
5793557944
}
57945+
async function ImportCertificate(certificate) {
57946+
const tempCredential = core.getState('tempCredential');
57947+
if (!tempCredential) {
57948+
throw new Error('Missing tempCredential state');
57949+
}
57950+
const keychainPath = `${temp}/${tempCredential}.keychain-db`;
57951+
const certificatePath = `${temp}/${certificate.attributes.name}.cer`;
57952+
core.info('Importing certificate...');
57953+
}
5793657954

5793757955

5793857956
/***/ }),
@@ -58017,6 +58035,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
5801758035
exports.GetProjectDetails = GetProjectDetails;
5801858036
exports.ArchiveXcodeProject = ArchiveXcodeProject;
5801958037
exports.ExportXcodeArchive = ExportXcodeArchive;
58038+
exports.isAppBundleNotarized = isAppBundleNotarized;
5802058039
exports.ValidateApp = ValidateApp;
5802158040
exports.UploadApp = UploadApp;
5802258041
const XcodeProject_1 = __nccwpck_require__(1981);
@@ -58427,17 +58446,18 @@ async function ExportXcodeArchive(projectRef) {
5842758446
if (!projectRef.isAppStoreUpload()) {
5842858447
const notarizeInput = core.getInput('notarize') || 'true';
5842958448
projectRef.executablePath = await getFirstPathWithGlob(`${projectRef.exportPath}/**/*.app`);
58430-
const notarize = notarizeInput === 'true';
58431-
core.debug(`Notarize? ${notarize}`);
58432-
if (notarize) {
58449+
if (notarizeInput === 'true') {
5843358450
await signMacOSAppBundle(projectRef);
5843458451
if (!projectRef.isSteamBuild) {
5843558452
projectRef.executablePath = await createMacOSInstallerPkg(projectRef);
5843658453
}
5843758454
else {
58438-
const zipPath = path.join(projectRef.exportPath, projectRef.executablePath.replace('.app', '.zip'));
58439-
await (0, exec_1.exec)('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', projectRef.executablePath, zipPath]);
58440-
await notarizeArchive(projectRef, zipPath, projectRef.executablePath);
58455+
const isNotarized = await isAppBundleNotarized(projectRef.executablePath);
58456+
if (!isNotarized) {
58457+
const zipPath = path.join(projectRef.exportPath, projectRef.executablePath.replace('.app', '.zip'));
58458+
await (0, exec_1.exec)('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', projectRef.executablePath, zipPath]);
58459+
await notarizeArchive(projectRef, zipPath, projectRef.executablePath);
58460+
}
5844158461
}
5844258462
}
5844358463
}
@@ -58458,6 +58478,16 @@ async function ExportXcodeArchive(projectRef) {
5845858478
core.setOutput('executable', projectRef.executablePath);
5845958479
return projectRef;
5846058480
}
58481+
async function isAppBundleNotarized(appPath) {
58482+
let output = '';
58483+
const exitCode = await (0, exec_1.exec)('stapler', ['validate', appPath], {
58484+
listeners: {
58485+
stdout: (data) => { output += data.toString(); }
58486+
},
58487+
ignoreReturnCode: true
58488+
});
58489+
return exitCode === 0 && output.includes('The validate action worked!');
58490+
}
5846158491
async function getFirstPathWithGlob(globPattern) {
5846258492
const globber = await glob.create(globPattern);
5846358493
const files = await globber.glob();
@@ -58474,7 +58504,6 @@ async function signMacOSAppBundle(projectRef) {
5847458504
throw new Error(`Not a valid app bundle: ${appPath}`);
5847558505
}
5847658506
const signAppBundlePath = __nccwpck_require__.ab + "sign-app-bundle.sh";
58477-
core.info(`Signing app bundle: ${appPath}`);
5847858507
let codesignOutput = '';
5847958508
const codesignExitCode = await (0, exec_1.exec)('sh', [__nccwpck_require__.ab + "sign-app-bundle.sh", appPath, projectRef.entitlementsPath], {
5848058509
listeners: {
@@ -58485,10 +58514,8 @@ async function signMacOSAppBundle(projectRef) {
5848558514
ignoreReturnCode: true
5848658515
});
5848758516
if (codesignExitCode !== 0) {
58488-
(0, utilities_1.log)(codesignOutput, 'error');
5848958517
throw new Error(`Failed to code sign the app!`);
5849058518
}
58491-
core.info(codesignOutput);
5849258519
}
5849358520
async function createMacOSInstallerPkg(projectRef) {
5849458521
core.info('Creating macOS installer pkg...');
@@ -58513,6 +58540,11 @@ async function createMacOSInstallerPkg(projectRef) {
5851358540
catch (error) {
5851458541
throw new Error(`Failed to create the pkg at: ${pkgPath}!`);
5851558542
}
58543+
const developerIdInstallerCert = await (0, AppStoreConnectClient_1.GetCertificate)(projectRef, 'MAC_INSTALLER_DISTRIBUTION');
58544+
core.info(`Found Developer ID Installer certificate: [${developerIdInstallerCert.id}] ${developerIdInstallerCert.attributes.name}`);
58545+
const certPath = path.join(process.env.RUNNER_TEMP, 'developer_id_installer.cer');
58546+
await fs.promises.writeFile(certPath, developerIdInstallerCert.attributes.certificateContent);
58547+
core.info(`Saved Developer ID Installer certificate to: ${certPath}`);
5851658548
const signPkgPath = __nccwpck_require__.ab + "sign-app-pkg.sh";
5851758549
core.info(`Signing pkg: ${pkgPath}`);
5851858550
let codesignOutput = '';

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/AppStoreConnectClient.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,19 +304,17 @@ function normalizeVersion(version: string): string {
304304
return version.split('.').map(part => parseInt(part, 10).toString()).join('.');
305305
}
306306

307-
/*
308-
* List all the certificates for the given certificate type.
309-
* @param project The Xcode project.
310-
* @param certificateType The type of certificate to list.
307+
/**
311308
* https://developer.apple.com/documentation/appstoreconnectapi/list_and_download_certificates
312309
*/
313-
export async function ListCertificates(project: XcodeProject, certificateType: Array<('APPLE_PAY' | 'APPLE_PAY_MERCHANT_IDENTITY' | 'APPLE_PAY_PSP_IDENTITY' | 'APPLE_PAY_RSA' | 'DEVELOPER_ID_KEXT' | 'DEVELOPER_ID_KEXT_G2' | 'DEVELOPER_ID_APPLICATION' | 'DEVELOPER_ID_APPLICATION_G2' | 'DEVELOPMENT' | 'DISTRIBUTION' | 'IDENTITY_ACCESS' | 'IOS_DEVELOPMENT' | 'IOS_DISTRIBUTION' | 'MAC_APP_DISTRIBUTION' | 'MAC_INSTALLER_DISTRIBUTION' | 'MAC_APP_DEVELOPMENT' | 'PASS_TYPE_ID' | 'PASS_TYPE_ID_WITH_NFC')>): Promise<Certificate[]> {
310+
export async function GetCertificate(project: XcodeProject, certificateType: 'APPLE_PAY' | 'APPLE_PAY_MERCHANT_IDENTITY' | 'APPLE_PAY_PSP_IDENTITY' | 'APPLE_PAY_RSA' | 'DEVELOPER_ID_KEXT' | 'DEVELOPER_ID_KEXT_G2' | 'DEVELOPER_ID_APPLICATION' | 'DEVELOPER_ID_APPLICATION_G2' | 'DEVELOPMENT' | 'DISTRIBUTION' | 'IDENTITY_ACCESS' | 'IOS_DEVELOPMENT' | 'IOS_DISTRIBUTION' | 'MAC_APP_DISTRIBUTION' | 'MAC_INSTALLER_DISTRIBUTION' | 'MAC_APP_DEVELOPMENT' | 'PASS_TYPE_ID' | 'PASS_TYPE_ID_WITH_NFC' = null): Promise<Certificate> {
314311
await getOrCreateClient(project);
315-
const certificateQuery: CertificatesGetCollectionData = {
316-
query: {
317-
"filter[certificateType]": certificateType,
312+
const certificateQuery: CertificatesGetCollectionData = {};
313+
if (certificateType) {
314+
certificateQuery.query = {
315+
'filter[certificateType]': [certificateType],
318316
}
319-
};
317+
}
320318
log(`GET /certificates?${JSON.stringify(certificateQuery.query)}`);
321319
const { data: response, error: responseError } = await appStoreConnectClient.api.CertificatesService.certificatesGetCollection(certificateQuery);
322320
if (responseError) {
@@ -325,8 +323,13 @@ export async function ListCertificates(project: XcodeProject, certificateType: A
325323
}
326324
const responseJson = JSON.stringify(response, null, 2);
327325
if (!response || !response.data || response.data.length === 0) {
328-
throw new Error(`No certificates found! ${responseJson}`);
326+
return null;
329327
}
330328
log(responseJson);
331-
return response.data;
329+
const validCerts = response.data.filter(certificate => {
330+
if (!certificate.attributes) { return false; }
331+
const isExpired = new Date(certificate.attributes.expirationDate) < new Date();
332+
return certificate.attributes.activated && !isExpired;
333+
});
334+
return validCerts.length === 0 ? null : validCerts[0];
332335
}

src/AppleCredential.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import core = require('@actions/core');
22
import exec = require('@actions/exec');
33
import uuid = require('uuid');
44
import fs = require('fs');
5+
import { Certificate } from '@rage-against-the-pixel/app-store-connect-api/dist/app_store_connect_api';
56

67
const security = '/usr/bin/security';
78
const temp = process.env['RUNNER_TEMP'] || '.';
@@ -180,3 +181,18 @@ export async function RemoveCredentials(): Promise<void> {
180181
core.error(`Failed to remove app store connect key!\n${error.stack}`);
181182
}
182183
}
184+
185+
/**
186+
* Imports the specified certificate from app store connect into the keychain created by the action in ImportCredentials.
187+
* @param certificate
188+
* @returns void
189+
*/
190+
export async function ImportCertificate(certificate: Certificate): Promise<void> {
191+
const tempCredential = core.getState('tempCredential');
192+
if (!tempCredential) {
193+
throw new Error('Missing tempCredential state');
194+
}
195+
const keychainPath = `${temp}/${tempCredential}.keychain-db`;
196+
const certificatePath = `${temp}/${certificate.attributes.name}.cer`;
197+
core.info('Importing certificate...');
198+
}

src/xcode.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
GetLatestBundleVersion,
1212
UpdateTestDetails,
1313
UnauthorizedError,
14-
GetAppId
14+
GetAppId,
15+
GetCertificate
1516
} from './AppStoreConnectClient';
1617
import { log } from './utilities';
1718
import core = require('@actions/core');
@@ -428,16 +429,17 @@ export async function ExportXcodeArchive(projectRef: XcodeProject): Promise<Xcod
428429
if (!projectRef.isAppStoreUpload()) {
429430
const notarizeInput = core.getInput('notarize') || 'true'
430431
projectRef.executablePath = await getFirstPathWithGlob(`${projectRef.exportPath}/**/*.app`);
431-
const notarize = notarizeInput === 'true';
432-
core.debug(`Notarize? ${notarize}`);
433-
if (notarize) {
432+
if (notarizeInput === 'true') {
434433
await signMacOSAppBundle(projectRef);
435434
if (!projectRef.isSteamBuild) {
436435
projectRef.executablePath = await createMacOSInstallerPkg(projectRef);
437436
} else {
438-
const zipPath = path.join(projectRef.exportPath, projectRef.executablePath.replace('.app', '.zip'));
439-
await exec('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', projectRef.executablePath, zipPath]);
440-
await notarizeArchive(projectRef, zipPath, projectRef.executablePath);
437+
const isNotarized = await isAppBundleNotarized(projectRef.executablePath);
438+
if (!isNotarized) {
439+
const zipPath = path.join(projectRef.exportPath, projectRef.executablePath.replace('.app', '.zip'));
440+
await exec('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', projectRef.executablePath, zipPath]);
441+
await notarizeArchive(projectRef, zipPath, projectRef.executablePath);
442+
}
441443
}
442444
}
443445
}
@@ -457,6 +459,17 @@ export async function ExportXcodeArchive(projectRef: XcodeProject): Promise<Xcod
457459
return projectRef;
458460
}
459461

462+
export async function isAppBundleNotarized(appPath: string): Promise<boolean> {
463+
let output = '';
464+
const exitCode = await exec('stapler', ['validate', appPath], {
465+
listeners: {
466+
stdout: (data: Buffer) => { output += data.toString(); }
467+
},
468+
ignoreReturnCode: true
469+
});
470+
return exitCode === 0 && output.includes('The validate action worked!');
471+
}
472+
460473
async function getFirstPathWithGlob(globPattern: string): Promise<string> {
461474
const globber = await glob.create(globPattern);
462475
const files = await globber.glob();
@@ -473,24 +486,19 @@ async function signMacOSAppBundle(projectRef: XcodeProject): Promise<void> {
473486
if (!stat.isDirectory()) {
474487
throw new Error(`Not a valid app bundle: ${appPath}`);
475488
}
476-
// sign the app bundle using ./sign-app-bundle.sh
477489
const signAppBundlePath = path.join(__dirname, 'sign-app-bundle.sh');
478-
core.info(`Signing app bundle: ${appPath}`);
479490
let codesignOutput = '';
480491
const codesignExitCode = await exec('sh', [signAppBundlePath, appPath, projectRef.entitlementsPath], {
481492
listeners: {
482493
stdout: (data: Buffer) => {
483494
codesignOutput += data.toString();
484495
}
485496
},
486-
// silent: !core.isDebug(),
487497
ignoreReturnCode: true
488498
});
489499
if (codesignExitCode !== 0) {
490-
log(codesignOutput, 'error');
491500
throw new Error(`Failed to code sign the app!`);
492501
}
493-
core.info(codesignOutput);
494502
}
495503

496504
async function createMacOSInstallerPkg(projectRef: XcodeProject): Promise<string> {
@@ -515,6 +523,13 @@ async function createMacOSInstallerPkg(projectRef: XcodeProject): Promise<string
515523
} catch (error) {
516524
throw new Error(`Failed to create the pkg at: ${pkgPath}!`);
517525
}
526+
// TODO get Developer ID Installer signing certificate from app store connect API
527+
const developerIdInstallerCert = await GetCertificate(projectRef, 'MAC_INSTALLER_DISTRIBUTION');
528+
core.info(`Found Developer ID Installer certificate: [${developerIdInstallerCert.id}] ${developerIdInstallerCert.attributes.name}`);
529+
// save certificate contents to runner.temp directory
530+
const certPath = path.join(process.env.RUNNER_TEMP, 'developer_id_installer.cer');
531+
await fs.promises.writeFile(certPath, developerIdInstallerCert.attributes.certificateContent);
532+
core.info(`Saved Developer ID Installer certificate to: ${certPath}`);
518533
// sign the .pkg using ./sign-app-pkg.sh
519534
const signPkgPath = path.join(__dirname, 'sign-app-pkg.sh');
520535
core.info(`Signing pkg: ${pkgPath}`);

0 commit comments

Comments
 (0)