Skip to content

Commit c6ecbf7

Browse files
author
Mitko-Kerezov
committed
Implement publish command for iOS
Publishes to itunesconnect via Xcode's iTMS Transporter tool. Introduce itmstransporter-service to talk to the tool along with minor refactorings in the build process, allowing for custom setting of CODE_SIGN_IDENTITY and PROVISIONING_PROFILE during build.
1 parent ea53638 commit c6ecbf7

9 files changed

+180
-3
lines changed

lib/bootstrap.ts

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ $injector.requireCommand("test|ios", "./commands/test");
5252
$injector.requireCommand("test|init", "./commands/test-init");
5353
$injector.requireCommand("dev-generate-help", "./commands/generate-help");
5454

55+
$injector.requireCommand("publish|ios", "./commands/publish-ios");
56+
$injector.require("itmsTransporterService", "./services/itmstransporter-service");
57+
5558
$injector.require("npm", "./node-package-manager");
5659
$injector.require("npmInstallationManager", "./npm-installation-manager");
5760
$injector.require("lockfile", "./lockfile");

lib/commands/publish-ios.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
///<reference path="../.d.ts"/>
2+
"use strict";
3+
4+
import {StringCommandParameter} from "../common/command-params";
5+
6+
export class PublishIOS implements ICommand {
7+
constructor(private $injector: IInjector,
8+
private $itmsTransporterService: IITMSTransporterService,
9+
private $logger: ILogger,
10+
private $options: IOptions,
11+
private $prompter: IPrompter,
12+
private $stringParameterBuilder: IStringParameterBuilder) { }
13+
14+
public allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("Missing required Apple ID parameter."),
15+
new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)];
16+
17+
public execute(args: string[]): IFuture<void> {
18+
return (() => {
19+
let app_id = args[0],
20+
userName = args[1],
21+
password = args[2],
22+
mobileProvisionIdentifier = args[3],
23+
codeSignIdentity = args[4];
24+
25+
if(!userName) {
26+
userName = this.$prompter.getString("Apple ID", { allowEmpty: false }).wait();
27+
}
28+
29+
if(!password) {
30+
password = this.$prompter.getPassword("Apple ID password").wait();
31+
}
32+
33+
if(!mobileProvisionIdentifier) {
34+
this.$logger.warn("Mobile Provision identifier not set - a default one will be used. You can set one in App_Resources/iOS/build.xcconfig");
35+
}
36+
37+
if(!codeSignIdentity) {
38+
this.$logger.warn("Code Sign Identity not set - a default one will be used. You can set one in App_Resources/iOS/build.xcconfig");
39+
}
40+
41+
this.$options.release = true;
42+
this.$itmsTransporterService.upload(app_id, userName, password, mobileProvisionIdentifier, codeSignIdentity).wait();
43+
}).future<void>()();
44+
}
45+
}
46+
$injector.registerCommand("publish|ios", PublishIOS);

lib/common

lib/declarations.ts

+17
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,23 @@ interface IInitService {
8989
initialize(): IFuture<void>;
9090
}
9191

92+
/**
93+
* Used for communicating with Xcode's iTMS Transporter tool.
94+
*/
95+
interface IITMSTransporterService {
96+
/**
97+
* Builds and uploads an .ipa package to itunesconnect.
98+
* Note that if mobileProvisionIdentifier and/or codeSignIdentity are passed to this method they will override any options set through .xcconfig files.
99+
* @param {string} appId The application's Apple ID. It can be found on itunesconnect.apple.com.
100+
* @param {string} username Username for authentication with itunesconnect.
101+
* @param {string} password Password for authentication with itunesconnect.
102+
* @param {string} mobileProvisionIdentifier? The identifier of the mobile provision used for building.
103+
* @param {string} codeSignIdentity? The Code Sign Identity used for building.
104+
* @returns IFuture<void>
105+
*/
106+
upload(appId: string, username: string, password: string, mobileProvisionIdentifier?: string, codeSignIdentity?: string): IFuture<void>;
107+
}
108+
92109
/**
93110
* Provides access to information about installed Android tools and SDKs versions.
94111
*/

lib/definitions/project.d.ts

+14
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ interface IBuildConfig {
5252
architectures?: string[];
5353
}
5454

55+
/**
56+
* Describes iOS-specific build configuration properties
57+
*/
58+
interface IiOSBuildConfig extends IBuildConfig {
59+
/**
60+
* Identifier of the mobile provision which will be used for the build. If not set a provision will be selected automatically if possible.
61+
*/
62+
mobileProvisionIdentifier?: string;
63+
/**
64+
* Code sign identity used for build. If not set iPhone Developer is used as a default when building for device.
65+
*/
66+
codeSignIdentity?: string;
67+
}
68+
5569
interface IPlatformProjectService {
5670
platformData: IPlatformData;
5771
validate(): IFuture<void>;

lib/services/init-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class InitService implements IInitService {
9494
return defaultAppId;
9595
}
9696

97-
return this.$prompter.getString("Id:", () => defaultAppId).wait();
97+
return this.$prompter.getString("Id:", { defaultAction: () => defaultAppId }).wait();
9898
}).future<string>()();
9999
}
100100

lib/services/ios-project-service.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
157157
}).future<void>()();
158158
}
159159

160-
public buildProject(projectRoot: string, buildConfig?: IBuildConfig): IFuture<void> {
160+
public buildProject(projectRoot: string, buildConfig?: IiOSBuildConfig): IFuture<void> {
161161
return (() => {
162162
let basicArgs = [
163163
"-configuration", this.$options.release ? "Release" : "Debug",
@@ -204,6 +204,14 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
204204
]);
205205
}
206206

207+
if (buildConfig && buildConfig.codeSignIdentity) {
208+
args.push(`CODE_SIGN_IDENTITY=${buildConfig.codeSignIdentity}`);
209+
}
210+
211+
if (buildConfig && buildConfig.mobileProvisionIdentifier) {
212+
args.push(`PROVISIONING_PROFILE=${buildConfig.mobileProvisionIdentifier}`);
213+
}
214+
207215
this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", {cwd: this.$options, stdio: 'inherit'}).wait();
208216

209217
if (buildForDevice) {
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
///<reference path="../.d.ts"/>
2+
"use strict";
3+
import * as path from "path";
4+
import * as temp from "temp";
5+
let md5 = require("cryptojs").Crypto.MD5;
6+
7+
export class ITMSTransporterService implements IITMSTransporterService {
8+
private _itmsTransporterPath: string = null;
9+
10+
constructor(private $childProcess: IChildProcess,
11+
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
12+
private $errors: IErrors,
13+
private $fs: IFileSystem,
14+
private $platformService: IPlatformService,
15+
private $staticConfig: IStaticConfig,
16+
private $sysInfo: ISysInfo) { }
17+
18+
public upload(appId: string, username: string, password: string, mobileProvisionIdentifier?: string, codeSignIdentity?: string): IFuture<void> {
19+
return (() => {
20+
temp.track();
21+
let itmsTransporterPath = this.getITMSTransporterPath().wait(),
22+
ipaFileName = "app.ipa",
23+
itmsDirectory = temp.mkdirSync("itms-"),
24+
innerDirectory = path.join(itmsDirectory, "mybundle.itmsp"),
25+
ipaFileLocation = path.join(innerDirectory, ipaFileName),
26+
platform = this.$devicePlatformsConstants.iOS,
27+
forDevice = true,
28+
iosBuildConfig: IiOSBuildConfig = {
29+
buildForDevice: forDevice,
30+
mobileProvisionIdentifier: mobileProvisionIdentifier,
31+
codeSignIdentity: codeSignIdentity
32+
};
33+
34+
this.$fs.createDirectory(innerDirectory).wait();
35+
36+
this.$platformService.preparePlatform(platform).wait();
37+
this.$platformService.buildPlatform(platform, iosBuildConfig).wait();
38+
this.$platformService.copyLastOutput(platform, ipaFileLocation, { isForDevice: forDevice }).wait();
39+
40+
let ipaFileHash = md5(this.$fs.readFile(ipaFileLocation).wait()),
41+
ipaFileSize = this.$fs.getFileSize(ipaFileLocation).wait(),
42+
metadata = `<?xml version="1.0" encoding="UTF-8"?>
43+
<package version="software4.7" xmlns="http://apple.com/itunes/importer">
44+
<software_assets apple_id="${appId}">
45+
<asset type="bundle">
46+
<data_file>
47+
<file_name>${ipaFileName}</file_name>
48+
<checksum type="md5">${ipaFileHash}</checksum>
49+
<size>${ipaFileSize}</size>
50+
</data_file>
51+
</asset>
52+
</software_assets>
53+
</package>`;
54+
55+
this.$fs.writeFile(path.join(innerDirectory, "metadata.xml"), metadata).wait();
56+
57+
this.$childProcess.spawnFromEvent(itmsTransporterPath, ["-m", "upload", "-f", itmsDirectory, "-u", username, "-p", password, "-v", "informational"], "close", { stdio: "inherit" }).wait();
58+
}).future<void>()();
59+
}
60+
61+
private getITMSTransporterPath(): IFuture<string> {
62+
return (() => {
63+
if (!this._itmsTransporterPath) {
64+
let sysInfo = this.$sysInfo.getSysInfo(path.join(__dirname, "..", "..", this.$staticConfig.PROJECT_FILE_NAME)).wait(),
65+
xcodeVersionMatch = sysInfo.xcodeVer.match(/Xcode (.*)/),
66+
result = path.join("/Applications", "Xcode.app", "Contents", "Applications", "Application Loader.app", "Contents");
67+
68+
if (xcodeVersionMatch && xcodeVersionMatch[1]) {
69+
let [major, minor] = xcodeVersionMatch[1].split(".");
70+
// iTMS Transporter's path has been modified in Xcode 6.3
71+
// https://github.com/nomad/shenzhen/issues/243
72+
if (+major <= 6 && +minor < 3) {
73+
result = path.join(result, "MacOS");
74+
}
75+
}
76+
77+
this._itmsTransporterPath = path.join(result, "itms", "bin", "iTMSTransporter");
78+
}
79+
80+
if(!this.$fs.exists(this._itmsTransporterPath).wait()) {
81+
this.$errors.failWithoutHelp('iTMS Transporter not found on this machine - make sure your Xcode installation is not damaged.');
82+
}
83+
84+
return this._itmsTransporterPath;
85+
}).future<string>()();
86+
}
87+
}
88+
$injector.register("itmsTransporterService", ITMSTransporterService);

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"cli-table": "https://github.com/telerik/cli-table/tarball/v0.3.1.1",
3636
"clui": "0.3.1",
3737
"colors": "1.1.2",
38+
"cryptojs": "2.5.3",
3839
"esprima": "2.7.0",
3940
"ffi": "https://github.com/icenium/node-ffi/tarball/v2.0.0.1",
4041
"fibers": "https://github.com/icenium/node-fibers/tarball/v1.0.6.3",

0 commit comments

Comments
 (0)