npm package: @servant/servant-generators
Description: Generator core for servant
Author: Stanislav Hacker
Latest version: 1.0.11

# ![Servant][logo] Servant generators

Quick references: **[Command line][4]**, **[Node API][3]**, **[servant.json][2]**, **[dev-server][5]**

## What is it?

Servants generators is a runner tool that is used to run custom or prepared generator
code to generate, update or change some components, prepared piece of codes and
another files into specified folder. Generator can create files, messages and
data by answering questions that are generated or prepared by generator or another
generators. These generators are possible to extend and use generator in generator.

### Installation

#### 1. Globally installed

You need the latest version of nodejs and then run:

> `npm install @servant/servant-generators -g`

Then you can run in current directory:

> `sg`

> **Caveats**
>
> Installing globally is not recommended.

#### 2. Locally installed

You need the latest version of nodejs and then run:

> `npm install @servant/servant-generators`

Then you can run in current directory:

> `"node_modules/.bin/sg"`

#### 3. Installed and init with `npx`

You need the latest version of nodejs and then run

> `npx @servant/servant-generators`

This command run generators runtime.

### Command line API

Simple generators command line api provide simple way how to run generators. You need to be in folder with
generators or specified directories with generator and also output folder for generated content. For examples
we will count with globally installed module.

**Run without parameters**

> `sg`

This simple commands load generators from current folder or `node_modules` folder and generate
outputs into `./.output` folder.

**Run with generators folders**

> `sg /pth/to/dir1 /pth/to/dir2`
>
> `sg --dir=/pth/to/dir1 --dir=/pth/to/dir2`
>
> `sg /pth/to/dir1,/pth/to/dir2`
>
> `sg --dir=/pth/to/dir1,/pth/to/dir2`

This command load generators from current folder, `node_modules` folder and also `/pth/to/dir1` and `/pth/to/dir2`
folders. Outputs will be generated into `./.output` folder.

**Run with output folder**

> `sg /pth/to/dir1 /pth/to/dir2 --output=/pth/output`

This command load generators from current folder, `node_modules` folder and also `/pth/to/dir1` and `/pth/to/dir2`
folders. Outputs will be generated into `/pth/output` folder.

**Run with predefined generator**

> `sg /pth/to/dir1 /pth/to/dir2 --generator=generator-name`

This command load generators from current folder, `node_modules` folder and also `/pth/to/dir1` and `/pth/to/dir2`
folders and automatically run generator with name `generator-name`. If not found, there will be error reported.
Outputs will be generated into `./.output` folder.

**Run with debug flag**

> `sg /pth/to/dir1 /pth/to/dir2 --debug`

This flag is used for reporting more info for errors for better debugging.

### Node API + Ink React components

Main nodejs api is hidden in `api` property in module.

## .loader method

#### `api.loader(directories: Array<string>): Promise<GeneratorsList>`

**This method is used to loading generators from defined directories.**

Method get list of directories and return `GeneratorsList` that contains list of all loaded generators and list
of errors that occurred during loading. Result of this method is provided to next one, that is `runner` or `multiRunner`.

```typescript
import { api } from "@servant/servant-generators";

api.loader(["path/to/generators1", "path/to/generators2"]).then((list: GeneratorsList) => {
	//some stuff
});
```

## .runner method

#### `api.runner<T, D>(previous: GeneratorResults<D>, input: GeneratorInput, generator: Generator<T, D>, props?: T): Promise<GeneratorRunnerResults<D>>`

**This method is used to run one generator and get results from it.**

Method get previous results of previous generator as parameter `previous`. If you run first generator in row, it's possible to
use define constant `GeneratorEmptyResults` that can be provided.

Next parameter is `input` that is basically loaded generator from `loaded` method, that contains questions, answers, messages and
manifest file definition.

Last required parameter is `generator` that is function of generator to run and collect results.

Optional properties parameter `props` is used to provide some data into generator and used tma in generator function body.

```typescript
import { api, GeneratorEmptyResults, Generator } from "@servant/servant-generators";

const generator: Generator = (data, fn, next, props) => {
	next("seccess");
};

api.runner(
	GeneratorEmptyResults,
	{ manifest, answers: [], questions: [], messages: [] },
	generator,
	{}
).then((list: GeneratorRunnerResults<D>) => {
	//some stuff
});
```

## .multiRunner method

#### `api.multiRunner<T, D>(previous: GeneratorResults<D>, loaded: GeneratorLoaded<T, D>, props?: T): Promise<GeneratorRunnerResults<D>>`

**This method is used to run one generator tha use another generators (by using use property in manifest) and get results from all.**

Method get previous results of previous generator as parameter `previous`. If you run first generator in row, it's possible to
use define constant `GeneratorEmptyResults` that can be provided.

Next parameter is `loaded` that is loaded generator from `loaded` method, that contains questions, answers, messages and
manifest file definition.

Optional properties parameter `props` is used to provide some data into generator and used tma in generator function body.

```typescript
import { api, GeneratorEmptyResults } from "@servant/servant-generators";

api.runner(GeneratorEmptyResults, loaded, {}).then((list: GeneratorRunnerResults<D>) => {
	//some stuff
});
```

## .saver method

#### `api.saver<D>(into: string | undefined, results: GeneratorResults<D>): Promise<GeneratorSaverResults>`

**This method is used to save all generated files and its content physical on disk by using result from `runner` and `multiRunner`**

Parameter `into` used to set directory where generator generate content. This must be existing directory. Its possible
to use also `results.output` property, that can be set by generator code inside.

Parameter `results` needs to get results that was return from `runner` or `multiRunner` functions.

```typescript
import { api } from "@servant/servant-generators";

api.saver("path/to/output", results).then((list: GeneratorSaverResults) => {
	//some stuff
});
```

## ink view `Generator`

**This is view for rendering generator using ink.** This method internally run `api.runner` method.

```typescript jsx
import { view, GeneratorEmptyResults } from "@servant/servant-generators";

<view.Generator
	previous={GeneratorEmptyResults} //OPTIONAL: previous generator results
	columns={[10, 20]} //OPTIONAL: sizes of columns in chars
	config={config} //config of generator
	manifest={manifest} //manifest file of generator
	generator={generator} //main function of generator
	use={use} //all another used generators
	props={props} //OPTIONAL: Custom props
	onDone={(results) => {}} //OPTIONAL: on done callbkac with results and helpers
/>;
```

## ink view `Generators`

**This is view for rendering generators list and select using ink.** This method internally run `api.loader` method.

```typescript jsx
import { view, useGeneratorsList } from "@servant/servant-generators";

const { directories, status, list } = useGeneratorsList(["path/to/gen1", "path/to/gen2"]);

<view.Generators
	list={list} //list of all loaded generators
	status={status} //status of loading
	directories={directories} //all directories used for loading
	debug={false} //OPTIONAL: debug mode
	onSelect={(gen) => {}} // on generator select or "exit" string
	onErrors={(errors) => {}} //OPTIONAL: list of all errors occured during loading
/>;
```

## ink view `Generated`

**This is view for rendering save results using ink.** This method internally run `api.saver` method.

```typescript jsx
import { view } from "@servant/servant-generators";

<view.Generated
	debug={false} //OPTIONAL: debug mode
	loaded={loadedGenerator} //root loaded generator that creates results
	results={results} //results of root generator
	onDone={(results) => {}} //OPTIONAL:save done and provide save results
/>;
```

## ink view `Main`

**This is view used for composing all previous components.** Loads, select, runs and save generators results. All in one view.

```typescript jsx
import { view } from "@servant/servant-generators";

<view.Main
	dirs={["/path/to/generators"]} //list of directories with generators
	output="/path/to/output" //OPTIONAL: Directory where ouput will be generated, optional because can be set inside generator
	generator="generator-name" //OPTIONAL: Generator name tha will be run instantly if exists, selection will be skiped
	debug={false} //OPTIONAL: debug mode
	props={{}} //OPTIONAL: Custom props
	onDone={(results) => {}} //OPTIONAL: on done callback that provide saved results
	onErrors={(errors) => {}} //OPTIONAL: on errors callback that provide list of errors
	preparedQuestions={[]} //OPTIONAL:
/>;
```

## Structure of generator, manifest and usage

Generator program can be written as **typescript** or **javascript** and need to have 2 required files with specified
structure and content.

     .
     ├── generator-folder          # Folder with generator
     │   ├── manifest.json         # Manifest file with info about generator and entry data
     │   ├── index.js (index.ts)   # File that is named in manifest.json and is main entry file of generator
     │   └── ...                   # OPTIONAL: Other files that are imported by entry file
     └── ...

So there needs to be at lest 2 file in generator folder. 1) **`manifest.json`** that contains generator info and some run data
that are used after generator loaded and 2) **`index.js`** or **`index.ts`** file that is used as entry and named in **`manifest.json`**.

#### Structure of **`manifest.json`**

```json
{
	"engine": "servant-generator",
	"name": "my-app",
	"description": "My App",
	"entry": "index.js",
	"use": ["local:my-app-2"],
	"compiled": false
}
```

#### `"engine": "servant-generator"` **required**

This is a special property that is used to determine running engine. For now only valid value is
**"servant-generator"**. Every manifest, that has no this particular engine will not be marked
as generator compatible manifest and will be ignored. This is because manifest json are used for
more applications.

#### `"name": string` **required**

Name of generator that will be used in list of generators. Must be unique so its recommended to use
prefixes and other stuff that can identify your generator if you choose to publish it.

#### `"description": string` **required**

Description of simple usage for generator. It's recommended to summarize base function of generator
for user that want to use it.

#### `"entry": string`

This is main file of generator that will be loaded by Servant. Generator can be written in **javascript** or
**typescript** sou you can use js or ts file in entry.

**Default:** `./index.js`

#### `"use": Array<string>`

List of generators that are will be used and run before this one. It's good for composing more generators into one
bigger. For now oly local generators are supported.

**Local generators `local:` prefix**

Local generators mean that's generator needs to be loaded with generator that use this generator. Will be matched
by name and throw error if loader can not found this generator in list. Basically if you have **dir1/generator1** which
use **dir2/generator2** by directive `"use": ["local:generator2"]` you must load generators from directory **dir1** and
from directory **dir2**. If not, loaded generates error that is not able to load generator1.

**Default:** `[]`

**Examples:**

-   `"use": ["local:my-app3", "local:my-app4"]`

#### `"compiled": boolean`

This flag is optional and mark generator as already build, so Servant skip rebuilding of this generator and run it
directly. Its boost load time and recommended to use this flag when publish generator into npm registry or some
similar registry for packages.

**Default:** `false`

#### Structure of **`index.ts`** entry file

This is an entry file of generator and is used for setup generator by config and also implement core of
generator that can create, update or delete files and other things to create what you need.

```typescript
import { Generator, GeneratorConfig } from "@servant/servant-generators";

type CustomData = {
	//custom data object
};

//generator config object
export const config: GeneratorConfig = {
	//list of all questions that you need to get from user, generator will start asking and after all
	//responses will be answered
	questions: [
		{
			//type of question, can be "string" or "select"
			type: "string",
			//id of question, will be used for creating answers, must be unique
			id: "filename",
			//OPTIONAL: category of question, used for better visual experience, determine category of question
			category: "packagejson",
			//human redable question for user
			question: "File name to save?",
		},
		{
			//type of question, can be "string" or "select"
			type: "string",
			//id of question, will be used for creating answers, must be unique
			id: "version",
			//OPTIONAL: category of question, used for better visual experience, determine category of question
			category: "packagejson",
			//human redable question for user
			question: "What version to use?",
			//OPTIONAL: default preselected value of answer, will be used when user do not enter anything and proceesd to next question
			defaultValue: { label: "1.0.0", value: () => "1.0.0" },
			//OPTIONAL: some hits or examples for question answer
			tip: "Write version in semantic version system.",
			//OPTIONAL: This is unique key that can be used in generator, if ommited, id is used
			key: "version",
			//OPTIONAL: Conditions object that is used to determine when show question to user, will be described later in this file
			condition: {},
		},
		{
			//type of question, can be "string" or "select"
			type: "select",
			//id of question, will be used for creating answers, must be unique
			id: "type",
			//OPTIONAL: category of question, used for better visual experience, determine category of question
			category: "packagejson",
			//human redable question for user
			question: "What type will be used?",
			//OPTIONAL: some hits or examples for question answer
			tip: "Select type of project. This is similar to type package.json property.",
			//OPTIONAL: default preselected value of answer, will be used when user do not enter anything and proceesd to next question
			defaultValue: { label: "module", value: () => "module" },
			//List of all possible values for select
			values: [
				{ label: "module", value: () => "module" },
				{ label: "es5", value: () => "es5" },
				{ label: "es2020", value: () => "es2020" },
			],
			//OPTIONAL: Set limit of values that are displayd on screen, default 10
			limit: 10,
			//OPTIONAL: This is unique key that can be used in generator, if ommited, id is used
			key: "type",
			//OPTIONAL: Conditions object that is used to determine when show question to user, will be described later in this file
			condition: {},
		},
	],
	//list of all pre answer questions, use this to provide already answered questions and
	//user dont need to fill it again
	answers: [
		{
			//id of question to answer
			id: "filename",
			//values of answer
			value: "package.json",
		},
	],
	//list of all messages that will be showed when generator finish work
	messages: [
		{
			//id of message, must be unique
			id: "message1",
			//type of message, can by "info" | "warning" | "error" | "success"
			type: "success",
			//OPTIONAL: Title of message, will be show diffrent way than text
			title: "Created",
			//Text of message to show to user
			text: "File was successfully created.",
			//OPTIONAL: Conditions object that is used to determine when show message to user, will be described later in this file
			condition: {},
		},
	],
	//map of categories and category color
	categories: {
		//[key] is category name used in question.category property
		//[value] is category color compatible with cli colors
		packagejson: "yellow",
	},
};

//generator function
// Inti generator there is provided handlers for answer / question manipulation and also
// handlers for file and directories creating, updating and removing. Its possible to also set
// custom data or add new messages and answers / questions
//Function next => used for call to end generator resultion and move to next one or end of generation.
// This function get status as "success" or "error"
//Props => These are props that are provided into generator runner or into Main component of exterted package
export const generator: Generator<unknown, CustomData> = ({ getAnswer }, fn, next, props) => {
	const filename = getAnswer("filename")?.value;
	const version = getAnswer("version")?.value;
	const type = getAnswer("type")?.value;

	fn.createFile(
		"./" + filename,
		JSON.stringify({
			name: "my-cool-project",
			version,
			type,
		})
	);

	next("success");
};
```

#### How to use `condition` for questions?

Every question can also have defined conditions. This is object, that contains rules, when question will be server when
condition is meet. If not, this question was never answer and generator skip it. This is good if more complex generator
is created.

**Basic setup of AND band OR condition**

```typescript
export const config: GeneratorConfig = {
	questions: [
		{
			//REST PROPS OF QUESTION OBJECT
			//...
			//This condition will be meet when target = "web" and entry = "true"
			condition: {
				and: {
					target: "web",
					entry: "true",
				},
			},
		},
		{
			//REST PROPS OF QUESTION OBJECT
			//...
			//This condition will be meet when target = "web" or entry = "true"
			condition: {
				or: {
					target: "web",
					entry: "true",
				},
			},
		},
		{
			//REST PROPS OF QUESTION OBJECT
			//...
			//This condition will be meet when (target = "web" and entry = "true") AND (force = "true" or skip = "true")
			condition: {
				and: {
					target: "web",
					entry: "true",
				},
				//AND => there is automatically AND operator
				or: {
					force: "true",
					skip: "true",
				},
			},
		},
	],
	//...
};
```

**Complex setup of composed conditions**

If key in `or` or `and` condition is object, its behave automatically as inner condition. So conditions can be nested.

```typescript
export const config: GeneratorConfig = {
	questions: [
		{
			//REST PROPS OF QUESTION OBJECT
			//...
			//This condition will be meet when (target = "web" and entry = "true") OR (target = "page" and entry = "true")
			condition: {
				or: {
					//name of condition, its doesnt matter, its only for better understanding of inner condition
					target_is_web: {
						and: {
							target: "web",
							entry: "true",
						},
					},
					//name of condition, its doesnt matter, its only for better understanding of inner condition
					target_is_page: {
						and: {
							target: "page",
							entry: "true",
						},
					},
				},
			},
		},
	],
	//...
};
```

After setup this two files in directory, you can run `sg /path/to/root/dir` where directory with generator is and
see that servant generators will run and give to a list of available generators.

### Donate me

| QR                                                                                                           | Paypal                                                                                                                                                              |
| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![](https://gitlab.com/uploads/-/system/personal_snippet/1929487/66399a49a06fa8eb9a0758b8673758c5/qr_sh.png) | [![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DUT8W343NVGQQ&source=url) |

### License

[Licensed under MIT][license]

[logo]: https://gitlab.com/stanislavhacker/servant/raw/master/logo.png
[1]: https://docs.npmjs.com/files/package.json
[2]: https://gitlab.com/stanislavhacker/servant/blob/master/servant/doc/servant.json.md
[3]: https://gitlab.com/stanislavhacker/servant/blob/master/servant/doc/servant.nodejs.md
[4]: https://gitlab.com/stanislavhacker/servant/blob/master/servant-cli/doc/servant.clia.md
[5]: https://gitlab.com/stanislavhacker/servant/blob/master/servant-development/doc/servant.devserver.md
[license]: https://gitlab.com/stanislavhacker/servant-generators/blob/master/LICENSE