{"id":13876136,"url":"https://github.com/galElmalah/scaffolder","last_synced_at":"2025-07-16T10:33:19.071Z","repository":{"id":42986457,"uuid":"174677502","full_name":"galElmalah/scaffolder","owner":"galElmalah","description":" Scaffolder - Increasing dev velocity and standardizing file conventions.","archived":false,"fork":false,"pushed_at":"2023-05-28T09:35:48.000Z","size":79640,"stargazers_count":156,"open_issues_count":13,"forks_count":11,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-20T19:02:45.500Z","etag":null,"topics":["boilerplate","boilerplate-template","boilerplates","generator","scaffolder","velocity"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/galElmalah.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-03-09T10:06:39.000Z","updated_at":"2024-11-16T02:19:30.000Z","dependencies_parsed_at":"2024-01-13T19:46:24.111Z","dependency_job_id":"ca48d177-9ba9-4e1b-927a-e216d68a27c5","html_url":"https://github.com/galElmalah/scaffolder","commit_stats":{"total_commits":563,"total_committers":6,"mean_commits":93.83333333333333,"dds":"0.13143872113676736","last_synced_commit":"853f0b4b570c9aac4f76d63e59c00bc6965b9c23"},"previous_names":["galelmalah/ctf"],"tags_count":132,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/galElmalah%2Fscaffolder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/galElmalah%2Fscaffolder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/galElmalah%2Fscaffolder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/galElmalah%2Fscaffolder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/galElmalah","download_url":"https://codeload.github.com/galElmalah/scaffolder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226126067,"owners_count":17577472,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["boilerplate","boilerplate-template","boilerplates","generator","scaffolder","velocity"],"created_at":"2024-08-06T06:01:03.239Z","updated_at":"2024-11-24T04:30:55.244Z","avatar_url":"https://github.com/galElmalah.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","boilerplate"],"sub_categories":[],"readme":"\n\n\u003cdiv align=\"center\"\u003e\n\n![Scaffolder logo](images/scaffolder-logo.png)  \n\n\u003c/div\u003e\n\n\u003ch1 align=\"center\"\u003eScaffolder\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![npm version](https://badge.fury.io/js/scaffolder-cli.svg)](https://badge.fury.io/js/scaffolder-cli)\n[![GalElmalah](https://circleci.com/gh/galElmalah/scaffolder.svg?style=svg)](https://app.circleci.com/pipelines/github/galElmalah/scaffolder)\n[![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/)\n[![vscode extension](https://vsmarketplacebadge.apphb.com/version-short/ctf-vscode.scaffolder-vscode.svg)](https://marketplace.visualstudio.com/items?itemName=ctf-vscode.scaffolder-vscode)\n\n\u003c/div\u003e\n\n\n\u003cp align=\"center\"\u003e \n  Copy pasting is prone to mistakes.\u003cbr/\u003e\n  Keeping your project file structure consistent is annoying.\u003cbr/\u003e\n  Sharing templates is too damn complicated!\u003cbr/\u003e\n  This is where Scaffolder come in.\n  \u003cbr\u003e \n\u003c/p\u003e\n\n\n**For a brief introduction and motivation for this tool, [read this](https://dev.to/galelmalah/what-is-scaffolder-and-how-you-can-use-it-to-increase-your-team-dev-velocity-558l).**\n\n**check out the [vscode extension](https://github.com/galElmalah/scaffolder-vscode)**\n\n---\n\n### TOC\n\n- [Getting started](#getting-started)\n  * [Setup](#setup)\n  * [Usage](#usage)\n    + [Create a templates folder in your project root directory](#create-a-templates-folder-in-your-project-root-directory)\n- [API](#api)\n  * [**interactive, i**](#interactive-i)\n  * [**create** _\\\u003ctemplateName\u003e_](#create-templatename)\n  * [**save-remote** _\\\u003calias\u003e_](#save-remote-alias)\n  * [**delete-remote** _\\\u003calias\u003e_](#delete-remote-alias)\n  * [**list**, **ls**](#list-ls)\n  * [**show** _\\\u003ctemplateName\u003e_](#show-templatename)\n- [Sharing templates](#sharing-templates)\n- [Scaffolder config file](#scaffolder-config-file)\n  * [transformers](#transformers)\n    + [Default transformers](#default-transformers)\n  * [functions](#functions)\n  * [parametersOptions](#parametersoptions)\n    + [parameter options object](#parameter-options-object)\n  * [context object](#context-object)\n    + [logger](#logger)\n  * [templatesOptions](#templatesoptions)\n    + [templates options object](#templates-options-object)\n    + [hooks object](#hooks-object)\n- [Motivation and gaals](#motivation-and-goals)\n  * [Why I wrote Scaffolder?](#why-i-wrote-scaffolder-)\n  * [Why I didn’t use any existing solutions?](#why-i-didnt-use-any-existing-solutions)\n  * [My goals while writing this tool](#my-goals-while-writing-this-tool)\n\n## Getting started\n\n### Setup\nInstall scaffolder globally\n```bash\nnpm i -g scaffolder-cli\n```\nthis will make the `scaff` command available globally, you can now type `scaff i` in the terminal, to enter the cli in [interactive mode](#interactive-i).\n\nYou can also use `npx` for example `npx scaffolder-cli i` will start scaffolder in [interactive mode](#interactive-i).\n\n### Usage\n\n#### Create a templates folder in your project root directory\n\nThe templates folder should be named **scaffolder** and should contain folders where each folder represents a different template and inside of that folder, there is the template structure you wish to create.  \n\u003e The templates available are the templates defined in the **scaffolder** folder.  \n\nIf you have more scaffolder folders in the current file system hierarchy then all of them will be included with precedence to the nearest **scaffolder** folder.  \n**For example:**  \nIn our current project root\n\n```bash\nscaffolder\n├── component\n│   ├── index.js\n│   ├── {{componentName}}.js\n│   └── {{componentName}}.spec.js\n└── index\n    └── index.js\n```\n\nIn our desktop\n\n```bash\nscaffolder\n├── component\n│   ├── index.js\n│   ├── {{lol}}.js\n│   └── {{wattt}}.spec.js\n└── coolFile\n    └── coolFile.sh\n```\n\nFrom the above structure, we will have three commands **component** (from the project scaffolder), **index** (from the project scaffolder) and **coolFile** (from the desktop scaffolder).  \nLets look at the content of **{{componentName}}.js** and **{{componentName}}.spec.js**.\n**{{componentName}}.js** from the current project **scaffolder** folder.\n\n```javascript\nimport React from 'react'\n\nexport const {{componentName}} = (props) =\u003e {\n  return (\n    \u003cdiv\u003e\n      Such a cool component\n    \u003c/div\u003e\n  )\n}\n```\n\n**{{componentName}}.spec.js**\n\n```javascript\nimport React from 'react';\nimport { mount } from 'enzyme';\nimport { {{componentName}} } from './{{componentName}}';\n\ndescribe('{{componentName}}', () =\u003e {\n  it('should have a div', () =\u003e {\n    const wrapper = mount(\n      \u003c{{componentName}} /\u003e\n    );\n   expect(wrapper.find('div').exists()).toBeTruthy()\n  });\n});\n```\n\nNow let's run the following command somewhere in our project\n\n```bash\nscaff create component componentName=CoolAFComponent --folder MyCoolComp\n```\n\nA new folder will be created under our current working directory, let's look at what we got.\n\n```bash\nMyCoolComp\n├── CoolAFComponent.js\n├── CoolAFComponent.spec.js\n└── index.js\n```\n\n**CoolAFComponent.js**\n\n```javascript\nimport React from \"react\";\n\nexport const CoolAFComponent = (props) =\u003e {\n  return \u003cdiv\u003eSuch a cool component\u003c/div\u003e;\n};\n```\n\n**CoolAFComponent.spec.js**\n\n```javascript\nimport React from \"react\";\nimport { mount } from \"enzyme\";\nimport { CoolAFComponent } from \"./CoolAFComponent\";\n\ndescribe(\"CoolAFComponent\", () =\u003e {\n  it(\"should have a div\", () =\u003e {\n    const wrapper = mount(\u003cCoolAFComponent /\u003e);\n    expect(wrapper.find(\"div\").exists()).toBeTruthy();\n  });\n});\n```\n\nThis could also be achieved using the interactive mode!\n![](images/scaffolder.gif)\n\nHow cool is this, right?  \nAs you can see our params got injected to the right places and we created our template with little effort.  \nHooray!! :sparkles: :fireworks: :sparkler: :sparkles:\n\n\n## API\n\n### **interactive, i**\n\nRun Scaffolder in interactive mode, meaning, it will prompt the user to choose a template and a value for each parameter.  \n\u003e This command is the most recommended one as it simplifies the process for the user a lot.\n\n**options:**\n- _--from-github_  \n  Passing this flag will cause a prompt to appear, asking the user to enter a github repository (https/ssh) and consume the templates defined on that repository.  \n  More info about [sharing templates](#sharing-templates).\n- _--entry-point_ _\\\u003cabsolutePath\u003e_  \n  Generate the template to a specified location.\n- _--path-prefix_ _\\\u003crelativePath\u003e_  \n  Path that will be appended the the location the template is generated into.\n  \u003e If the path does not exists Scaffolder will automatically create it and notify the user what paths were created for him.\n\n  For example: `\u003centry-point\u003e/\u003cpath-prefix\u003e/\u003ctemplate-will-be-created-here\u003e`\n- _--template_ _\\\u003ctemplateName\u003e_  \n  Start the interactive mode with a preselected template.\n- _--values_ _\\\u003ccommaSeparatedParametersValue\u003e_   \n\tPredefine values for specific parameters param1=val1,param2=val2...\n\n\n### **create** _\\\u003ctemplateName\u003e_\n\n_\\\u003ctemplateName\u003e_: One of the templates defined in the **scaffolder** folder. \u003cbr/\u003e\n \n**options:**\n\n- _--load-from_ _\\\u003cabsolutePath\u003e_  \n  Load the templates from a specific location.\n- _--entry-point_ _\\\u003cabsolutePath\u003e_  \n  Generate the template to a specified location.\n- _--path-prefix_ _\\\u003crelativePath\u003e_  \n  Path that will be appended the the location the template is generated into.\n  \u003e If the path does not exists Scaffolder will automatically create it and notify the user what paths were created for him.\n\n  For example: `\u003centry-point\u003e/\u003cpath-prefix\u003e/\u003ctemplate-will-be-created-here\u003e`\n- _--folder, -f_ _\\\u003cfolderName\u003e_  \n  _\\\u003cfolderName\u003e_: The name of the folder you want the template to be generated into. If none is supplied the template will be generated to the current working directory.\n- _\\\u003cparameter\u003e=\\\u003cvalue\u003e_  \n  _\\\u003cparameter\u003e_: One of the parameters for a specific template  \n  _\\\u003cvalue\u003e_: The value you want the parameter to be replaced with.\n\n### **save-remote** _\\\u003calias\u003e_\n\n_\\\u003calias\u003e_: The alias that will map to your remote templates. \u003cbr/\u003e\n\n**options:**\n\n- _--location_ _\\\u003cgithubSrc\u003e_  \n  The location of your remotes, e.g github ssh or https.  \n  For example: `git@github.com:galElmalah/scaffolder-templates-example.git` or `https://github.com/galElmalah/scaffolder-templates-example.git`\n\n\n### **delete-remote** _\\\u003calias\u003e_\n\n_\\\u003calias\u003e_: The alias you wish to delete. \u003cbr/\u003e\n\n### **list**, **ls**\n\nShow the available templates from the current working directory.\n\n### **show** _\\\u003ctemplateName\u003e_\n\nShow a specific template files  \n **options:**\n\n- _--show-content_  \n  Also show the full content of the template files.\n\n---\n\n## Sharing templates  \nOften you find yourself wanting to share a template while not making every consumer of that template to save it on his machine.  \n\nIn order to address that problem, Scaffolder lets you consume templates from Github repositories that have a **scaffolder** folder at their root.  \nFor example, you can see this [repository](https://github.com/galElmalah/scaffolder-templates-example) which contains 3 templates and a config file.  \nTo generate one of those templates you can run `npx scaffolder-cli i --from-github` and enter `https://github.com/galElmalah/scaffolder-templates-example.git` and you'll be promoted to choose one of those templates.  \n\nYou can now save remote templates repos under aliases!   \nFor example, assuming you have scaffolder-cli installed globaly, you can run\n`scaff save-remote my-remote-templates --location https://github.com/galElmalah/scaffolder-templates-example.git`\nand from now on, you will have a new entry named `(remote) my-remote-templates`  when you run [scffolder in interactive mode](#interactive-i) that will list all of the available templates in that repo.\n\n\u003e Any improvement suggestions? go ahead and [open an issue](https://github.com/galElmalah/scaffolder/issues)!\n\n---\n\n## Scaffolder config file\n\nScaffolder lets you extend and define all sorts of things via a config file.  \nthe config file should be placed inside the **scaffolder** folder that the template you are generating is defined in and named `scaffolder.config.js`.\n\nThrough the `scaffolder.config.js` file you can extend and customize scaffolder in several ways.  \nExample config file\n\n```javascript\nmodule.exports = {\n  transformers: {\n    toLowerCase: (parameterValue, context) =\u003e parameterValue.toLowerCase(),\n  },\n  functions: {\n    date: (context) =\u003e Date.now(),\n  },\n  parametersOptions: {\n    someParameter: {\n      question:\n        \"this text will be shown to the user in the interactive mode when he will be asked to enter the value for 'someParameter'\",\n    },\n  },\n   templatesOptions: {\n    someTemplate: {\n      hooks: {\n        preTemplateGeneration: (context) =\u003e {\n        // do something before generating a template\n        },\n        postTemplateGeneration: (context) =\u003e {\n        // do something after generating a template\n        }\n      },\n      // transformers, functions, parametersOptions can be scoped to specific templates\n      transformers: {...},\n      functions: {...},\n      parametersOptions: {...}\n    }\n  }\n};\n```\n\n\u003e transformers, functions, parametersOptions can be scoped to specific templates, and will have precedents over non scoped options.\n\n### transformers\n\nTransformers can be used to transform a parameter value.  \nFor example, you can write the following:\n`{{ someParameter | toLowerCase | someOtherTransformer }}`\t\nand the value that will be injected in your template will be the value after all of the transformations.\n\n- Transformers can be chained together.\n- Transformers are invoked with the value supplied for that parameter as the first argument and the [context](#context-object) object as the second argument.\n\n#### Default transformers\nYou can use the following transformers without defining anything in your config file  \n  1. toLowerCase\n  2. \ttoUpperCase\n  3. \tcapitalize - capitalize each word in your parameter value\n  4. \ttoCamelCase - takes Kebab or snake case and transform them to camelCase format\n  5. \tcamelCaseToSnakeCase\n  6. \tcamelCaseToKebabCase\n\n### functions\n\nfunctions are very similar to transformations, but they are unary, meaning, they are invoked without any parameter value supplied to them.  \nFor example, you can write the following:\n`{{date()}}` and the value returned from are date function (defined in our config file) will be injected to the template.\n\n### parametersOptions\n\nparametersOptions is a map from parameters to their options.  \nFor example, lets say we have a parameter named myReactComponentName and we want to show a custom question to the user when he is asked to enter a value for that parameter, we can add the following to our config file:\n\n```javascript\n{\n  ...\n  ...\n  parametersOptions: {\n    myReactComponentName: {\n      question: \"Enter a name for your react component:\",\n      validation: (value) =\u003e value.length \u003e 3 ? true : \"The component name must be longer than 3 chars\" \n    },\n    parameterTwo: {\n      question: \"Enter a name for your react component:\",\n      choices: {\n        values:[\"These\", \"values\", \"will\", \"be\", \"shown\", \"to the user to choose from\" ]\n      }\n    }\n  }\n}\n```\n#### parameter options object\n| property        | type                                                        | description                                                                                            |\n| :-------------- | :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- |\n| question    | string                                     | The question that will be shown to the user when he will enter a value for the matching parameter             |\n| validation    | (value: string): string \\| true                                                      | this function will be invoked to validate the user input.\u003cbr/\u003eReturn a string  if the value is invalid, this string will be shown to the user as an error message. Return true if the value is valid.                                                              |\n| choices     | {values: string[]} | Object containing a values property from which the user will be asked to choose a value for that parameter. |\n\n\n\n\n### context object\n\n| property        | type                                                        | description                                                                                            |\n| :-------------- | :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- |\n| parametersValues | Object\u003cstring, string\u003e                                      | Key value pairs containing each parameter and his associated value in the current template.                  |\n| templateName    | string                                                      | The name of the template being generated.                                                              |\n| templateRoot    | string                                                      | Absolute path to the template being generated.                                                         |\n| targetRoot      | string                                                      | Absolute path to the location the template is being generated into.                                    |\n| currentFilePath | string                                                      | The path to the file being created.                                                                    |\n| type            | string, one of: `\"FILE_NAME\"`, `\"FILE_CONTENT\"`, `\"FOLDER\"` | The current type being operated upon - file/folder/content.                                            |\n| fileName        | string                                                      | The name of the file being operated upon. Available only if the type is \"FILE_NAME\" or \"FILE_CONTENT\". |\n| logger        | [Logger](#logger)                                                      | A [logger](#logger) instance |\n\n\n\n### logger\n\n| property        | type                                                        | description                                                                                            |\n| :-------------- | :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- |\n| info    | (message: string): void                                  | Output general info.            |\n| warning    | (message: string): void                                  | Output warnings, meaning, it will be colored orange.              |\n| error    | (message: string): void                                  | Output error, meaning, it will be colored red.            |\n\n\u003e In vscode context, the loggers will pass the message to \"scaffolder\" output channel prefixed with the log level used.\n\nvscode logs example\n```\n[error][context-logger]:: some message\n[warning][context-logger]:: some message\n[info][context-logger]:: some message\n```\n\n\n---\n\n\n### templatesOptions\ntemplatesOptions is a map from templates names to their options.  \n\u003e transformers, functions, parametersOptions can be scoped to specific templates, and will have precedents over non scoped options.\n\n\n#### templates options object\n| property        | type                                                        | description                                                                                            |\n| :-------------- | :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- |\n| hooks    | [hooks object](#hooks-object)                                     | Hooks are functions to be executed at some point throughout the template\n| parametersOptions    | [parameter options object](#parameter-options-object)                                    \n| functions    | [functions](#functions)                                    \n| transformers    | [transformers](#transformers)                                    \n\n\n\n#### hooks object\nIf an hook function returns a Promise then it will be awaited and only then the template generation process will continue.\n\n\n\n| property        | type                                                        | description                                                                                            |\n| :-------------- | :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- |\n| preAskingQuestions    | ([context](#context-object)): any \\| Promise\\\u003cany\u003e                             | Executed before the user is prompted for the template questions.\u003c/br\u003e**The context object will include an empty `parametersValues` object.**        |\n| preTemplateGeneration    | ([context](#context-object)): any \\| Promise\\\u003cany\u003e                                    | Executed before the template is generated.             |\n| postTemplateGeneration    | ([context](#context-object)): any \\| Promise\\\u003cany\u003e                                    | Executed after the template is generated.             |\n\n\u003e By default all errors thrown inside of hooks are ignored. To stop execution inside a hook you can use `process.exit(1)` .\n\n\n\n\n_________________\n\n## Motivation and goals\n### Why I wrote Scaffolder?\nWorking on several big projects, I noticed that there a few time-consuming tasks that keep popping up. One of those tasks is creating folders and filling in all the boilerplate code while keeping the project structure consistent. After realizing that this process needs to be automated, I set out to find a solution and ended up creating my own CLI tool 🌈.\n\nThe first thing I had to do is to understand **WHY** it’s so annoying, and I realized that this happens for two reasons:\n\n- Creating files and folders can be repetitive, annoying and a waste of time. Especially if some content repeats itself for every new file.\n- Keeping a project file structure consistent is becoming more and more complex as the number of people working on that project increases — each team member has his preference for naming files and exposing functionality.\n\n### Why I didn’t use any existing solutions?\n***First***, came [Yeoman](https://yeoman.io/). I gave yeoman a try but found it too complex. Furthermore, I want the templates to be a part of the project (in some cases), and committed to git alongside the code. Thus, supporting template generation offline and tight coupling between the project and the templates. All of the above seemed too complex or not possible at all with yeoman, so one hour after trying it out I moved on to other prospects.\n\n***Second***, came [boiler](https://github.com/tmrts/boilr), I did not like this one for the same reasons I did not like Yeoman. Also, the fact that it’s not managed with npm is a bit annoying.\n\n***Third***, came frustration 😞. After trying two of the most popular solutions out there I realized that If I want something tailored to my needs I should just go ahead and write it myself.\n\nBoth of these tools are **AWESOME** but for my needs, they weren’t right.\n\n### My goals while writing this tool\n- making this process as easy and seamless as possible.\n\n- Addressing the general problem. Meaning, it won’t be language-specific e.g only React or Vue templates. I could potentially create templates in any shape, structure, and language I want.\n\n- Having the ability to create scoped templates. Meaning, creating project-specific templates that can be committed to git with the rest of the code.\n- Having the ability to create “global” templates that will be available from anywhere.\n- Managed with npm.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FgalElmalah%2Fscaffolder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FgalElmalah%2Fscaffolder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FgalElmalah%2Fscaffolder/lists"}