genobi
v0.7.0
Published
Flat text file generator and modifier configured with your templates, prompt input, and instructions.
Maintainers
Readme
Genobi
Help me Obi-Wan Genobi, you're our only hope.
Genobi is a flexible, customizable file generator and modifier tool designed to streamline your development workflow. It allows you to generate and modify text files through templates, prompts, and operations configured to your specific needs.
Why Genobi?
I like to work smarter. I like tools that make my life easier. I started out using plopjs, but then, I wanted to do more and differently.
To put it simply, sometimes, I am a Burger King, and I like to have it my way.
TODO to major release version:
- [x] Logs are improved
- [x] Operation for multi-gen
- [x] Validation for api functions is implemented
- [ ] Custom operations are implemented
- [ ] File path validation
- [ ] More examples
- Genobi
Installation
Install Genobi globally:
npm install -g genobiWhy would I install Genobi globally?
When you run Genobi, it will look in the current directory for the Genobi config file, and if it doesn't find it there, it will traverse up through parent directories to find one. That means that you can store your Genobi config and templates outside of your project and use them in any other project.
Install as a dev dependency in your project:
npm install -D genobiUsage
Create a Config File
Create a genobi.config.js file in the root of your project. The extension can be any of: js, ts, mjs, or cjs.
Run Genobi
pnpm genobi [generator] [options]
genobi [generator] [options] // globalArgs
generator: Optional ID of the generator to use
genobi react-componentOptions
-d, --destination <path>: Root directory for generating files (relative paths will resolve from here)-v, --verbose: Progress information logs-d, --debug: Technical detail logs
Configuration
This file exports a function that receives the Genobi API as its parameter:
Config API
The Genobi API provides the following methods:
| Method | Parameters | Return Type | Description |
|--------------------------|--------------------------------------------------------|-----------------------------------------------|--------------------------------------------------------------|
| setConfigFilePath | (configFilePath: string) | void | Sets the path to the config file |
| getConfigFilePath | () | string | Returns the current config file path |
| setDestinationBasePath | (destinationDirPath: string) | void | Sets the base directory for generating files |
| getDestinationBasePath | () | string | Returns the base directory for generating files |
| setSelectionPrompt | (message: string) | void | Sets the prompt message displayed during generator selection |
| getSelectionPrompt | () | string | Returns the current prompt message |
| addGenerator | (id: string, config: GeneratorConfig) | void | Adds a new generator to the configuration |
| getGenerator | (generatorId: string) | GeneratorConfig | Returns a specific generator by ID |
| getGenerators | () | Record<string, GeneratorConfig> | Returns all registered generators |
| addHelper* | (name: string, helper: HelperDelegate) | void | Adds a custom Handlebars helper |
| getHelper | (name: string) | HelperDelegate | Returns a specific helper by name |
| getHelpers | () | Record<string, HelperDelegate> | Returns all registered helpers |
| addPartial* | (name: string, partial: Template\| TemplateDelegate) | void | Adds a custom Handlebars template partial |
| addPartialFromFile | (name: string, partialFilePath:string) | void | Adds a custom Handlebars template partial from file |
| getPartial | (name: string) | Template\| TemplateDelegate | Returns a specific partial by name |
| getPartials | () | Record<string, Template\| TemplateDelegate> | Returns all registered partials |
Note: Handlebars helpers and partials docs can be found on their website.
// genobi.config.js
export default (genobi) => {
genobi.setDestinationBasePath("src/")
genobi.addGenerator("react-component", {
description: "React component",
prompts: [
{
type: "input",
name: "name",
message: "What is the name of this component?"
}
],
operations: [
{
type: "create",
filePath: "src/components/{{kebabCase name}}/{{kebabCase name}}.tsx",
templateStr: `export function {{pascalCase name}}() {
return (
<div className="{{kebabCase name}}" />
);
}`
},
{
type: "append",
filePath: "src/css/components.css",
templateStr: `@import "../components/{{kebabCase name}}/{{kebabCase name}}.css";`
}
]
});
};Generators
Generators are defined with the following structure:
| Property | Type | Description | Required |
|---------------|----------------------|-----------------------------------------------------------------------------------|----------|
| description | string | Human-readable description of the generator | Yes |
| prompts | DistinctQuestion[] | Array of Inquirer.js question objects | No |
| operations | Operation[] | Array of operations to perform | Yes |
Example:
const reactGenerator = {
description: "React component",
prompts: [
{
type: "input",
name: "name",
message: "Component name?",
default: "Button"
}
],
operations: [
// Create component file
{
type: "create",
filePath: "src/components/{{kebabCase name}}.tsx",
templateStr: `export const {{pascalCase name}} = () => {\n return <div>{{name}}</div>;\n};\n`
}
]
}Operations
Genobi supports several operation types:
Create Operation
Creates a new file.
| Property | Type | Description | Default |
|--------------------|--------------------------|-----------------------------------------------|------------|
| type | string | Must be "create" | required |
| filePath | string | Handlebars template for output file path | required |
| templateStr | string | Handlebars template string for file content | - |
| templateFilePath | string | Path to a Handlebars template file | - |
| skipIfExists | boolean | Skip operation if file exists | false |
| overwrite | boolean | Overwrite file if it exists | false |
| data | Record<string, any> | Additional data for templates | {} |
| skip | (data: any) => boolean | Function to determine if op should be skipped | - |
| haltOnError | boolean | Whether to stop execution on error | true |
Note: Either
templateStrortemplateFilePathmust be provided.
CreateAll Operation
Creates multiple files matching a glob pattern.
| Property | Type | Description | Default |
|---------------------|--------------------------|-----------------------------------------------------------|------------|
| type | string | Must be "createAll" | required |
| destinationPath | string | Handlebars template for destination directory | required |
| templateFilesGlob | string | Glob pattern to match template files | required |
| templateBasePath | string | Section of template path to exclude when generating files | - |
| data | Record<string, any> | Additional data for templates | {} |
| skipIfExists | boolean | Skip if file already exists | false |
| overwrite | boolean | Overwrite files if they exist | false |
| skip | (data: any) => boolean | Function to determine if op should be skipped | - |
| haltOnError | boolean | Whether to stop execution on error | true |
| verbose | boolean | Log each time a file is created | true |
ForMany Operation
Runs a generator multiple times with different inputs.
| Property | Type | Description | Default |
|-----------------|----------------------------------------------------------------------|---------------------------------------------------|---------------------------------------------------------|
| type | string | Must be "forMany" | required |
| generatorId | string | ID of the generator to run multiple times | required |
| items | any[] | ((data: Record<string, any>) => any[]) | Array of data objects or function that returns an array | required |
| transformItem | (item: any, index: number, parentData: Record<string, any>) => any | Function to transform each item before processing | - |
| data | Record<string, any> | Additional data for templates | {} |
| skip | (data: any) => boolean | Function to determine if op should be skipped | - |
| haltOnError | boolean | Whether to stop execution on error | true |
Example:
const forManyOperation = {
type: "forMany",
generatorId: "react-component",
items: (data) => {
return data.componentTypes.map(component => ({
name: component
}));
}
}Append Operation
Appends content to an existing file.
| Property | Type | Description | Default |
|--------------------|--------------------------|----------------------------------------------------------|------------|
| type | string | "append" | required |
| filePath | string | Path to the file to append to | required |
| templateStr | string | Handlebars template string for content to append | - |
| templateFilePath | string | Path to a Handlebars template file for content to append | - |
| pattern | string \| RegExp | Pattern to find where to append content | - |
| separator | string | String to insert between existing and new content | "\n" |
| unique | boolean | Skip if content already exists in file | true |
| data | Record<string, any> | Additional data for templates | {} |
| skip | (data: any) => boolean | Function to determine if op should be skipped | - |
| haltOnError | boolean | Whether to stop execution on error | true |
Note: Either
templateStrortemplateFilePathmust be provided.
Prepend Operation
Prepends content to an existing file.
| Property | Type | Description | Default |
|--------------------|--------------------------|-----------------------------------------------------------|------------|
| type | string | "prepend" | required |
| filePath | string | Path to the file to prepend to | required |
| templateStr | string | Handlebars template string for content to prepend | - |
| templateFilePath | string | Path to a Handlebars template file for content to prepend | - |
| pattern | string \| RegExp | Pattern to find where to prepend content | - |
| separator | string | String to insert between new and existing content | "\n" |
| unique | boolean | Skip if content already exists in file | true |
| data | Record<string, any> | Additional data for templates | {} |
| skip | (data: any) => boolean | Function to determine if op should be skipped | - |
| haltOnError | boolean | Whether to stop execution on error | true |
Note: Either
templateStrortemplateFilePathmust be provided.
Custom Helpers
You can add your own Handlebars helpers:
export default (genobi) => {
genobi.addHelper("awesomize", (str) => {
return `${str} is awesome!`;
});
}Built-in Handlebars Helpers
Genobi includes several helpful string transformation helpers:
Basic String Transformers
camelCase: Converts string to camelCasesnakeCase: Converts string to snake_casekebabCase(alias:dashCase): Converts string to kebab-casedotCase: Converts string to dot.casepascalCase(alias:properCase): Converts string to PascalCasepathCase: Converts string to path/casescreamingSnakeCase(alias:constantCase): Converts string to SCREAMING_SNAKE_CASEsentenceCase: Converts string to Sentence casetitleCase: Converts string to Title CaselowerCase: Converts string to lowercaseupperCase: Converts string to UPPERCASE
String Helpers with Additional Args
| Helper | Arguments | Description | Example |
|-----------------|----------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------|
| truncate | (str, limit, suffix) | Trims string to a maximum length with optional suffix (default: "...") | {{truncate "Hello world" 5}} → "Hello..." |
| truncateWords | (str, wordLimit, suffix) | Trims string to a maximum number of words with optional suffix (default: "...") | {{truncateWords "Hello beautiful world" 2}} → "Hello beautiful..." |
| ellipsis | (str, limit) | Adds an ellipsis to limited text | {{ellipsis "Hello world" 5}} → "Hello..." |
| append | (str, toAppend) | Appends a string to another | {{append "Hello" " world"}} → "Hello world" |
| prepend | (str, toPrepend) | Prepends a string to another | {{prepend "world" "Hello "}} → "Hello world" |
| remove | (str, toRemove) | Removes all occurrences of substring | {{remove "Hello world" "o"}} → "Hell wrld" |
Things to Know
Templates and Prompts
Genobi uses Inquirer.js for prompts and Handlebars for templates.
Template File Size
I would avoid template files over 50MB. I started working on a streaming option, but it got rocky when considering control flow logic, loops, and the prepend operation. So, instead, I started to work on extending Handlebars template parsing to chunk with logic and loop boundaries but that will take a little time wrap up with higher priority things on my plate.
License
MPL-2.0 (Mozilla Public License 2.0)
