Servant generators
Quick references: Command line, Node API, servant.json, dev-server
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"
npx
3. Installed and init with 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
.
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.
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.
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.
import { api } from "@servant/servant-generators";
api.saver("path/to/output", results).then((list: GeneratorSaverResults) => {
//some stuff
});
Generator
ink view This is view for rendering generator using ink. This method internally run api.runner
method.
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
/>;
Generators
ink view This is view for rendering generators list and select using ink. This method internally run api.loader
method.
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
/>;
Generated
ink view This is view for rendering save results using ink. This method internally run api.saver
method.
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
/>;
Main
ink view This is view used for composing all previous components. Loads, select, runs and save generators results. All in one view.
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
.
manifest.json
Structure of {
"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
index.ts
entry file
Structure of 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.
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");
};
condition
for questions?
How to use 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
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.
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 |
---|---|