From d17998b0aea3d3648d715dac27e07b413080629a Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 15 Apr 2019 09:07:47 +0300 Subject: [PATCH 1/3] chore: add changelog for 5.3.2 release --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8715ce074..83fcdccb7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ NativeScript CLI Changelog ================ +5.3.2 (2019, April 12) +== + +### Fixed +* [Fixed #1798](https://github.com/NativeScript/nativescript-cli/issues/1798): Test init command doesn't add a sample test in TypeScript for TypeScript/Angular projects +* [Fixed #4498](https://github.com/NativeScript/nativescript-cli/pull/4498): API: Change the AppStore ids for kinvey scanner and preview app +* [Fixed #4504](https://github.com/NativeScript/nativescript-cli/issues/4504): Custom tagged versions of android runtime are not supported +* [Fixed #4510](https://github.com/NativeScript/nativescript-cli/pull/4510): Handle HTTP 304 response status code + 5.3.0 (2019, March 27) == From 28390c1291e2f5f02fa46255b7c20fadcccc35e4 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 18 Apr 2019 11:58:58 +0300 Subject: [PATCH 2/3] fix: fix upload to appstore for accounts without 2 factor authentication --- lib/bootstrap.ts | 4 + lib/commands/appstore-list.ts | 18 +- lib/commands/appstore-upload.ts | 11 +- lib/declarations.d.ts | 11 +- .../apple-portal-application-service.ts | 54 +++++ .../apple-portal-cookie-service.ts | 41 ++++ .../apple-portal-session-service.ts | 95 +++++++++ lib/services/apple-portal/definitions.d.ts | 72 +++++++ lib/services/itmstransporter-service.ts | 197 +++++------------- npm-shrinkwrap.json | 2 +- package.json | 2 +- 11 files changed, 346 insertions(+), 161 deletions(-) create mode 100644 lib/services/apple-portal/apple-portal-application-service.ts create mode 100644 lib/services/apple-portal/apple-portal-cookie-service.ts create mode 100644 lib/services/apple-portal/apple-portal-session-service.ts create mode 100644 lib/services/apple-portal/definitions.d.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index cc08b41de5..7acde30d4a 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -192,3 +192,7 @@ $injector.require("qrCodeTerminalService", "./services/qr-code-terminal-service" $injector.require("testInitializationService", "./services/test-initialization-service"); $injector.require("networkConnectivityValidator", "./helpers/network-connectivity-validator"); + +$injector.require("applePortalSessionService", "./services/apple-portal/apple-portal-session-service"); +$injector.require("applePortalCookieService", "./services/apple-portal/apple-portal-cookie-service"); +$injector.require("applePortalApplicationService", "./services/apple-portal/apple-portal-application-service"); diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 6313a2f64b..c88bcaee82 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -5,7 +5,7 @@ export class ListiOSApps implements ICommand { public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)]; constructor(private $injector: IInjector, - private $itmsTransporterService: IITMSTransporterService, + private $applePortalApplicationService: IApplePortalApplicationService, private $logger: ILogger, private $projectData: IProjectData, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, @@ -31,18 +31,26 @@ export class ListiOSApps implements ICommand { password = await this.$prompter.getPassword("Apple ID password"); } - const iOSApplications = await this.$itmsTransporterService.getiOSApplications({ username, password }); + const applications = await this.$applePortalApplicationService.getApplications({ username, password }); - if (!iOSApplications || !iOSApplications.length) { + if (!applications || !applications.length) { this.$logger.out("Seems you don't have any applications yet."); } else { - const table: any = createTable(["Application Name", "Bundle Identifier", "Version"], iOSApplications.map(element => { - return [element.name, element.bundleId, element.version]; + const table: any = createTable(["Application Name", "Bundle Identifier", "In Flight Version"], applications.map(application => { + return [application.name, application.bundleId, this.getVersion(application)]; })); this.$logger.out(table.toString()); } } + + private getVersion(application: IApplePortalApplicationSummary): string { + if (application && application.versionSets && application.versionSets.length && application.versionSets[0].inFlightVersion) { + return application.versionSets[0].inFlightVersion.version; + } + + return ""; + } } $injector.registerCommand("appstore|*list", ListiOSApps); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 03168cde65..70f21385a3 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -6,14 +6,16 @@ export class PublishIOS implements ICommand { public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)]; - constructor(private $errors: IErrors, + constructor( private $injector: IInjector, private $itmsTransporterService: IITMSTransporterService, private $logger: ILogger, private $projectData: IProjectData, private $options: IOptions, private $prompter: IPrompter, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) { + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $hostInfo: IHostInfo, + private $errors: IErrors) { this.$projectData.initializeProjectData(); } @@ -107,11 +109,16 @@ export class PublishIOS implements ICommand { username, password, ipaFilePath, + ipaFileOption: !!this.$options.ipa, verboseLogging: this.$logger.getLevel() === "TRACE" }); } public async canExecute(args: string[]): Promise { + if (!this.$hostInfo.isDarwin) { + this.$errors.failWithoutHelp("iOS publishing is only available on Mac OS X."); + } + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e7c4a4456d..25f6ed06f9 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -622,6 +622,11 @@ interface IITMSData extends ICredentials { * @type {string} */ ipaFilePath: string; + + /** + * Specified if the ipa options is provided + */ + ipaFileOption: boolean; /** * Specifies whether the logging level of the itmstransporter command-line tool should be set to verbose. * @type {string} @@ -639,12 +644,6 @@ interface IITMSTransporterService { * @return {Promise} */ upload(data: IITMSData): Promise; - /** - * Queries Apple's content delivery API to get the user's registered iOS applications. - * @param {ICredentials} credentials Credentials for authentication with iTunes Connect. - * @return {Promise} The user's iOS applications. - */ - getiOSApplications(credentials: ICredentials): Promise; } /** diff --git a/lib/services/apple-portal/apple-portal-application-service.ts b/lib/services/apple-portal/apple-portal-application-service.ts new file mode 100644 index 0000000000..791e420bb1 --- /dev/null +++ b/lib/services/apple-portal/apple-portal-application-service.ts @@ -0,0 +1,54 @@ +export class ApplePortalApplicationService implements IApplePortalApplicationService { + constructor( + private $applePortalSessionService: IApplePortalSessionService, + private $errors: IErrors, + private $httpClient: Server.IHttpClient + ) { } + + public async getApplications(credentials: ICredentials): Promise { + let result: IApplePortalApplicationSummary[] = []; + + const user = await this.$applePortalSessionService.createUserSession(credentials); + for (const account of user.associatedAccounts) { + const contentProviderId = account.contentProvider.contentProviderId; + const dsId = user.sessionToken.dsId; + const applications = await this.getApplicationsByProvider(contentProviderId, dsId); + result = result.concat(applications.summaries); + } + + return result; + } + + public async getApplicationsByProvider(contentProviderId: number, dsId: string): Promise { + const webSessionCookie = await this.$applePortalSessionService.createWebSession(contentProviderId, dsId); + const response = await this.$httpClient.httpRequest({ + url: "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/manageyourapps/summary/v2", + method: "GET", + body: JSON.stringify({ + contentProviderId + }), + headers: { + 'Content-Type': 'application/json', + 'Cookie': webSessionCookie + } + }); + + return JSON.parse(response.body).data; + } + + public async getApplicationByBundleId(credentials: ICredentials, bundleId: string): Promise { + const applications = await this.getApplications(credentials); + if (!applications || !applications.length) { + this.$errors.failWithoutHelp(`Cannot find any registered applications for Apple ID ${credentials.username} in iTunes Connect.`); + } + + const application = _.find(applications, app => app.bundleId === bundleId); + + if (!application) { + this.$errors.failWithoutHelp(`Cannot find registered applications that match the specified identifier ${bundleId} in iTunes Connect.`); + } + + return application; + } +} +$injector.register("applePortalApplicationService", ApplePortalApplicationService); diff --git a/lib/services/apple-portal/apple-portal-cookie-service.ts b/lib/services/apple-portal/apple-portal-cookie-service.ts new file mode 100644 index 0000000000..241652cd82 --- /dev/null +++ b/lib/services/apple-portal/apple-portal-cookie-service.ts @@ -0,0 +1,41 @@ +export class ApplePortalCookieService implements IApplePortalCookieService { + private userSessionCookies: IStringDictionary = {}; + private validUserSessionCookieNames = ["myacinfo", "dqsid", "itctx", "itcdq", "acn01"]; + private validWebSessionCookieNames = ["wosid", "woinst", "itctx"]; + + public getWebSessionCookie(cookiesData: string[]): string { + const webSessionCookies = _.cloneDeep(this.userSessionCookies); + + const parsedCookies = this.parseCookiesData(cookiesData, this.validWebSessionCookieNames); + _.each(parsedCookies, parsedCookie => webSessionCookies[parsedCookie.key] = parsedCookie.cookie); + + return _.values(webSessionCookies).join("; "); + } + + public getUserSessionCookie(): string { + return _.values(this.userSessionCookies).join("; "); + } + + public updateUserSessionCookie(cookiesData: string[]): void { + const parsedCookies = this.parseCookiesData(cookiesData, this.validUserSessionCookieNames); + _.each(parsedCookies, parsedCookie => this.userSessionCookies[parsedCookie.key] = parsedCookie.cookie); + } + + private parseCookiesData(cookiesData: string[], validCookieNames: string[]): IDictionary<{key: string, value: string, cookie: string}> { + const result: IDictionary<{key: string, value: string, cookie: string}> = {}; + + for (const c of cookiesData) { + const parts = c.split(";"); + for (const cookie of parts) { + const trimmedCookie = cookie.trim(); + const [cookieKey, cookieValue] = trimmedCookie.split("="); + if (_.includes(validCookieNames, cookieKey)) { + result[cookieKey] = { key: cookieKey, value: cookieValue, cookie: trimmedCookie }; + } + } + } + + return result; + } +} +$injector.register("applePortalCookieService", ApplePortalCookieService); diff --git a/lib/services/apple-portal/apple-portal-session-service.ts b/lib/services/apple-portal/apple-portal-session-service.ts new file mode 100644 index 0000000000..83adf60272 --- /dev/null +++ b/lib/services/apple-portal/apple-portal-session-service.ts @@ -0,0 +1,95 @@ +export class ApplePortalSessionService implements IApplePortalSessionService { + private loginConfigEndpoint = "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com"; + private defaultLoginConfig = { + authServiceUrl : "https://idmsa.apple.com/appleautcodh", + authServiceKey : "e0b80c3bf78523bfe80974d320935bfa30add02e1bff88ec2166c6bd5a706c42" + }; + + constructor( + private $applePortalCookieService: IApplePortalCookieService, + private $httpClient: Server.IHttpClient, + private $logger: ILogger + ) { } + + public async createUserSession(credentials: ICredentials): Promise { + const loginConfig = await this.getLoginConfig(); + const loginUrl = `${loginConfig.authServiceUrl}/auth/signin`; + const loginResponse = await this.$httpClient.httpRequest({ + url: loginUrl, + method: "POST", + body: JSON.stringify({ + accountName: credentials.username, + password: credentials.password, + rememberMe: true + }), + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + 'X-Apple-Widget-Key': loginConfig.authServiceKey, + 'Accept': 'application/json, text/javascript' + } + }); + + this.$applePortalCookieService.updateUserSessionCookie(loginResponse.headers["set-cookie"]); + + const sessionResponse = await this.$httpClient.httpRequest({ + url: "https://appstoreconnect.apple.com/olympus/v1/session", + method: "GET", + headers: { + 'Cookie': this.$applePortalCookieService.getUserSessionCookie() + } + }); + + this.$applePortalCookieService.updateUserSessionCookie(sessionResponse.headers["set-cookie"]); + + const userDetailResponse = await this.$httpClient.httpRequest({ + url: "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/user/detail", + method: "GET", + headers: { + 'Content-Type': 'application/json', + 'Cookie': this.$applePortalCookieService.getUserSessionCookie(), + } + }); + + this.$applePortalCookieService.updateUserSessionCookie(userDetailResponse.headers["set-cookie"]); + + return JSON.parse(userDetailResponse.body).data; + } + + public async createWebSession(contentProviderId: number, dsId: string): Promise { + const webSessionResponse = await this.$httpClient.httpRequest({ + url: "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/v1/session/webSession", + method: "POST", + body: JSON.stringify({ + contentProviderId, + dsId, + ipAddress: null + }), + headers: { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Encoding': 'gzip, deflate, br', + 'X-Csrf-Itc': 'itc', + 'Content-Type': 'application/json;charset=UTF-8', + 'Cookie': this.$applePortalCookieService.getUserSessionCookie() + } + }); + + const webSessionCookie = this.$applePortalCookieService.getWebSessionCookie(webSessionResponse.headers["set-cookie"]); + + return webSessionCookie; + } + + private async getLoginConfig(): Promise<{authServiceUrl: string, authServiceKey: string}> { + let config = null; + + try { + const response = await this.$httpClient.httpRequest({ url: this.loginConfigEndpoint, method: "GET" }); + config = JSON.parse(response.body); + } catch (err) { + this.$logger.trace(`Error while executing request to ${this.loginConfigEndpoint}. More info: ${err}`); + } + + return config || this.defaultLoginConfig; + } +} +$injector.register("applePortalSessionService", ApplePortalSessionService); diff --git a/lib/services/apple-portal/definitions.d.ts b/lib/services/apple-portal/definitions.d.ts new file mode 100644 index 0000000000..fdaf16968e --- /dev/null +++ b/lib/services/apple-portal/definitions.d.ts @@ -0,0 +1,72 @@ +interface IApplePortalSessionService { + createUserSession(credentials: ICredentials): Promise; + createWebSession(contentProviderId: number, dsId: string): Promise; +} + +interface IApplePortalCookieService { + getWebSessionCookie(cookiesData: string[]): string; + getUserSessionCookie(): string; + updateUserSessionCookie(cookie: string[]): void; +} + +interface IApplePortalApplicationService { + getApplications(credentials: ICredentials): Promise + getApplicationsByProvider(contentProviderId: number, dsId: string): Promise; + getApplicationByBundleId(credentials: ICredentials, bundleId: string): Promise; +} + +interface IApplePortalUserDetail { + associatedAccounts: IApplePortalAssociatedAccountData[]; + sessionToken: { + dsId: string; + contentProviderId: number; + ipAddress: string; + } + contentProviderFeatures: string[]; + contentProviderId: number; + firstname: string; + displayName: string; + userName: string; + userId: string; + contentProvider: string; + visibility: boolean; + DYCVisibility: boolean; +} + +interface IApplePortalAssociatedAccountData { + contentProvider: { + name: string; + contentProviderId: number; + contentProviderPublicId: string; + contentProviderTypes: string[]; + }; + roles: string[]; + lastLogin: number; +} + +interface IApplePortalApplication { + summaries: IApplePortalApplicationSummary[]; + showSharedSecret: boolean; + macBundlesEnabled: boolean; + canCreateMacApps: boolean; + cloudStorageEnabled: boolean; + sharedSecretLink: string; + gameCenterGroupLink: string; + enabledPlatforms: string[]; + cloudStorageLink: string; + catalogReportsLink: string; + canCreateIOSApps: boolean; +} + +interface IApplePortalApplicationSummary { + name: string; + adamId: string; + vendorId: string; + bundleId: string; + appType: any; + versionSets: any[]; + lastModifiedDate: number; + iconUrl: string; + issuesCount: number; + priceTier: string; +} \ No newline at end of file diff --git a/lib/services/itmstransporter-service.ts b/lib/services/itmstransporter-service.ts index 1536c4d1d8..607081bf41 100644 --- a/lib/services/itmstransporter-service.ts +++ b/lib/services/itmstransporter-service.ts @@ -1,23 +1,18 @@ import * as path from "path"; import * as temp from "temp"; -import { EOL } from "os"; import { ITMSConstants, INFO_PLIST_FILE_NAME } from "../constants"; -import { ItunesConnectApplicationTypes } from "../constants"; -import { quoteString, versionCompare } from "../common/helpers"; +import { quoteString } from "../common/helpers"; +import { cache } from "../common/decorators"; export class ITMSTransporterService implements IITMSTransporterService { - private _itmsTransporterPath: string = null; - private _itunesConnectApplications: IiTunesConnectApplication[] = null; - private _bundleIdentifier: string = null; - - constructor(private $plistParser: IPlistParser, + constructor( + private $applePortalApplicationService: IApplePortalApplicationService, private $childProcess: IChildProcess, private $errors: IErrors, private $fs: IFileSystem, - private $hostInfo: IHostInfo, - private $httpClient: Server.IHttpClient, private $injector: IInjector, private $logger: ILogger, + private $plistParser: IPlistParser, private $xcodeSelectService: IXcodeSelectService) { } private get $projectData(): IProjectData { @@ -25,171 +20,81 @@ export class ITMSTransporterService implements IITMSTransporterService { } public async upload(data: IITMSData): Promise { - if (!this.$hostInfo.isDarwin) { - this.$errors.failWithoutHelp("iOS publishing is only available on Mac OS X."); - } - temp.track(); - const itmsTransporterPath = await this.getITMSTransporterPath(), - ipaFileName = "app.ipa", - itmsDirectory = temp.mkdirSync("itms-"), - innerDirectory = path.join(itmsDirectory, "mybundle.itmsp"), - ipaFileLocation = path.join(innerDirectory, ipaFileName), - loggingLevel = data.verboseLogging ? ITMSConstants.VerboseLoggingLevels.Verbose : ITMSConstants.VerboseLoggingLevels.Informational, - bundleId = await this.getBundleIdentifier(data.ipaFilePath), - iOSApplication = await this.getiOSApplication(data.username, data.password, bundleId); + const itmsTransporterPath = await this.getITMSTransporterPath(); + const ipaFileName = "app.ipa"; + const itmsDirectory = temp.mkdirSync("itms-"); + const innerDirectory = path.join(itmsDirectory, "mybundle.itmsp"); + const ipaFileLocation = path.join(innerDirectory, ipaFileName); + const loggingLevel = data.verboseLogging ? ITMSConstants.VerboseLoggingLevels.Verbose : ITMSConstants.VerboseLoggingLevels.Informational; + const bundleId = await this.getBundleIdentifier(data); + const application = await this.$applePortalApplicationService.getApplicationByBundleId(data, bundleId); this.$fs.createDirectory(innerDirectory); this.$fs.copyFile(data.ipaFilePath, ipaFileLocation); - const ipaFileHash = await this.$fs.getFileShasum(ipaFileLocation, { algorithm: "md5" }), - ipaFileSize = this.$fs.getFileSize(ipaFileLocation), - metadata = this.getITMSMetadataXml(iOSApplication.adamId, ipaFileName, ipaFileHash, ipaFileSize); + const ipaFileHash = await this.$fs.getFileShasum(ipaFileLocation, { algorithm: "md5" }); + const ipaFileSize = this.$fs.getFileSize(ipaFileLocation); + const metadata = this.getITMSMetadataXml(application.adamId, ipaFileName, ipaFileHash, ipaFileSize); this.$fs.writeFile(path.join(innerDirectory, ITMSConstants.ApplicationMetadataFile), metadata); await this.$childProcess.spawnFromEvent(itmsTransporterPath, ["-m", "upload", "-f", itmsDirectory, "-u", quoteString(data.username), "-p", quoteString(data.password), "-v", loggingLevel], "close", { stdio: "inherit" }); } - public async getiOSApplications(credentials: ICredentials): Promise { - if (!this._itunesConnectApplications) { - const requestBody = this.getContentDeliveryRequestBody(credentials), - contentDeliveryResponse = await this.$httpClient.httpRequest({ - url: "https://contentdelivery.itunes.apple.com/WebObjects/MZLabelService.woa/json/MZITunesProducerService", - method: "POST", - body: requestBody, - headers: { - "Content-Length": requestBody.length - } - }), - contentDeliveryBody: IContentDeliveryBody = JSON.parse(contentDeliveryResponse.body); - - if (!contentDeliveryBody.result.Success || !contentDeliveryBody.result.Applications) { - let errorMessage = ["Unable to connect to iTunes Connect"]; - if (contentDeliveryBody.result.Errors && contentDeliveryBody.result.Errors.length) { - errorMessage = errorMessage.concat(contentDeliveryBody.result.Errors); - } - - this.$errors.failWithoutHelp(errorMessage.join(EOL)); + private async getBundleIdentifier(data: IITMSData): Promise { + const { ipaFileOption, ipaFilePath } = data; + + if (ipaFileOption) { + if (!this.$fs.exists(ipaFilePath) || path.extname(ipaFilePath) !== ".ipa") { + this.$errors.failWithoutHelp(`Cannot use specified ipa file ${ipaFilePath}. File either does not exist or is not an ipa file.`); } - this._itunesConnectApplications = contentDeliveryBody.result.Applications.filter(app => app.type === ItunesConnectApplicationTypes.iOS); - } + this.$logger.trace("--ipa set - extracting .ipa file to get app's bundle identifier"); + temp.track(); + const destinationDir = temp.mkdirSync("ipa-"); + await this.$fs.unzip(ipaFilePath, destinationDir); - return this._itunesConnectApplications; - } + const payloadDir = path.join(destinationDir, "Payload"); + let allFiles = this.$fs.readDirectory(payloadDir); - /** - * Gets iTunes Connect application corresponding to the given bundle identifier. - * @param {string} username For authentication with iTunes Connect. - * @param {string} password For authentication with iTunes Connect. - * @param {string} bundleId Application's Bundle Identifier - * @return {IFuture} The iTunes Connect application. - */ - private async getiOSApplication(username: string, password: string, bundleId: string): Promise { - const iOSApplications = await this.getiOSApplications({ username, password }); - if (!iOSApplications || !iOSApplications.length) { - this.$errors.failWithoutHelp(`Cannot find any registered applications for Apple ID ${username} in iTunes Connect.`); - } + this.$logger.debug("ITMSTransporter .ipa Payload files:"); + allFiles.forEach(f => this.$logger.debug(" - " + f)); - const iOSApplication = _.find(iOSApplications, app => app.bundleId === bundleId); + allFiles = allFiles.filter(f => path.extname(f).toLowerCase() === ".app"); + if (allFiles.length > 1) { + this.$errors.failWithoutHelp("In the .ipa the ITMSTransporter is uploading there is more than one .app file. We don't know which one to upload."); + } else if (allFiles.length <= 0) { + this.$errors.failWithoutHelp("In the .ipa the ITMSTransporter is uploading there must be at least one .app file."); + } + const appFile = path.join(payloadDir, allFiles[0]); - if (!iOSApplication) { - this.$errors.failWithoutHelp(`Cannot find registered applications that match the specified identifier ${bundleId} in iTunes Connect.`); - } + const plistObject = await this.$plistParser.parseFile(path.join(appFile, INFO_PLIST_FILE_NAME)); + const bundleId = plistObject && plistObject.CFBundleIdentifier; + if (!bundleId) { + this.$errors.failWithoutHelp(`Unable to determine bundle identifier from ${ipaFilePath}.`); + } - return iOSApplication; - } + this.$logger.trace(`bundle identifier determined to be ${bundleId}`); - /** - * Gets the application's bundle identifier. If ipaFileFullPath is provided will extract the bundle identifier from the .ipa file. - * @param {string} ipaFileFullPath Optional full path to .ipa file - * @return {IFuture} Application's bundle identifier. - */ - private async getBundleIdentifier(ipaFileFullPath?: string): Promise { - if (!this._bundleIdentifier) { - if (!ipaFileFullPath) { - this._bundleIdentifier = this.$projectData.projectIdentifiers.ios; - } else { - if (!this.$fs.exists(ipaFileFullPath) || path.extname(ipaFileFullPath) !== ".ipa") { - this.$errors.failWithoutHelp(`Cannot use specified ipa file ${ipaFileFullPath}. File either does not exist or is not an ipa file.`); - } - - this.$logger.trace("--ipa set - extracting .ipa file to get app's bundle identifier"); - temp.track(); - const destinationDir = temp.mkdirSync("ipa-"); - await this.$fs.unzip(ipaFileFullPath, destinationDir); - - const payloadDir = path.join(destinationDir, "Payload"); - let allApps = this.$fs.readDirectory(payloadDir); - - this.$logger.debug("ITMSTransporter .ipa Payload files:"); - allApps.forEach(f => this.$logger.debug(" - " + f)); - - allApps = allApps.filter(f => path.extname(f).toLowerCase() === ".app"); - if (allApps.length > 1) { - this.$errors.failWithoutHelp("In the .ipa the ITMSTransporter is uploading there is more than one .app file. We don't know which one to upload."); - } else if (allApps.length <= 0) { - this.$errors.failWithoutHelp("In the .ipa the ITMSTransporter is uploading there must be at least one .app file."); - } - const appFile = path.join(payloadDir, allApps[0]); - - const plistObject = await this.$plistParser.parseFile(path.join(appFile, INFO_PLIST_FILE_NAME)); - const bundleId = plistObject && plistObject.CFBundleIdentifier; - if (!bundleId) { - this.$errors.failWithoutHelp(`Unable to determine bundle identifier from ${ipaFileFullPath}.`); - } - - this.$logger.trace(`bundle identifier determined to be ${bundleId}`); - this._bundleIdentifier = bundleId; - } + return bundleId; } - return this._bundleIdentifier; + return this.$projectData.projectIdentifiers.ios; } + @cache() private async getITMSTransporterPath(): Promise { - if (!this._itmsTransporterPath) { - const xcodePath = await this.$xcodeSelectService.getContentsDirectoryPath(); - const xcodeVersion = await this.$xcodeSelectService.getXcodeVersion(); - let result = path.join(xcodePath, "Applications", "Application Loader.app", "Contents"); - - xcodeVersion.patch = xcodeVersion.patch || "0"; - // iTMS Transporter's path has been modified in Xcode 6.3 - // https://github.com/nomad/shenzhen/issues/243 - if (xcodeVersion.major && xcodeVersion.minor && - versionCompare(xcodeVersion, "6.3.0") < 0) { - result = path.join(result, "MacOS"); - } + const xcodePath = await this.$xcodeSelectService.getContentsDirectoryPath(); + const loaderAppContentsPath = path.join(xcodePath, "Applications", "Application Loader.app", "Contents"); + const itmsTransporterPath = path.join(loaderAppContentsPath, ITMSConstants.iTMSDirectoryName, "bin", ITMSConstants.iTMSExecutableName); - this._itmsTransporterPath = path.join(result, ITMSConstants.iTMSDirectoryName, "bin", ITMSConstants.iTMSExecutableName); - } - - if (!this.$fs.exists(this._itmsTransporterPath)) { + if (!this.$fs.exists(itmsTransporterPath)) { this.$errors.failWithoutHelp('iTMS Transporter not found on this machine - make sure your Xcode installation is not damaged.'); } - return this._itmsTransporterPath; - } - - private getContentDeliveryRequestBody(credentials: ICredentials): Buffer { - // All of those values except credentials are hardcoded - // Apple's content delivery API is very picky with handling requests - // and if only one of these ends up missing the API returns - // a response with 200 status code and an error - return Buffer.from(JSON.stringify({ - id: "1", // magic number - jsonrpc: "2.0", - method: "lookupSoftwareApplications", - params: { - Username: credentials.username, - Password: credentials.password, - Version: "2.9.1 (441)", - Application: "Application Loader", - OSIdentifier: "Mac OS X 10.8.5 (x86_64)" - } - }), "utf8"); + return itmsTransporterPath; } private getITMSMetadataXml(appleId: string, ipaFileName: string, ipaFileHash: string, ipaFileSize: number): string { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b32d662fac..7d4916d98d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "5.3.1", + "version": "5.3.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c1d433b63f..05ddfb92a6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "5.3.2", + "version": "5.3.3", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From b2bd5b4f540948f856351fbf99281211ba361f90 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 19 Apr 2019 10:51:11 +0300 Subject: [PATCH 3/3] chore: fix PR comments --- lib/commands/appstore-list.ts | 11 ++--------- lib/commands/appstore-upload.ts | 2 +- lib/declarations.d.ts | 4 ++-- lib/services/itmstransporter-service.ts | 4 ++-- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index c88bcaee82..40391c7047 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -37,20 +37,13 @@ export class ListiOSApps implements ICommand { this.$logger.out("Seems you don't have any applications yet."); } else { const table: any = createTable(["Application Name", "Bundle Identifier", "In Flight Version"], applications.map(application => { - return [application.name, application.bundleId, this.getVersion(application)]; + const version = (application && application.versionSets && application.versionSets.length && application.versionSets[0].inFlightVersion && application.versionSets[0].inFlightVersion.version) || ""; + return [application.name, application.bundleId, version]; })); this.$logger.out(table.toString()); } } - - private getVersion(application: IApplePortalApplicationSummary): string { - if (application && application.versionSets && application.versionSets.length && application.versionSets[0].inFlightVersion) { - return application.versionSets[0].inFlightVersion.version; - } - - return ""; - } } $injector.registerCommand("appstore|*list", ListiOSApps); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 70f21385a3..8b40694abf 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -109,7 +109,7 @@ export class PublishIOS implements ICommand { username, password, ipaFilePath, - ipaFileOption: !!this.$options.ipa, + shouldExtractIpa: !!this.$options.ipa, verboseLogging: this.$logger.getLevel() === "TRACE" }); } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 25f6ed06f9..e3e75e604b 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -624,9 +624,9 @@ interface IITMSData extends ICredentials { ipaFilePath: string; /** - * Specified if the ipa options is provided + * Specify if the service should extract the `.ipa` file into `temp` directory in order to get bundleIdentifier from info.plist */ - ipaFileOption: boolean; + shouldExtractIpa: boolean; /** * Specifies whether the logging level of the itmstransporter command-line tool should be set to verbose. * @type {string} diff --git a/lib/services/itmstransporter-service.ts b/lib/services/itmstransporter-service.ts index 607081bf41..c129e9332f 100644 --- a/lib/services/itmstransporter-service.ts +++ b/lib/services/itmstransporter-service.ts @@ -44,9 +44,9 @@ export class ITMSTransporterService implements IITMSTransporterService { } private async getBundleIdentifier(data: IITMSData): Promise { - const { ipaFileOption, ipaFilePath } = data; + const { shouldExtractIpa, ipaFilePath } = data; - if (ipaFileOption) { + if (shouldExtractIpa) { if (!this.$fs.exists(ipaFilePath) || path.extname(ipaFilePath) !== ".ipa") { this.$errors.failWithoutHelp(`Cannot use specified ipa file ${ipaFilePath}. File either does not exist or is not an ipa file.`); }