From 9897e181464a5150838b0139e71e782b366aa644 Mon Sep 17 00:00:00 2001 From: Richard Yu Date: Tue, 5 Mar 2024 16:38:19 +0800 Subject: [PATCH] update docs --- docs/.vuepress/config.js | 64 ++--- docs/en/README.md | 24 +- docs/en/community/contrib/README.md | 23 ++ docs/en/community/qa/README.md | 80 ++++++ docs/en/guide/README.md | 32 +-- docs/en/guide/config/README.md | 145 +++++----- docs/en/guide/core-commands/README.md | 342 ++++++++++++------------ docs/en/guide/custom-commands/README.md | 117 ++++++++ docs/en/guide/hook/README.md | 112 ++++++++ docs/en/guide/plugin/README.md | 261 ++++++++++++++++++ docs/en/guide/quickstart/README.md | 89 +++--- docs/en/usage/README.md | 24 ++ docs/en/usage/distribution/README.md | 5 + docs/en/usage/integration/README.md | 49 ++++ docs/en/usage/plugin/README.md | 50 ++++ docs/en/usage/solution/README.md | 21 ++ packages/cli/src/commands/generate.ts | 2 +- packages/core/src/common/utils.ts | 182 +++++++------ 18 files changed, 1180 insertions(+), 442 deletions(-) create mode 100644 docs/en/community/contrib/README.md create mode 100644 docs/en/community/qa/README.md create mode 100644 docs/en/guide/custom-commands/README.md create mode 100644 docs/en/guide/hook/README.md create mode 100644 docs/en/guide/plugin/README.md create mode 100644 docs/en/usage/README.md create mode 100644 docs/en/usage/distribution/README.md create mode 100644 docs/en/usage/integration/README.md create mode 100644 docs/en/usage/plugin/README.md create mode 100644 docs/en/usage/solution/README.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 05698db6..8b0b541e 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,4 +1,5 @@ -const host = process.env.NODE_ENV === 'development' ? 'localhost:8080' : 'semo.js.org' +const host = + process.env.NODE_ENV === 'development' ? 'localhost:8080' : 'semo.js.org' module.exports = { base: '/', @@ -9,13 +10,13 @@ module.exports = { '/': { lang: 'zh-CN', // 将会被设置为 的 lang 属性 title: 'Semo', - description: '一个Node项目命令行开发规范' + description: '一个Node项目命令行开发规范', }, '/en/': { lang: 'en-US', title: 'Semo', - description: 'A Node.js CLI building rules.' - } + description: 'A Node.js CLI building rules.', + }, }, themeConfig: { repo: 'semojs/semo', @@ -30,7 +31,7 @@ module.exports = { { text: '首页', link: '/' }, { text: '指南', link: '/guide/' }, { text: '用法', link: '/usage/' }, - { text: '参考', link: `http://${host}/typedoc/` } + { text: '参考', link: `http://${host}/typedoc/` }, ], sidebarDepth: 1, sidebar: [ @@ -45,7 +46,7 @@ module.exports = { '/guide/config/', '/guide/hook/', '/guide/plugin/', - ] + ], }, { title: '用法', @@ -56,25 +57,22 @@ module.exports = { '/usage/integration/', '/usage/solution/', '/usage/distribution/', - ] + ], }, { title: '社区', collapsable: false, - children: [ - '/community/contrib/', - '/community/qa/', - ] - } - ] + children: ['/community/contrib/', '/community/qa/'], + }, + ], }, '/en/': { editLinkText: 'Help improve this page', nav: [ { text: 'Home', link: '/en/' }, { text: 'Guide', link: '/en/guide/' }, - { text: 'Usage', link: '/usage/' }, - { text: 'Reference', link: `http://${host}/typedoc/` } + { text: 'Usage', link: '/en/usage/' }, + { text: 'Reference', link: `http://${host}/typedoc/` }, ], sidebarDepth: 1, sidebar: [ @@ -85,34 +83,30 @@ module.exports = { '/en/guide/', '/en/guide/quickstart/', '/en/guide/core-commands/', - '/guide/custom-commands/', + '/en/guide/custom-commands/', '/en/guide/config/', - '/guide/hook/', - '/guide/plugin/', - ] + '/en/guide/hook/', + '/en/guide/plugin/', + ], }, { title: 'Usage', collapsable: false, children: [ - '/usage/', - '/usage/plugin/', - '/usage/integration/', - '/usage/solution/', - '/usage/distribution/', - ] + '/en/usage/', + '/en/usage/plugin/', + '/en/usage/integration/', + '/en/usage/solution/', + '/en/usage/distribution/', + ], }, { title: 'Community', collapsable: false, - children: [ - '/community/contrib/', - '/community/qa/', - ] - } - ] - } - } - } - + children: ['/en/community/contrib/', '/en/community/qa/'], + }, + ], + }, + }, + }, } diff --git a/docs/en/README.md b/docs/en/README.md index 47aa8ccd..eef443d8 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -1,29 +1,29 @@ --- home: true -actionText: Get started → -actionLink: /en/guide/quickstart/ +actionText: Quick Start → +actionLink: /guide/quickstart/ features: -- title: Consistency - details: No matter what framework used and how structured, you can use this package to implement consistent command line scripts. -- title: Flexibility - details: You can extend by plugins, you can override commands, config, and you can hook with core or other plugins. -- title: Efficiency - details: The command rule is simple, so it easy to use, if you use it frequently, it can dramatically improve you performant. -footer: Enterprise level Node CLI building rules +- title: Consistent + details: Regardless of the framework used in the Node project or how abstraction is layered, this framework can be used to implement command-line scripts with a unified style. +- title: Extensible + details: Plugins can be extended, commands can be overridden, configurations can be overwritten, and hook mechanisms can interact with hooks defined by built-in or third-party plugins. +- title: Efficient + details: Due to its simple rules, development efficiency is high, and due to frequent use, work efficiency is high. +footer: Standardized Command Line System Construction Specification for Enterprise-level Node Projects --- ```bash -# Install globally to your local machine. +# For local environment, global installation is generally recommended npm install -g @semo/cli semo help -# Install to your project +# First integration in the project cd YOUR_PROJECT npm install @semo/cli semo init semo generate command test -# Use application convention to reduce first level commands +# Using the application command line specification npm install semo-plugin-application semo generate command application/test --extend=application ``` \ No newline at end of file diff --git a/docs/en/community/contrib/README.md b/docs/en/community/contrib/README.md new file mode 100644 index 00000000..3cbd43e7 --- /dev/null +++ b/docs/en/community/contrib/README.md @@ -0,0 +1,23 @@ +# How to Contribute + +## Adoption and Use + +There are many choices for standardizing command-line implementation methods. In general, having a standard is better than not having one. If your team already has its own standards, then there's no need to adopt `Semo`'s standard. However, if your team hasn't standardized command-line operations before, you might want to give `Semo` a try. If you happen to favor `yargs`, some of `Semo`'s ideas might be insightful to you as well. + +`Semo` can be used in many areas, including development, testing, deployment, operations, and even non-business scenarios like web scraping. + +## Writing Documentation + +The documentation for `Semo` needs to be gradually improved with everyone's help. Some usage tips may not be mentioned in the documentation yet, so feel free to explore and share your findings. + +## Writing Plugins + +There are two types of plugins here. One type can play a role in technology accumulation in enterprise-level projects by encapsulating common operations and logic into plugins. The other type is personal creative plugins, unrelated to business, which can implement and share creative works. + +## Contributing Code + +Contributing code, whether it's for using in business projects, creating creative plugins, or participating in the development of core features, is a great way to give back to the community. + +## Helping Identify Issues + +Whether it's code or documentation, many issues may remain undiscovered, such as outdated code, typos in documentation, or inaccuracies due to continuous improvement and upgrades. It requires everyone's collaborative efforts to maintain and improve them. diff --git a/docs/en/community/qa/README.md b/docs/en/community/qa/README.md new file mode 100644 index 00000000..41a26cf4 --- /dev/null +++ b/docs/en/community/qa/README.md @@ -0,0 +1,80 @@ +--- +sidebar: auto +--- +# Frequently Asked Questions + +## Why is `Semo` so slow and how can it be optimized? + +Compared to some scripts with relatively simple and pure logic, Semo considers many flexible settings, including but not limited to multi-layer scanning of plugins, configuration override rules, hook mechanisms, and so on. Among them, the most significant impact comes from the IO burden of plugin scanning. Currently, some optimizations have been made (such as introducing internal caching), which have had some effects. If the scanning results of plugins are thoroughly persisted, further performance improvements can be achieved, but this is a double-edged sword and requires consideration of update mechanisms. Continuous optimization will be carried out in the future. + +Furthermore, up to now, various possibilities of `Semo` in business development are being explored, and the temporary performance issues have not had a significant impact. Therefore, more emphasis is placed on exploring and compatibility with various possibilities. + +Speed can be further improved by narrowing down the scope of plugin scanning: + +```bash +semo status --disable-global-plugin --disable-home-plugin +``` + +If you don't want to enter it every time, you can put it in the `.semorc.yml` file: + +```yaml +--disable-global-plugin: true +--disable-home-plugin: true +``` + +or + +```yaml +disableGlobalPlugin: true +disableHomePlugin: true +``` + +## Can `Semo` directly run `Typescript` commands? + +Simply put, no, if it could, wouldn't it be `Deno`? However, it is possible under special conditions. Here are the steps: + +**1. There should be `typescript` and `ts-node` packages in the project** + +```bash +yarn add typescript ts-node -D +``` + +**2. Initialize tsconfig.json** + +```bash +npx tsc --init +``` + +**Configuration should be modified according to needs. The minimum required configuration here is:** + +```json +"target": "es6" +``` + +The reason is that the converted code contains `async/await`. + +**3. Configure a scripts command in package.json** + +```json +"scripts": { + "semo": "node --require ts-node/register ./node_modules/@semo/cli/lib/bin.js" +} +``` + +**4. Modify `.semorc.yml`** + +Add support for Typescript + +```yaml +typescript: true +``` + +**5. Finally, create a TypeScript command line script** + +```bash +semo g command test +yarn semo test +``` + +This approach is more suitable for defining local commands. The performance is slower than executing compiled code, but the development experience is better. Generally, the most commonly used method is to let Semo execute the compiled commands. + diff --git a/docs/en/guide/README.md b/docs/en/guide/README.md index 3db337f5..abe22944 100644 --- a/docs/en/guide/README.md +++ b/docs/en/guide/README.md @@ -1,28 +1,28 @@ # Introduction -Semo is a CLI framework, based on excellent `yargs`. Semo do not want to do CLI parsing jobs, defining commands, but giving rules about how to use `yargs`, so it's easy to used in your Node.js projects, provide consitent CLI style and flexibility. +Semo is a command-line development framework, built on top of the excellent `yargs` package with encapsulation and extension. Semo aims not to solve problems such as parsing command-line options and parameters, defining commands, and triggering commands, but to standardize and specify how to implement them in business scenarios. This ensures a consistent command-line architecture for numerous Node microservice projects within a company, while also providing various extension features. ## Principles -- **Consistency**: No matter what framework used and how structured, you can use this package to implement consistent command line scripts. -- **Flexibility**: You can extend by plugins, you can override commands, config, and you can hook with core or other plugins. -- **Efficiency**: The command rule is simple, so it easy to use, if you use it frequently, it can dramatically improve you performant. +- **Consistency**: Regardless of the framework used in Node projects or how abstraction is layered, this framework can be used to implement command-line scripts with a unified style. +- **Extensibility**: Plugins can be extended, commands can be overridden, configurations can be overwritten, and the hook mechanism can interact with hooks defined by built-in or third-party plugins. +- **Efficiency**: Simple to get started with, high development efficiency, consistent style, high maintenance efficiency, frequent use, and high work efficiency. ## Features -- Core concepts are less, just plugin, command, script, config, and hook. -- Plugin, command, config all can be extended or overriden. -- Provide a useful REPL environment, support `await`. -- Add sub commands for other plugins's commands. -- Provide a simple code generator rule, support plugin, command generator. -- Plugin is by name convention. +- Few but powerful core concepts, including plugins, commands, scripts, configurations, hooks, etc. +- Plugins, commands, and configurations can be extended or overridden according to conventions. +- Provides an extensible REPL environment and supports `await`. +- Child commands can be added to commands defined by other plugins. +- Provides a simple code generation mechanism; basic plugin, command, and script boilerplate code can be automatically generated, with support for extensions. +- Supports plugins with npm organization package name format. -## Name story +## Origin of the Name -Semo is from World language, the meaning is like `tinder` in English. +As is well known, naming is hard. Semo is a concept born out of the spark in my mind, translated from various languages, meaning "seed" in Esperanto, symbolizing a starting point and hope. -## About this doc +## Conventions -- Semo support `Typescript` and `Javascript`, but demo code in this doc mostly use `Typescript` style. -- By default, suppose `semo` and `yarn` are installed already. -- This is a personal project, only tested on `Mac` and `Linux`, but not `Windows`. \ No newline at end of file +- All example code is based on `Typescript`. Although the core of `Semo` is also written in `Typescript`, `Semo` supports projects written purely in `js`, as explained in the configuration management section. +- The documentation assumes that `semo` and `yarn` are globally installed in the environment and does not explicitly mention them in specific chapters. +- The development environment of this project is `Mac`, and the runtime environment is either the local machine or the container environment online. It has not been tested on `Windows` and may have compatibility issues. \ No newline at end of file diff --git a/docs/en/guide/config/README.md b/docs/en/guide/config/README.md index c03dea6f..bb448c76 100644 --- a/docs/en/guide/config/README.md +++ b/docs/en/guide/config/README.md @@ -1,12 +1,12 @@ -# Configuration +# Configuration Management -`Configuration` is one of the core concept of `Semo`, we can use many ways to interfere `Semo` behavior, then influence core or plugins. +A core concept of `Semo` is configuration. We can intervene in the configuration of `Semo` in multiple ways to influence the behavior of both the core and plugins. -## Global configuration +## Global Configuration -There is a global `Semo` directory in system home directory, and there is a global `Semo` configration, located at `~/.semo/.semorc.yml` +There is a global `Semo` directory in the home directory, containing a configuration file that will take effect globally under the current account, located at `~/.semo/.semorc.yml`. -This global `Semo` configration can set default value for commands, then you don't need to provide commands options each time you running commands. So comparing with normal CLI commands, you can change commands default value, for example: +This global configuration can adjust default values for some commands, allowing options to be omitted when using commands each time, for example: ```yml $plugin: @@ -16,61 +16,68 @@ $plugin: branch: master ``` -This config means `semo create` command normal format is: +This means that the `semo create` command, which initializes a project based on a template project, would originally be written as follows: ``` semo create PROJECT_NAME PROJECT_REPO_URL master -f ``` -But we set specific default option value, then it reduce two arguments when we use is, and changed to: +However, with default configuration, we can omit two parameters and write: ``` semo create PROJECT_NAME -f ``` -We often use this kind of global settings, if we find out we always set some command arguments, we can settle those settings down in global config file, here is another example: +:::tip +You can see that the configuration is placed under the `commandDefault` key. This is because if it's configured at the top level, it will affect all commands. If this is desired behavior, it can be placed at the top level. Otherwise, it can be placed under `commandDefault` to only affect a single command. +::: -When we run `semo repl` command, there is `--hook` option, if we pass this option, `hook_repl` can trigger other business logics, and inject some objects into REPL. But the default value is `--hook=false`, this can launch faster, but if you need the hook every time, you can put `--hook=true` into global config file `~/.semo/.semorc.yml`. +We often use global configuration, especially for some functional commands. If we find ourselves needing to pass certain parameters every time, we can fix them through global configuration. For example: + +When executing the `semo repl` command, there is a `--hook` parameter. If passed, it will call `hook_repl` to inject some business logic. However, the default for the core is `--hook=false`, which starts a bit faster, but later it was found that in the business scenario, `--hook=true` was needed every time. In this case, we can add this configuration to the global configuration. ```yml -$plugin: - semo: - repl: - hook: true +commandDefault: + repl: + hook: true +``` + +Now, when executing the `repl` command, business logic will be injected by default. + +``` +semo repl ``` -## Plugin configuration +## Plugin Configuration -Plugin directory also has a `.semorc.yml` file, only 3 settings affected. +There is also a `.semorc.yml` file under the plugin directory, with similar configuration file names and principles, but with fewer configurations that actually take effect. By default, only three are generated: -```yml +```json commandDir: src/commands extendDir: src/extends hookDir: src/hooks ``` -`commandDir` is for keeping commands, `extendDir` is for extending other plugins, `hookDir` is for hooking. - -Except above configs, plugins would expose some configs also, these configs has special config path, if not defined, will also try to take from root level. +As the project evolves, more configurations that can take effect here may be added. Currently, these three control the command directory, plugin extension command directory, and hook directory during plugin development. +In addition to the commonly used plugin configurations mentioned above, plugins sometimes expose some configuration options externally. These configuration lines generally agree to be retrieved from both the root and the namespace of the plugin name. ```yml -$plugin: - xxx: - foo: bar +semo-plugin-xxx: + foo: bar ``` -This config can be read in this way: +The effectiveness of this configuration depends on the plugin's own implementation trying to retrieve it actively. ```js -const foo = Utils._.get(argv, '$plugin.xxx.foo', argv.foo) +const foo = Utils._.get(argv, 'semo-plugin-xxx.foo', argv.foo) ``` -This give an opportunity for plugin to define their own config, if plugin use more top level config, it will conflict with each other. This style config is compatible with `commandDefault`, `$plugin.PLUGIN` is for plugins, `commandDefault` is for commands, the former depends on code logic, the latter works automatically. If you are a plugin author, you need describe how to use your plugin in your README.md. +This provides an opportunity for plugins to flexibly agree on exclusive parameters internally. If a plugin uses too many top-level configuration parameters internally, it is likely to conflict with parameters from other plugins. This style of configuration agreement is a supplement to configurations like `commandDefault`. The focus of plugin configuration is configuration, while `commandDefault` is the override order from the perspective of command parameters. The former is actively obtained, while the latter can be automatically recognized. The specific plugin should clearly indicate which method it uses. -## Project configuration +## Project Configuration -When we integrate `Semo` with our projects, there are also commands, extends, hooks directories, and there are more like plugins and scripts and so on. +When we integrate `Semo` into a project, the project also has command directories, plugin extension command directories, and hook directories, but there are more, such as plugin directories and script directories: ```yml commandDir: bin/semo/commands @@ -81,54 +88,53 @@ hookDir: bin/semo/hooks ``` :::tip -`Semo` does not support define plugins in plugin, but support project define plugins. +The reason there is no plugin directory in the plugin is because we do not support the nested declaration of plugins in plugins, but we support defining plugins in projects. ::: -Except for configing some directories, we can also override some command options, like the `repl` command mentioned above. +In addition to configuring some directories, we can also configure some options to override commands, such as the `repl` command option override mentioned above: ```yml hook: true ``` -For another example: `semo init` command has an option `--typescript`, if using this option in project configuration file, it will share with other commands using this option like `semo generate`, it will let all generated file has typescript code style. +For example, the `semo init` command has an `--typescript` option. If this option is added to initialize the directory structure, there will also be a corresponding override configuration in the project configuration. This way, when executing the `semo generate` command, many code generation commands that support both `js` and `ts` versions will be automatically generated as `typescript` style. ```json typescript: true ``` -Congurations in project configration file only work in current project. because of plugins, there may be many more configurations in project level Semo config file, that can provide specific needed features. +Options configured in the project configuration only take effect in the current project directory. This is just a demonstration of usage. In fact, we can provide multiple options when developing plugins, and limit behaviors when using plugins in projects to support flexibility and personalization. -## Hidden configrations +## Hidden Configuration -`Semo` has some hidden options, those are rarely used in common cases. You can run `semo help --show-hidden` to see them. +`Semo` has some hidden options that are rarely used in ordinary times, which can be viewed by `semo help --show-hidden`: ``` -Option: - --script-name Rename script name. [String] [Default: "semo"] - --plugin-prefix Set plugin prefix. [Default: "semo"] +Options: + --script-name Rename script name. [string] [default: "semo"] + --plugin-prefix Set plugin prefix. [default: "semo"] --disable-core-command, --disable-core Disable core commands. --disable-completion-command, --disable-completion Disable completion command. --hide-completion-command, --hide-completion Hide completion command. --disable-global-plugin, --disable-global-plugins Disable global plugins. --disable-home-plugin, --disable-home-plugins Disable home plugins. --hide-epilog Hide epilog. - --set-epilog Set epilog. [Default: false] + --set-epilog Set epilog. [default: false] --set-version Set version. - --node-env-key, --node-env Set node env key [Default: "NODE_ENV"] + --node-env-key, --node-env Set node env key [default: "NODE_ENV"] ``` -As we see, by passing these options, we can change some core behaviours, even the command name itself. - +As seen, by passing these options, we can change some core behaviors, and even change our own command names and versions. Here, two of them are emphasized: ```yml --disable-global-plugin: true --disable-home-plugin: true ``` -We can use these two options in project to disable global plugins, that will improve performance a little bit. +We generally add these two configurations in project configuration, so that when scanning plugins and hooks, only the current project directory is scanned, which can slightly improve command performance. :::tip -In `Semo` Configrations below are all equivalent. +In the Semo configuration environment, the following configurations are completely equivalent --foo-bar --foo--bar --fooBar @@ -136,9 +142,9 @@ foo-bar fooBar ::: -## Changing configration by CLI commands +## Modifying Configuration via Command Line -Of course, we could modify configuration by editing, but `Semo` also provide commands to do this job, so with this, you can modify configuration in scripts. +Of course, we can modify the configuration by editing the configuration file, but Semo also provides a command line tool to edit the configuration. With this command line tool, we can customize certain configurations through scripts. ``` semo config set a.b.c d 'some comment' -g @@ -148,42 +154,41 @@ semo config list semo config list --watch ``` -## Application configuraion +## Application Environment Configuration -> Added since `v0.8.0` +> This feature was introduced in `v0.8.0` -In application root, we use `Semo` way to construct our code, like commands, cronjobs, hooks and extensions, scripts and so on. we can only recognize `.semorc.yml` before, but now it can load another env config file, e.g. when `NODE_ENV=development`, then `.semorc.development.yml` will be loaded and can override default configurations (using `Lodash` `_.merge`) +In the application directory (usually the current directory where the semo command is run), we organize our project code using Semo's mechanism, such as command line tools, scheduled tasks, hook extensions, command extensions, scripts, etc. Previously, the system could only recognize the `.semorc.yml` configuration file, but the latest version can continue to load an environment configuration. For example, if the current `NODE_ENV=development` (default value), then `.semorc.development.yml` will also be recognized and loaded, and will override configurations with the same name in the main configuration (using Lodash's `_.merge`). -## 特殊配置项 +## Special Configuration Items -## Special configrations +> This feature was introduced in `v0.9.0` -> Added since `v0.9.0` +Semo's configuration and command line `argv` are closely coupled. The original intention of `argv` was only to store command line parameters. Semo further expands its capabilities, hoping it can take on the responsibility of project configuration management. Here, several configurations starting with `$` have special meanings: -Semo's configurations are combined with yargs's argv, argv was designed to store command line arguments and options. Semo extended this behaviour, make it to be configuration management tool, here added several special configuration started by `$`, and have special meanings. ### `$plugin` -This key defines plugin level configurations, make some configurations are not needed to pass by CLI, but in config file. +This configuration defines plugin-level configuration items. Previously, commands could only agree on configurations through parameters, but there are some complex configurations that do not need to be declared as parameters. Therefore, this configuration item was designed: -Take `$plugin.ssh.key = 1` as an example, it means to set `key=` to all commands of `semo-plugin-ssh`. But where can we get the config value? Semo has processed it into `argv.$config`, so you can get values of `$plugin.ssh` in `argv.$config` in plugin `semo-plugin-ssh` code. +Taking `$plugin.ssh.key = 1` as an example, it means that each command under the `semo-plugin-ssh` plugin is provided with a configuration `key=1`. Where does this configuration go? Semo has already helped assemble it into `argv.$config`, so you can retrieve `argv.$config` under the command of the ssh plugin, and all configurations obtained are under `$plugin.ssh`. -In order to archive this, each command must add `export const plugin = 'ssh'` in command defination. +To achieve this, each command adds a declaration like `export const plugin = 'ssh'` when declared. ### `$plugins` -The above `$plugin` is to set specific settings for each plugin, but this `$plugins` is for the whole environment. There are 3 config keys. +The `$plugin` mentioned above adds configurations for each specific plugin, while this one determines the effective plugins for the entire environment, supporting three configurations: -* `$plugins.register` Whether or not to enable active plugin registration mechanism, if enabled, auto-detect mode will not work anymore. -* `$plugins.include` Secondary filter to included plugins, it's an array, support short plugin style。 -* `$plugins.exclude` Secondary filter to excluded plugins, it's an array, support short plugin style。 +* `$plugins.register` determines whether to enable the active registration mechanism. If enabled, the automatic scanning mechanism is disabled. Refer to [Active Registration Mechanism for Plugins](../plugin/README.md). +* `$plugins.include` performs secondary filtering on registered plugins. This is a whitelist and is an array that supports shorthand notation for plugin names. +* `$plugins.exclude` performs secondary filtering on registered plugins. This is a blacklist and is an array that supports shorthand notation for plugin names. ### `$config` -This is auto-parsed configuration. Often it will be used in plugin development. For project application project, we often use `$app` to manage app configration. +Automatically parsed plugin configurations. Generally, this is only needed during plugin development. If it is an application, it is recommended to use `$app` to manage configurations. ### `$app` or `$application` -`$app` have nothing special, but a suggestion to put all app configration together, then it will not conflict with `Semo` internal options. +There is no special function here. It is only suggested that the application's own configuration be grouped together to prevent confusion with command line options. For example: ```yml $app: @@ -192,25 +197,25 @@ $app: ### `$input` -This is for command line pipe feature, `$input` will be the output by pipe from previous command. It can be not `Semo` output, but the format is not defined. +The purpose of this is when implementing commands that support piping, `$input` can automatically receive the output of previous commands, regardless of whether it is the output of Semo plugins, but the format of the output is uncertain and needs to be verified and constrained by the current command itself. ### `$0` -This is inlucded by `yargs`, stands for the current script file name. +This is built into `yargs`, indicating the name of the current script being run. ### `$command` -This is about the current command info. +This contains information about the current command. Generally, its usefulness is not very significant. ### `$semo` -This is an reference to `Utils`, the reason of this key is that you need to know and process the internal info of `Semo`, but if you import `@semo/core`, you may lose some runtime info. By running `argv.$semo.Utils.getInternalCache().get('argv')` you can get correct runtime info. +This contains a reference to the utility function library `Utils`. The main reason for using this is that sometimes plugins also want to know and process internal information. However, if a plugin depends on and imports `@semo/core` internally, due to different positions, it actually occupies two separate memories, and the imported part is missing necessary information due to lack of initialization. By using `argv.$semo.Utils.getInternalCache().get('argv')`, you can correctly obtain the runtime data. -## Internal configration management methods +## Built-in Configuration Management Methods ### `Utils.extendConfig` -This method support to extend a new config file, so you don't need to put all configuration in `.semorc.yml`, also support configuration by environment. +This method supports extending a new configuration file, which allows for configuration file groups without putting all configurations in `.semorc.yml`, while also supporting environment configurations. For example: ```js Utils.extendConfig('application.yml') @@ -224,15 +229,15 @@ application.production.yml ### `Utils.config` -This method is for getting part of configuration, based on `_.get`. +This method is used to extract a section of the total configuration, with all sections extracted by default, based on Lodash's `_.get` method. ### `Utils.pluginConfig` -This method is for getting plugin configuration, it only works in command handler, by default command options is to take precedence over the plugin configration. +This method is used to extract plugin configurations and only works in the `handler` of commands. By default, it still takes precedence over command line parameters, but if the command line parameters are not specified and there is no default value, plugin-level configurations can be obtained. -## `.env` support +## Setting Environment Variables `.env` -By npm package `dotenv`, we support `.env` file, and enabled by default for CLI, but need to manually enable for project. +By integrating `dotenv`, we have introduced support for the `.env` file, which is enabled by default for command line tools. For programs, you need to enable it manually. ```typescript import { Utils } from '@semo/core' diff --git a/docs/en/guide/core-commands/README.md b/docs/en/guide/core-commands/README.md index f9665622..cb94a6a0 100644 --- a/docs/en/guide/core-commands/README.md +++ b/docs/en/guide/core-commands/README.md @@ -1,51 +1,47 @@ -# Core commands - -:::tip -Translating... -::: +# Core Commands ## `semo` -Semo provides default behavior to run any `Semo` command style file. +Please note that `Semo` provides a default mechanism to run any file that conforms to the `Semo` command line file syntax. ``` semo command.js ``` -Normally our commands do not have `.js` extension, so if you pass an argument with `.js` you must want to execute the file. If you run a `.ts` file, you need to construct `ts` environment, please refer to our FAQ about how to do it. +Typically, the commands we define don't include file extensions like `.js`, so the file is executed directly as a command. If you need to execute a `.ts` command file style, you'll need the `ts` environment, please refer to the FAQ section. -What is this way used for? We can use this way to define scripts, then you combine the concepts of commands and scripts.You can define scripts first, then if they are used frequently, you can make a plugin with a proper name. We also have a plugin called `semo-plugin-script`, which is also used to define scripts, but it has a code template generator with will not be provided by `Semo` core. +The significance of this mechanism is that you can use `Semo` to define and execute script files, which blurs the distinction between commands and scripts. They are interchangeable. Scripts come first, and if you find them frequently used, give them a good name and then encapsulate them into a plugin. In the early days, there was also a `semo-plugin-script` plugin that aimed to do this. Now with this default mechanism, support for script files can be built into `Semo`. However, the `semo-plugin-script` plugin still has a script template code generator feature, which the `Semo` core does not intend to provide. Because this is more inclined to be understood as a simplified way to develop command-line tools quickly. ## `semo application` > alias: `app` :::tip -This command has been migrated to `semo-plugin-application`. +This command has been moved to the `semo-plugin-application` plugin. ::: -By default this command does not have any functionalities. it's just a convention, suggest that you add your application commands under `semo application`. Your application need to add commands by using `semo` command extension feature. +By default, this command has no functionality. Its purpose is to establish a convention with business projects, suggesting that commands added to the business project should be written as subcommands of this command. The reason why a business project can add subcommands to this command is that it utilizes the command extension mechanism of `Semo`. ```bash npm install semo-plugin-application semo generate command application/test --extend=application ``` -In this way, it add an `test` command for `semo application`, and you can run it by `semo application test` +This way, you can add a test command to the project, and this command needs to be called using `semo application test`. -By running `semo application help`, you can see all top level commands in this project. Because you may have many commands in many levels, so you may need to run help command very often. +By running `semo application help`, you can see all top-level subcommands defined by the current business project. Since it's difficult to remember all commands and parameters, especially when a project implements many commands and has multiple levels, the help command is something we often need to execute. ## `semo cleanup` > alias: clean -This command is used to clean some `Semo` internal files, e.g. repl command history, shell command history, repl temp downloaded files, run command temp downloaded files, and global plugin directories and files. +This command is used to clean up some files generated internally by Semo, including the history of the repl command, shell command history, temporarily downloaded packages in repl, temporarily downloaded packages in the run command, and the global plugin directory. -For now, it only supports a limited extension, and only allows applications defining clean directories, but not support plugins adding clean directories for security reasons. +Currently, only limited extensions are provided, allowing only the application directory to define cleanup directories. Support for plugins to add cleanup directories is not provided, mainly for security reasons. ## `semo config` -We can use this command to view and modify config, including project config and global config. +We can use this core built-in command to view and modify configuration files, operate on the configuration files of the current project, or operate on the global configuration file. ``` semo config @@ -64,15 +60,15 @@ Options: --watch Watch config change, maybe only work on Mac ``` -Here, ``'s format is `a.b.c`, means hierarchical config. It also supports adding comments to the last level config. +Note that the `` here is in the format of `a.b.c`, representing multi-level configuration. In addition, it supports adding comments to the configuration set at the last level. ## `semo hook` :::tip -This command has been migrated to `semo-plugin-hook` +This command has been moved to the `semo-plugin-hook` plugin. ::: -This command's output shows all available hooks in current environment, all implemented hooks can be invoked. You can see the hook name, description, and definition location. +The output of this command displays all available hooks in the current environment. All logic implementing these hooks can be executed. In the output, you can see the name, description, and module declaration of the hooks: ``` Hook : Package : Description @@ -85,21 +81,21 @@ Hook : Package : Description hook_create_project_template : semo : Hook triggered in create command. ``` -Here you can see a special hook `hook_hook`, using this hook to declare new hooks. Any plugin can declare its own hooks, and other plugins can hook them to influence plugin behaviors. Most of applications projects do not need to declare hooks, except it need to build their application level plugin system. +Here you can see that there is a special hook called `hook_hook`. Implementing this hook allows you to declare hooks, and any plugin can declare its own hooks for other commands to call, thereby affecting its own behavior. Generally, business projects do not need to declare their own hooks, unless the business project deeply uses this mechanism to constitute its own business plugin system. -Another thing to note, it's not necessary to declare hooks before using, declaration code is just for clarification. +It is also important to note that even if not declared, hooks can still be used as long as they are implemented. Declaring hooks here is just for transparency. Details on how to declare and implement hooks will be explained in the hooks-related section. ::: warning -Maybe we do not allow to invoke non-declared hooks in the future. +In the future, it may be possible to change the logic so that hooks that are not declared cannot be used. ::: ## `semo init` > alias: `i` -This command is for initialization for projects or plugins, these two scenarios have a little differences. +This command is used for initialization and can implement two scenarios: initialization of a business project or initialization of a plugin. The difference between these two scenarios lies in the directory structure. -In application projects, we put `Semo` directory into `bin` under the project root. +In a business project, we default to placing the directory structure of `Semo` in the `bin` directory: ``` ├── .semorc.yml @@ -114,7 +110,7 @@ In application projects, we put `Semo` directory into `bin` under the project ro ``` -But in plugin project, we put all code into `src` directory. +In a plugin project, we put all the code in the `src` directory: ``` ├── .semorc.yml @@ -125,25 +121,25 @@ But in plugin project, we put all code into `src` directory. └── package.json ``` -This command is to save developers seconds to init a project. It's OK to create those files and directories manually. +The existence of this command is only to save a few seconds for engineers. In other words, if you don't use this command, manually creating these directories and files is also OK. :::tip -The structure and usage of `.semorc.yml` is located at `Configuration management` section. +The structure and usage of `.semorc.yml` will be explained in the configuration management section. ::: -If we want to build a new plugin, it is still not so easy to do, here we suggest using project template as follows. +In addition, if we really want to create a plugin, it is too slow to do it through initialization. Here, it is recommended to use the plugin project template. The specific command is as follows: ``` semo create semo-plugin-xxx --template=plugin ``` -Obviously, there are more templates. Please refer to the `create` command. +Clearly, other project templates can also be used here. Regarding the `create` command, see below for an introduction to the `create` command. ## `semo create [repo] [branch]` -> alias: `c` +> alias: `n` -This command is different from `generate` and `init` command. It's used to create a new project directory. It can be application projects or plugin projects. It has some options to control. +This command, unlike `generate` and `init`, is used to initialize a new project directory. This project can be a business project or a plugin. This command has many parameters and some conventions: ``` $ semo create help @@ -152,44 +148,41 @@ semo create [repo] [branch] Create a create project from specific repo -选项: - --version 显示版本号 [布尔] - --yarn use yarn command [默认值: false] - --yes, -y run npm/yarn init with --yes [默认值: true] +Options: + --version Show version number [boolean] + --yarn use yarn command [default: false] + --yes, -y run npm/yarn init with --yes [default: true] --force, -F force download, existed folder will be deleted! --merge, -M merge config with exist project folder! --empty, -E force empty project, ignore repo --template, -T select from default repos - --add, -A add npm package to package.json dependencies [默认值: false] - --add-dev, -D add npm package to package.json devDependencies [默认值: false] + --add, -A add npm package to package.json dependencies [default: false] + --add-dev, -D add npm package to package.json devDependencies [default: false] --init-semo, -i init new project - -h, --help 显示帮助信息 [布尔] + -h, --help Show help [boolean] ``` -The above are specific command descriptions, and next we describe the some senarios. +Single explanations have been provided above, below we'll explain with specific usage scenarios. -### Initialize project from any repo +### Initialize from Any Repository ``` semo create PROJECT_NAME PROJECT_REPO_URL master -f ``` -We use `create` command to download code from any git repo, that means any git repo can be our project template. `master` is the default branch name, so we can ignore it, `-f` means `--force`, it will override existed directory. +Here we can see that with the create command, we can download code from any git repository address, and any code repository can be our project template. Where `master` is the branch name, which defaults to `master` so it can be omitted, `-f` means if the directory already exists, it will first delete the original one and then recreate it. -### Create an empty project, no project template +In addition to downloading the code, the create command also removes the original `.git` directory and reinitializes an empty `.git` directory, then automatically downloads all project dependencies. -``` +### Creating an Empty Project, Not Based on Any Project Template + +```bash semo create PROJECT_NAME -yfie ``` -The `-yfie` means `-y -f -i -e`, it's a `yargs` feature. +Here we can see a feature of `yargs`, where short parameters can be chained together. Here, it's equivalent to `-y -f -i -e`, which means `-y` automatically answers `yes` when creating the `package.json`, `-f` forces the deletion of an existing directory, `-i` automatically executes `semo init` to initialize the project directory, and `-e` indicates that it's an empty project declaration, not based on any code repository or built-in template. -> `-i` is to run `semo init` automatically. -> `-y` is to answer `yes` when creating `package.json`. -> `-f` is to override exsited directories. -> `-e` is to tell it's just an empty repo. - -The structure is as follows. +The directory structure of the project is as follows: ``` ├── .semorc.yml @@ -203,17 +196,17 @@ The structure is as follows. └── package.json ``` -### Create a `Semo` plugin directory +### Creating a `Semo` Plugin Directory -If we do not base on code template repo, we can create basic plugin structure manually. +If not based on a plugin template, we can manually create a basic plugin structure: -``` +```bash semo create semo-plugin-[PLUGIN_NAME] -yfie ``` -It's similar with the above case, except that project name has a `plugin` convention. If the project name starts with `semo-plugin-`, then `Semo` knows it's a plugin project intialization, and `semo init --plugin` executed automatically. +Similar to the previous command, except for the project name. Here, there's a naming convention for the project name. If the project name starts with `semo-plugin-`, it's considered as initializing a `Semo` plugin, and `semo init --plugin` will be executed during initialization. -The structure of this project is: +The directory structure of the project is as follows: ``` ├── .semorc.yml @@ -224,15 +217,15 @@ The structure of this project is: └── hooks ``` -### 基于内置模板创建项目 +### Creating a Project Based on Built-in Templates -If we create a project by running follow command: +If we create a project using the following command: -``` +```bash semo create PROJECT_NAME --template ``` -Then we'll see the output as follows: +We'll see the following output: ``` ? Please choose a pre-defined repo to continue: (Use arrow keys) @@ -240,67 +233,66 @@ Then we'll see the output as follows: ❯ ... ``` -Here you can choose an internal template, and do not need to input repo any URLs. there is a default plugin template, and you can add more templates by implementing `hook_create_project_template` +Here, we can choose from available built-in templates, eliminating the need to manually input repository addresses. Currently, there's only one plugin template by default, but additional templates can be injected using the `hook_create_project_template`: -Here is a simple demo code. For more details about Semo hooks, please refer to relative docs. +Example hook implementation for injecting templates: ```js export const hook_create_project_template = { demo_repo: { - repo: "demo_repo.git", - branch: "master", - alias: ["demo"], + repo: 'demo_repo.git', + branch: 'master', + alias: ['demo'] }, -}; +} ``` -If you already know the template identifier, you can use it in commands as follows: +If we already know which template and identifier to use during initialization, we can specify it directly: -``` +```bash semo create PROJECT_NAME --template=demo semo create PROJECT_NAME --template=demo_repo ``` -:::tip -When you create a business application or a plugin, it's not suggested to build from scratch. Because you need to think about many things like CICD, lint, test and so on. You can come up with your own code template and make the template better and create new project from it. -::: +### Tip +When creating a business project or a plugin, it's not recommended to start from an empty project because many engineering and technology selection issues need to be considered. It's recommended to summarize commonly used scaffolding projects within the company and then initialize them using a unified method. For example, after initializing the built-in plugin template, you can directly write logic and then upload the code to `Github` and execute `npm version patch && npm publish` to publish it to the npm repository. Instructions on how to develop a plugin and publish it to the `npm` repository will be provided separately. Additionally, note that the scaffolding project here can be implemented in any language. -The rest of options are all easy to understand, `--yarn` means install deps by `yarn`, `--add` and `--add-dev` are to set new deps. `--merge` means it will not delete old project but init in project directory. +The remaining options are also straightforward. `--yarn` declares the use of `yarn` to initialize and install dependencies, `--add` and `--add-dev` are used to specify new dependencies during initialization. `--merge` means not to delete the original project, but to enter the project directory and then apply `--init`, `--add`, `--add-dev`. ## `semo generate ` > alias: `generate`, `g` -This command is a code generator, `Semo` defines command, plugin, and script generator, and plugins of `Semo` can create their own generators under `generate` sub command. e.g. controller, model, migration, unit test and so on. It's best to keep same style in a team. +This command is used for generating component code. Here, the term "component" refers to abstracted and categorized development targets. For example, the `Semo` core defines three concepts: plugins, commands, and scripts. Therefore, there are corresponding code generation subcommands for these three concepts. Similarly, `Semo` plugins or integrated projects can create their own abstract concepts and provide corresponding code generators. For instance, backend business projects may have concepts like routes, controllers, models, database migration files, unit tests, etc. These concepts may not be universal across projects but maintaining consistent style within a project is recommended. Generating boilerplate code automatically helps maintain consistency. -``` +```bash $ semo generate help semo generate Generate component sample code -命令: - semo generate command [description] Generate a command template - semo generate plugin Generate a plugin structure - semo generate script Generate a script file +Commands: + semo generate command [description] Generate a command template + semo generate plugin Generate a plugin structure + semo generate script Generate a script file -选项: - --version Show version [Boolean] - -h, --help Show help [Boolean] +Options: + --version Show version number [boolean] + -h, --help Show help [boolean] ``` -### Extend `generate` command and add sub command +### Extending the `generate` Command to Add Subcommands -It's similar with extend `application` command above. +Similar to extending the `application` command: ```bash semo generate command generate/test --extend=semo ``` -具体怎么实现这些代码生成命令,这里是没有做约束的,因为首先 es6 内置的模板字符串机制可以解决大多数问题,然后 `Semo` 还内置了 `lodash`,其 `_.template` 方法也比较灵活,最后只要把组装好的样板代码放到想放的位置即可。 +The implementation details of these code generation commands are not constrained here because firstly, the built-in template string mechanism in ES6 can solve most problems. Secondly, `Semo` also incorporates `lodash`, and its `_.template` method is quite flexible. Finally, once the template code is assembled, it can be placed wherever desired. -因为这部分都是基于 `Semo` 的,所以相关的配置建议放到 `.semorc.yml` 文件,例如自动生成的配置里就有的: +Since this part is based on `Semo`, related configurations are suggested to be placed in the `.semorc.yml` file. For example, the default configuration generated by `create` command includes: ```yml commandDir: src/commands @@ -308,62 +300,62 @@ extendDir: src/extends hookDir: src/hooks ``` -可以看到,`create` 命令生成默认配置也仅仅是约定了一些代码自动生成的目录,同时也给出一种定义目录的配置风格,如果想保持配置的一致性,可以用同样的风格定义其他目录。 +As seen, the `create` command generates default configurations by merely specifying some directories for code auto-generation. It also provides a consistent style for defining other directories if maintaining configuration consistency is desired. ## `semo plugin` > alias: p :::tip -这条命令已经转移到 `semo-plugin-plugin` 插件 +This command has been moved to the `semo-plugin-plugin` plugin. ::: -这个命令用于安装在家目录的全局插件,也可以用于优化当前项目的 semo 执行效率。 +This command is used for managing globally installed plugins in the home directory or for optimizing the execution efficiency of the current project's `semo`. -``` +```bash $ semo plugin help semo plugin Plugin management tool -命令: - semo p install Install plugin [aliases: i] - semo p list List all plugins [aliases: l, ls] - semo p uninstall Uninstall plugin [aliases: un] +Commands: + semo p install Install plugin [aliases: i] + semo p list List all plugins [aliases: l, ls] + semo p uninstall Uninstall plugin [aliases: un] ``` ## `semo repl [replFile]` > alias: `r` -REPL(read-eval-print-loop):交互式解析器,每一个现代的编程语言大概都有这类交互环境,在里面我们可以写一些简单的代码,做为一个快速了解和学习语言特性的工具。但是当 REPL 可以和框架或者业务项目结合以后,可以发挥出更大的作用。 +REPL (read-eval-print-loop): an interactive evaluator, a tool available in most modern programming languages. It allows writing simple code snippets for quick understanding and learning of language features. When REPL is combined with frameworks or business projects, it can have greater utility. -### 对 `REPL` 的一些扩展 +### Some Extensions to the `REPL` -在开发 Semo 和这个脚手架时,Node 的 REPL 还不支持 `await`,这里是模拟实现了这个机制,目的是可以触发执行项目中的一些 promise 或 generator 方法。通过这个能力,再加上我们可以把一些业务代码注入到 `REPL` 我们就可以在接口控制器,脚本,单元测试之外多了一种执行方式,而这种执行方式还是交互式的。 +When developing Semo and this scaffolding, Node's REPL didn't support `await`. Here, it's simulated to enable triggering execution of promises or generator methods from the project. With this capability, combined with injecting some business logic into `REPL`, we gain an additional execution mode beyond controllers, scripts, and unit tests, and it's interactive. -### 为 `REPL` 注入新的对象 +### Injecting New Objects into `REPL` -这里需要实现内置的 `hook_repl` 钩子,并且在业务项目的声明的钩子目录配置: `hookDir`,下面代码仅供参考。 +Here, the internal `hook_repl` hook needs to be implemented, and configured in the business project's hook directory: `hookDir`. The following code is for reference only: ```js // src/hooks/index.ts export const hook_repl = () => { return { add: async (a, b) => { - return a + b; + return a + b }, multiple: async (a, b) => { - return a * b; - }, - }; -}; + return a * b + } + } +} ``` -然后在 REPL 环境,就可以使用了: +Then, in the REPL environment, it can be used: :::tip -`hook_repl` 返回的信息都注入到了 REPL 里的 Semo 对象。 +The information returned by `hook_repl` is injected into the `Semo` object in the REPL. ::: ``` @@ -377,45 +369,45 @@ export const hook_repl = () => { 12 ``` -在实际的业务项目中,会把项目中的公共方法,工具函数等等都注入进去,这对开发以及后面的排查问题都是很有帮助的。默认 `Semo` 把自己的 `Utils` 工具对象注入进去了,里面有一些是 `Semo` 自定义的工具函数,更多的是把 `Semo` 引入的依赖包暴露出来,比如 `lodash`。 +In actual business projects, common methods, utility functions, etc., from the project are injected, which is helpful for development and later troubleshooting. By default, `Semo` injects its own `Utils` utility object, containing some custom utility functions of `Semo`, as well as exposing dependencies imported by `Semo`, such as `lodash`. :::tip -在具体的实践中,我们把数据库,缓存,OSS,Consul, ElasticSearch 等等多种公司的基础设施注入了进来,写成插件,使得我们更容易的直接访问基础设施。 +In practical applications, we inject various company infrastructure such as databases, caches, OSS, Consul, ElasticSearch, etc., into the REPL, write them as plugins, and make it easier for us to directly access the infrastructure. ::: -### 重新载入一遍钩子文件 +### Reloading Hook Files -`.reload` 或者 `Semo.reload()` 可以重新执行一遍 `hook_repl` 钩子,然后把最新的结果注入 Semo。这个的用途是希望在不退出 REPL 环境的情况下能够调用最新的钩子结果,这里只能保证重新加载和执行钩子文件本身,如果钩子内部用了 `require` 还是会被缓存,这部分就需要用户自己来处理了,比如每次 `require` 之前先尝试删除 `require.cache` +`.reload` or `Semo.reload()` can re-execute the `hook_repl` hook and inject the latest results into Semo. This is useful when we want to call the latest hook results without exiting the REPL environment. It only guarantees the reloading and execution of hook files themselves. If `require` is used inside the hooks, it will still be cached, which users need to handle themselves, such as attempting to delete `require.cache` before each `require`. -### 临时试用 npm 包 +### Temporarily Trying npm Packages -在 REPL 下支持用 `Semo.import` 临时下载和调试一些包,这个调试包下载不会进入当前项目的 node_modules 目录。(还有一个等价方法是:Semo.require) +In the REPL, support is provided to temporarily download and debug packages using `Semo.import`, without these downloaded debugging packages entering the current project's `node_modules` directory. (There's also an equivalent method: `Semo.require`) ``` >>> let _ = Semo.import('lodash') >>> _.VERSION ``` -#### 使用内部命令的方式 +#### Using Internal Commands -> v1.5.14 新增 -> `.require` 和 `.import` 是等价的,可以快速导入一些常用包用于调试,例如: +> Added in v1.5.14 +`.require` and `.import` are equivalent and can be used to quickly import some commonly used packages for debugging. For example: ``` >>> .import lodash:_ dayjs:day ``` -冒号后面的是别名,意思是导入后存成什么变量名。 +The part after the colon is the alias, meaning how the imported module will be stored as a variable. -### 释放对象的属性到 REPL 环境 +### Releasing Object Properties to the REPL Environment ``` >>> Semo.extract(Semo) ``` -这个操作的潜在风险就是会覆盖 REPL 环境里内置的对象,但是这个 API 的目的和作用是释放一些明确的对象,比如从 ORM 里释放一个数据库中所有的表模型。 +The potential risk of this operation is that it may overwrite built-in objects in the REPL environment. However, the purpose of this API is to release specific objects, such as releasing all table models from an ORM. -这个操作也支持在配置中进行,比如要将 Semo 对象里的 `Utils` 注入进去,可以在配置文件中配置: +This operation also supports configuration. For example, to inject the `Utils` from the Semo object, it can be configured in the configuration file: ``` $plugin: @@ -423,7 +415,7 @@ $plugin: extract: Semo ``` -这种方式会把 Semo 下的所有属性都注入进去,如果只想注入 `Utils`,支持这么写。 +This method injects all properties under Semo. If only `Utils` needs to be injected, it can be configured as follows: ``` $plugin: @@ -432,27 +424,27 @@ $plugin: Semo: [Utils] ``` -### Semo 对象简介 +### Introduction to the Semo Object -最早的时候本来打算核心和插件可以自由的注入到 REPL 环境,后来觉得不可控,所以决定核心和插件都只能注入到 `Semo` 对象。下面说一下 Semo 对象的结构 +Initially, it was intended that the core and plugins could be freely injected into the REPL environment. However, due to concerns about control, it was decided that both the core and plugins can only be injected into the `Semo` object. Below is a brief overview of the structure of the Semo object: -- Semo.hooks 为了对各个插件的信息进行隔离,所有插件注入的信息按照插件名称注入到这里,各个插件不会相互干扰 -- Semo.argv 这个是进入命令的 `yargs` argv 参数,有时可以用于看看配置合并是否生效,以及实验 yargs 的参数解析。 -- Semo.repl 当前 REPL 环境的对象实例 -- Semo.Utils 核心工具包,里面除了自定义的若干函数之外,会暴露出一些常用的第三方包,比如 `lodash`, `chalk` 等 -- Semo.reload 重新执行 `hook_repl` 钩子,使用最新的钩子文件 -- Semo.import 用于临时实验一些 npm 包, 可以用 `semo cleanup` 清理缓存 -- Semo.extract 用于释放内部对象的键值到当前作用域,可以算作是把所有钩子的注入都放到 `Semo` 对象的一个补偿 +- `Semo.hooks`: To isolate information injected by various plugins, all injected information from plugins is stored here, ensuring that plugins do not interfere with each other. +- `Semo.argv`: Represents the `yargs` argv parameters entered into the command. Sometimes useful for checking if configuration merging is effective or for experimenting with yargs parameter parsing. +- `Semo.repl`: The current object instance of the REPL environment. +- `Semo.Utils`: Core utility package, containing several custom functions and exposing some commonly used third-party packages such as `lodash`, `chalk`, etc. +- `Semo.reload`: Re-executes the `hook_repl` hook with the latest hook files. +- `Semo.import`: Used for temporary experiments with npm packages. Can be cleaned up using `semo cleanup`. +- `Semo.extract`: Releases key-value pairs of internal objects into the current scope, serving as a compensation for injecting all hooks into the `Semo` object. -### 默认注入到全局作用域的方法 +### Methods Automatically Injected into the Global Scope -如果觉得默认的对象层次比较深,可以通过配置或者参数使得默认注入到 REPL 的全局作用域。方式就是 `--extract` +If you find that the default object hierarchy is too deep, you can inject it into the global scope of REPL by configuration or parameters. The method is `--extract`. ``` semo repl --extract Semo.hooks ``` -配置的方式: 修改 `.semorc.yml` +Configuration method: Modify `.semorc.yml` ``` $plugin: @@ -460,7 +452,7 @@ $plugin: extract: Semo.hooks ``` -或 +or ``` CommandDefault: @@ -468,134 +460,134 @@ CommandDefault: extract: Semo.hooks ``` -### 支持执行一个 repl 文件 +### Support for Executing a REPL File -用途也是执行逻辑后将一些结果注入到 REPL,文件也是 Node 模块,需要符合指定格式。 +The purpose is also to execute logic and inject some results into the REPL. The file is also a Node module and needs to adhere to a specified format. ```js -exports.handler = async (argv, context) => {}; +exports.handler = async (argv, context) => {} -// 或者 +// Or -module.exports = async (argv, context) => {}; +module.exports = async (argv, context) => {} ``` -需要注意,只有通过 `context` 才能注入到 REPL,如果你的代码不放到函数当中,也是可以执行的,但是不能获取到之前其他逻辑注入到 context 中的内容,也不同获得 `argv` 对象,另外,必须通过 `global` 对象注入到 REPL 当中。 +It's worth noting that only through `context` can injections be made into the REPL. If your code is not within a function, it can still be executed, but it cannot access the content injected into `context` by previous logic, nor can it obtain the `argv` object. Additionally, it must be injected into the REPL through the `global` object. -这个机制有什么作用呢?主要用途是在我们开发调试时,有一些想在 REPL 里进行的调试是有许多前置逻辑的,如果都在 REPL 里一行一行的输入太麻烦,所以通过这种方式就可以把调试逻辑固化下来。 +What is the purpose of this mechanism? The main purpose is to simplify debugging during development. Some debugging tasks in the REPL may involve many preconditions. Typing them line by line in the REPL can be cumbersome. This approach allows us to solidify debugging logic. -还有一个角度是: `semo repl --require` 这种方式,如果全局设置会比较固定,不宜太多,而通过命令行设置又需要每次都输入,不够方便,利用执行脚本的方式,我们可以灵活的组织逻辑,将我们想要注入的常用工具库注入,甚至在注入之前还可以做一番设置,部分可以取代之前的 `--require` 机制和 hook 机制。 +Another aspect is the `semo repl --require` mechanism. If set globally, it may be too fixed and not suitable for many cases. Setting via command line requires inputting every time, which is inconvenient. By executing scripts, we can flexibly organize logic, inject commonly used utility libraries, and even make settings before injection, partially replacing the previous `--require` mechanism and hook mechanism. ## `semo run [COMMAND]` -这个命令可以像 yarn create 一样,实现直接执行远程插件包里的命令的效果 +This command functions similarly to `yarn create`, allowing for the direct execution of commands from remote plugin packages. -例如: +For example: ``` semo run semo-plugin-serve serve ``` -这里是调用了 semo-plugin-serve 插件实现简单的 HTTP 服务,也许我们会觉得这样写起来还是不是很方便,那么我们可以简化一下。 +Here, the `semo-plugin-serve` plugin is invoked to create a simple HTTP server. However, to simplify this command, we can omit certain parts. ``` semo run serve ``` -这样看是不是简洁多了,这里能把 `semo-plugin-` 省略的原因是这里只支持 semo 系列插件,而不是所有的 npm 包,所以可以内部帮着加上,而后面的 serve 命令去掉是因为插件为此实现了一个约定,插件就是一个普通的 node 包,可以对外暴露方法,这里暴露了一个 handler 方法,而这个 handler 方法又去掉了包里的 serve 命令,因为这个命令文件也是一个 Node 模块。如果插件里面包含多个命令,可以用这个机制对外暴露最常用的,其他的还是应该明确传参。另外,需要注意的是一些命令需要传递参数,这里需要把所有的参数和选项都改造成选项。 +This is much cleaner. The reason we can omit the `semo-plugin-` prefix is because this command only supports Semo series plugins, not all npm packages. Therefore, it can be internally appended. Additionally, the `serve` command is removed because the plugin follows a convention where it exposes a `handler` method that handles the execution of commands. If a plugin contains multiple commands, only the most commonly used ones are exposed using this mechanism, while others should be explicitly passed as arguments. Furthermore, it's essential to note that some commands require arguments, which should all be transformed into options. -之前是命令的时候: +Previously, for a command like: ``` semo serve [publicDir] ``` -在用 `run` 命令调度时:注意,插件命令里的参数需要放到 `--` 后 +When dispatched using the `run` command, note that command arguments within plugin commands need to be placed after `--`. ``` semo run serve -- --public-dir=. ``` -如果你在 npm 的 semo 插件包也是在 scope 下的,在用 run 时需要指定 scope +If your npm Semo plugin package is scoped, you need to specify the scope when using `run`. ``` semo run xxx --SCOPE yyy ``` -`run` 命令运行的插件肯定是缓存到本地了,只不过不在全局插件目录 `.semo/node_modules`, 而是在 `.semo/run_plugin_cache/node_modules` 目录,默认如果存在就会用缓存里的插件,如果想更新需要用参数 --upgrade +Plugins run by the `run` command are cached locally, but not in the global plugin directory `.semo/node_modules`. Instead, they are cached in the `.semo/run_plugin_cache/node_modules` directory. By default, if the cache exists, the plugin will use it. To update, the `--upgrade` parameter is used. ``` semo run serve --UPGRADE|--UP ``` -有些插件可能依赖于另一些插件,如果有这种情况,就需要手动指定依赖插件,实现一起下载,为什么不能基于 npm 的依赖关系呢,可以看一下下面这个例子: +Some plugins may depend on others. In such cases, dependencies need to be manually specified to ensure they are downloaded together. Why not rely on npm's dependency mechanism? Consider the following example: :::tip -此特性 v0.8.2 引入 +This feature was introduced in v0.8.2. ::: ``` semo run read READ_URL --format=editor --DEP=read-extend-format-editor ``` -editor 这个插件在开发时是依赖于 read 的,但是在运行时,read 指定的参数却是 editor 这个插件实现的,所以只能手动指定依赖了。 +The `editor` plugin depends on `read` during development. However, at runtime, the parameter specified for `read` is implemented by the `editor` plugin. Therefore, manual dependency specification is required. -你可能已经发现这个命令的所有参数和选项都是大写的,这是为了减少与其他插件的冲突,我们最好约定所有的插件的参数和选项都用小写。 +You may have noticed that all parameters and options of this command are in uppercase. This is to reduce conflicts with other plugins. It's best to agree that all plugin parameters and options use lowercase. ## `semo script [file]` > alias: `scr` :::tip -这条命令已经转移到 `semo-plugin-script` 插件 +This command has been moved to the `semo-plugin-script` plugin. ::: -很多时候我们都需要跑一些脚本,这些脚本是在项目服务之外的,需要我们主动触发,可能是做数据迁移,可能是数据导出,可能是数据批量修改,也可能是执行业务逻辑,比如发邮件,发短信,发通知等等。在遇到这样的需求的时候,我们都需要写脚本,但是我们会遇到几个问题: +Often, we need to run scripts outside of project services, such as for data migration, exporting data, batch data modification, or executing business logic like sending emails, SMS, or notifications. When faced with such requirements, we need to write scripts, but encounter several issues: -- 放哪里 -- 怎么写 -- 脚本参数怎么解析 +- Where to place them? +- How to write them? +- How to parse script arguments? -很多时候这些需求都是一次性的,或者有前提的,不是很适合写成命令,不然命令就太多了,在这种场景下,`Semo` 通过这条命令给出了一个统一的方案。 +In many cases, these requirements are one-time or have preconditions, making them unsuitable for commands. Otherwise, there would be too many commands. In such scenarios, `Semo` provides a unified solution through this command. -### 放哪里 +### Where to Place Scripts -在配置中有一个 `scriptDir`,默认是 `src/scripts`,我们默认把脚本都放到这里,因为这些脚本不会被服务访问到,所以没必要和项目核心逻辑放的太近。 +There is a `scriptDir` configuration in which scripts are stored, defaulting to `src/scripts`. Since these scripts are not accessible to services, there's no need to keep them too close to the project's core logic. -### 怎么写,怎么解析参数 +### How to Write and Parse Arguments -当然可以手动建脚本,然后用这个命令来触发,但是因脚本还需要起名字,而且还有一定的格式要求,所以,推荐使用 `semo generate script` 命令来生成。 +Of course, you can manually create scripts and then trigger them using this command. However, scripts need to be named, and there are certain formatting requirements. Therefore, it's recommended to use the `semo generate script` command to generate them. ``` semo generate script test ``` -自动生成的样板代码及文件名: +Automatically generated boilerplate code and filename: ```js // src/bin/semo/scripts/20191025130716346_test.ts -export const builder = function(yargs: any) { +export const builder = function (yargs: any) { // yargs.option('option', {default, describe, alias}) -}; +} -export const handler = async function(argv: any) { - console.log("Start to draw your dream code!"); -}; +export const handler = async function (argv: any) { + console.log('Start to draw your dream code!') +} ``` -可以看到,作为一个脚本,不是一上来就写业务逻辑,也不需要声明 `shebang` 标识,只需要定义两个方法,一个是 `builder`,一个是 `handler`。其中 `builder` 用于声明脚本的参数,格式可以参考 `yargs`,如果脚本不需要参数,其实也可以不定义,由于是模板自动生成,放到那里即可,以备不时之需。`handler` 是具体的执行逻辑,传入的参数就是解析好的脚本参数,也包含了项目的 `.semorc.yml` 里的配置。可以看到 `handler` 支持 `async` 所以这里可以执行一些异步操作。 +As a script, instead of immediately writing business logic, or needing to declare a `shebang` identifier, you only need to define two methods: `builder` and `handler`. The `builder` method is used to declare script parameters, and the format can refer to `yargs`. If the script doesn't require parameters, it can be omitted. Since it's template-generated, it's placed there for future needs. The `handler` contains the specific execution logic, with the parsed script parameters passed as arguments, including the project's `.semorc.yml` configuration. Note that `handler` supports `async`, allowing for asynchronous operations here. -所以,脚本和命令最大的区别其实就是使用的频率,以及业务的定位,我们经常做的分层是定义原子命令,然后在脚本中调度。 +Therefore, the most significant difference between scripts and commands lies in their frequency of use and business positioning. We often define atomic commands and then orchestrate them in scripts. ## `semo shell` > alias: `sh` :::tip -这条命令已经转移到 `semo-plugin-shell` 插件 +This command has been moved to the `semo-plugin-shell` plugin. ::: -这个命令是个很简单的命令,目的是不用每次敲命令都输入前面的 `semo`,例如: +This command is straightforward—it eliminates the need to type `semo` before every command. For example: ``` semo shell @@ -619,7 +611,7 @@ git: log > alias: `st` -这个命令的作用很简单,就是看 `Semo` 当前所处的环境,例如: +This command simply displays the environment in which `Semo` is currently operating. For example: ``` $ semo st @@ -634,12 +626,12 @@ $ semo st shell : [MY_SHELL] ``` -这里实现了一个 hook, `hook_status`,实现了这个 hook 的插件,可以在这里展示插件的相关信息,如果是业务项目实现了这个钩子,也可以在这里显示项目信息。 +This command implements a hook, `hook_status`. Plugins implementing this hook can display relevant plugin information here. If a business project implements this hook, it can also display project information. ## `semo completion` -这个命令的作用是输出一段 `Shell` 脚本,放到 `.bashrc` 或者 `.zshrc` 里,就能够获得子命令的自动补全效果。 +This command outputs a shell script that, when placed in `.bashrc` or `.zshrc`, enables automatic completion of subcommands. :::warning -由于 `Semo` 的性能有些差,所以这个自动补全虽然能用,但是体验极差,不建议使用。 -::: +Due to Semo's poor performance, while this auto-completion can be used, it provides a poor user experience and is not recommended. +::: \ No newline at end of file diff --git a/docs/en/guide/custom-commands/README.md b/docs/en/guide/custom-commands/README.md new file mode 100644 index 00000000..15c9e28c --- /dev/null +++ b/docs/en/guide/custom-commands/README.md @@ -0,0 +1,117 @@ +# Custom Commands + +Whether it's developing plugins for `Semo` or building applications and utility scripts based on `Semo`, one thing that cannot be avoided is adding `Semo` command lines before encapsulating plugins. In order to achieve a smooth development experience, `Semo` has been continuously optimizing the entire process, which is still ongoing. This article will discuss how to define commands in `Semo`. + +## Preparation Stage + +As mentioned later, we don't need to create command code templates ourselves, as it would be repetitive work. Therefore, a command line code generator is provided. But where should this code be placed? First, it needs to be declared. The configuration file recognized by `Semo` here is `.semorc.yml`, and the effective configuration is `commandDir`. Sometimes, if the project is based on TypeScript, you also need to configure a TypeScript command line directory `commandMakeDir`. If you are defining commands for other plugins, you need to define the corresponding `extandDir` and `extandMakeDir`. + +Taking plugin development as an example, the configuration file is roughly as follows: + +```yml +typescript: true + +commandDir: lib/commands +commandMakeDir: src/commands +extendDir: lib/extends +extendMakeDir: src/extends +``` + +## Creating a Command + +`Semo` has a built-in code generation mechanism, including a command to generate code for adding new commands: + +``` +semo generate command COMMAND_NAME COMMAND_DESCRIPTION +``` + +## Example Command Code Template + +Here's an example of a TypeScript-based command: + +```bash +semo generate command test 'test description' +``` + +```typescript +export const disabled = false // Set to true to disable this command temporarily +// export const plugin = '' // Set this for importing plugin config +export const command = 'test' +export const desc = 'test description' +// export const aliases = '' +// export const middleware = (argv) => {} + +export const builder = function (yargs: any) { + // yargs.option('option', { default, describe, alias }) + // yargs.commandDir('test') +} + +export const handler = async function (argv: any) { + console.log('Start to draw your dream code!') +} +``` + +You can see that there are some differences compared to commands in the yargs framework because `Semo` is based on `yargs`, so these differences are customized based on the extensibility of `yargs`. + +## Explanation of Command Properties + +### `disabled` + +This indicates whether the command is disabled. When disabled, it is not visible or functional. It's mainly used in scenarios where you want to disable a command without deleting the code. + +### `command`, `desc`, `aliases`, `builder`, `handler` + +These properties are all part of the `yargs` command specification and are straightforward. They can be referred to in the [yargs documentation](https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module). + +### `middleware` + +Yes, middleware is supported here. The advantage is that you can extract similar processing logic into middleware and reuse the code in multiple commands. It's generally needed in complex business scenarios. + +### `plugin` + +This is an attribute exclusive to `Semo` plugins. If you define commands within a plugin, the benefit of declaring this attribute is that you can retrieve global configuration from the configuration file using `Utils.pluginConfig`. + +~/.semo/.semorc.yml + +```yml +$plugin: + test: + a: 1 +``` + +In the code, you can use: + +``` +const a = Utils.pluginConfig('a', 1) +``` + +Internally, it calculates from which plugin configuration to retrieve the value. + +## About the Return Value of `handler` + +Since `Semo` has unified entry points, if you really need to recycle resources through `onFinishCommand`, it cannot be directly called by the command line. However, you can achieve this by enabling the `after_command` hook. Additionally, the return value of the command also participates in the logic. + +- Returning `true` or nothing executes `onFinishCommand`. +- Returning `false` doesn't execute `onFinishCommand`. Even if the `after_command` hook is enabled, it won't execute. + +Resource recycling for command lines should be implemented using the `after_command` hook. + +## About Subcommands + +Note the line in the code template: + +``` +yargs.commandDir('test') +``` + +The effect of this line is to look for subcommands in the `test` directory. There is a flaw here: when a plugin wants other plugins to extend this subcommand, other plugins cannot do so. How to do it then? `Semo` encapsulates this method based on this method. + +``` +Utils.extendSubCommand('test', 'test-plugin', yargs, __dirname) +``` + +The key here is to fill in the first two parameters correctly. Then, how do other plugins extend subcommands? When creating a command, write it like this: + +``` +semo generate command test/subcommand --extend=test-plugin +``` \ No newline at end of file diff --git a/docs/en/guide/hook/README.md b/docs/en/guide/hook/README.md new file mode 100644 index 00000000..e03fb51f --- /dev/null +++ b/docs/en/guide/hook/README.md @@ -0,0 +1,112 @@ +# Hook Mechanism + +As a command-line development framework that leans toward the lower level, having a plugin system is essential. This is especially true for frameworks like `Semo`, which don't inherently provide direct business value. Besides providing users with plugin scanning, command extension, and configuration management mechanisms, the hook mechanism is also a significant enhancement for flexibility and extensibility, forming part of the `Semo` plugin system. + +The concept of hooks is quite understandable and ubiquitous. For instance, consider Windows startup programs. At certain stages during startup, Windows checks if any other applications need to start concurrently. Implementing this functionality certainly requires configuration, such as in the Windows registry or a configuration file. + +The hook mechanism in `Semo` is convention-based and dynamically recognized. Each plugin's hooks are determined during command execution, causing some performance overhead in terms of disk I/O and traversal. However, considering that command execution logic is generally not overly complex, this approach is deemed sufficient for now. If complex hook invocation chains arise in the future, optimization may be considered, which typically involves transitioning from dynamic to static or utilizing caching for speed enhancement. + +## Hook Definition + +```js +// Define a hook named 'hook_bar' +const hookData = Utils.invokeHook('semo-plugin-foo:hook_bar', { mode: 'group' }) +``` + +:::info +Starting from `v1.0.0`, hook invocation requires specifying the hook prefix, indicating who created the hook. When implementing the hook, it's necessary to specify which plugin defined the hook. Failure to specify may cause confusion when multiple plugins define hooks with the same name. Once the defining party explicitly specifies the hook prefix, the implementation party won't be recognized unless it also specifies it. Both parties need to adhere to this convention. +::: + +## Hook Implementation + +Hooks can only be recognized in designated hook directories, which are configured in the `.semorc.yml` file of the plugin under the `hookDir` key, and then recognized inside the `index.js`. + +Currently, there are two styles for implementing hooks: + +The first style prefixes the hook with the hook prefix, separated by hyphens. + +```js +exports.semo_plugin_foo__hook_bar = () => {} +``` + +The second style declares the hook prefix using `Semo`'s built-in hook class object. + +```js +exports.hook_bar = new Utils.Hook('semo-plugin-foo', () => {}) +``` + +When multiple plugins define hooks with the same name and you need them all, you can use the second style like this: + +```js +exports.hook_bar = new Utils.Hook({ + 'semo-plugin-foo1': () => {}, + 'semo-plugin-foo2': () => {}, +}) +``` + +For third-party plugins, if they need to use `Utils.Hook`, which requires adding a dependency on `@semo/core`, another style can be used to omit this dependency. + +```js +export = (Utils) { + return { + hook_bar: new Utils.Hook({ + 'semo-plugin-foo1': () => {}, + 'semo-plugin-foo2': () => {}, + }) + } +} +``` + +## Hook Return Values + +The main purpose of hook implementation is to perform certain operations at program execution nodes or provide certain information. For flexibility, direct object `{}` returns, functions, or even `Promise` functions are supported. If it's a function, the result of the function's execution will be merged. `Promise` hooks are widely used because they allow for asynchronous operations, including but not limited to database, network, Redis, and Elasticsearch operations. + +If the purpose of a hook definition is to collect information, the defining party may have various merging requirements. Currently, the following merging methods are supported, with `assign` being the default: + +* `assign`: Overrides based on the keys of the returned object +* `replace`: Mutual override, retaining only the last hook's return value +* `group`: Grouping based on plugin names +* `push`: Places all return values into an array, generally used for basic data types +* `merge`: Performs deep merging + +## Explanation of Core Built-in Hooks + +As the purpose and return value format of hooks are determined by the hook definition party, the defining party has the obligation to explicitly specify these details in a clear location, enabling plugin users to extend their own plugins or applications accordingly. Here are explanations of some core hooks: + +* `before_command`: Triggered before command execution, does not collect return values +* `after_command`: Triggered after command execution, does not collect return values +* `component`: Used to collect some components defined in plugins, generally returns an object containing instances, for example `{ redis, db }` +* `hook`: Used to declare hooks and their purposes. While not mandatory, it's a convention that informs others of which hooks are defined +* `repl`: Injects information into the REPL, does not override each other, typically used for debugging, format is not fixed +* `repl_command`: Allows third-party plugins to extend commands in the REPL +* `status`: Injects new property information into the `semo status` command +* `create_project_template`: Injects optional templates into the `semo create` command's `--template` parameter + +:::tip +Starting from `v1.15.1`, the `before_command` and `after-command` hooks are set to not execute by default. + +To enable them during command startup, add `--enable-core-hook=before_command` and `--enable-core-hook=after_command`. +::: + +Examples of using some core hooks + +### `repl_command` + +Defines a .hello command in REPL mode, accepting parameters + +```js +const hook_repl_command = new Utils.Hook('semo', () => { + return { + hello: { + help: 'hello', + action(name) { + this.clearBufferedCommand() + console.log('hello1', name ? name : 'world') + this.displayPrompt() + } + } + } +}) +``` + +Here, `this.clearBufferedCommand()` and `this.displayPrompt()` are methods of Node's REPL class. Note two things: one is that the `action` here supports `async/await`, and the other is that for `this` to correctly point to the REPL, arrow functions should not be used. diff --git a/docs/en/guide/plugin/README.md b/docs/en/guide/plugin/README.md new file mode 100644 index 00000000..780110d4 --- /dev/null +++ b/docs/en/guide/plugin/README.md @@ -0,0 +1,261 @@ +# Plugin Development + +## Quick Start + +A `Semo` plugin is essentially a standard `Node` module, albeit with some conventions regarding directory and file structure. These conventions can be challenging to remember, so we provide various auxiliary tools for plugin developers or tool users, such as code generation. Here, we describe the recommended plugin development process. However, once you're familiar with the development process, you can also manually build a plugin from an empty directory. + +### Step 1: Create Plugin Directory Based on Template + +```bash +semo create semo-plugin-xyz --template=plugin +``` + +Here, we use the built-in plugin template. As mentioned earlier in configuration management, you can override the `repo` and `branch` options or the `--template` option to avoid passing default parameters each time. + +### Step 2: Enter Plugin Directory and Execute Default Command to Confirm Everything is Fine + +```bash +cd semo-plugin-xyz +semo hi +``` + +This is a command built into the plugin template. After initialization, you can execute it by entering the directory, confirming the initial interaction with the plugin command. If you see it respond with `Hey you!`, everything is ready, and you can proceed to write scripts that will truly change the world. + +## Adding Commands + +It's worth noting that this plugin template is based on `Typescript`, so you need some `Typescript` basics. We recommend keeping the `yarn watch` command running during development to compile in real-time while developing and testing simultaneously. + +```bash +semo generate command xyz +``` + +Typically, there's a correlation between the plugin name and the commands it encapsulates. Here, we add an `xyz` command, but you can also modify the existing `hi` command. Once you've mastered plugin development, it's advisable to remove the default `hi` command. + +## Implementing Hooks + +Implementing hooks is another purpose of plugin development. Hooks are often defined by other plugins or business projects, and implementing hooks can influence and change the behavior of other plugins. + +Use the following command to query which hooks are supported in the current environment: + +```bash +semo hook list +``` + +### Example 1: Implementing `hook_create_project_template` + +```typescript +// src/hooks/index.ts +export const semo__hook_create_project_template = { + demo_repo: { + repo: 'demo_repo.git', + branch: 'master', + alias: ['demo'] + }, +} +``` + +With this hook, we can select a custom project template when executing the `semo create [PROJECT] --template` command, just by remembering the alias, without needing to remember the address. Another advantage is that you don't need to manage how each engineer sets the global `--repo` option on their personal computer. As long as the specified plugin is installed, everyone can initialize projects with the same project alias. + +### Example 2: Implementing `hook_repl` + +```typescript +// src/hooks/index.ts +export const semo__hook_repl = () => { + return { + add: async (a, b) => { + return a + b + }, + multiple: async (a, b) => { + return a * b + } + } +} +``` + +Then, in the REPL environment, you can use these: + +:::tip +Information returned by `hook_repl` is injected into the Semo object in the REPL. +::: + +```bash +semo repl +>>> add +[Function: add] +>>> await Semo.add(1, 2) +3 +>>> multiple +[Function: multiple] +>>> await Semo.multiple(3, 4) +12 +``` + +The motivations behind implementing this hook differ between plugins and business projects. Business projects generally inject specific business logic, while plugins typically inject common methods with some degree of reusability, such as injecting instance methods of underlying services, commonly used libraries, etc. For example, the `Utils` core injection contains the `lodash` library. + +## Exposing Methods + +Another primary purpose of implementing plugins is to expose instances, methods, or libraries externally. In this case, we can define modules in a standard way, for example: + +:::warning +Since `Semo` later introduced the `run` command, which depends on the entry file for locating, it requires plugins of `Semo` to declare an entry, regardless of whether this entry exposes methods. +::: + +```json +// package.json +{ + "main": "lib/index.js" +} +``` + +```typescript +// index.js +export const func = () => {} +``` + +This approach is fine, but typically, modules defined in this way don't need to adhere to `Semo`'s conventions. As long as they comply with `node` and `npm` standards, they are acceptable. Here, `Semo` defines another way to expose methods based on the hook mechanism. + +```typescript +// src/hooks/index.ts +export const semo__hook_component = async () { + return { + a: 'b' + } +} +``` + +Usage: + +```typescript +import { Utils } from '@semo/core' +const { a } = await Utils.invokeHook('semo:component') +console.log(a) +// -> 'b' +``` + +With this approach, we can encapsulate some common methods of business projects for cross-project use. These common methods typically lean towards the lower level, such as various middlewares or underlying services. + +## Publishing Plugins + +After using commands, hooks, or + + library extensions, we've developed a `Semo` plugin. If you want to share your plugin with others, you need to do some preparation work. + +### 1. Upload Code to a Git Repository + +If it's open-source, you can choose `Github`. If it's an internal plugin, upload it to an internal repository, which could be a private `Github` repository or a company `Gitlab` repository. + +### 2. Modify `package.json` + +Mainly modify the package name, version, license, repository address, homepage address, etc. + +If it's an internal plugin, you can modify the `registry` address in the `.npmrc` file. + +### 3. Obtain an Account for the npm Registry and Log In + +For open-source plugins, you can register at `https://npmjs.org`. For privately deployed npm repositories, you can get an account from your operations team. + +```bash +npm login --registry=[YOUR_REGISTRY] +``` + +### 4. Test the Plugin Package + +```bash +npm pack --dry-run +``` + +Through packaging testing, check whether the package contains any unnecessary files, and adjust the configuration of the `.npmignore` file accordingly. + +### 5. Publish Your Plugin + +```bash +npm version [patch|minor|major] +npm publish +``` + +### 6. Promote Your Plugin and Share Development Insights + +Good documentation is crucial, as well as actively promoting and encouraging others to use and provide feedback on your plugin. + +### 7. Actively Maintain + +Any npm package can gradually become outdated or have security risks, so it's essential to actively maintain your plugin to ensure it functions as intended. + +## Plugin Hierarchy + +The `Semo` plugin system scans multiple locations to increase flexibility, with each level serving different purposes and restrictions. + +* Installed globally via `npm install -g semo-plugin-xxx`, so commands from installed plugins are globally available. This is the default global installation method for npm packages. +* Installed in the home directory's `.semo/home-plugin-cache` directory via `semo plugin install semo-plugin-xxx`, and the plugin commands are also globally available. In certain cases where the current user lacks permission to install globally via npm, this method can be used. +* Installed in the current project directory via `npm install semo-plugin-xxx`. Plugin commands installed this way are only effective within the current project. + +Why would some plugins need to be installed globally? Because plugins can not only fulfill our project's business requirements but also serve as part of our development toolchain or even implement some non-business functionalities. With some imagination, any terminal functionality can be implemented, either completely handwritten or encapsulated and integrated with other excellent projects. Here, excellent projects are not limited to specific languages or language extension package repositories. + +## Running Remote Plugins Directly + +This is just an illusion; it still needs to be downloaded locally, but the download directory is different, avoiding interference with your implementation. You can freely test plugins you're interested in. + +```bash +semo run semo-plugin-serve +``` + +This plugin provides a simple HTTP service. The first time you run it, it will download, and subsequently, it will reuse the previously downloaded plugin. Use `--force` to force an update. + +:::tip +Subsequent development will include a feature to clean plugin caches. +::: + +## Special Home Directory Plugins + +> This feature was introduced in `v0.8.0` + +To add global configurations to `Semo`, you need to add a `.semorc.yml` configuration file in the `~/.semo` directory. Once this configuration file is established, the `.semo` directory is automatically recognized as a global plugin (other global plugins are in the `.semo/home-plugin-cache` directory). Here, you can define your own commands, extend other plugins' commands, or extend other plugins' hooks, etc. This special plugin is globally recognizable. Also, because it's present by default, if you have some locally common logic and don't want to publish it as an npm package, you can quickly start here. However, be aware of its global availability, as errors here can affect the local global state. + +We don't prescribe a specific implementation approach for this special plugin. You can use `js` or `typescript` to write it. You can initialize the basic directory structure using `semo init`, or regenerate a `.semo` directory with the template using `semo create .semo --template=pluging` (backup the `.semo` directory in advance, then merge the contents back). + +## Recognizing Plugins in Any Directory + +We can see the `pluginDir` configuration in the configuration file. If you manually specify this parameter on the command line, you can achieve any specific directory purpose, and it also supports multiple directories: + +```bash +semo help --plugin-dir=dir1 --plugin-dir=dir2 +``` + +Additionally, it supports specifying via constants: + +```bash +SEMO_PLUGIN_DIR=dir3 semo help +``` + +## Issue with Internal Plugins Defined in Applications in Typescript Mode + +This is because `tsc` can only recognize `ts` and `js` related files during compilation and cannot recognize our `yml` format. Moreover, the official doesn't intend to support copying files other than `ts`. As `ts` is not a complete build tool, we need to manually copy the required files. This can be achieved using `cpy-cli` or `copyfiles`. Taking `copyfiles` as an example: + +```json +// package.json +{ + "scripts": { + "copyfiles": "copyfiles -u 1 -a src/**/*.yml dist -E" + } +} +``` + +## Plugin's Active Registration Mechanism + +> Introduced in `v1.3.0` + +In the early days, `Semo` only supported the automatic registration mechanism for plugins. To increase flexibility, it could traverse multiple locations, albeit with some IO performance loss. Therefore, the active registration mechanism was introduced. Once the active registration mechanism is used, the automatic registration mechanism becomes ineffective. + +### Activation Method + +Write plugin key-value pairs in the `$plugins` section of `.semorc.yml` + +```yml +$plugins: + register: + plugin-a: /absolute/path + plugin-b: ./relative/path + plugin-c: true +``` + +Three styles are supported: absolute paths, relative paths, and using Node.js module loading mechanisms for declaration. Here, the `semo-plugin-` prefix can be omitted for plugin names used as keys. Additionally, the shorthand `~` for the home directory is supported. \ No newline at end of file diff --git a/docs/en/guide/quickstart/README.md b/docs/en/guide/quickstart/README.md index e8a3f34e..12c0a68c 100644 --- a/docs/en/guide/quickstart/README.md +++ b/docs/en/guide/quickstart/README.md @@ -1,8 +1,8 @@ -# Get started +# Quick Start ## Global Installation -`Semo` is also a developer tool to help development, devops and debug. So you can install it globally. +`Semo` command-line tool is also a tool for engineers' daily development, operation, and debugging. It is recommended to install it globally in the local environment. For specific usage instructions, you can refer to [here](https://semo.js.org). ``` $ npm i -g @semo/cli @@ -38,73 +38,72 @@ Examples: Find more information at https://semo.js.org ``` -We can see that there are many internal commands, these commands have special usage, Semo provides rules not specific functions, so mostly you need to install plugins. +You can see that there are many built-in commands inside. However, it is important to note that these commands are scenario-specific. Without any plugins or specific business projects, they may not be very helpful. In the development process, the core of `Semo` mainly focuses on defining extension specifications, and specific business logic needs to be implemented by developers. The true value and functionality of `Semo` can only be realized when integrated with specific business logic. +## Project Integration -## Project integration - -`Semo` 's main usage is interated with an existed project to provide CLI machanism. You can implement your own CLI implementation, but different projects have different ways to add CLI, This difference brings about an increase in maintenance costs. For enterprise-level development, reducing costs means increasing profits +The main use case of `Semo` is to add a command-line mechanism to an existing business project. Without `Semo`, individual business projects can certainly develop their own command lines. However, this usually leads to redundant efforts, and the solutions implemented by different teams are bound to differ. This difference increases maintenance costs, and in enterprise-level development, reducing costs leads to increased profits. ``` cd YOUR_PROJECT semo init ``` -If you just use basic Semo rules, you do not need `@semo/core`, if you want to use methods of `@semo/core`, then you need add `@semo/core` as dependency. There are several usage modes: +If only the plugin dispatching method of Semo is used, it is not necessary to install `@semo/core`. If you want to use the methods in `@semo/core`, the business project needs to add `@semo/core` as a project dependency. Whether it should be added to `devDependencies` or `dependencies` depends on the actual situation. There are several usage patterns for business projects using `@semo/core`: -- If you use `@semo/core` in business logic, you need add `@semo/core` to `package.json`'s `dependencies`. -- If you do not use `@semo/core` in business logic, and only use Semo in commands and scripts and these need to use online, then you also need to add `@semo/core` to `package.json`'s `dependencies`. -- If you do not use `@semo/core` in business logic, only use REPL in development stage, then you can add `@semo/core` to `package.json`'s `devDependencies`. -- If Semo CLI not included in docker container, you can add `@semo/cli`, then use `npx semo` to trigger commands. +- The core logic of the business project relies on `@semo/core`, which is invasive and must be added to `dependencies`. +- The core logic of the business project does not rely on `@semo/core`, but `@semo/core` is used to define command lines or scripts, and the scripts need to be executed online: this is non-invasive, but since it needs to be executed online, it also needs to be added to `dependencies`. +- The core logic of the business project does not rely on `@semo/core`, nor is `@semo/core` used to define command lines or scripts. It only uses the REPL extension mechanism to put common classes and functions of the project into the `REPL` environment to assist in development and debugging. This is also non-invasive and does not need to be executed online, so it can be added to `devDependencies`. +- If `Semo` is not installed in the container environment itself, `@semo/cli` can also be added to the project dependencies, and then scheduled through `npx semo`. -### Add a project command +### Adding a Project Command -You need to plan you project commands levels, it's best not add all command in first level. +When defining project commands, it's important to consider the future planning of project command-line tools. If there are many commands, it's best to divide them into hierarchies. Additionally, the first layer of sub-commands should be core commands. If all commands are placed in the first layer, it may be easy to confuse and misuse them. -**Define a first level command** +**Defining a First-level Sub-command** -``` +```bash semo generate command test -semo test +semo test # Execute the newly added command ``` -**Define a second level command** +**Defining a Second-level Sub-command** -``` +```bash npm install semo-plugin-application semo generate command application/test --extend=application semo application test ``` -For seperating project commands, plugin commands and core commands, here we suggest use the second way to add project commands, and if the project is complicated, the project commands can be hierachical. The problem is the more hierachical commands, the more difficult to memorize, so you can add some bash alias to shorten input. +To isolate project commands from those defined by core and plugins, it is recommended to add project commands using the second method above. Furthermore, for complex projects, it is recommended to further divide them into hierarchies. However, this approach increases the burden of memorization for command hierarchy and requires entering many preceding commands to find the command to execute. Therefore, it is generally necessary to add several `alias` to the `bashrc` of the runtime environment: +**Assuming the production environment is deployed using Docker containers** -**Suppose the production is deployed in Docker** - -``` +```bash // Dockerfile RUN echo 'alias semo="npx semo"' >> /home/node/.bashrc RUN echo 'alias app="npx semo app"' >> /home/node/.bashrc ``` -The above code shows how to shorten commands length, if you have many deep commands, you can define more here. +This command demonstrates a method to shorten the length of commands. In actual use, if the command hierarchy is particularly deep, more `alias` can be defined here. -## Plugin development +## Developing Plugins -If you just want to implement some features not integrating with project, you can juse `Semo` plugin support. +If you're not using `Semo` in a project but just want to quickly implement some script commands to improve your work efficiency, you can start quickly with `Semo`. -``` -cd ~/.semo/node_modules # This directory hold global Semo plugins. -semo create semo-plugin-xxx --template=plugin # Choose template repo. +```bash +cd ~/.semo/node_modules # Plugins defined under this directory will be globally loaded +semo create semo-plugin-xxx --template=plugin # Choose the plugin template cd semo-plugin-xxx -semo hi # There is a default commands in the template repo. -code . # Develop using vscode. -yarn watch # Based on `Typescript`, watch files change. +semo hi # There is a sample command by default +code . # Start development with Vscode +yarn watch # Develop based on `Typescript` and require real-time compilation ``` +If you are satisfied with your plugin and want to share it with others, you can directly publish your code to `npm`. -``` -git remove add origin GIT_REPO_URL +```bash +git remote add origin GIT_REPO_URL git add . git commit -m 'init' npm login @@ -112,37 +111,35 @@ npm version patch && npm publish ``` :::warning -`Semo` does not guarantee the isolation of plugin commands. If you install many plugins, you may meet commands conflicts. But it's just rare case. For simplicity `Semo` does not design absolute command isolation. +Note that `Semo` does not guarantee the isolation of commands defined by each plugin. Therefore, if too many plugins are installed, there may be some command conflicts due to name duplication. However, this situation rarely occurs in daily use, and for simplicity, no special design has been made here. ::: -## Install third plugins +## Installing Plugins Developed by Others -``` +```bash npm i -g @semo/cli semo-plugin-xxx ``` -If the plugins commands only useful to yourself, you can install it globally, if the plugin commands needed in business project, you need to install plugins in `dependencies`. +If someone else's plugin only defines some commands you need, you can install the commands globally. If someone else's plugin needs to be used in a business project, it needs to be placed in the project dependencies. -``` +```bash cd YOUR_PROJECT npm install semo-plugin-xxx -yarn add semo-plugin-xxx // 或 +yarn add semo-plugin-xxx // Or ``` -Semo plugins are also normal `Node` module, so we can also export lib functions, imported by other projects. +Since `Semo`'s plugins are also `Node` modules, we can also define some library functions in plugins and import them into projects by others. -```js +```javascript import lib from 'semo-plugin-xxx' ``` -Use `Semo` hooks, we can use another style to use plugin features. +By using the hook mechanism provided by `Semo`, another style of using business logic support provided by plugins can be achieved. -```js +```javascript import { Utils } from '@semo/core' const { xxx } = await Utils.invokeHook('semo:component') ``` -In this way, we do not need to import packages explicitly. This way use directory scan, so performance is an issue and no IDE support, but in command scenario, this style is OK to use. - - +As seen in the latter approach, there is no need to explicitly import packages. This method uses directory scanning, which has poor performance and lacks support for IDE auto-completion. However, for the command line scenario, having a simple and unified code style is also good. \ No newline at end of file diff --git a/docs/en/usage/README.md b/docs/en/usage/README.md new file mode 100644 index 00000000..12c32b75 --- /dev/null +++ b/docs/en/usage/README.md @@ -0,0 +1,24 @@ +# Overview + +As `Semo` provides almost no direct benefits, it is necessary to elaborate on how to leverage `Semo` effectively, from its original design perspective. `Semo` is designed to improve the efficiency of enterprise-level project development, so anywhere that can be solved through code is a suitable application for `Semo`. + +## Division by Project Stage + +Depending on the stage of the project, `Semo` can play a role in various phases: + +- Project initiation phase: Quick initialization of projects +- Development phase: Validate input and output of methods, encapsulate infrastructure, and reduce wheel reinvention by accessing core methods quickly +- Maintenance phase: Write a large number of management commands or operation and maintenance scripts +- Online troubleshooting: When bugs occur online and can only be reproduced online, use `REPL` to step closer to the truth +- Online operations and maintenance: Use well-written scripts and commands to easily solve various requirements proposed by stakeholders, thereby improving trust between departments + +## Division by Form + +`Semo` fully considers various usage scenarios, and its role varies in different scenarios: + +- Developing plugins: Different plugins have different functionalities but consistent code styles +- Project integration: Provide command-line infrastructure for projects; if combined with other plugins, even the entire project can be built on `Semo` +- Solutions: Provide scaffolding for various business scenarios, precipitate best practices, and improve the startup speed of new projects +- Distribution: Based on solutions, further integrate to build complete and usable products, thus generating commercial value + +The division here is not absolute, and there is no scenario that must be addressed by `Semo`. Moreover, there are countless solutions to any problem encountered. The purpose of `Semo` is to provide consistency, reduce redundant development, improve communication efficiency, and continuously solidify enterprise technical capabilities. \ No newline at end of file diff --git a/docs/en/usage/distribution/README.md b/docs/en/usage/distribution/README.md new file mode 100644 index 00000000..c088e8dd --- /dev/null +++ b/docs/en/usage/distribution/README.md @@ -0,0 +1,5 @@ +# Distribution + +The concept of the `Semo` distribution is based on building the entire product using `Semo`. For example, in projects with frontend and backend separation, the frontend is based on the `Semo` frontend scaffolding, the backend is based on the `Semo` backend scaffolding, and there is integration of underlying command-line infrastructure. This results in a high degree of completeness, suitable for direct deployment in production environments. It can be a SaaS-level product, used for sales demonstrations, private deployments, and other scenarios. It also involves related technologies such as container orchestration and code encryption. + +Unfortunately, as of now, there has not been any `Semo` distribution available. It is still in the conceptual stage, and we hope it will become available in the future. \ No newline at end of file diff --git a/docs/en/usage/integration/README.md b/docs/en/usage/integration/README.md new file mode 100644 index 00000000..e6036030 --- /dev/null +++ b/docs/en/usage/integration/README.md @@ -0,0 +1,49 @@ +# Project Integration + +Integrating with existing business projects was the original intention behind `Semo` development. If a project already has a custom and functional command-line tool, it should be carefully considered whether to switch to the `Semo` style. Fortunately, integrating with `Semo` is relatively simple. If the project lacks command-line infrastructure, it's recommended to give `Semo` a try. + +## Why Integrate + +- **Access to Command-line Infrastructure:** Every project has operations that are not suitable or feasible to perform in the backend. Through a command-line tool, interactions with the system and data become simpler. +- **Access to Scripting Infrastructure:** Some scripts need to be executed, and common requirements include naming, location, and how to interact with business or data. +- **Ability to Use Related Semo Plugins:** Configure and influence plugin behavior. +- **Access to a Business-related REPL Environment:** Invoke methods encapsulated in the project or interact with encapsulated infrastructure. + +## Project Integration Methods + +Not all features are necessarily needed; use them as needed. + +### 1. Add `Semo` as a Project Dependency + +Using `yarn` as an example: + +``` +yarn add @semo/cli +``` + +### 2. Initialize in the Project Root Directory + +``` +semo init [--typescript] +``` + +Determine whether the project is based on TypeScript. If so, include the `--typescript` parameter. The initialization process creates a configuration file `.semorc.yml` in the project's root directory and adds a `bin/semo` directory. In theory, this should not conflict with existing projects. + +### 3. Add Some Commands or Scripts + +``` +semo generate command xxx +semo generate script yyy +``` + +### 4. Define Project-specific Plugins + +Following the progressive development concept, if a plugin is only intended for use within the project, it can be considered part of the project code. Once it matures, it can easily be converted into an npm package for sharing with other projects. + +``` +semo generate plugin zzz +``` + +### 5. Inject Business Code into the REPL Environment + +Refer to "Plugin Development->Example 2: Implement hook_repl" to see how methods are injected into the REPL. Note that all methods can only be injected into the Semo object in the REPL variable space, which protects the REPL variable space. For business methods, import them and return them according to the format requirements of `hook_repl`. To make the methods effective, you need to handle the dependencies of the methods on the environment, such as database connections. \ No newline at end of file diff --git a/docs/en/usage/plugin/README.md b/docs/en/usage/plugin/README.md new file mode 100644 index 00000000..dcb03bf7 --- /dev/null +++ b/docs/en/usage/plugin/README.md @@ -0,0 +1,50 @@ +# Plugins + +In the section "Basics->Plugin Development," the method and considerations for plugin development have been introduced. Here, we mainly discuss why and when to develop plugins. + +## Business Plugins + +Firstly, without plugins, several built-in commands in `Semo` are not very useful. All the value needs to be unleashed through extending `Semo`, and plugins are the most important form of extension. The most common feature of plugins is defining commands, which is not surprising because `Semo` itself is a command and is designed as a command-line development framework. The key point here is that commands can be defined within plugins, and plugins, as independent Node modules, can be published to `npm` or a company's custom `registry`, allowing a command to be installed in multiple projects. + +It's challenging to ensure that a project is useful in all of the company's projects, but there may be intersections among different projects within the same business line. We can further standardize plugin names to delineate the scope of plugin applicability, for example: + +``` +semo-plugin-[company_identifier]-[business_line_identifier]-[purpose_identifier] +``` + +## Innovative Plugins + +Additionally, as mentioned in previous documentation, we can develop plugins that are not related to business attributes. As long as they are interesting and have ideas, they can be tried, such as: + +``` +semo-plugin-music-download +semo-plugin-video-download +semo-plugin-todolist +semo-plugin-puzzle-me +semo-plugin-convert-a-to-b +``` + +The above are just random names; in fact, these plugins do not exist yet. + +## Local Plugins + +Not all plugins need to be published to `npm`. We can develop many plugins known only to ourselves to meet our own needs. These plugins are generally placed in `~/.semo/node_modules`, allowing them to be called from anywhere in the current account. + +## Community Plugins + +If you are satisfied with your plugin work and want to share it with others, you can publish the plugin to `npm` and then tell others to use it. Of course, since `Semo` only acts as a command dispatcher, in most cases, you don't necessarily have to write such `npm` packages based on `Semo`, unless you are a fan of `Semo` ^_^. + +So, what community plugins are available now? The community is still in its infancy, and plugins are relatively scarce, including but not limited to the following: + +* **semo-plugin-application** - [Core] Defines a specification for adding subcommands to a Node project. +* **semo-plugin-script** - [Core] Defines a specification for scripts in a Node project. +* **semo-plugin-plugin** - [Core] Provides a global plugin management command-line tool for Semo. +* **semo-plugin-shell** - [Core] Provides a simple command-line environment to save keystrokes. +* **semo-plugin-hook** - [Core] Provides information related to hooks. +* **semo-plugin-ssh** - [Extension] Provides simple `SSH` account management functionality. +* **semo-plugin-read** - [Extension] Provides tools for converting URLs to `Markdown` and various other formats. + * **semo-plugin-read-extend-format-wechat** - This is an extension of the read plugin, providing functionality for editing WeChat Official Account articles online, to be used with read. + * ... There may be many related sub-plugins, which are not listed one by one. +* **semo-plugin-serve** - [Extension] Provides functionality for a simple `HTTP` server, similar to `serve`. +* **semo-plugin-sequelize** - [Extension] Integrates with `Sequelize` to provide database access capabilities. +* **semo-plugin-redis** - [Extension] Integrates with `Redis` to provide cache access capabilities. \ No newline at end of file diff --git a/docs/en/usage/solution/README.md b/docs/en/usage/solution/README.md new file mode 100644 index 00000000..2f14290f --- /dev/null +++ b/docs/en/usage/solution/README.md @@ -0,0 +1,21 @@ +# Solutions + +`Semo` solutions are first and foremost solutions tailored to specific scenarios. Secondly, they leverage some of `Semo`'s capabilities. If `Semo` is not used, then it is still a solution, unrelated to `Semo`. + +## Enterprise Solutions + +``` +- Frontend project scaffolding +- Backend project scaffolding +- Admin project scaffolding +- Mini-program development scaffolding +... +``` + +If combined with different types of businesses, such as e-commerce, finance, and so on, various variants can be formed. Even the same type can have different solutions. Therefore, in the end, we accumulate technical expertise that suits our own enterprise, enabling us to achieve standardization and technological accumulation in the enterprise development process. These scaffolding projects are designed to improve the consistency and efficiency of internal projects in an enterprise. They may not necessarily need to be open-source or meet the usage scenarios of other companies. However, internally within the enterprise, scaffolding can reduce redundant construction, improve project consistency, and save costs for future development and maintenance. + +## Open-Source Solutions + +Although solutions vary from one enterprise to another, there can still be open-source solutions. For example, [Ant Design's Scaffold Market](https://scaffold.ant.design/). Currently, there are no open-source scaffolding projects based on `Semo`, but hopefully, there will be in the future. + +Generally, we believe that an open-source solution would achieve better quality, including security, scalability, and functionality. \ No newline at end of file diff --git a/packages/cli/src/commands/generate.ts b/packages/cli/src/commands/generate.ts index 5a27227b..1518d3b0 100644 --- a/packages/cli/src/commands/generate.ts +++ b/packages/cli/src/commands/generate.ts @@ -3,7 +3,7 @@ import { Utils } from '@semo/core' export const plugin = 'semo' export const command = 'generate ' export const desc = 'Generate component sample code' -export const aliases = ['g'] +export const aliases = ['g', 'gen'] export const builder = function (yargs) { const argv: any = Utils.getInternalCache().get('argv') || {} diff --git a/packages/core/src/common/utils.ts b/packages/core/src/common/utils.ts index 29bcfff2..9fd210cb 100644 --- a/packages/core/src/common/utils.ts +++ b/packages/core/src/common/utils.ts @@ -149,7 +149,7 @@ interface IHookOption { const invokeHook = async function ( hook: any = null, options: IHookOption = { mode: 'assign' }, - argv: any = null + argv: any = null, ): Promise { const splitHookName = hook.split(':') let moduler, originModuler @@ -180,7 +180,7 @@ const invokeHook = async function ( exclude: [], opts: {}, }, - options + options, ) try { @@ -200,7 +200,7 @@ const invokeHook = async function ( { [scriptName]: path.resolve(argv.packageDirectory), }, - getAllPluginsMapping(argv) + getAllPluginsMapping(argv), ) : getAllPluginsMapping(argv) @@ -236,6 +236,7 @@ const invokeHook = async function ( for (let i = 0, length = Object.keys(plugins).length; i < length; i++) { let plugin = Object.keys(plugins)[i] + // Process include option if ( _.isArray(options.include) && options.include.length > 0 && @@ -244,6 +245,7 @@ const invokeHook = async function ( continue } + // Process exclude option if ( _.isArray(options.exclude) && options.exclude.length > 0 && @@ -277,7 +279,7 @@ const invokeHook = async function ( if ( hookDir && fileExistsSyncCache( - path.resolve(plugins[plugin], hookDir, 'index.js') + path.resolve(plugins[plugin], hookDir, 'index.js'), ) ) { pluginEntryPath = path.resolve(plugins[plugin], hookDir, 'index.js') @@ -373,7 +375,7 @@ const extendSubCommand = function ( command: string, moduleName: string, yargs: any, - basePath: string + basePath: string, ): void { let argv: any = cachedInstance.get('argv') || {} if (_.isEmpty(argv)) { @@ -403,7 +405,7 @@ const extendSubCommand = function ( argv['$config'] = {} if (command.plugin) { command.plugin = command.plugin.startsWith( - argv.scriptName + '-plugin-' + argv.scriptName + '-plugin-', ) ? command.plugin.substring(argv.scriptName + '-plugin-'.length) : command.plugin @@ -411,7 +413,7 @@ const extendSubCommand = function ( if (command.plugin && argv['$plugin']) { if (argv['$plugin'][command.plugin]) { argv['$config'] = formatRcOptions( - argv['$plugin'][command.plugin] || {} + argv['$plugin'][command.plugin] || {}, ) } else if ( argv['$plugin'][argv.scriptName + '-plugin-' + command.plugin] @@ -419,7 +421,7 @@ const extendSubCommand = function ( argv['$config'] = formatRcOptions( argv['$plugin'][ argv.scriptName + '-plugin-' + command.plugin - ] || {} + ] || {}, ) } } @@ -458,17 +460,17 @@ const extendSubCommand = function ( path.resolve( plugins[plugin], `${config.pluginConfigs[plugin].extendDir}/${moduleName}/src/commands`, - command - ) + command, + ), ) ) { yargs.commandDir( path.resolve( plugins[plugin], `${config.pluginConfigs[plugin].extendDir}/${moduleName}/src/commands`, - command + command, ), - opts + opts, ) } } @@ -482,17 +484,17 @@ const extendSubCommand = function ( path.resolve( process.cwd(), `${config.extendDir}/${moduleName}/src/commands`, - command - ) + command, + ), ) ) { yargs.commandDir( path.resolve( process.cwd(), `${config.extendDir}/${moduleName}/src/commands`, - command + command, ), - opts + opts, ) } } @@ -585,13 +587,13 @@ const getAllPluginsMapping = function (argv: any = {}): { noext: true, cwd: path.resolve( argv.packageDirectory, - argv.orgMode ? '../../' : '../' + argv.orgMode ? '../../' : '../', ), }).map(function (plugin): void { plugins[plugin] = path.resolve( argv.packageDirectory, argv.orgMode ? '../../' : '../', - plugin + plugin, ) }) @@ -611,13 +613,13 @@ const getAllPluginsMapping = function (argv: any = {}): { noext: true, cwd: path.resolve( argv.packageDirectory, - argv.orgMode ? '../../' : '../' + argv.orgMode ? '../../' : '../', ), }).map(function (plugin): void { plugins[plugin] = path.resolve( argv.packageDirectory, argv.orgMode ? '../../' : '../', - plugin + plugin, ) }) } @@ -629,14 +631,14 @@ const getAllPluginsMapping = function (argv: any = {}): { path.resolve( process.env.HOME, '.' + scriptName, - `.${scriptName}rc.yml` - ) + `.${scriptName}rc.yml`, + ), ) ) { // So home plugin directory will not be overridden by other places normally. plugins['.' + scriptName] = path.resolve( process.env.HOME, - '.' + scriptName + '.' + scriptName, ) } @@ -647,7 +649,7 @@ const getAllPluginsMapping = function (argv: any = {}): { process.env.HOME, `.${scriptName}`, 'home-plugin-cache', - 'node_modules' + 'node_modules', ), }).map(function (plugin): void { if (process.env.HOME) { @@ -656,7 +658,7 @@ const getAllPluginsMapping = function (argv: any = {}): { `.${scriptName}`, 'home-plugin-cache', 'node_modules', - plugin + plugin, ) } }) @@ -671,7 +673,7 @@ const getAllPluginsMapping = function (argv: any = {}): { process.env.HOME, `.${scriptName}`, 'node_modules', - plugin + plugin, ) } }) @@ -737,7 +739,7 @@ const getAllPluginsMapping = function (argv: any = {}): { extraPluginDirEnvName && process.env[extraPluginDirEnvName] && fileExistsSyncCache( - getAbsolutePath(process.env[extraPluginDirEnvName] as string) + getAbsolutePath(process.env[extraPluginDirEnvName] as string), ) ) { let envDir = getAbsolutePath(String(process.env[extraPluginDirEnvName])) @@ -821,8 +823,8 @@ const getApplicationConfig = function (opts: any = {}) { let scriptName = opts.scriptName ? opts.scriptName : argv && argv.scriptName - ? argv.scriptName - : 'semo' + ? argv.scriptName + : 'semo' argv = Object.assign(argv, opts, { scriptName }) let applicationConfig @@ -855,8 +857,8 @@ const getApplicationConfig = function (opts: any = {}) { applicationConfig.applicationDir = opts.cwd ? opts.cwd : configPath - ? path.dirname(configPath) - : process.cwd() + ? path.dirname(configPath) + : process.cwd() // Inject some core config, hard coded applicationConfig = Object.assign({}, applicationConfig, opts, { @@ -866,13 +868,12 @@ const getApplicationConfig = function (opts: any = {}) { // Load application rc, if same dir with core, it's a dup process, rare case. if ( fileExistsSyncCache( - path.resolve(applicationConfig.applicationDir, 'package.json') + path.resolve(applicationConfig.applicationDir, 'package.json'), ) ) { - let packageInfo = require(path.resolve( - applicationConfig.applicationDir, - 'package.json' - )) + let packageInfo = require( + path.resolve(applicationConfig.applicationDir, 'package.json'), + ) if (packageInfo.name) { applicationConfig.name = packageInfo.name @@ -888,7 +889,7 @@ const getApplicationConfig = function (opts: any = {}) { applicationConfig = Object.assign( {}, applicationConfig, - packageInfo[scriptName] + packageInfo[scriptName], ) } } @@ -1133,7 +1134,7 @@ const splitByChar = function (input: string, char: string) { const outputTable = function ( columns: string[][], caption: string = '', - borderOptions = {} + borderOptions = {}, ) { // table config const config = { @@ -1147,7 +1148,7 @@ const outputTable = function ( border: Object.assign( getBorderCharacters(`void`), { bodyJoin: `:` }, - borderOptions + borderOptions, ), } @@ -1181,7 +1182,7 @@ const parsePackageNames = function (input: string | string[]) { */ const getPackagePath = function ( pkg: string | undefined = undefined, - paths: any = [] + paths: any = [], ): any { const packagePath = findUp.sync('package.json', { cwd: pkg ? path.dirname(require.resolve(pkg, { paths })) : process.cwd(), @@ -1196,7 +1197,7 @@ const getPackagePath = function ( */ const loadPackageInfo = function ( pkg: string | undefined = undefined, - paths: any = [] + paths: any = [], ): any { const packagePath = getPackagePath(pkg, paths) return packagePath ? require(packagePath) : {} @@ -1329,7 +1330,7 @@ const launchDispatcher = async (opts: any = {}) => { 'argv', Object.assign(parsedArgv, { scriptName: opts.scriptName || 'semo', - }) + }), ) // set argv first time let appConfig = getApplicationConfig() @@ -1395,7 +1396,7 @@ const launchDispatcher = async (opts: any = {}) => { // Give command a plugin level config if (command.plugin) { command.plugin = command.plugin.startsWith( - appConfig.scriptName + '-plugin-' + appConfig.scriptName + '-plugin-', ) ? command.plugin.substring(appConfig.scriptName + '-plugin-'.length) : command.plugin @@ -1403,7 +1404,7 @@ const launchDispatcher = async (opts: any = {}) => { if (command.plugin && argv['$plugin']) { if (argv['$plugin'][command.plugin]) { argv['$config'] = formatRcOptions( - argv['$plugin'][command.plugin] || {} + argv['$plugin'][command.plugin] || {}, ) } else if ( argv['$plugin'][ @@ -1413,7 +1414,7 @@ const launchDispatcher = async (opts: any = {}) => { argv['$config'] = formatRcOptions( argv['$plugin'][ appConfig.scriptName + '-plugin-' + command.plugin - ] || {} + ] || {}, ) } } @@ -1431,15 +1432,16 @@ const launchDispatcher = async (opts: any = {}) => { return command.disabled === true ? false : command }, } + if ( !parsedArgv.disableCoreCommand && opts.packageDirectory && packageConfig.name !== scriptName ) { - // Load local commands + // Load core commands yargs.commandDir( path.resolve(opts.packageDirectory, appConfig.coreCommandDir), - yargsOpts + yargsOpts, ) } @@ -1450,15 +1452,18 @@ const launchDispatcher = async (opts: any = {}) => { config.pluginConfigs[plugin] && config.pluginConfigs[plugin].commandDir && fileExistsSyncCache( - path.resolve(plugins[plugin], config.pluginConfigs[plugin].commandDir) + path.resolve( + plugins[plugin], + config.pluginConfigs[plugin].commandDir, + ), ) ) { yargs.commandDir( path.resolve( plugins[plugin], - config.pluginConfigs[plugin].commandDir + config.pluginConfigs[plugin].commandDir, ), - yargsOpts + yargsOpts, ) } }) @@ -1471,7 +1476,7 @@ const launchDispatcher = async (opts: any = {}) => { ) { yargs.commandDir( path.resolve(process.cwd(), appConfig.commandDir), - yargsOpts + yargsOpts, ) } @@ -1527,7 +1532,7 @@ const launchDispatcher = async (opts: any = {}) => { ) { debugCore('Core hook before_command triggered') let beforeHooks = await invokeHook( - `${scriptName}:before_command` + `${scriptName}:before_command`, ) Object.keys(beforeHooks).map(function (hook) { beforeHooks[hook](parsedArgv, yargs) @@ -1560,7 +1565,6 @@ const launchDispatcher = async (opts: any = {}) => { }) yargs.completion('completion', 'Generate completion script') } else { - // @ts-ignore, @types/yargs type def not correct yargs.completion('completion', false) } } @@ -1580,7 +1584,7 @@ const launchDispatcher = async (opts: any = {}) => { }) } - if (!parsedArgv.hideEpilog) { + if (!parsedArgv.hideEpilog && !parsedArgv.disableCoreCommand) { yargs.hide('hide-epilog').option('hide-epilog', { describe: 'Hide epilog.', }) @@ -1601,7 +1605,7 @@ const launchDispatcher = async (opts: any = {}) => { } return 'Find more information at https://semo.js.org' - })(parsedArgv.setEpilog) + })(parsedArgv.setEpilog), ) } @@ -1650,18 +1654,20 @@ const launchDispatcher = async (opts: any = {}) => { } catch (e) {} } - if (parsedArgv._[0] !== 'completion') { + if (parsedArgv._[0] !== 'completion' && !parsedArgv.disableCoreCommand) { yargs.command( '$0', 'Execute a Semo style command file', defaultCommand.builder, - defaultCommand.handler + defaultCommand.handler, ) } - yargs.command('version', 'Show version number', () => { - console.log(pkg.version) - }) + if (!parsedArgv.disableCoreCommand) { + yargs.command('version', 'Show version number', () => { + console.log(pkg.version) + }) + } // eslint-disable-next-line yargs @@ -1674,19 +1680,6 @@ const launchDispatcher = async (opts: any = {}) => { 'sort-commands': true, 'populate--': true, }) - .example([ - ['$0 run hello-world', 'Run a remote plugin command.'], - [ - '$0 run --with project-templates — create PROJECT_NAME -T', - 'Clone project template as a starter.', - ], - [ - '$0 repl --require lodash:_', - 'Start Semo repl and inject lodash object to _.', - ], - ['$0 generate command test', 'Generate command template.'], - ['$0 clean all', 'Clean all cache files and installed npm packages.'], - ]) .fail(false) // .onFinishCommand(async (hook) => { // if (hook !== false) { @@ -1726,6 +1719,21 @@ const launchDispatcher = async (opts: any = {}) => { // } // process.exit(0); // } + if (!parsedArgv.disableCoreCommand) { + yargs.example([ + ['$0 run hello-world', 'Run a remote plugin command.'], + [ + '$0 run --with project-templates — create PROJECT_NAME -T', + 'Clone project template as a starter.', + ], + [ + '$0 repl --require lodash:_', + 'Start Semo repl and inject lodash object to _.', + ], + ['$0 generate command test', 'Generate command template.'], + ['$0 clean all', 'Clean all cache files and installed npm packages.'], + ]) + } } catch (e) { if (!e.name || e.name !== 'YError') { error(e.stack) @@ -1796,8 +1804,8 @@ const installPackage = (name, location = '', home = true, force = false) => { if (force) { exec( `npm install ${nameArray.join( - ' ' - )} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links` + ' ', + )} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links`, ) } @@ -1807,7 +1815,7 @@ const installPackage = (name, location = '', home = true, force = false) => { } catch (err) { if (err.code == 'MODULE_NOT_FOUND') { exec( - `npm install ${pkg} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links` + `npm install ${pkg} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links`, ) } } @@ -1831,8 +1839,8 @@ const uninstallPackage = (name, location = '', home = true) => { exec( `npm uninstall ${nameArray.join( - ' ' - )} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links` + ' ', + )} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links`, ) } @@ -1866,7 +1874,7 @@ const importPackage = (name, location = '', home = true, force = false) => { if (force) { exec( - `npm install ${name} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links` + `npm install ${name} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links`, ) } @@ -1876,7 +1884,7 @@ const importPackage = (name, location = '', home = true, force = false) => { } catch (err) { if (err.code == 'MODULE_NOT_FOUND') { exec( - `npm install ${name} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links` + `npm install ${name} --prefix ${downloadDir} --no-package-lock --no-audit --no-fund --no-bin-links`, ) try { pkgPath = require.resolve(name, { paths: [downloadDir] }) @@ -1917,8 +1925,8 @@ const pluginConfig = (key: string, defaultValue: any = undefined) => { return !_.isNull(argv[key]) && !_.isUndefined(argv[key]) ? argv[key] : !_.isNull(argv.$config[key]) && !_.isUndefined(argv.$config[key]) - ? argv.$config[key] - : _.get(argv.$config, key, defaultValue) + ? argv.$config[key] + : _.get(argv.$config, key, defaultValue) } /** @@ -1992,7 +2000,7 @@ const run = async (func, getPath = '', ...args) => { */ const extendConfig = ( extendRcPath: string[] | string, - prefix: any = undefined + prefix: any = undefined, ) => { let argv: any = getInternalCache().get('argv') || {} @@ -2027,7 +2035,7 @@ const extendConfig = ( const nodeEnv = getNodeEnv(argv) let extendRcEnvPath = path.resolve( path.dirname(rcPath), - `${path.basename(rcPath, '.yml')}.${nodeEnv}.yml` + `${path.basename(rcPath, '.yml')}.${nodeEnv}.yml`, ) if (extendRcEnvPath && fileExistsSyncCache(extendRcEnvPath)) { try { @@ -2063,7 +2071,7 @@ const extendConfig = ( */ const consoleReader = ( content: string, - opts: { plugin?: string; identifier?: string; tmpPathOnly?: boolean } = {} + opts: { plugin?: string; identifier?: string; tmpPathOnly?: boolean } = {}, ) => { const argv: any = getInternalCache().get('argv') const scriptName = argv && argv.scriptName ? argv.scriptName : 'semo' @@ -2076,7 +2084,7 @@ const consoleReader = ( process.env.HOME, `.${scriptName}/cache`, opts.plugin, - md5(opts.identifier) + md5(opts.identifier), ) fs.ensureDirSync(path.dirname(tmpPath)) fs.writeFileSync(tmpPath, content) @@ -2104,7 +2112,7 @@ const consoleReader = ( const clearConsole = () => { process.stdout.isTTY && process.stdout.write( - process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H' + process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H', ) }