2.5.3 • Published 5 years ago

scf v2.5.3

Weekly downloads
22
License
MIT
Repository
github
Last release
5 years ago

SCF

A new way to scaffold code, SCF provides a declarative way to build project scaffolders.

At the most basic level, SCF copies (scaffolds) a directory from one location to another. As SCF copies files, it checks each file to see if it contains YAML front matter (YFM). YFM is not scaffolded out in the final file but is instead used to build CLI prompts. Responses are gathered and then interpolated in the remainging file contents as it is scaffolded out. SCF uses templating engines, ES template strings by default, to interpolate prompt variables. Think static site generators, such as Jekyll, that use YFM to scaffold out static websites, only SCF uses YFM to scaffold out command line interfaces and project templates instead.

Any project or directory will work as a template for SCF to scaffold. To build out a custom CLI experience for your project scaffolder, add YFM and ES template strings to files. Doing so adds support for dynamic data and allows you to control the flow of the scaffolding process.

Usage

scf <command> [options]

Commands

  • Create \<name> as: Scaffolds out the named template.
  • install \<src> as: Installs template from github, gitlab or bitbucket.
  • rm \: Remove template.
  • list: List available templates.
  • link src: Link current directory to global templates directory.
  • help \: Display help for a specific command

Global options

  • -h, --help: Display help
  • -V, --version: Display version
  • --no-color: Disable colors
  • --quiet: Quiet mode - only display warn and error messages.
  • -v, --verbose: Verbose mode - will also output debug messages.

Example

Let's start by scaffolding out a simple, predefined static site template.

1. Install scf

npm install scf -g

May also use npx instead of installing scf globally.

2. Scaffold template

scf create dworthen/scf-static-site-template#basic MyApp -s

This command will scaffold out dworthen/scf-static-site-template/tree/basic, a simple static website template, to MyApp.

Simple static site example

Outputting

MyApp
|-- css
    |-- site.css
|-- js
    |-- app.js
|-- index.html

Which matches the contents of dworthen/scf-static-site-template/tree/basic.

This is a straight forward example. None of the template files contain any YFM so SCF only copies the project structure to the desired location, MyApp, without prompting for any input.

The create command runs the install command internally, if necessary. SCF uses degit to install templates from github, gitlab and butbucket. View Rich-Harris/degit for more information.

A more complete example

Let's scaffold a project template that will prompt for input, dworthen/scf-static-site-template.

scf create dworthen/scf-static-site-template MyApp

Example with prompts

No code was written to prompt for input. SCF builds CLI prompts from the YFM present in template files.

The above command and responses produces the following structure.

MyApp
|-- css
    |-- style.css
|-- scripts
    |-- bundle.js
|-- index.html
|-- package.json

cd MyApp && npm install && npm start will start up a static file server accessible at localhost:5000/index.html.

Notice that the above structure does not quite match dworthen/scf-static-site-template.

dworthen/scf-static-site-template
|-- css
    |-- site.css
|-- js
    |-- app.js
|-- index.html
|-- package.json

For example, site.css was renamed to style.css during the scaffolding process and app.js was not only renamed to bundle.js but was also moved to scripts/. SCF let's you rename files and and even move them by using relative paths when scaffolding.

How does SCF handle file references and links if files can be renamed and moved? Let's take a look at the outputed MyApp/index.html for clues.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>My Awesome App</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <h1>Hello Derek</h1>
    <script src="scripts/bundle.js"></script>
  </body>
</html>

Notice that the link and script elements reference the correct, renamed and moved files, style.css and scripts/bundle.js respectively. MyApp/index.html also references some other information provided during the scaffolding process such as My Awesome App and Derek.

Compare this to the template file, dworthen/scf-static-site-template/blob/master/index.html.

---
- filename: index.html
- name: title
  message: Document title
- name: name
  default: World
  message: Name to say hello to
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>${title ? title : "Basic HTML Template"}</title>
    <link rel="stylesheet" href="${files['css/site.css'].dest}" />
  </head>
  <body>
    <h1>Hello ${name}</h1>
    <script src="${files['js/app.js'].dest}"></script>
  </body>
</html>

The file starts with YFM (content between the opening and closing ---). YFM is not scaffolded out. Instead, SCF uses YFM to control the flow of the scaffolding process and to prompt for input.

SCF scaffolds out the remaining contents after expanding variables using ES template string. ${title ? title : "Basic HTML Template"} demonstrates using a ternary expression within an ES template.

Both link and script elements reference a file object instead of a static path. The file object is a global object accessible to all template files and represents a mapping of original file locations to the scaffolded out location. The following is the file object for the current example.

{
  "css/sites.css": {
    dest: "css/style.css"
  },
  "js/app.js": {
    dest: "scripts/bundle.js"
  }
}

This allows one to reference other files within a project template without knowing where the referenced files will end up after scaffolding.

Prompts

Prompts take the following YAML shape.

  • name\ (required): The name of the variable that will store the user response.
  • type\<input|number|confirm|list> (optional): Type of prompt to display. Defaults to input.
  • Choices\<string[]> (required with type=list): A list of choices to display when type is set to list.
  • messge\ (optional): Message to use when prompting for input.
  • default\ (optional): Default value if one is not provided.
  • pattern\ (optionl): Regular expression used to validate user input. May be a string or, as in the above example, a js regular expression using the !!js/regexp label.
  • validate\ (optional): A js function used to validate user input. Should return either true or a string to display when input does not satisfy requirements. Validate and pattern cannot be used together.

Example

---
- name: greeting
  type: list
  choices:
    - Hello
    - Greetings
- name: person
  message: Person to say hello to.
  default: World
  pattern: !!js/regexp /^[A-Z]+.*/
- name: punctuationMark
  message: Define punctuation mark to use.
  validate: !!js/function >
    function(input) {
      return /^[.!]$/.test(input) || `Provided ${input}. Can only use \".\" or !`;
    }
---

<h1>${greeting} ${person}${punctuationMark}</h1>

The first prompt presents a list of choices, Hello and Greetings, and stores the selected choice in a variable titled greeting.

The second prompt takes user input and ensures it passes the requirements defined by the pattern regular expression before storing it in a variable titled person.

The third prompt takes user input and validate it using the validate function instead of the pattern regular expression. validate is more flexible and can provide better feedback for when user input does not meet requirements.

Can use either pattern or validate to validate user input but not both at the same time. If neither pattern nor validate is defined then the input is optional and the user may leave the response blank.

Global prompts

Prompts defined in globals.yaml are prompted first and act as global variables and are accessible to all template files. Since globals.yaml is a YAML file it does not need opening and closing ---.

Reserved YAML keys

  • filename\ (optional): Define the filename, skipping user input when scaffolding. Useful for files that have signicant meaning such as package.json or webpack.config.js. package.json and index.html in dworthen/scf-static-site-template demonstrate using filename.
  • templateEngine<es|ejs> (optional): set the template engine for rendering. Defaults to es.
  • skip\ (optional): if true, SCF skips scaffolding out the file.
  • conditions\<object[]> (optional): Defines a set of conditions for when the file should be scaffolded out. Conditions are checked against global prompts.
    • conditions object:
      • name\ (required): name of global prompt variable to check against.
      • operator\<eq|neq|gt|gte|lt|lte|includes> (required): defines how to check against global variable.
      • value\ (required): value used to check against global variable.

Conditional scaffolding

SCF supports conditional scaffolding. Here is an example of using conditions to control when the build file, webpack.config.js in this case, will be scaffolded.

---
# webpack.config.js
# Top level conditions are ORed
- conditions:
  # These conditions are ANDed
  - name: useBuildSystem
    operator: eq
    value: true
  - name: useWebpack
    operator: eq
    value: true
# OR
- conditions:
  - name: simpleScaffold
    operator: eq
    value: true
---

# File contents for webpack.config.js

SCF scaffolds out the above file if (useBuildSystem === true && useWebpack === true) || simpeScaffold === true. This is a contrived example but it demonstrates both ANDed and ORed conditionals.

Conditions work by checking values against global prompt variables. Here is the globals.yaml file for this example:

- name: useBuildSystem
  type: confirm
  message: Do you wish to use a build tool?
- name: useWebpack
  type: confirm
  message: Do you wish to use webpack?
- name: simpleScaffold
  type: confirm
  message: Do you not want to worry about configuration and just scaffold?

globals.yaml is a YAML file and does not need to contain the opening and closing --- tags.

The package.json and globals.yaml files of dworthen/scf-static-site-template demonstrates conditional scaffolding.

Template engines

SCF uses ES template strings by default to interpolate variables. ejs may also be used.

Here is what is available to templates.

  • YFM prompts: prompts defined within the YFM are available to the template content.

    ---
    - name: person
    ---
    
    <h1>Hello ${person}</h1>
  • Global prompts: prompts defined in globals.yaml are accessible in all template files.

  • src\: The path of the template source, e.g., css/site.css.
  • dest\: Where the file is going to be scaffolded, e.g., css/style.css.
  • __path\: a path object for the destination path as returned by path.parse
  • files\: An object that represents a mapping of template source paths to the selected scaffolded path.

    Example files object

    {
      "path/to/template/source/file.ext": {
        "dest": "path/to/scaffolded/location/file.ext",
        "__path": { // path.parse object for destion path 
          "root": "",
          "dir": "path/to/scaffolded/location",
          "base": "file.ext",
          "ext": ".ext",
          "name": "file"
        } 
      }
    }

dworthen/scf-static-site-template/blob/master/index.html demonstrates using the files object to reference files in the link and scripts elements.

ES templates support expressions but not statements. This means that ternary expressions are supported but if statements or loops are not supported. EJS is a full templating engine and does support conditionals and other control structures.

Special files

  • globals.yaml: defines a list of global prompts that will be accessible to all files and can be used for conditional scaffolding.
  • .scfignore: gitignore style ignore file. SCF will ignore files and directories listed in .scfignore when scaffolding. May also define gitignore style ignore rules/globs as an array in scfignore key of package.json.

Conditional scaffolding strategies

Branch based

scf create dworthen/scf-static-site-template#basic MyApp and scf create dworthen/scf-static-site-template MyApp both scaffolded out dworthen/scf-static-site-template with one differnce. The former scaffolds out the basic branch while the latter scaffolds the master branch.

Template developers can then create multiple branches to support a variety of scaffold templates. The downside to this approach is that consumers need to which branch to target when scaffolding to get their desired project template.

SCF uses degit to install templates from github, gitlab and butbucket. View Rich-Harris/degit for more information on targeting specific branches and tags.

File based using YFM

One can use global prompts and YFM to achieve file-based conditional scaffolding.

For example, the following global prompts (globals.yaml) can be used to support multiple js based build tools.

- name: buildTool
  message: Select your desired build tool.
  type: list
  choices:
    - webpack
    - rollup

The template repo will then contain a webpack.config.js and a rollup.config.js config file. Both files with conditions dictating when each should be scaffolded.

---
- filename: webpack.config.js
- conditions
  - name: buildTool
    operator: eq
    value: webpack
---

// webpack.config.js
---
- filename: rollup.config.js
- conditions
  - name: buildTool
    operator: eq
    value: rollup
---

// rollup.config.js

SCF scaffolds out the correct config file based on which option the user selects during the scaffolding process. The advantage with this approach is that developers use the same scf create username/repo <ProjectName> command. SCF will then prompt the user for which build tool to use. SCF walks the use through the available options. No need to change the target branch or repo in the scf create command.

Creating an SCF project template

  1. Start with a project structure you want to scaffold.
  2. Add global prompts variables with globals.yaml.
  3. Declaratively define prompts and SCF metadata using YAML front matter.
  4. Add conditions for when a file should be scaffolded out using YAML front matter.
  5. Add ES template strings, ${VARIABLE_NAME}, to replace static content with variables. (Or use ejs)
  6. Add .scfignore file to specify which files should be ignored by SCF. readme files are good candidates for .scfignore.
  7. Use. Can either upload to github and use scf create username/repo <ProjectName> Or run scf link . TEMPLATE_NAME within the template project directory. Then run scf create TEMPLATE_NAME <ProjectName> anywhere. scf link works like npm link, allowing one to install local templates as global templates and then scaffold them from anywhere.

There is no need for template developers to write generators or code to specify what to prompt users, how to respond to prompts, and how to copy files from one place to another. Instead, template developers start with the directory structure they wish to scaffold and begin to add YFM to declaritively create prompts, ES template strings to support dynamic data, and conditions to determine what is copied and when.

This is the approach taken with dworthen/scf-static-site-template. The basic branch shows a simple project directory before adding any YFM or SCF specific content. The master branch shows what the project template looks like after adding SCF specific content and YFM. Scaffolding out the master branch allows the user to control what is scaffolded out.

Sample SCF project templates

TODO

A todo list is accumulating at dworthen/scf/issues/1.

2.5.3

5 years ago

2.5.2

5 years ago

2.5.1

5 years ago

2.5.0

5 years ago

2.4.1

5 years ago

2.4.0

5 years ago

2.3.1

5 years ago

2.3.0

5 years ago

2.2.1

5 years ago

2.1.3

5 years ago

2.1.2

5 years ago

2.1.1

5 years ago

2.1.0

5 years ago

2.0.0

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago

0.2.0

5 years ago

0.0.1

6 years ago