From 0b1e75051d50f1c5ae5f59af0c40059028131633 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 25 Jul 2019 16:36:40 +0300 Subject: [PATCH 01/15] feat: support accounts with two-factor authentication on publish --- lib/commands/appstore-list.ts | 8 +- lib/commands/appstore-upload.ts | 15 +- lib/common/constants.ts | 1 + lib/common/http-client.ts | 4 +- lib/declarations.d.ts | 10 +- lib/options.ts | 4 +- .../apple-portal-application-service.ts | 9 +- .../apple-portal-cookie-service.ts | 4 +- .../apple-portal-session-service.ts | 165 ++++++++++++++---- lib/services/apple-portal/definitions.d.ts | 23 ++- lib/services/itmstransporter-service.ts | 13 +- 11 files changed, 206 insertions(+), 50 deletions(-) diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 94acbabec3..1704ecc5b6 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -6,6 +6,7 @@ export class ListiOSApps implements ICommand { constructor(private $injector: IInjector, private $applePortalApplicationService: IApplePortalApplicationService, + private $applePortalSessionService: IApplePortalSessionService, private $logger: ILogger, private $projectData: IProjectData, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, @@ -31,7 +32,12 @@ export class ListiOSApps implements ICommand { password = await this.$prompter.getPassword("Apple ID password"); } - const applications = await this.$applePortalApplicationService.getApplications({ username, password }); + const user = await this.$applePortalSessionService.createUserSession({ username, password }); + if (!user.areCredentialsValid) { + this.$errors.failWithoutHelp(`Invalid username and password combination. Used '${username}' as the username.`); + } + + const applications = await this.$applePortalApplicationService.getApplications(user); if (!applications || !applications.length) { this.$logger.info("Seems you don't have any applications yet."); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index e8dfc56508..9d239cedd6 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -8,6 +8,7 @@ export class PublishIOS implements ICommand { new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)]; constructor( + private $applePortalSessionService: IApplePortalSessionService, private $injector: IInjector, private $itmsTransporterService: IITMSTransporterService, private $logger: ILogger, @@ -38,6 +39,15 @@ export class PublishIOS implements ICommand { password = await this.$prompter.getPassword("Apple ID password"); } + const user = await this.$applePortalSessionService.createUserSession({ username, password }, { + applicationSpecificPassword: this.$options.appleApplicationSpecificPassword, + sessionBase64: this.$options.appleSessionBase64, + ensureConsoleIsInteractive: true + }); + if (!user.areCredentialsValid) { + this.$errors.failWithoutHelp(`Invalid username and password combination. Used '${username}' as the username.`); + } + if (!mobileProvisionIdentifier && !ipaFilePath) { this.$logger.warn("No mobile provision identifier set. A default mobile provision will be used. You can set one in app/App_Resources/iOS/build.xcconfig"); } @@ -69,8 +79,9 @@ export class PublishIOS implements ICommand { } await this.$itmsTransporterService.upload({ - username, - password, + credentials: { username, password }, + user, + applicationSpecificPassword: this.$options.appleApplicationSpecificPassword, ipaFilePath, shouldExtractIpa: !!this.$options.ipa, verboseLogging: this.$logger.getLevel() === "TRACE" diff --git a/lib/common/constants.ts b/lib/common/constants.ts index 699647df64..c49c5aa15c 100644 --- a/lib/common/constants.ts +++ b/lib/common/constants.ts @@ -95,6 +95,7 @@ export class HttpStatusCodes { static NOT_MODIFIED = 304; static PAYMENT_REQUIRED = 402; static PROXY_AUTHENTICATION_REQUIRED = 407; + static CONFLICTING_RESOURCE = 409; } export const HttpProtocolToPort: IDictionary = { diff --git a/lib/common/http-client.ts b/lib/common/http-client.ts index 5c970b7eea..fe2dd0b105 100644 --- a/lib/common/http-client.ts +++ b/lib/common/http-client.ts @@ -255,7 +255,9 @@ private defaultUserAgent: string; this.$logger.error(`You can run ${EOL}\t${clientNameLowerCase} proxy set .${EOL}In order to supply ${clientNameLowerCase} with the credentials needed.`); return "Your proxy requires authentication."; } else if (statusCode === HttpStatusCodes.PAYMENT_REQUIRED) { - return util.format("Your subscription has expired."); + return "Your subscription has expired."; + } else if (statusCode === HttpStatusCodes.CONFLICTING_RESOURCE) { + return "The request conflicts with the current state of the server."; } else { this.$logger.trace("Request was unsuccessful. Server returned: ", body); try { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 389e2c17b7..f7bf0994fe 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -568,6 +568,8 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai analyticsLogFile: string; performance: Object; cleanupLogFile: string; + appleApplicationSpecificPassword: string; + appleSessionBase64: string; } interface IEnvOptions { @@ -609,7 +611,13 @@ interface IAndroidResourcesMigrationService { /** * Describes properties needed for uploading a package to iTunes Connect */ -interface IITMSData extends ICredentials { +interface IITMSData { + credentials: ICredentials; + + user: IApplePortalUserDetail; + + applicationSpecificPassword: string; + /** * Path to a .ipa file which will be uploaded. * @type {string} diff --git a/lib/options.ts b/lib/options.ts index 4c9d1fa83f..f2fe7bfa2f 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -145,7 +145,9 @@ export class Options { hooks: { type: OptionType.Boolean, default: true, hasSensitiveValue: false }, link: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, aab: { type: OptionType.Boolean, hasSensitiveValue: false }, - performance: { type: OptionType.Object, hasSensitiveValue: true } + performance: { type: OptionType.Object, hasSensitiveValue: true }, + appleApplicationSpecificPassword: { type: OptionType.String, hasSensitiveValue: true }, + appleSessionBase64: { type: OptionType.String, hasSensitiveValue: true }, }; } diff --git a/lib/services/apple-portal/apple-portal-application-service.ts b/lib/services/apple-portal/apple-portal-application-service.ts index 791e420bb1..56c3194043 100644 --- a/lib/services/apple-portal/apple-portal-application-service.ts +++ b/lib/services/apple-portal/apple-portal-application-service.ts @@ -5,10 +5,9 @@ export class ApplePortalApplicationService implements IApplePortalApplicationSer private $httpClient: Server.IHttpClient ) { } - public async getApplications(credentials: ICredentials): Promise { + public async getApplications(user: IApplePortalUserDetail): 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; @@ -36,10 +35,10 @@ export class ApplePortalApplicationService implements IApplePortalApplicationSer return JSON.parse(response.body).data; } - public async getApplicationByBundleId(credentials: ICredentials, bundleId: string): Promise { - const applications = await this.getApplications(credentials); + public async getApplicationByBundleId(user: IApplePortalUserDetail, bundleId: string): Promise { + const applications = await this.getApplications(user); if (!applications || !applications.length) { - this.$errors.failWithoutHelp(`Cannot find any registered applications for Apple ID ${credentials.username} in iTunes Connect.`); + this.$errors.failWithoutHelp(`Cannot find any registered applications for Apple ID ${user.userName} in iTunes Connect.`); } const application = _.find(applications, app => app.bundleId === bundleId); diff --git a/lib/services/apple-portal/apple-portal-cookie-service.ts b/lib/services/apple-portal/apple-portal-cookie-service.ts index 241652cd82..f58d6701ba 100644 --- a/lib/services/apple-portal/apple-portal-cookie-service.ts +++ b/lib/services/apple-portal/apple-portal-cookie-service.ts @@ -1,6 +1,6 @@ export class ApplePortalCookieService implements IApplePortalCookieService { private userSessionCookies: IStringDictionary = {}; - private validUserSessionCookieNames = ["myacinfo", "dqsid", "itctx", "itcdq", "acn01"]; + private validUserSessionCookieNames = ["myacinfo", "dqsid", "itctx", "itcdq", "acn01", "DES"]; private validWebSessionCookieNames = ["wosid", "woinst", "itctx"]; public getWebSessionCookie(cookiesData: string[]): string { @@ -29,7 +29,7 @@ export class ApplePortalCookieService implements IApplePortalCookieService { for (const cookie of parts) { const trimmedCookie = cookie.trim(); const [cookieKey, cookieValue] = trimmedCookie.split("="); - if (_.includes(validCookieNames, cookieKey)) { + if (_.includes(validCookieNames, cookieKey) || _.some(validCookieNames, validCookieName => cookieKey.startsWith(validCookieName))) { result[cookieKey] = { key: cookieKey, value: cookieValue, cookie: trimmedCookie }; } } diff --git a/lib/services/apple-portal/apple-portal-session-service.ts b/lib/services/apple-portal/apple-portal-session-service.ts index 83adf60272..12da130f86 100644 --- a/lib/services/apple-portal/apple-portal-session-service.ts +++ b/lib/services/apple-portal/apple-portal-session-service.ts @@ -1,3 +1,5 @@ +import { isInteractive } from "../../common/helpers"; + export class ApplePortalSessionService implements IApplePortalSessionService { private loginConfigEndpoint = "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com"; private defaultLoginConfig = { @@ -7,42 +9,33 @@ export class ApplePortalSessionService implements IApplePortalSessionService { constructor( private $applePortalCookieService: IApplePortalCookieService, + private $errors: IErrors, private $httpClient: Server.IHttpClient, - private $logger: ILogger + private $logger: ILogger, + private $prompter: IPrompter ) { } - 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"]); + public async createUserSession(credentials: ICredentials, opts?: IAppleCreateUserSessionOptions): Promise { + const loginResult = await this.login(credentials, opts); - const sessionResponse = await this.$httpClient.httpRequest({ - url: "https://appstoreconnect.apple.com/olympus/v1/session", - method: "GET", - headers: { - 'Cookie': this.$applePortalCookieService.getUserSessionCookie() + if (!opts || !opts.sessionBase64) { + if (loginResult.isTwoFactorAuthenticationEnabled) { + const authServiceKey = (await this.getLoginConfig()).authServiceKey; + await this.handleTwoFactorAuthentication(loginResult.scnt, loginResult.xAppleIdSessionId, authServiceKey); } - }); - this.$applePortalCookieService.updateUserSessionCookie(sessionResponse.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({ + const userDetailsResponse = await this.$httpClient.httpRequest({ url: "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/user/detail", method: "GET", headers: { @@ -51,9 +44,12 @@ export class ApplePortalSessionService implements IApplePortalSessionService { } }); - this.$applePortalCookieService.updateUserSessionCookie(userDetailResponse.headers["set-cookie"]); + this.$applePortalCookieService.updateUserSessionCookie(userDetailsResponse.headers["set-cookie"]); - return JSON.parse(userDetailResponse.body).data; + const userdDetails = JSON.parse(userDetailsResponse.body).data; + const result = { ...userdDetails, ...loginResult, userSessionCookie: this.$applePortalCookieService.getUserSessionCookie() }; + + return result; } public async createWebSession(contentProviderId: number, dsId: string): Promise { @@ -79,6 +75,72 @@ export class ApplePortalSessionService implements IApplePortalSessionService { return webSessionCookie; } + private async login(credentials: ICredentials, opts?: IAppleCreateUserSessionOptions): Promise { + const result = { + scnt: null, + xAppleIdSessionId: null, + isTwoFactorAuthenticationEnabled: false, + areCredentialsValid: true + }; + + if (opts && opts.sessionBase64) { + const decodedSession = Buffer.from(opts.sessionBase64, "base64").toString("utf8"); + + this.$applePortalCookieService.updateUserSessionCookie([decodedSession]); + + result.isTwoFactorAuthenticationEnabled = decodedSession.indexOf("DES") > -1; + } else { + try { + await this.loginCore(credentials); + } catch (err) { + const statusCode = err && err.response && err.response.statusCode; + result.areCredentialsValid = statusCode !== 401 && statusCode !== 403; + result.isTwoFactorAuthenticationEnabled = statusCode === 409; + if (result.isTwoFactorAuthenticationEnabled && opts && !opts.applicationSpecificPassword) { + this.$errors.failWithoutHelp(`Your account has two-factor authentication enabled but --appleApplicationSpecificPassword option is not provided. +To generate an application-specific password, please go to https://appleid.apple.com/account/manage. +This password will be used for the iTunes Transporter, which is used to upload your application.`); + } + + if (result.isTwoFactorAuthenticationEnabled && opts && opts.ensureConsoleIsInteractive && !isInteractive()) { + this.$errors.failWithoutHelp(`Your account has two-factor authentication enabled, but your console is not interactive. +For more details how to set up your environment, please execute "tns publish ios --help".`); + } + + const headers = (err && err.response && err.response.headers) || {}; + result.scnt = headers.scnt; + result.xAppleIdSessionId = headers['x-apple-id-session-id']; + } + } + + return result; + } + + private async loginCore(credentials: ICredentials): Promise { + const loginConfig = await this.getLoginConfig(); + const loginUrl = `${loginConfig.authServiceUrl}/auth/signin`; + const headers = { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + 'X-Apple-Widget-Key': loginConfig.authServiceKey, + 'Accept': 'application/json, text/javascript' + }; + const body = JSON.stringify({ + accountName: credentials.username, + password: credentials.password, + rememberMe: true + }); + + const loginResponse = await this.$httpClient.httpRequest({ + url: loginUrl, + method: "POST", + body, + headers + }); + + this.$applePortalCookieService.updateUserSessionCookie(loginResponse.headers["set-cookie"]); + } + private async getLoginConfig(): Promise<{authServiceUrl: string, authServiceKey: string}> { let config = null; @@ -91,5 +153,46 @@ export class ApplePortalSessionService implements IApplePortalSessionService { return config || this.defaultLoginConfig; } + + private async handleTwoFactorAuthentication(scnt: string, xAppleIdSessionId: string, authServiceKey: string): Promise { + const headers = { + 'scnt': scnt, + 'X-Apple-Id-Session-Id': xAppleIdSessionId, + 'X-Apple-Widget-Key': authServiceKey, + 'Accept': 'application/json' + }; + const authResponse = await this.$httpClient.httpRequest({ + url: "https://idmsa.apple.com/appleauth/auth", + method: "GET", + headers + }); + + const data = JSON.parse(authResponse.body); + if (data.trustedPhoneNumbers && data.trustedPhoneNumbers.length) { + const parsedAuthResponse = JSON.parse(authResponse.body); + const token = await this.$prompter.getString(`Please enter the ${parsedAuthResponse.securityCode.length} digit code`, { allowEmpty: false }); + + await this.$httpClient.httpRequest({ + url: `https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode`, + method: "POST", + body: JSON.stringify({ + securityCode: { + code: token.toString() + } + }), + headers: { ...headers, 'Content-Type': "application/json" } + }); + + const authTrustResponse = await this.$httpClient.httpRequest({ + url: "https://idmsa.apple.com/appleauth/auth/2sv/trust", + method: "GET", + headers + }); + + this.$applePortalCookieService.updateUserSessionCookie(authTrustResponse.headers["set-cookie"]); + } else { + this.$errors.failWithoutHelp(`Although response from Apple indicated activated Two-step Verification or Two-factor Authentication, NativeScript CLI don't know how to handle this response: ${data}`); + } + } } $injector.register("applePortalSessionService", ApplePortalSessionService); diff --git a/lib/services/apple-portal/definitions.d.ts b/lib/services/apple-portal/definitions.d.ts index fdaf16968e..d53865ff54 100644 --- a/lib/services/apple-portal/definitions.d.ts +++ b/lib/services/apple-portal/definitions.d.ts @@ -1,6 +1,6 @@ interface IApplePortalSessionService { - createUserSession(credentials: ICredentials): Promise; createWebSession(contentProviderId: number, dsId: string): Promise; + createUserSession(credentials: ICredentials, opts?: IAppleCreateUserSessionOptions): Promise; } interface IApplePortalCookieService { @@ -10,12 +10,25 @@ interface IApplePortalCookieService { } interface IApplePortalApplicationService { - getApplications(credentials: ICredentials): Promise + getApplications(user: IApplePortalUserDetail): Promise getApplicationsByProvider(contentProviderId: number, dsId: string): Promise; - getApplicationByBundleId(credentials: ICredentials, bundleId: string): Promise; + getApplicationByBundleId(user: IApplePortalUserDetail, bundleId: string): Promise; } -interface IApplePortalUserDetail { +interface IAppleCreateUserSessionOptions { + applicationSpecificPassword: string; + sessionBase64: string; + ensureConsoleIsInteractive: boolean; +} + +interface IAppleLoginResult { + scnt: string; + xAppleIdSessionId: string; + isTwoFactorAuthenticationEnabled: boolean; + areCredentialsValid: boolean; +} + +interface IApplePortalUserDetail extends IAppleLoginResult { associatedAccounts: IApplePortalAssociatedAccountData[]; sessionToken: { dsId: string; @@ -29,8 +42,10 @@ interface IApplePortalUserDetail { userName: string; userId: string; contentProvider: string; + authServiceKey: string; visibility: boolean; DYCVisibility: boolean; + userSessionCookie: string; } interface IApplePortalAssociatedAccountData { diff --git a/lib/services/itmstransporter-service.ts b/lib/services/itmstransporter-service.ts index c129e9332f..fc3cfc2bc7 100644 --- a/lib/services/itmstransporter-service.ts +++ b/lib/services/itmstransporter-service.ts @@ -28,7 +28,7 @@ export class ITMSTransporterService implements IITMSTransporterService { 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); + const application = await this.$applePortalApplicationService.getApplicationByBundleId(data.user, bundleId); this.$fs.createDirectory(innerDirectory); @@ -40,7 +40,16 @@ export class ITMSTransporterService implements IITMSTransporterService { 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" }); + const password = data.user.isTwoFactorAuthenticationEnabled ? data.applicationSpecificPassword : data.credentials.password; + await this.$childProcess.spawnFromEvent(itmsTransporterPath, + [ + "-m", "upload", + "-f", itmsDirectory, + "-u", quoteString(data.credentials.username), + "-p", quoteString(password), + "-v", loggingLevel + ], + "close", { stdio: "inherit" }); } private async getBundleIdentifier(data: IITMSData): Promise { From 802e8e6ccd12e2dbae3eb2b992118b277e89e016 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 25 Jul 2019 16:37:51 +0300 Subject: [PATCH 02/15] feat: add apple-login command --- lib/bootstrap.ts | 1 + lib/commands/apple-login.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 lib/commands/apple-login.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 00cbc57b25..704c759f47 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -100,6 +100,7 @@ $injector.requireCommand("dev-generate-help", "./commands/generate-help"); $injector.requireCommand("appstore|*list", "./commands/appstore-list"); $injector.requireCommand("appstore|upload", "./commands/appstore-upload"); $injector.requireCommand("publish|ios", "./commands/appstore-upload"); +$injector.requireCommand("apple-login", "./commands/apple-login"); $injector.require("itmsTransporterService", "./services/itmstransporter-service"); $injector.requireCommand("setup|*", "./commands/setup"); diff --git a/lib/commands/apple-login.ts b/lib/commands/apple-login.ts new file mode 100644 index 0000000000..3fcd3a6076 --- /dev/null +++ b/lib/commands/apple-login.ts @@ -0,0 +1,34 @@ +import { StringCommandParameter } from "../common/command-params"; + +export class AppleLogin implements ICommand { + public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)]; + + constructor( + private $applePortalSessionService: IApplePortalSessionService, + private $errors: IErrors, + private $injector: IInjector, + private $logger: ILogger, + private $prompter: IPrompter + ) { } + + public async execute(args: string[]): Promise { + let username = args[0]; + if (!username) { + username = await this.$prompter.getString("Apple ID", { allowEmpty: false }); + } + + let password = args[1]; + if (!password) { + password = await this.$prompter.getPassword("Apple ID password"); + } + + const user = await this.$applePortalSessionService.createUserSession({ username, password }); + if (!user.areCredentialsValid) { + this.$errors.failWithoutHelp(`Invalid username and password combination. Used '${username}' as the username.`); + } + + const output = Buffer.from(user.userSessionCookie).toString("base64"); + this.$logger.info(output); + } +} +$injector.registerCommand("apple-login", AppleLogin); From e8106ec535874eb778596a3edad671f4a59cfd43 Mon Sep 17 00:00:00 2001 From: Ryan Pendergast Date: Thu, 25 Jul 2019 15:30:17 -0500 Subject: [PATCH 03/15] fix: missing --template arg --- docs/man_pages/project/creation/create.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/man_pages/project/creation/create.md b/docs/man_pages/project/creation/create.md index 1f920a84c7..1391f6a90e 100644 --- a/docs/man_pages/project/creation/create.md +++ b/docs/man_pages/project/creation/create.md @@ -43,16 +43,16 @@ Below you can see a list of the recommended NativeScript starting templates and Template | Command ---------|---------- `JavaScript - Hello World`, `--js`, `--javascript` | tns create --template tns-template-hello-world -`JavaScript - SideDrawer` | tns create tns-template-drawer-navigation -`JavaScript - Tabs` | tns create tns-template-tab-navigation -`TypeScript - Hello World`, `--ts`, `--tsc`, `--typescript` | tns create tns-template-hello-world-ts -`TypeScript - SideDrawer` | tns create tns-template-drawer-navigation-ts -`TypeScript - Tabs` | tns create tns-template-tab-navigation-ts -`Angular - Hello World`, `--ng`, `--angular` | tns create tns-template-hello-world-ng -`Angular - SideDrawer` | tns create tns-template-drawer-navigation-ng -`Angular - Tabs` | tns create tns-template-tab-navigation-ng -`Vue.js - Blank`, `--vue`, `--vuejs` | tns create tns-template-blank-vue -`Vue.js - SideDrawer`, | tns create tns-template-drawer-navigation-vue +`JavaScript - SideDrawer` | tns create --template tns-template-drawer-navigation +`JavaScript - Tabs` | tns create --template tns-template-tab-navigation +`TypeScript - Hello World`, `--ts`, `--tsc`, `--typescript` | tns create --template tns-template-hello-world-ts +`TypeScript - SideDrawer` | tns create --template tns-template-drawer-navigation-ts +`TypeScript - Tabs` | tns create --template tns-template-tab-navigation-ts +`Angular - Hello World`, `--ng`, `--angular` | tns create --template tns-template-hello-world-ng +`Angular - SideDrawer` | tns create --template tns-template-drawer-navigation-ng +`Angular - Tabs` | tns create --template tns-template-tab-navigation-ng +`Vue.js - Blank`, `--vue`, `--vuejs` | tns create --template tns-template-blank-vue +`Vue.js - SideDrawer`, | tns create --template tns-template-drawer-navigation-vue ### Related Commands From a6e17e53638a4ab241792d43a0844f5cfc97221b Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 25 Jul 2019 16:38:04 +0300 Subject: [PATCH 04/15] test: fix unit tests --- test/tns-appstore-upload.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index 053b784c1b..9c5fa90520 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -71,6 +71,13 @@ class AppStore { return this.iOSPlatformData; } }, + "applePortalSessionService": { + createUserSession: () => { + return { + areCredentialsValid: true + }; + } + } } }); @@ -120,8 +127,8 @@ class AppStore { this.itmsTransporterService.upload = (options: IITMSData) => { this.itmsTransporterServiceUploadCalls++; chai.assert.equal(options.ipaFilePath, "/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"); - chai.assert.equal(options.username, AppStore.itunesconnect.user); - chai.assert.equal(options.password, AppStore.itunesconnect.pass); + chai.assert.equal(options.credentials.username, AppStore.itunesconnect.user); + chai.assert.equal(options.credentials.password, AppStore.itunesconnect.pass); chai.assert.equal(options.verboseLogging, false); return Promise.resolve(); }; From 9ca5ba5f181f8d036cea3f35664d97dc0ec582d2 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 26 Jul 2019 13:38:57 +0300 Subject: [PATCH 05/15] docs: add help for new options and command --- docs/man_pages/publishing/apple-login.md | 39 ++++++++++++++++++++ docs/man_pages/publishing/appstore-upload.md | 2 + docs/man_pages/publishing/publish-ios.md | 4 +- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 docs/man_pages/publishing/apple-login.md diff --git a/docs/man_pages/publishing/apple-login.md b/docs/man_pages/publishing/apple-login.md new file mode 100644 index 0000000000..fd74d704d6 --- /dev/null +++ b/docs/man_pages/publishing/apple-login.md @@ -0,0 +1,39 @@ +<% if (isJekyll) { %>--- +title: tns appstore +position: 5 +---<% } %> + +# tns apple-login + +### Description + +Uses the provided Apple credentials to obtain Apple session which can be used when publishing to Apple AppStore. + +### Commands + +Usage | Synopsis +---|--- +General | `$ tns apple-login [] []` + +<% if((isConsole && isMacOS) || isHtml) { %> + +### Options + +### Arguments + +* `` and `` are your credentials for logging into iTunes Connect. + +### Command Limitations + +### Related Commands + +Command | Description +----------|---------- +[appstore](appstore.html) | Lists applications registered in iTunes Connect. +[appstore upload](appstore-upload.html) | Uploads project to iTunes Connect. +[build](../project/testing/build.html) | Builds the project for the selected target platform and produces an application package that you can manually deploy on device or in the native emulator. +[build ios](../project/testing/build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS Simulator or on device, respectively. +[deploy](../project/testing/deploy.html) | Builds and deploys the project to a connected physical or virtual device. +[run](../project/testing/run.html) | Runs your project on a connected device or in the native emulator for the selected platform. +[run ios](../project/testing/run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. +<% } %> \ No newline at end of file diff --git a/docs/man_pages/publishing/appstore-upload.md b/docs/man_pages/publishing/appstore-upload.md index 60dbe1f214..121966a8c7 100644 --- a/docs/man_pages/publishing/appstore-upload.md +++ b/docs/man_pages/publishing/appstore-upload.md @@ -22,6 +22,8 @@ Upload package | `$ tns appstore upload [ []] --ipa []] --ipa ` and `` are your credentials for logging into iTunes Connect. From b63f34ed428f90a90900260869421a0b909b2b36 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 30 Jul 2019 08:18:24 +0300 Subject: [PATCH 06/15] fix: fix PR comments --- docs/man_pages/publishing/apple-login.md | 8 ++------ docs/man_pages/publishing/appstore-upload.md | 5 +++-- docs/man_pages/publishing/publish-ios.md | 5 +++-- lib/commands/appstore-list.ts | 7 +++++-- lib/commands/appstore-upload.ts | 2 +- lib/services/apple-portal/apple-portal-session-service.ts | 2 +- lib/services/apple-portal/definitions.d.ts | 4 ++-- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/man_pages/publishing/apple-login.md b/docs/man_pages/publishing/apple-login.md index fd74d704d6..4e14cb6046 100644 --- a/docs/man_pages/publishing/apple-login.md +++ b/docs/man_pages/publishing/apple-login.md @@ -1,5 +1,5 @@ <% if (isJekyll) { %>--- -title: tns appstore +title: tns apple-login position: 5 ---<% } %> @@ -15,15 +15,11 @@ Usage | Synopsis ---|--- General | `$ tns apple-login [] []` -<% if((isConsole && isMacOS) || isHtml) { %> - -### Options - ### Arguments * `` and `` are your credentials for logging into iTunes Connect. -### Command Limitations +<% if(isHtml) { %>s ### Related Commands diff --git a/docs/man_pages/publishing/appstore-upload.md b/docs/man_pages/publishing/appstore-upload.md index 121966a8c7..67df8607ee 100644 --- a/docs/man_pages/publishing/appstore-upload.md +++ b/docs/man_pages/publishing/appstore-upload.md @@ -8,6 +8,7 @@ position: 1 ### Description Uploads project to iTunes Connect. The command either issues a production build and uploads it to iTunes Connect, or uses an already built package to upload. +The user will be prompted interactively for verification code when two-factor authentication enabled account is used. As on non-interactive console (CI), you will not be prompt for verification code. In this case, you need to generate a login session for your apple's account in advance using `tns apple-login` command. The generated value must be provided via the `--appleSessionBase64` option and is only valid for up to a month. Meaning you'll need to create a new session every month. <% if(isConsole && (isLinux || isWindows)) { %>WARNING: You can run this command only on macOS systems. To view the complete help for this command, run `$ tns help appstore upload`<% } %> <% if((isConsole && isMacOS) || isHtml) { %> @@ -22,8 +23,8 @@ Upload package | `$ tns appstore upload [ []] --ipa WARNING: You can run this command only on macOS systems. To view the complete help for this command, run `$ tns help publish ios`<% } %> @@ -24,8 +25,8 @@ Upload package | `$ tns publish ios [ []] --ipa Date: Tue, 30 Jul 2019 09:45:40 +0300 Subject: [PATCH 07/15] fix: hardcode karma-webpack to 3.0.5 --- config/test-dependencies.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/test-dependencies.json b/config/test-dependencies.json index 879c04a950..a1557d3bfe 100644 --- a/config/test-dependencies.json +++ b/config/test-dependencies.json @@ -7,7 +7,8 @@ }, { "name": "karma-webpack", - "excludedPeerDependencies": ["webpack"] + "excludedPeerDependencies": ["webpack"], + "version": "3.0.5" }, { "name": "mocha", From 87f2b1fc366dd32a4e9cecea55fcebd6b3d18ded Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 30 Jul 2019 13:53:52 +0300 Subject: [PATCH 08/15] fix: fix appstore list command for accounts with 2fa Currently `tns appstore list` command throws an error when account is with 2fa and `--appleApplicationSpecificPassword` option is not provided. We shouldn't throw an error in this situation as we don't need `--appleApplicationSpecificPassword` on `tns appstore list` command as we don't use third party application. This option is mandatory only for `tns appstore upload` command where we use iTunesTransporter app when uploading the application. --- lib/commands/appstore-upload.ts | 3 ++- lib/services/apple-portal/apple-portal-session-service.ts | 3 ++- lib/services/apple-portal/definitions.d.ts | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 45557a1050..9c2c582b34 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -42,7 +42,8 @@ export class PublishIOS implements ICommand { const user = await this.$applePortalSessionService.createUserSession({ username, password }, { applicationSpecificPassword: this.$options.appleApplicationSpecificPassword, sessionBase64: this.$options.appleSessionBase64, - requireInteractiveConsole: true + requireInteractiveConsole: true, + requireApplicationSpecificPassword: true }); if (!user.areCredentialsValid) { this.$errors.failWithoutHelp(`Invalid username and password combination. Used '${username}' as the username.`); diff --git a/lib/services/apple-portal/apple-portal-session-service.ts b/lib/services/apple-portal/apple-portal-session-service.ts index 786ef9833c..9d63e0270d 100644 --- a/lib/services/apple-portal/apple-portal-session-service.ts +++ b/lib/services/apple-portal/apple-portal-session-service.ts @@ -96,7 +96,8 @@ export class ApplePortalSessionService implements IApplePortalSessionService { const statusCode = err && err.response && err.response.statusCode; result.areCredentialsValid = statusCode !== 401 && statusCode !== 403; result.isTwoFactorAuthenticationEnabled = statusCode === 409; - if (result.isTwoFactorAuthenticationEnabled && opts && !opts.applicationSpecificPassword) { + + if (result.isTwoFactorAuthenticationEnabled && opts && opts.requireApplicationSpecificPassword && !opts.applicationSpecificPassword) { this.$errors.failWithoutHelp(`Your account has two-factor authentication enabled but --appleApplicationSpecificPassword option is not provided. To generate an application-specific password, please go to https://appleid.apple.com/account/manage. This password will be used for the iTunes Transporter, which is used to upload your application.`); diff --git a/lib/services/apple-portal/definitions.d.ts b/lib/services/apple-portal/definitions.d.ts index 3d8a3b074d..74f6dea05d 100644 --- a/lib/services/apple-portal/definitions.d.ts +++ b/lib/services/apple-portal/definitions.d.ts @@ -19,6 +19,7 @@ interface IAppleCreateUserSessionOptions { applicationSpecificPassword?: string; sessionBase64: string; requireInteractiveConsole?: boolean; + requireApplicationSpecificPassword?: boolean; } interface IAppleLoginResult { From 2dfbeaad605dfce43639bf0a18c124a25295b78a Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Wed, 31 Jul 2019 16:21:38 +0300 Subject: [PATCH 09/15] fix: re-implement lost improvements for faster LiveSync and avoid getting crashed LiveSync when the fast sync fails --- lib/controllers/run-controller.ts | 51 ++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index d6cc35912e..3af46e945c 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -2,6 +2,7 @@ import { HmrConstants, DeviceDiscoveryEventNames } from "../common/constants"; import { PREPARE_READY_EVENT_NAME, TrackActionNames, DEBUGGER_DETACHED_EVENT_NAME, RunOnDeviceEvents, USER_INTERACTION_NEEDED_EVENT_NAME } from "../constants"; import { cache, performanceLog } from "../common/decorators"; import { EventEmitter } from "events"; +import * as util from "util"; export class RunController extends EventEmitter implements IRunController { private prepareReadyEventHandler: any = null; @@ -270,7 +271,7 @@ export class RunController extends EventEmitter implements IRunController { } private async syncInitialDataOnDevices(projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { - const rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = { }; + const rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = {}; const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); @@ -333,7 +334,7 @@ export class RunController extends EventEmitter implements IRunController { error: err, }); - await this.stop({ projectDir: projectData.projectDir, deviceIdentifiers: [device.deviceInfo.identifier], stopOptions: { shouldAwaitAllActions: false }}); + await this.stop({ projectDir: projectData.projectDir, deviceIdentifiers: [device.deviceInfo.identifier], stopOptions: { shouldAwaitAllActions: false } }); } }; @@ -341,7 +342,8 @@ export class RunController extends EventEmitter implements IRunController { } private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { - const rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = { }; + const successfullySyncedMessageFormat = `Successfully synced application %s on device %s.`; + const rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = {}; const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptors = this.$liveSyncProcessDataService.getDeviceDescriptors(projectData.projectDir); @@ -380,28 +382,47 @@ export class RunController extends EventEmitter implements IRunController { await this.$deviceInstallAppService.installOnDevice(device, deviceDescriptor.buildData, rebuiltInformation[platformData.platformNameLowerCase].packageFilePath); await platformLiveSyncService.syncAfterInstall(device, watchInfo); await this.refreshApplication(projectData, { deviceAppData, modifiedFilesData: [], isFullSync: false, useHotModuleReload: liveSyncInfo.useHotModuleReload }, data, deviceDescriptor); + this.$logger.info(util.format(successfullySyncedMessageFormat, deviceAppData.appIdentifier, device.deviceInfo.identifier)); } else { const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; if (isInHMRMode) { this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); } - let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); - await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor); - - if (!liveSyncResultInfo.didRecover && isInHMRMode) { - const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); - if (status === HmrConstants.HMR_ERROR_STATUS) { - watchInfo.filesToSync = data.hmrData.fallbackFiles; - liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); - // We want to force a restart of the application. - liveSyncResultInfo.isFullSync = true; - await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor); + const watchAction = async (): Promise => { + let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); + await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor); + + if (!liveSyncResultInfo.didRecover && isInHMRMode) { + const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); + if (status === HmrConstants.HMR_ERROR_STATUS) { + watchInfo.filesToSync = data.hmrData.fallbackFiles; + liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); + // We want to force a restart of the application. + liveSyncResultInfo.isFullSync = true; + await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor); + } + } + + this.$logger.info(util.format(successfullySyncedMessageFormat, deviceAppData.appIdentifier, device.deviceInfo.identifier)); + }; + + if (liveSyncInfo.useHotModuleReload) { + try { + this.$logger.trace("Try executing watch action without any preparation of files."); + await watchAction(); + this.$logger.trace("Successfully executed watch action without any preparation of files."); + return; + } catch (err) { + this.$logger.trace(`Error while trying to execute fast sync. Now we'll check the state of the app and we'll try to resurrect from the error. The error is: ${err}`); } } + + await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, deviceDescriptor.buildData); + watchInfo.connectTimeout = null; + await watchAction(); } - this.$logger.info(`Successfully synced application ${deviceAppData.appIdentifier} on device ${device.deviceInfo.identifier}.`); } catch (err) { this.$logger.warn(`Unable to apply changes for device: ${device.deviceInfo.identifier}. Error is: ${err && err.message}.`); From 1e2109f8cd89598788f440c2a571568af941caf3 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Wed, 31 Jul 2019 16:27:17 +0300 Subject: [PATCH 10/15] fix: avoid 30 seconds delay on each LiveSync --- lib/commands/test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/commands/test.ts b/lib/commands/test.ts index e412e72ab0..07269c3835 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -1,5 +1,9 @@ abstract class TestCommandBase { public allowedParameters: ICommandParameter[] = []; + public dashedOptions = { + hmr: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, + }; + protected abstract platform: string; protected abstract $projectData: IProjectData; protected abstract $testExecutionService: ITestExecutionService; @@ -50,6 +54,13 @@ abstract class TestCommandBase { async canExecute(args: string[]): Promise { if (!this.$options.force) { + if (this.$options.hmr) { + // With HMR we are not restarting after LiveSync which is causing a 30 seconds app start on Android + // because the Runtime does not watch for the `/data/local/tmp-livesync-in-progress` file deletion. + // The App is closing itself after each test execution and the bug will be reproducible on each LiveSync. + this.$errors.fail("The `--hmr` option is not supported for this command."); + } + await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms: [this.platform] }); } From 196cd3c2e36419e47897c4542039a9025c7f8280 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Wed, 31 Jul 2019 16:55:06 +0300 Subject: [PATCH 11/15] chore: bump version --- npm-shrinkwrap.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2aeebbccc8..f193d85f2b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "6.0.2", + "version": "6.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -8313,4 +8313,4 @@ "integrity": "sha512-99p+ohUBZ2Es0AXrw/tpazMcJ0/acpdQXr0UPrVWF0p7i8XiOYvjiXTdwXUVCTPopBGCSDtWBzOoYNPtF3z/8w==" } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 468664b837..7570824618 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "6.0.2", + "version": "6.0.3", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { @@ -139,4 +139,4 @@ "engines": { "node": ">=10.0.0 <13.0.0" } -} +} \ No newline at end of file From 45f3284607a663cae33816c7c1961a4f7e21bbf3 Mon Sep 17 00:00:00 2001 From: miroslavaivanova Date: Tue, 6 Aug 2019 10:55:21 +0300 Subject: [PATCH 12/15] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59a079ef84..291586b657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ NativeScript CLI Changelog ================ +6.0.3 (2019, August 05) +== +* [Fixed #4914](https://github.com/NativeScript/nativescript-cli/issues/4914): livesync not working with command tns test android +* [Fixed #4746](https://github.com/NativeScript/nativescript-cli/issues/4746): Unable to work with `karma-webpack@4.0.2` on test command +* [Fixed #4586](https://github.com/NativeScript/nativescript-cli/issues/4586): publish ios fails because of hsa2 + 6.0.2 (2019, July 22) == * [Fixed #4885](https://github.com/NativeScript/nativescript-cli/issues/4885): `migrate` and `update` commands are failing where everything is up-to-date From ffa256ddaab4a7b7571711006b8e6cc95eda1144 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 1 Aug 2019 17:41:29 +0300 Subject: [PATCH 13/15] fix: include all chunkFiles in fallbackFiles in order to avoid wrong lazy Angular chunks --- .../webpack/webpack-compiler-service.ts | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 6a6fbcb94a..29f6c4c7f8 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -221,33 +221,25 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return args; } - private getUpdatedEmittedFiles(emittedFiles: string[], chunkFiles: string[]) { - let fallbackFiles: string[] = []; + private getUpdatedEmittedFiles(allEmittedFiles: string[], chunkFiles: string[]) { + const hotHash = this.getCurrentHotUpdateHash(allEmittedFiles); + const emittedHotUpdateFiles = _.difference(allEmittedFiles, chunkFiles); + + return { emittedFiles: emittedHotUpdateFiles, fallbackFiles: chunkFiles, hash: hotHash }; + } + + private getCurrentHotUpdateHash(emittedFiles: string[]) { let hotHash; - let result = emittedFiles.slice(); const hotUpdateScripts = emittedFiles.filter(x => x.endsWith('.hot-update.js')); - if (chunkFiles && chunkFiles.length) { - result = result.filter(file => chunkFiles.indexOf(file) === -1); + if (hotUpdateScripts && hotUpdateScripts.length) { + // the hash is the same for each hot update in the current compilation + const hotUpdateName = hotUpdateScripts[0]; + const matcher = /^(.+)\.(.+)\.hot-update/gm; + const matches = matcher.exec(hotUpdateName); + hotHash = matches[2]; } - hotUpdateScripts.forEach(hotUpdateScript => { - const { name, hash } = this.parseHotUpdateChunkName(hotUpdateScript); - hotHash = hash; - // remove bundle/vendor.js files if there's a bundle.XXX.hot-update.js or vendor.XXX.hot-update.js - result = result.filter(file => file !== `${name}.js`); - }); - // if applying of hot update fails, we must fallback to the full files - fallbackFiles = emittedFiles.filter(file => hotUpdateScripts.indexOf(file) === -1); - - return { emittedFiles: result, fallbackFiles, hash: hotHash }; - } - private parseHotUpdateChunkName(name: string) { - const matcher = /^(.+)\.(.+)\.hot-update/gm; - const matches = matcher.exec(name); - return { - name: matches[1] || "", - hash: matches[2] || "", - }; + return hotHash || ""; } private async stopWebpackForPlatform(platform: string) { From 50588b11f81ce6f2f1388ea91bd1632de539ac86 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 13 Aug 2019 15:56:58 +0300 Subject: [PATCH 14/15] chore: bump version to 6.0.4 --- npm-shrinkwrap.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f193d85f2b..7c5dcaacf9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "6.0.3", + "version": "6.0.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7570824618..a560b0999a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "6.0.3", + "version": "6.0.4", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 4d3419e7971844c499d57169f7cd4f90f1dd700b Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 14 Aug 2019 13:54:29 +0300 Subject: [PATCH 15/15] chore: replace failWithoutHelp with fail --- lib/commands/apple-login.ts | 2 +- lib/commands/appstore-list.ts | 2 +- lib/commands/appstore-upload.ts | 2 +- lib/services/apple-portal/apple-portal-session-service.ts | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/commands/apple-login.ts b/lib/commands/apple-login.ts index 3fcd3a6076..be820e487f 100644 --- a/lib/commands/apple-login.ts +++ b/lib/commands/apple-login.ts @@ -24,7 +24,7 @@ export class AppleLogin implements ICommand { const user = await this.$applePortalSessionService.createUserSession({ username, password }); if (!user.areCredentialsValid) { - this.$errors.failWithoutHelp(`Invalid username and password combination. Used '${username}' as the username.`); + this.$errors.fail(`Invalid username and password combination. Used '${username}' as the username.`); } const output = Buffer.from(user.userSessionCookie).toString("base64"); diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 8d8380996b..1633015cfc 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -37,7 +37,7 @@ export class ListiOSApps implements ICommand { sessionBase64: this.$options.appleSessionBase64, }); if (!user.areCredentialsValid) { - this.$errors.failWithoutHelp(`Invalid username and password combination. Used '${username}' as the username.`); + this.$errors.fail(`Invalid username and password combination. Used '${username}' as the username.`); } const applications = await this.$applePortalApplicationService.getApplications(user); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index e4fff12cdc..60642b2352 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -46,7 +46,7 @@ export class PublishIOS implements ICommand { requireApplicationSpecificPassword: true }); if (!user.areCredentialsValid) { - this.$errors.failWithoutHelp(`Invalid username and password combination. Used '${username}' as the username.`); + this.$errors.fail(`Invalid username and password combination. Used '${username}' as the username.`); } if (!mobileProvisionIdentifier && !ipaFilePath) { diff --git a/lib/services/apple-portal/apple-portal-session-service.ts b/lib/services/apple-portal/apple-portal-session-service.ts index 9d63e0270d..14a09d5a59 100644 --- a/lib/services/apple-portal/apple-portal-session-service.ts +++ b/lib/services/apple-portal/apple-portal-session-service.ts @@ -98,13 +98,13 @@ export class ApplePortalSessionService implements IApplePortalSessionService { result.isTwoFactorAuthenticationEnabled = statusCode === 409; if (result.isTwoFactorAuthenticationEnabled && opts && opts.requireApplicationSpecificPassword && !opts.applicationSpecificPassword) { - this.$errors.failWithoutHelp(`Your account has two-factor authentication enabled but --appleApplicationSpecificPassword option is not provided. + this.$errors.fail(`Your account has two-factor authentication enabled but --appleApplicationSpecificPassword option is not provided. To generate an application-specific password, please go to https://appleid.apple.com/account/manage. This password will be used for the iTunes Transporter, which is used to upload your application.`); } if (result.isTwoFactorAuthenticationEnabled && opts && opts.requireInteractiveConsole && !isInteractive()) { - this.$errors.failWithoutHelp(`Your account has two-factor authentication enabled, but your console is not interactive. + this.$errors.fail(`Your account has two-factor authentication enabled, but your console is not interactive. For more details how to set up your environment, please execute "tns publish ios --help".`); } @@ -192,7 +192,7 @@ For more details how to set up your environment, please execute "tns publish ios this.$applePortalCookieService.updateUserSessionCookie(authTrustResponse.headers["set-cookie"]); } else { - this.$errors.failWithoutHelp(`Although response from Apple indicated activated Two-step Verification or Two-factor Authentication, NativeScript CLI don't know how to handle this response: ${data}`); + this.$errors.fail(`Although response from Apple indicated activated Two-step Verification or Two-factor Authentication, NativeScript CLI don't know how to handle this response: ${data}`); } } }