Skip to content

Commit 5069f29

Browse files
Merge pull request #4949 from NativeScript/vladimirov/merge-rel-master
chore: merge release in master
2 parents 9320a1d + 4d3419e commit 5069f29

23 files changed

+379
-103
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
NativeScript CLI Changelog
22
================
33

4+
6.0.3 (2019, August 05)
5+
==
6+
* [Fixed #4914](https://github.com/NativeScript/nativescript-cli/issues/4914): livesync not working with command tns test android
7+
* [Fixed #4746](https://github.com/NativeScript/nativescript-cli/issues/4746): Unable to work with `[email protected]` on test command
8+
* [Fixed #4586](https://github.com/NativeScript/nativescript-cli/issues/4586): publish ios fails because of hsa2
9+
410
6.0.2 (2019, July 22)
511
==
612
* [Fixed #4885](https://github.com/NativeScript/nativescript-cli/issues/4885): `migrate` and `update` commands are failing where everything is up-to-date

config/test-dependencies.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
},
88
{
99
"name": "karma-webpack",
10-
"excludedPeerDependencies": ["webpack"]
10+
"excludedPeerDependencies": ["webpack"],
11+
"version": "3.0.5"
1112
},
1213
{
1314
"name": "mocha",

docs/man_pages/project/creation/create.md

+10-10
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,16 @@ Below you can see a list of the recommended NativeScript starting templates and
4343
Template | Command
4444
---------|----------
4545
`JavaScript - Hello World`, `--js`, `--javascript` | tns create --template tns-template-hello-world
46-
`JavaScript - SideDrawer` | tns create tns-template-drawer-navigation
47-
`JavaScript - Tabs` | tns create tns-template-tab-navigation
48-
`TypeScript - Hello World`, `--ts`, `--tsc`, `--typescript` | tns create tns-template-hello-world-ts
49-
`TypeScript - SideDrawer` | tns create tns-template-drawer-navigation-ts
50-
`TypeScript - Tabs` | tns create tns-template-tab-navigation-ts
51-
`Angular - Hello World`, `--ng`, `--angular` | tns create tns-template-hello-world-ng
52-
`Angular - SideDrawer` | tns create tns-template-drawer-navigation-ng
53-
`Angular - Tabs` | tns create tns-template-tab-navigation-ng
54-
`Vue.js - Blank`, `--vue`, `--vuejs` | tns create tns-template-blank-vue
55-
`Vue.js - SideDrawer`, | tns create tns-template-drawer-navigation-vue
46+
`JavaScript - SideDrawer` | tns create --template tns-template-drawer-navigation
47+
`JavaScript - Tabs` | tns create --template tns-template-tab-navigation
48+
`TypeScript - Hello World`, `--ts`, `--tsc`, `--typescript` | tns create --template tns-template-hello-world-ts
49+
`TypeScript - SideDrawer` | tns create --template tns-template-drawer-navigation-ts
50+
`TypeScript - Tabs` | tns create --template tns-template-tab-navigation-ts
51+
`Angular - Hello World`, `--ng`, `--angular` | tns create --template tns-template-hello-world-ng
52+
`Angular - SideDrawer` | tns create --template tns-template-drawer-navigation-ng
53+
`Angular - Tabs` | tns create --template tns-template-tab-navigation-ng
54+
`Vue.js - Blank`, `--vue`, `--vuejs` | tns create --template tns-template-blank-vue
55+
`Vue.js - SideDrawer`, | tns create --template tns-template-drawer-navigation-vue
5656

5757
### Related Commands
5858

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<% if (isJekyll) { %>---
2+
title: tns apple-login
3+
position: 5
4+
---<% } %>
5+
6+
# tns apple-login
7+
8+
### Description
9+
10+
Uses the provided Apple credentials to obtain Apple session which can be used when publishing to Apple AppStore.
11+
12+
### Commands
13+
14+
Usage | Synopsis
15+
---|---
16+
General | `$ tns apple-login [<Apple ID>] [<Password>]`
17+
18+
### Arguments
19+
20+
* `<Apple ID>` and `<Password>` are your credentials for logging into iTunes Connect.
21+
22+
<% if(isHtml) { %>s
23+
24+
### Related Commands
25+
26+
Command | Description
27+
----------|----------
28+
[appstore](appstore.html) | Lists applications registered in iTunes Connect.
29+
[appstore upload](appstore-upload.html) | Uploads project to iTunes Connect.
30+
[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.
31+
[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.
32+
[deploy](../project/testing/deploy.html) | Builds and deploys the project to a connected physical or virtual device.
33+
[run](../project/testing/run.html) | Runs your project on a connected device or in the native emulator for the selected platform.
34+
[run ios](../project/testing/run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured.
35+
<% } %>

docs/man_pages/publishing/appstore-upload.md

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ position: 1
88
### Description
99

1010
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.
11+
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.
1112

1213
<% 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`<% } %>
1314
<% if((isConsole && isMacOS) || isHtml) { %>
@@ -22,6 +23,8 @@ Upload package | `$ tns appstore upload [<Apple ID> [<Password>]] --ipa <Ipa Fil
2223
### Options
2324

2425
* `--ipa` - If set, will use provided .ipa file instead of building the project.
26+
* `--appleApplicationSpecificPassword` - Specifies the password for accessing the information you store in iTunes Transporter application.
27+
* `--appleSessionBase64` - The session that will be used instead of triggering a new login each time NativeScript CLI communicates with Apple's APIs.
2528

2629
### Arguments
2730

docs/man_pages/publishing/publish-ios.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ position: 3
88
### Description
99

1010
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.
11+
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.
1112

1213
<% 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 publish ios`<% } %>
1314

@@ -24,7 +25,9 @@ Upload package | `$ tns publish ios [<Apple ID> [<Password>]] --ipa <Ipa File Pa
2425

2526
* `--ipa` - If set, will use provided .ipa file instead of building the project.
2627
* `--team-id` - Specified the team id for which Xcode will try to find distribution certificate and provisioning profile when exporting for AppStore submission.
27-
28+
* `--appleApplicationSpecificPassword` - Specifies the password for accessing the information you store in iTunes Transporter application.
29+
* `--appleSessionBase64` - The session that will be used instead of triggering a new login each time NativeScript CLI communicates with Apple's APIs.
30+
2831
### Arguments
2932

3033
* `<Apple ID>` and `<Password>` are your credentials for logging into iTunes Connect.

lib/bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ $injector.requireCommand("dev-generate-help", "./commands/generate-help");
100100
$injector.requireCommand("appstore|*list", "./commands/appstore-list");
101101
$injector.requireCommand("appstore|upload", "./commands/appstore-upload");
102102
$injector.requireCommand("publish|ios", "./commands/appstore-upload");
103+
$injector.requireCommand("apple-login", "./commands/apple-login");
103104
$injector.require("itmsTransporterService", "./services/itmstransporter-service");
104105

105106
$injector.requireCommand("setup|*", "./commands/setup");

lib/commands/apple-login.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { StringCommandParameter } from "../common/command-params";
2+
3+
export class AppleLogin implements ICommand {
4+
public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)];
5+
6+
constructor(
7+
private $applePortalSessionService: IApplePortalSessionService,
8+
private $errors: IErrors,
9+
private $injector: IInjector,
10+
private $logger: ILogger,
11+
private $prompter: IPrompter
12+
) { }
13+
14+
public async execute(args: string[]): Promise<void> {
15+
let username = args[0];
16+
if (!username) {
17+
username = await this.$prompter.getString("Apple ID", { allowEmpty: false });
18+
}
19+
20+
let password = args[1];
21+
if (!password) {
22+
password = await this.$prompter.getPassword("Apple ID password");
23+
}
24+
25+
const user = await this.$applePortalSessionService.createUserSession({ username, password });
26+
if (!user.areCredentialsValid) {
27+
this.$errors.fail(`Invalid username and password combination. Used '${username}' as the username.`);
28+
}
29+
30+
const output = Buffer.from(user.userSessionCookie).toString("base64");
31+
this.$logger.info(output);
32+
}
33+
}
34+
$injector.registerCommand("apple-login", AppleLogin);

lib/commands/appstore-list.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ export class ListiOSApps implements ICommand {
66

77
constructor(private $injector: IInjector,
88
private $applePortalApplicationService: IApplePortalApplicationService,
9+
private $applePortalSessionService: IApplePortalSessionService,
910
private $logger: ILogger,
1011
private $projectData: IProjectData,
1112
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
1213
private $platformValidationService: IPlatformValidationService,
1314
private $errors: IErrors,
14-
private $prompter: IPrompter) {
15+
private $prompter: IPrompter,
16+
private $options: IOptions) {
1517
this.$projectData.initializeProjectData();
1618
}
1719

@@ -31,7 +33,14 @@ export class ListiOSApps implements ICommand {
3133
password = await this.$prompter.getPassword("Apple ID password");
3234
}
3335

34-
const applications = await this.$applePortalApplicationService.getApplications({ username, password });
36+
const user = await this.$applePortalSessionService.createUserSession({ username, password }, {
37+
sessionBase64: this.$options.appleSessionBase64,
38+
});
39+
if (!user.areCredentialsValid) {
40+
this.$errors.fail(`Invalid username and password combination. Used '${username}' as the username.`);
41+
}
42+
43+
const applications = await this.$applePortalApplicationService.getApplications(user);
3544

3645
if (!applications || !applications.length) {
3746
this.$logger.info("Seems you don't have any applications yet.");

lib/commands/appstore-upload.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export class PublishIOS implements ICommand {
88
new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)];
99

1010
constructor(
11+
private $applePortalSessionService: IApplePortalSessionService,
1112
private $injector: IInjector,
1213
private $itmsTransporterService: IITMSTransporterService,
1314
private $logger: ILogger,
@@ -38,6 +39,16 @@ export class PublishIOS implements ICommand {
3839
password = await this.$prompter.getPassword("Apple ID password");
3940
}
4041

42+
const user = await this.$applePortalSessionService.createUserSession({ username, password }, {
43+
applicationSpecificPassword: this.$options.appleApplicationSpecificPassword,
44+
sessionBase64: this.$options.appleSessionBase64,
45+
requireInteractiveConsole: true,
46+
requireApplicationSpecificPassword: true
47+
});
48+
if (!user.areCredentialsValid) {
49+
this.$errors.fail(`Invalid username and password combination. Used '${username}' as the username.`);
50+
}
51+
4152
if (!mobileProvisionIdentifier && !ipaFilePath) {
4253
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");
4354
}
@@ -69,8 +80,9 @@ export class PublishIOS implements ICommand {
6980
}
7081

7182
await this.$itmsTransporterService.upload({
72-
username,
73-
password,
83+
credentials: { username, password },
84+
user,
85+
applicationSpecificPassword: this.$options.appleApplicationSpecificPassword,
7486
ipaFilePath,
7587
shouldExtractIpa: !!this.$options.ipa,
7688
verboseLogging: this.$logger.getLevel() === "TRACE"

lib/commands/test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
abstract class TestCommandBase {
22
public allowedParameters: ICommandParameter[] = [];
3+
public dashedOptions = {
4+
hmr: { type: OptionType.Boolean, default: false, hasSensitiveValue: false },
5+
};
6+
37
protected abstract platform: string;
48
protected abstract $projectData: IProjectData;
59
protected abstract $testExecutionService: ITestExecutionService;
@@ -50,6 +54,13 @@ abstract class TestCommandBase {
5054

5155
async canExecute(args: string[]): Promise<boolean> {
5256
if (!this.$options.force) {
57+
if (this.$options.hmr) {
58+
// With HMR we are not restarting after LiveSync which is causing a 30 seconds app start on Android
59+
// because the Runtime does not watch for the `/data/local/tmp<appId>-livesync-in-progress` file deletion.
60+
// The App is closing itself after each test execution and the bug will be reproducible on each LiveSync.
61+
this.$errors.fail("The `--hmr` option is not supported for this command.");
62+
}
63+
5364
await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms: [this.platform] });
5465
}
5566

lib/common/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export class HttpStatusCodes {
9595
static NOT_MODIFIED = 304;
9696
static PAYMENT_REQUIRED = 402;
9797
static PROXY_AUTHENTICATION_REQUIRED = 407;
98+
static CONFLICTING_RESOURCE = 409;
9899
}
99100

100101
export const HttpProtocolToPort: IDictionary<number> = {

lib/common/http-client.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,9 @@ private defaultUserAgent: string;
255255
this.$logger.error(`You can run ${EOL}\t${clientNameLowerCase} proxy set <url> <username> <password>.${EOL}In order to supply ${clientNameLowerCase} with the credentials needed.`);
256256
return "Your proxy requires authentication.";
257257
} else if (statusCode === HttpStatusCodes.PAYMENT_REQUIRED) {
258-
return util.format("Your subscription has expired.");
258+
return "Your subscription has expired.";
259+
} else if (statusCode === HttpStatusCodes.CONFLICTING_RESOURCE) {
260+
return "The request conflicts with the current state of the server.";
259261
} else {
260262
this.$logger.trace("Request was unsuccessful. Server returned: ", body);
261263
try {

lib/controllers/run-controller.ts

+36-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { HmrConstants, DeviceDiscoveryEventNames } from "../common/constants";
22
import { PREPARE_READY_EVENT_NAME, TrackActionNames, DEBUGGER_DETACHED_EVENT_NAME, RunOnDeviceEvents, USER_INTERACTION_NEEDED_EVENT_NAME } from "../constants";
33
import { cache, performanceLog } from "../common/decorators";
44
import { EventEmitter } from "events";
5+
import * as util from "util";
56

67
export class RunController extends EventEmitter implements IRunController {
78
private prepareReadyEventHandler: any = null;
@@ -270,7 +271,7 @@ export class RunController extends EventEmitter implements IRunController {
270271
}
271272

272273
private async syncInitialDataOnDevices(projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise<void> {
273-
const rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = { };
274+
const rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = {};
274275

275276
const deviceAction = async (device: Mobile.IDevice) => {
276277
const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier);
@@ -333,15 +334,16 @@ export class RunController extends EventEmitter implements IRunController {
333334
error: err,
334335
});
335336

336-
await this.stop({ projectDir: projectData.projectDir, deviceIdentifiers: [device.deviceInfo.identifier], stopOptions: { shouldAwaitAllActions: false }});
337+
await this.stop({ projectDir: projectData.projectDir, deviceIdentifiers: [device.deviceInfo.identifier], stopOptions: { shouldAwaitAllActions: false } });
337338
}
338339
};
339340

340341
await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)));
341342
}
342343

343344
private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise<void> {
344-
const rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = { };
345+
const successfullySyncedMessageFormat = `Successfully synced application %s on device %s.`;
346+
const rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = {};
345347

346348
const deviceAction = async (device: Mobile.IDevice) => {
347349
const deviceDescriptors = this.$liveSyncProcessDataService.getDeviceDescriptors(projectData.projectDir);
@@ -380,28 +382,47 @@ export class RunController extends EventEmitter implements IRunController {
380382
await this.$deviceInstallAppService.installOnDevice(device, deviceDescriptor.buildData, rebuiltInformation[platformData.platformNameLowerCase].packageFilePath);
381383
await platformLiveSyncService.syncAfterInstall(device, watchInfo);
382384
await this.refreshApplication(projectData, { deviceAppData, modifiedFilesData: [], isFullSync: false, useHotModuleReload: liveSyncInfo.useHotModuleReload }, data, deviceDescriptor);
385+
this.$logger.info(util.format(successfullySyncedMessageFormat, deviceAppData.appIdentifier, device.deviceInfo.identifier));
383386
} else {
384387
const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash;
385388
if (isInHMRMode) {
386389
this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, data.hmrData.hash);
387390
}
388391

389-
let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo);
390-
await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor);
391-
392-
if (!liveSyncResultInfo.didRecover && isInHMRMode) {
393-
const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash);
394-
if (status === HmrConstants.HMR_ERROR_STATUS) {
395-
watchInfo.filesToSync = data.hmrData.fallbackFiles;
396-
liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo);
397-
// We want to force a restart of the application.
398-
liveSyncResultInfo.isFullSync = true;
399-
await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor);
392+
const watchAction = async (): Promise<void> => {
393+
let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo);
394+
await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor);
395+
396+
if (!liveSyncResultInfo.didRecover && isInHMRMode) {
397+
const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash);
398+
if (status === HmrConstants.HMR_ERROR_STATUS) {
399+
watchInfo.filesToSync = data.hmrData.fallbackFiles;
400+
liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo);
401+
// We want to force a restart of the application.
402+
liveSyncResultInfo.isFullSync = true;
403+
await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor);
404+
}
405+
}
406+
407+
this.$logger.info(util.format(successfullySyncedMessageFormat, deviceAppData.appIdentifier, device.deviceInfo.identifier));
408+
};
409+
410+
if (liveSyncInfo.useHotModuleReload) {
411+
try {
412+
this.$logger.trace("Try executing watch action without any preparation of files.");
413+
await watchAction();
414+
this.$logger.trace("Successfully executed watch action without any preparation of files.");
415+
return;
416+
} catch (err) {
417+
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}`);
400418
}
401419
}
420+
421+
await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, deviceDescriptor.buildData);
422+
watchInfo.connectTimeout = null;
423+
await watchAction();
402424
}
403425

404-
this.$logger.info(`Successfully synced application ${deviceAppData.appIdentifier} on device ${device.deviceInfo.identifier}.`);
405426
} catch (err) {
406427
this.$logger.warn(`Unable to apply changes for device: ${device.deviceInfo.identifier}. Error is: ${err && err.message}.`);
407428

0 commit comments

Comments
 (0)