Skip to content

chore: merge release in master #4625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
NativeScript CLI Changelog
================


5.4.0 (2019, May 15)
==

### Implemented
* [Implemented #3993](https://github.com/NativeScript/nativescript-cli/issues/3993): Improve `ctrl + c` handling.
* [Implemented #4374](https://github.com/NativeScript/nativescript-cli/issues/4374): Add `iCloudContainerEnvironment` build option.
* [Implemented #4394](https://github.com/NativeScript/nativescript-cli/issues/4394): Enable Using Hot Module Replacement by Default for New Projects
* [Implemented #4518](https://github.com/NativeScript/nativescript-cli/issues/4518): Show deprecation messages for things that will be dropped for 6.0.0 release
* [Implemented #4541](https://github.com/NativeScript/nativescript-cli/issues/4541): [Beta] Allow integration of Apple Watch application in NativeScript app
* [Implemented #4548](https://github.com/NativeScript/nativescript-cli/issues/4548): Deprecate support for the Legacy Workflow
* [Implemented #4602](https://github.com/NativeScript/nativescript-cli/issues/4602): Streamline CLI's logger


### Fixed
* [Fixed #4280](https://github.com/NativeScript/nativescript-cli/issues/4280): Incorrect message if you delete app's folder and run command with `--path` in it
* [Fixed #4512](https://github.com/NativeScript/nativescript-cli/issues/4512): App's Podfile should be applied last
* [Fixed #4573](https://github.com/NativeScript/nativescript-cli/pull/4573): logcat process is not restarted in some cases
* [Fixed #4593](https://github.com/NativeScript/nativescript-cli/issues/4593): Node.js processes not killed after `tns create` on macOS when analytics are enabled
* [Fixed #4598](https://github.com/NativeScript/nativescript-cli/issues/4598): app.css changes don't apply when debugging with --debug-brk
* [Fixed #4606](https://github.com/NativeScript/nativescript-cli/issues/4606): Unable to build application for iOS with nativescript-bottombar
* [Fixed #4616](https://github.com/NativeScript/nativescript-cli/issues/4616): `tns plugin create` command hangs

5.3.4 (2019, April 24)
==

Expand Down
2 changes: 2 additions & 0 deletions docs/man_pages/lib-management/plugin-create.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Create from a custom plugin seed | `$ tns plugin create <Plugin Repository Name>
* `--path` - Specifies the directory where you want to create the project, if different from the current directory.
* `--username` - Specifies the Github username, which will be used to build the URLs in the plugin's package.json file.
* `--pluginName` - Used to set the default file and class names in the plugin source.
* `--includeTypeScriptDemo` - Specifies if TypeScript demo should be created. Default value is `y` (i.e. `demo` will be created), in case you do not want to create this demo, pass `--includeTypeScriptDemo=n`
* `--includeAngularDemo` - Specifies if Angular demo should be created. Default value is `y` (i.e. `demo-angular` will be created), in case you do not want to create this demo, pass `--includeAngularDemo=n`
* `--template` - Specifies the custom seed archive, which you want to use to create your plugin. If `--template` is not set, the NativeScript CLI creates the plugin from the default NativeScript Plugin Seed. `<Template>` can be a URL or a local path to a `.tar.gz` file with the contents of a seed repository.<% if(isHtml) { %> This must be a clone of the [NativeScript Plugin Seed](https://github.com/NativeScript/nativescript-plugin-seed) and must contain a `src` directory with a package.json file and a script at `src/scripts/postclone.js`. After the archive is extracted, the postclone script will be executed with the username (`gitHubUsername`) and plugin name (`pluginName`) parameters given to the `tns plugin create` command prompts. For more information, visit the default plugin seed repository and [examine the source script](https://github.com/NativeScript/nativescript-plugin-seed/blob/master/src/scripts/postclone.js) there. Examples:

* Using a local file:
Expand Down
33 changes: 27 additions & 6 deletions lib/commands/plugin/create-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export class CreatePluginCommand implements ICommand {
public allowedParameters: ICommandParameter[] = [];
public userMessage = "What is your GitHub username?\n(will be used to update the Github URLs in the plugin's package.json)";
public nameMessage = "What will be the name of your plugin?\n(use lowercase characters and dashes only)";
public includeTypeScriptDemoMessage = 'Do you want to include a "TypeScript NativeScript" application linked with your plugin to make development easier?';
public includeAngularDemoMessage = 'Do you want to include an "Angular NativeScript" application linked with your plugin to make development easier?';
public pathAlreadyExistsMessageTemplate = "Path already exists and is not empty %s";
constructor(private $options: IOptions,
private $errors: IErrors,
Expand Down Expand Up @@ -62,15 +64,25 @@ export class CreatePluginCommand implements ICommand {

const gitHubUsername = await this.getGitHubUsername(config.username);
const pluginNameSource = await this.getPluginNameSource(config.pluginName, pluginRepoName);
const includeTypescriptDemo = await this.getShouldIncludeDemoResult(config.includeTypeScriptDemo, this.includeTypeScriptDemoMessage);
const includeAngularDemo = await this.getShouldIncludeDemoResult(config.includeAngularDemo, this.includeAngularDemoMessage);

if (!isInteractive() && (!config.username || !config.pluginName)) {
this.$logger.printMarkdown("Using default values for Github user and/or plugin name since your shell is not interactive.");
if (!isInteractive() && (!config.username || !config.pluginName || !config.includeAngularDemo || !config.includeTypeScriptDemo)) {
this.$logger.printMarkdown("Using default values for plugin creation options since your shell is not interactive.");
}

// run postclone script manually and kill it if it takes more than 10 sec
const pathToPostCloneScript = path.join("scripts", "postclone");
const params = [pathToPostCloneScript, `gitHubUsername=${gitHubUsername}`, `pluginName=${pluginNameSource}`, "initGit=y"];
const outputScript = (await this.$childProcess.spawnFromEvent(process.execPath, params, "close", { cwd, timeout: 10000 }));
const params = [
pathToPostCloneScript,
`gitHubUsername=${gitHubUsername}`,
`pluginName=${pluginNameSource}`,
"initGit=y",
`includeTypeScriptDemo=${includeTypescriptDemo}`,
`includeAngularDemo=${includeAngularDemo}`
];

const outputScript = (await this.$childProcess.spawnFromEvent(process.execPath, params, "close", { stdio: "inherit", cwd, timeout: 10000 }));
if (outputScript && outputScript.stdout) {
this.$logger.printMarkdown(outputScript.stdout);
}
Expand Down Expand Up @@ -101,7 +113,7 @@ export class CreatePluginCommand implements ICommand {
}
}

private async getGitHubUsername(gitHubUsername: string) {
private async getGitHubUsername(gitHubUsername: string): Promise<string> {
if (!gitHubUsername) {
gitHubUsername = "NativeScriptDeveloper";
if (isInteractive()) {
Expand All @@ -112,7 +124,7 @@ export class CreatePluginCommand implements ICommand {
return gitHubUsername;
}

private async getPluginNameSource(pluginNameSource: string, pluginRepoName: string) {
private async getPluginNameSource(pluginNameSource: string, pluginRepoName: string): Promise<string> {
if (!pluginNameSource) {
// remove nativescript- prefix for naming plugin files
const prefix = 'nativescript-';
Expand All @@ -124,6 +136,15 @@ export class CreatePluginCommand implements ICommand {

return pluginNameSource;
}

private async getShouldIncludeDemoResult(includeDemoOption: string, message: string): Promise<string> {
let shouldIncludeDemo = !!includeDemoOption;
if (!includeDemoOption && isInteractive()) {
shouldIncludeDemo = await this.$prompter.confirm(message, () => { return true; });
}

return shouldIncludeDemo ? "y" : "n";
}
}

$injector.registerCommand(["plugin|create"], CreatePluginCommand);
2 changes: 2 additions & 0 deletions lib/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,8 @@ interface IPort {
interface IPluginSeedOptions {
username: string;
pluginName: string;
includeTypeScriptDemo: string;
includeAngularDemo: string;
}

interface IAndroidBundleOptions {
Expand Down
2 changes: 2 additions & 0 deletions lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ export class Options {
background: { type: OptionType.String, hasSensitiveValue: false },
username: { type: OptionType.String, hasSensitiveValue: true },
pluginName: { type: OptionType.String, hasSensitiveValue: false },
includeTypeScriptDemo: { type: OptionType.String, hasSensitiveValue: false },
includeAngularDemo: { type: OptionType.String, hasSensitiveValue: false },
hmr: { type: OptionType.Boolean, hasSensitiveValue: false },
collection: { type: OptionType.String, alias: "c", hasSensitiveValue: false },
json: { type: OptionType.Boolean, hasSensitiveValue: false },
Expand Down
8 changes: 4 additions & 4 deletions lib/services/plugins-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,12 @@ export class PluginsService implements IPluginsService {
const installedFrameworkVersion = this.getInstalledFrameworkVersion(platform, projectData);
const pluginPlatformsData = pluginData.platformsData;
if (pluginPlatformsData) {
const pluginVersion = (<any>pluginPlatformsData)[platform];
if (!pluginVersion) {
const versionRequiredByPlugin = (<any>pluginPlatformsData)[platform];
if (!versionRequiredByPlugin) {
this.$logger.warn(`${pluginData.name} is not supported for ${platform}.`);
isValid = false;
} else if (semver.gt(pluginVersion, installedFrameworkVersion)) {
this.$logger.warn(`${pluginData.name} ${pluginVersion} for ${platform} is not compatible with the currently installed framework version ${installedFrameworkVersion}.`);
} else if (semver.gt(versionRequiredByPlugin, installedFrameworkVersion)) {
this.$logger.warn(`${pluginData.name} requires at least version ${versionRequiredByPlugin} of platform ${platform}. Currently installed version is ${installedFrameworkVersion}.`);
isValid = false;
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/services/workflow-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { LoggerConfigData } from "../constants";
export class WorkflowService implements IWorkflowService {
private legacyWorkflowDeprecationMessage = `With the upcoming NativeScript 6.0 the Webpack workflow will become the only way of building apps.
More info about the reasons for this change and how to migrate your project can be found in the link below:
<TODO: add link here>`;
https://www.nativescript.org/blog/the-future-of-building-nativescript-apps`;
private webpackWorkflowConfirmMessage = `Do you want to switch your app to the Webpack workflow?`;

constructor(private $bundleValidatorHelper: IBundleValidatorHelper,
Expand Down
5 changes: 3 additions & 2 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"minimatch": "3.0.2",
"mkdirp": "0.5.1",
"mute-stream": "0.0.5",
"nativescript-dev-xcode": "https://github.com/NativeScript/nativescript-dev-xcode/tarball/ec70f5d6032a72b65298ad56fa6ec0c4b2ef03a3",
"nativescript-dev-xcode": "0.2.0",
"nativescript-doctor": "1.9.2",
"nativescript-preview-sdk": "0.3.4",
"open": "0.0.5",
Expand Down
67 changes: 60 additions & 7 deletions test/plugin-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const dummyProjectName = "dummyProjectName";
const dummyArgs = [dummyProjectName];
const dummyUser = "devUsername";
const dummyName = "devPlugin";
const createDemoProjectAnswer = true;
const creteDemoProjectOption = "y";
const dummyPacote: IPacoteOutput = { packageName: "", destinationDirectory: "" };

function createTestInjector() {
Expand Down Expand Up @@ -104,48 +106,99 @@ describe("Plugin create command tests", () => {
await createPluginCommand.execute(dummyArgs);
});

it("should pass when only project name is set with prompts in interactive shell.", async () => {
it("should pass when all options are set with prompts in interactive shell.", async () => {
const prompter = testInjector.resolve("$prompter");
const strings: IDictionary<string> = {};
const confirmQuestions: IDictionary<boolean> = {};
strings[createPluginCommand.userMessage] = dummyUser;
strings[createPluginCommand.nameMessage] = dummyName;
confirmQuestions[createPluginCommand.includeTypeScriptDemoMessage] = createDemoProjectAnswer;
confirmQuestions[createPluginCommand.includeAngularDemoMessage] = createDemoProjectAnswer;

prompter.expect({
strings: strings
strings: strings,
confirmQuestions
});
await createPluginCommand.execute(dummyArgs);
prompter.assert();
});

it("should pass with project name and username set with one prompt in interactive shell.", async () => {
it("should pass when username is passed with command line option and all other options are populated with prompts in interactive shell.", async () => {
options.username = dummyUser;
const prompter = testInjector.resolve("$prompter");
const strings: IDictionary<string> = {};
const confirmQuestions: IDictionary<boolean> = {};
strings[createPluginCommand.nameMessage] = dummyName;
confirmQuestions[createPluginCommand.includeTypeScriptDemoMessage] = createDemoProjectAnswer;
confirmQuestions[createPluginCommand.includeAngularDemoMessage] = createDemoProjectAnswer;

prompter.expect({
strings: strings
strings: strings,
confirmQuestions
});
await createPluginCommand.execute(dummyArgs);
prompter.assert();
});

it("should pass with project name and pluginName set with one prompt in interactive shell.", async () => {
it("should pass when plugin name is passed with command line option and all other options are populated with prompts in interactive shell.", async () => {
options.pluginName = dummyName;
const prompter = testInjector.resolve("$prompter");
const strings: IDictionary<string> = {};
const confirmQuestions: IDictionary<boolean> = {};

strings[createPluginCommand.userMessage] = dummyUser;
confirmQuestions[createPluginCommand.includeTypeScriptDemoMessage] = createDemoProjectAnswer;
confirmQuestions[createPluginCommand.includeAngularDemoMessage] = createDemoProjectAnswer;

prompter.expect({
strings: strings
strings,
confirmQuestions
});
await createPluginCommand.execute(dummyArgs);
prompter.assert();
});

it("should pass with project name, username and pluginName set with no prompt in interactive shell.", async () => {
it("should pass when includeTypeScriptDemo is passed with command line option and all other options are populated with prompts in interactive shell.", async () => {
options.includeTypeScriptDemo = creteDemoProjectOption;
const prompter = testInjector.resolve("$prompter");
const strings: IDictionary<string> = {};
const confirmQuestions: IDictionary<boolean> = {};
strings[createPluginCommand.userMessage] = dummyUser;
strings[createPluginCommand.nameMessage] = dummyName;
confirmQuestions[createPluginCommand.includeAngularDemoMessage] = createDemoProjectAnswer;

prompter.expect({
strings: strings,
confirmQuestions
});
await createPluginCommand.execute(dummyArgs);
prompter.assert();
});

it("should pass when includeAngularDemo is passed with command line option and all other options are populated with prompts in interactive shell.", async () => {
options.includeAngularDemo = creteDemoProjectOption;
const prompter = testInjector.resolve("$prompter");
const strings: IDictionary<string> = {};
const confirmQuestions: IDictionary<boolean> = {};

strings[createPluginCommand.userMessage] = dummyUser;
strings[createPluginCommand.nameMessage] = dummyName;
confirmQuestions[createPluginCommand.includeTypeScriptDemoMessage] = createDemoProjectAnswer;

prompter.expect({
strings: strings,
confirmQuestions
});
await createPluginCommand.execute(dummyArgs);
prompter.assert();
});

it("should pass with all options passed through command line opts with no prompt in interactive shell.", async () => {
options.username = dummyUser;
options.pluginName = dummyName;
options.includeTypeScriptDemo = creteDemoProjectOption;
options.includeAngularDemo = creteDemoProjectOption;

await createPluginCommand.execute(dummyArgs);
});

Expand Down
2 changes: 1 addition & 1 deletion test/plugins-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ describe("Plugins service", () => {
});
it("fails when the plugin does not support the installed framework", async () => {
let isWarningMessageShown = false;
const expectedWarningMessage = "mySamplePlugin 1.5.0 for android is not compatible with the currently installed framework version 1.4.0.";
const expectedWarningMessage = "mySamplePlugin requires at least version 1.5.0 of platform android. Currently installed version is 1.4.0.";

// Creates plugin in temp folder
const pluginName = "mySamplePlugin";
Expand Down
12 changes: 10 additions & 2 deletions test/stubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,13 +571,15 @@ export class PrompterStub implements IPrompter {
private passwords: IDictionary<string> = {};
private answers: IDictionary<string> = {};
private questionChoices: IDictionary<any[]> = {};
private confirmQuestions: IDictionary<boolean> = {};

expect(options?: { strings?: IDictionary<string>, passwords?: IDictionary<string>, answers?: IDictionary<string>, questionChoices?: IDictionary<any[]> }) {
expect(options?: { strings?: IDictionary<string>, passwords?: IDictionary<string>, answers?: IDictionary<string>, questionChoices?: IDictionary<any[]>, confirmQuestions?: IDictionary<boolean> }) {
if (options) {
this.strings = options.strings || this.strings;
this.passwords = options.passwords || this.passwords;
this.answers = options.answers || this.answers;
this.questionChoices = options.questionChoices || this.questionChoices;
this.confirmQuestions = options.confirmQuestions || this.confirmQuestions;
}
}

Expand Down Expand Up @@ -607,7 +609,10 @@ export class PrompterStub implements IPrompter {
return result;
}
async confirm(message: string, defaultAction?: () => boolean): Promise<boolean> {
throw unreachable();
chai.assert.ok(message in this.confirmQuestions, `PrompterStub didn't expect to be asked for: ${message}`);
const result = this.confirmQuestions[message];
delete this.confirmQuestions[message];
return result;
}
dispose(): void {
throw unreachable();
Expand All @@ -620,6 +625,9 @@ export class PrompterStub implements IPrompter {
for (const key in this.passwords) {
throw unexpected(`PrompterStub was instructed to reply with "${this.passwords[key]}" to a "${key}" password request, but was never asked!`);
}
for (const key in this.confirmQuestions) {
throw unexpected(`PrompterStub was instructed to reply with "${this.confirmQuestions[key]}" to a "${key}" confirm question, but was never asked!`);
}
}
}

Expand Down