From da37095b61a8f1f4728cd1b5804b0a214c3f711d Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 25 Jul 2018 08:42:57 +0300 Subject: [PATCH 01/15] chore: Bump version to 4.3.0 --- 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 1e30d9550b..b6ffb369cf 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "4.2.0", + "version": "4.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b97ef7398c..44059e7275 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "4.2.0", + "version": "4.3.0", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 7299a62f1b56b9777510af6df99ac6f07b8c762c Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 3 Aug 2018 08:21:58 +0300 Subject: [PATCH 02/15] chore: update to latest common lib --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index b89d237685..67450b9350 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit b89d23768522987c2a56224d14d0c14997c82bc1 +Subproject commit 67450b93507cd7c535fb3f3f62a791626d3d7f4c From 141054c0a21a8dd63666e354192d6fb7fea1fffa Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Sat, 4 Aug 2018 21:35:19 +0300 Subject: [PATCH 03/15] chore: update to latest common lib --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 67450b9350..c635c1a6ea 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 67450b93507cd7c535fb3f3f62a791626d3d7f4c +Subproject commit c635c1a6ea1baf36fcd1dfd6aac868940d1ad8e2 From 392ae263783156344e1686fae4e3b96fd7512e07 Mon Sep 17 00:00:00 2001 From: Stoyan Stratev Date: Tue, 7 Aug 2018 11:11:50 +0300 Subject: [PATCH 04/15] feat: add create plugin command - `tns plugin create` command - add documentation - add tests --- .../man_pages/lib-management/plugin-create.md | 28 +++++ docs/man_pages/lib-management/plugin.md | 2 + lib/bootstrap.ts | 1 + lib/commands/plugin/create-plugin.ts | 100 +++++++++++++++ lib/declarations.d.ts | 7 +- lib/options.ts | 4 +- test/plugin-create.ts | 115 ++++++++++++++++++ test/stubs.ts | 8 ++ 8 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 docs/man_pages/lib-management/plugin-create.md create mode 100644 lib/commands/plugin/create-plugin.ts create mode 100644 test/plugin-create.ts diff --git a/docs/man_pages/lib-management/plugin-create.md b/docs/man_pages/lib-management/plugin-create.md new file mode 100644 index 0000000000..43b7b4ffe6 --- /dev/null +++ b/docs/man_pages/lib-management/plugin-create.md @@ -0,0 +1,28 @@ +<% if (isJekyll) { %>--- +title: tns plugin create +position: 1 +---<% } %> +# tns plugin create + +Usage | Synopsis +---|--- +Create a new plugin | `$ tns plugin create [--path ]` + +Creates a new project for NativeScript plugin development. The project uses the [NativeScript Plugin Seed](https://github.com/NativeScript/nativescript-plugin-seed) as a base and contains the following directories: + +* `src` - source code of the plugin +* `demo` - simple NativeScript application used to test and show plugin features +* `publish` - shell scripts used to build and pack the plugin source code and publish it in [NPM](https://www.npmjs.com/) + +The project is setup for easy commit in Github, which is why the command will ask you for your Github username. +<% if(isHtml) { %>Before starting to code your first plugin, you can visit the NativeScript documentation page for [building plugins](https://docs.nativescript.org/plugins/building-plugins#step-2-set-up-a-development-workflow) or the [plugin seed repository](https://github.com/NativeScript/nativescript-plugin-seed/blob/master/README.md).<% } %> + +### Options + +* `--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. + +### Attributes + +* `` is the name of repository where your plugin will reside. A directory with the same name will be created. For example: `nativescript-awesome-list`. If a directory with the name already exists and is not empty, the plugin create command will fail. \ No newline at end of file diff --git a/docs/man_pages/lib-management/plugin.md b/docs/man_pages/lib-management/plugin.md index 44a7d6c279..eaba35570d 100644 --- a/docs/man_pages/lib-management/plugin.md +++ b/docs/man_pages/lib-management/plugin.md @@ -20,6 +20,7 @@ Lets you manage the plugins for your project. * `find` - Finds NativeScript plugins in npm. * `search` - Finds NativeScript plugins in npm. * `build` - Builds the Android parts of a NativeScript plugin. +* `create` - Creates a project for building a new NativeScript plugin. <% if(isHtml) { %> ### Related Commands @@ -30,4 +31,5 @@ Command | Description [plugin remove](plugin-remove.html) | Uninstalls the specified plugin and its dependencies. [plugin update](plugin-update.html) | Updates the specified plugin(s) and its dependencies. [plugin build](plugin-build.html) | Builds the Android project of a NativeScript plugin, and updates the `include.gradle`. +[plugin create](plugin-create.html) | Builds the Android project of a NativeScript plugin, and updates the `include.gradle`. <% } %> diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index ac06ce0eed..2788fb7783 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -96,6 +96,7 @@ $injector.requireCommand("plugin|install", "./commands/plugin/add-plugin"); $injector.requireCommand("plugin|remove", "./commands/plugin/remove-plugin"); $injector.requireCommand("plugin|update", "./commands/plugin/update-plugin"); $injector.requireCommand("plugin|build", "./commands/plugin/build-plugin"); +$injector.requireCommand("plugin|create", "./commands/plugin/create-plugin"); $injector.require("doctorService", "./services/doctor-service"); $injector.require("xcprojService", "./services/xcproj-service"); diff --git a/lib/commands/plugin/create-plugin.ts b/lib/commands/plugin/create-plugin.ts new file mode 100644 index 0000000000..08a93e74b2 --- /dev/null +++ b/lib/commands/plugin/create-plugin.ts @@ -0,0 +1,100 @@ +import * as path from "path"; +import { isInteractive } from "../../common/helpers"; + +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 = ""; + constructor(private $options: IOptions, + private $errors: IErrors, + private $terminalSpinnerService: ITerminalSpinnerService, + private $logger: ILogger, + private $pacoteService: IPacoteService, + private $fs: IFileSystem, + private $childProcess: IChildProcess, + private $prompter: IPrompter) { } + + public async execute(args: string[]): Promise { + const pluginRepoName = args[0]; + const pathToProject = this.$options.path; + const selectedPath = path.resolve(pathToProject || "."); + const projectDir = path.join(selectedPath, pluginRepoName); + + this.$logger.printMarkdown("Downloading the latest version of NativeScript Plugin Seed..."); + await this.downloadPackage(projectDir); + + this.$logger.printMarkdown("Executing initial plugin configuration script..."); + await this.setupSeed(projectDir, pluginRepoName); + + this.$logger.printMarkdown("Solution for `%s` was successfully created.", pluginRepoName); + } + + public async canExecute(args: string[]): Promise { + if (!args[0]) { + this.$errors.fail("You must specify the plugin repository name."); + } + + return true; + } + + private async setupSeed(projectDir: string, pluginRepoName: string): Promise { + const config = this.$options; + const spinner = this.$terminalSpinnerService.createSpinner(); + const cwd = path.join(projectDir, "src"); + try { + spinner.start(); + await this.$childProcess.exec("npm i", { cwd: cwd }); + } finally { + spinner.stop(); + } + + let gitHubUsername = config.username; + if (!gitHubUsername) { + gitHubUsername = "NativeScriptDeveloper"; + if (isInteractive()) { + gitHubUsername = await this.$prompter.getString(this.userMessage, { allowEmpty: false, defaultAction: () => { return gitHubUsername; } }); + } + } + + let pluginNameSource = config.pluginName; + if (!pluginNameSource) { + // remove nativescript- prefix for naming plugin files + const prefix = 'nativescript-'; + pluginNameSource = pluginRepoName.toLowerCase().startsWith(prefix) ? pluginRepoName.slice(prefix.length, pluginRepoName.length) : pluginRepoName; + if (isInteractive()) { + pluginNameSource = await this.$prompter.getString(this.nameMessage, { allowEmpty: false, defaultAction: () => { return pluginNameSource; } }); + } + } + + 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."); + } + + const params = `gitHubUsername=${gitHubUsername} pluginName=${pluginNameSource} initGit=y`; + // run postclone script manually and kill it if it takes more than 10 sec + const outputScript = (await this.$childProcess.exec(`node scripts/postclone ${params}`, { cwd: cwd, timeout: 10000 })); + this.$logger.printMarkdown(outputScript); + } + + private async downloadPackage(projectDir: string): Promise { + this.$fs.createDirectory(projectDir); + + if (this.$fs.exists(projectDir) && !this.$fs.isEmptyDir(projectDir)) { + this.$errors.fail("Path already exists and is not empty %s", projectDir); + } + + const spinner = this.$terminalSpinnerService.createSpinner(); + const packageToInstall = "https://github.com/NativeScript/nativescript-plugin-seed/archive/master.tar.gz"; + try { + spinner.start(); + await this.$pacoteService.extractPackage(packageToInstall, projectDir); + } catch (err) { + this.$fs.deleteDirectory(projectDir); + throw err; + } finally { + spinner.stop(); + } + } +} + +$injector.registerCommand(["plugin|create"], CreatePluginCommand); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index f131ec9c0c..9663355013 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -446,7 +446,12 @@ interface IPort { port: Number; } -interface IOptions extends ICommonOptions, IBundleString, IPlatformTemplate, IHasEmulatorOption, IClean, IProvision, ITeamIdentifier, IAndroidReleaseOptions, INpmInstallConfigurationOptions, IPort, IEnvOptions { +interface IPluginSeedOptions { + username: string; + pluginName: string; +} + +interface IOptions extends ICommonOptions, IBundleString, IPlatformTemplate, IHasEmulatorOption, IClean, IProvision, ITeamIdentifier, IAndroidReleaseOptions, INpmInstallConfigurationOptions, IPort, IEnvOptions, IPluginSeedOptions { all: boolean; client: boolean; compileSdk: number; diff --git a/lib/options.ts b/lib/options.ts index c952b07f83..879ca4b596 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -39,7 +39,9 @@ export class Options extends commonOptionsLibPath.OptionsBase { inspector: { type: OptionType.Boolean }, clean: { type: OptionType.Boolean }, watch: { type: OptionType.Boolean, default: true }, - background: { type: OptionType.String } + background: { type: OptionType.String }, + username: {type: OptionType.String}, + pluginName: {type: OptionType.String} }, $errors, $staticConfig, $settingsService); diff --git a/test/plugin-create.ts b/test/plugin-create.ts new file mode 100644 index 0000000000..7f99ebe765 --- /dev/null +++ b/test/plugin-create.ts @@ -0,0 +1,115 @@ +import { Yok } from "../lib/common/yok"; +import * as stubs from "./stubs"; +import { CreatePluginCommand } from "../lib/commands/plugin/create-plugin"; +import { assert } from "chai"; +import helpers = require("../lib/common/helpers"); + +const originalIsInteractive = helpers.isInteractive; +const dummyArgs = ["dummyProjectName"]; +const dummyUser = "devUsername"; +const dummyName = "devPlugin"; + +function createTestInjector() { + const testInjector = new Yok(); + + testInjector.register("injector", testInjector); + testInjector.register("errors", stubs.ErrorsStub); + testInjector.register("logger", stubs.LoggerStub); + testInjector.register("childProcess", stubs.ChildProcessStub); + testInjector.register("prompter", new stubs.PrompterStub()); + testInjector.register("fs", stubs.FileSystemStub); + testInjector.register("options", { + username: undefined, + pluginName: undefined + }); + + testInjector.register("terminalSpinnerService", { + createSpinner: () => ({ + start: (): void => undefined, + stop: (): void => undefined, + message: (): void => undefined + }) + }); + + testInjector.register("pacoteService", { + manifest: () => Promise.resolve(), + extractPackage: () => Promise.resolve() + }); + + testInjector.register("createCommand", CreatePluginCommand); + + return testInjector; +} + +describe("Plugin create command tests", () => { + let testInjector: IInjector; + let options: IOptions; + let createPluginCommand: CreatePluginCommand; + + beforeEach(() => { + helpers.isInteractive = () => true; + testInjector = createTestInjector(); + options = testInjector.resolve("$options"); + createPluginCommand = testInjector.resolve("$createCommand"); + }); + + afterEach(() => { + helpers.isInteractive = originalIsInteractive; + }); + + describe("#CreatePluginCommand", () => { + it("should fail when project name is not set.", async () => { + assert.isRejected(createPluginCommand.canExecute([])); + }); + + it("should pass when only project name is set in non-interactive shell.", async () => { + helpers.isInteractive = () => false; + await createPluginCommand.execute(dummyArgs); + }); + + it("should pass when only project name is set with prompts in interactive shell.", async () => { + const prompter = testInjector.resolve("$prompter"); + const strings: IDictionary = {}; + strings[createPluginCommand.userMessage] = dummyUser; + strings[createPluginCommand.nameMessage] = dummyName; + + prompter.expect({ + strings: strings + }); + await createPluginCommand.execute(dummyArgs); + prompter.assert(); + }); + + it("should pass with project name and username set with one prompt in interactive shell.", async () => { + options.username = dummyUser; + const prompter = testInjector.resolve("$prompter"); + const strings: IDictionary = {}; + strings[createPluginCommand.nameMessage] = dummyName; + + prompter.expect({ + strings: strings + }); + await createPluginCommand.execute(dummyArgs); + prompter.assert(); + }); + + it("should pass with project name and pluginName set with one prompt in interactive shell.", async () => { + options.pluginName = dummyName; + const prompter = testInjector.resolve("$prompter"); + const strings: IDictionary = {}; + strings[createPluginCommand.userMessage] = dummyUser; + + prompter.expect({ + strings: strings + }); + await createPluginCommand.execute(dummyArgs); + prompter.assert(); + }); + + it("should pass with project name, username and pluginName set with no prompt in interactive shell.", async () => { + options.username = dummyUser; + options.pluginName = dummyName; + await createPluginCommand.execute(dummyArgs); + }); + }); +}); diff --git a/test/stubs.ts b/test/stubs.ts index 230cbc4ade..572d1d3bbb 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -629,10 +629,18 @@ export class AndroidToolsInfoStub implements IAndroidToolsInfo { export class ChildProcessStub { public spawnCount = 0; + public execCount = 0; public spawnFromEventCount = 0; public lastCommand = ""; public lastCommandArgs: string[] = []; + public async exec(command: string, options?: any, execOptions?: any): Promise { + this.execCount++; + this.lastCommand = command; + this.lastCommandArgs = command ? command.split(" ") : []; + return null; + } + public spawn(command: string, args?: string[], options?: any): any { this.spawnCount++; this.lastCommand = command; From 9124658860c9cc5e951441466164caeb6884c42e Mon Sep 17 00:00:00 2001 From: radeva Date: Tue, 21 Aug 2018 09:48:22 +0300 Subject: [PATCH 05/15] Update feature request lavel --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45961556c3..3a44a0f0d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ If your issue appears to be a bug, and hasn't been reported, open a new issue. T Request a Feature === -You can request a new feature by submitting an issue with the *enhancement* label to our [GitHub Repository][2]. +You can request a new feature by submitting an issue with the *feature* label to our [GitHub Repository][2]. If you want to implement a new feature yourself, consider submitting it to the [GitHub Repository][2] as a Pull Request. [Back to Top][1] From 7a207c7f167800f966290a2ad9b01478cb276c76 Mon Sep 17 00:00:00 2001 From: Stoyan Stratev Date: Wed, 22 Aug 2018 11:01:21 +0300 Subject: [PATCH 06/15] refactor: address review issues - $npm.install replaces $childProcess.exec("npm i") - extract prompts to separate functions - $childProcess.spawnFromEvent replaces $childProcess.exec - formatting - update tests --- lib/commands/plugin/create-plugin.ts | 60 +++++++++++++++++----------- lib/options.ts | 4 +- test/plugin-create.ts | 1 + 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/lib/commands/plugin/create-plugin.ts b/lib/commands/plugin/create-plugin.ts index 08a93e74b2..31afca0045 100644 --- a/lib/commands/plugin/create-plugin.ts +++ b/lib/commands/plugin/create-plugin.ts @@ -4,7 +4,7 @@ import { isInteractive } from "../../common/helpers"; 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 = ""; + public nameMessage = "What will be the name of your plugin?\n(use lowercase characters and dashes only)"; constructor(private $options: IOptions, private $errors: IErrors, private $terminalSpinnerService: ITerminalSpinnerService, @@ -12,7 +12,8 @@ export class CreatePluginCommand implements ICommand { private $pacoteService: IPacoteService, private $fs: IFileSystem, private $childProcess: IChildProcess, - private $prompter: IPrompter) { } + private $prompter: IPrompter, + private $npm: INodePackageManager) { } public async execute(args: string[]): Promise { const pluginRepoName = args[0]; @@ -43,37 +44,26 @@ export class CreatePluginCommand implements ICommand { const cwd = path.join(projectDir, "src"); try { spinner.start(); - await this.$childProcess.exec("npm i", { cwd: cwd }); + const npmOptions: any = { silent: true }; + await this.$npm.install(cwd, cwd, npmOptions); } finally { spinner.stop(); } - let gitHubUsername = config.username; - if (!gitHubUsername) { - gitHubUsername = "NativeScriptDeveloper"; - if (isInteractive()) { - gitHubUsername = await this.$prompter.getString(this.userMessage, { allowEmpty: false, defaultAction: () => { return gitHubUsername; } }); - } - } - - let pluginNameSource = config.pluginName; - if (!pluginNameSource) { - // remove nativescript- prefix for naming plugin files - const prefix = 'nativescript-'; - pluginNameSource = pluginRepoName.toLowerCase().startsWith(prefix) ? pluginRepoName.slice(prefix.length, pluginRepoName.length) : pluginRepoName; - if (isInteractive()) { - pluginNameSource = await this.$prompter.getString(this.nameMessage, { allowEmpty: false, defaultAction: () => { return pluginNameSource; } }); - } - } + const gitHubUsername = await this.getGitHubUsername(config.username); + const pluginNameSource = await this.getPluginNameSource(config.pluginName, pluginRepoName); 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."); } - const params = `gitHubUsername=${gitHubUsername} pluginName=${pluginNameSource} initGit=y`; // run postclone script manually and kill it if it takes more than 10 sec - const outputScript = (await this.$childProcess.exec(`node scripts/postclone ${params}`, { cwd: cwd, timeout: 10000 })); - this.$logger.printMarkdown(outputScript); + 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 })); + if (outputScript && outputScript.stdout) { + this.$logger.printMarkdown(outputScript.stdout); + } } private async downloadPackage(projectDir: string): Promise { @@ -95,6 +85,30 @@ export class CreatePluginCommand implements ICommand { spinner.stop(); } } + + private async getGitHubUsername(gitHubUsername: string) { + if (!gitHubUsername) { + gitHubUsername = "NativeScriptDeveloper"; + if (isInteractive()) { + gitHubUsername = await this.$prompter.getString(this.userMessage, { allowEmpty: false, defaultAction: () => { return gitHubUsername; } }); + } + } + + return gitHubUsername; + } + + private async getPluginNameSource(pluginNameSource: string, pluginRepoName: string) { + if (!pluginNameSource) { + // remove nativescript- prefix for naming plugin files + const prefix = 'nativescript-'; + pluginNameSource = pluginRepoName.toLowerCase().startsWith(prefix) ? pluginRepoName.slice(prefix.length, pluginRepoName.length) : pluginRepoName; + if (isInteractive()) { + pluginNameSource = await this.$prompter.getString(this.nameMessage, { allowEmpty: false, defaultAction: () => { return pluginNameSource; } }); + } + } + + return pluginNameSource; + } } $injector.registerCommand(["plugin|create"], CreatePluginCommand); diff --git a/lib/options.ts b/lib/options.ts index 879ca4b596..0a48916df6 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -40,8 +40,8 @@ export class Options extends commonOptionsLibPath.OptionsBase { clean: { type: OptionType.Boolean }, watch: { type: OptionType.Boolean, default: true }, background: { type: OptionType.String }, - username: {type: OptionType.String}, - pluginName: {type: OptionType.String} + username: { type: OptionType.String }, + pluginName: { type: OptionType.String } }, $errors, $staticConfig, $settingsService); diff --git a/test/plugin-create.ts b/test/plugin-create.ts index 7f99ebe765..5829ce6972 100644 --- a/test/plugin-create.ts +++ b/test/plugin-create.ts @@ -18,6 +18,7 @@ function createTestInjector() { testInjector.register("childProcess", stubs.ChildProcessStub); testInjector.register("prompter", new stubs.PrompterStub()); testInjector.register("fs", stubs.FileSystemStub); + testInjector.register("npm", stubs.NpmInstallationManagerStub); testInjector.register("options", { username: undefined, pluginName: undefined From d9171d29f3b2cc8bf9991473a1f280d4fa1a76b7 Mon Sep 17 00:00:00 2001 From: Stoyan Stratev Date: Thu, 23 Aug 2018 11:11:11 +0300 Subject: [PATCH 07/15] feat: add template option to plugin create command Accepting .tar.gz path/URL for custom plugin seed. + test + help --- .../man_pages/lib-management/plugin-create.md | 15 ++++++-- lib/commands/plugin/create-plugin.ts | 18 ++++++---- test/plugin-create.ts | 34 +++++++++++++++++-- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/docs/man_pages/lib-management/plugin-create.md b/docs/man_pages/lib-management/plugin-create.md index 43b7b4ffe6..dd4e0f11fa 100644 --- a/docs/man_pages/lib-management/plugin-create.md +++ b/docs/man_pages/lib-management/plugin-create.md @@ -6,7 +6,8 @@ position: 1 Usage | Synopsis ---|--- -Create a new plugin | `$ tns plugin create [--path ]` +Create from the default plugin seed | `$ tns plugin create [--path ]` +Create from a custom plugin seed | `$ tns plugin create [--path ] --template