Templatizing Github "Template Repos"
Adding the (obvious) missing feature to Github Template Repositories
tl;dr
Get better use out of Github Template Repositories by introducing your own templating mechanism. Using a combination of gomplate, tree
, and bash
can get you there pretty easily. This article demonstrates an example of how it's done.
Last year, Github added a new feature called Template Repositories. Template Repositories are a convenient way to allow users to clone a codebase without having to git clone
and remove the Git local repository (i.e. .git
).
However, this is where the utility of Github Template Repositories ends.
In my mind, the whole purpose of a "template repository" is to allow users to easily customize the repo for their own use. Unfortunately, Github does not offer any kind of mechanism for substituting placeholders with our own values (there is actually no real concept of a placeholder with Template Repositories).
There are many viable frameworks project scaffolding frameworks around, and if you are comfortable with one, please use it. I don't think many of them work well with Github Templates since you are starting with an existing codebase (not generating one). I also prefer a simple solution that doesn't take a ton of headspace to use. My requirements are simple:
- Use placeholders in files that can be substituted from a template file.
- Preserve the file structure of the template repo (having to use a flat file structure sucks).
- Don't have a lot of special rules (like sacred directories, naming schemes, etc.).
- Make the process easily repeatable (should you screw up the process).
- Gracefully clean up the template scaffolding.
Solution
Assuming you have tree
and gomplate
installed locally, start with a simple script called customize
:
#!/usr/bin/env bash
ignore_files=".git|node_modules|_templates|customize|README.md"
for input_file in `tree -I "${ignore_files}" -Ffai --noreport`
do
if [ ! -d "${input_file}" ]; then
echo "Processing file: ${input_file}"
gomplate \
-f "${input_file}" \
-o "${input_file}" \
--left-delim "<<[" \
--right-delim "]>>" \
-c cus=./customize.json
fi
done
# Clean up / implode
rm README.md
mv README_TEMPLATE.md README.md
mv github .github
rm customize
Basically, this script uses tree
to get the hierarchy of files in the project directory (ignoring files use the pattern ignore_files
), and treats every file as a template to be rendered. If there are no template variables, there will be no changes (at least in terms of .git
hashing -- though the file will be rewritten). If the file does have "placeholders", the placeholders will be rendered when the file is rewritten to disk.
In my setup, I use an unusual placeholder syntax: <<[ variable ]>>
(as represented by the --left-delim
and --right-delim
arguments). I do this to prevent collisions with code-based templating ( (for instance, Github Actions uses ${{ variable }}
and Handlebars uses{{ variable }}
).
Data is sourced from the file customize.json
, which is also located in the root of the project:
{
"foo": "bar",
"nesting": {
"is": {
"ok": true
}
}
}
The script argument -c cus=./customize.json
is used by gomplate
to create a "context,"
which is simply a helpful way to namespace variables. Using the existing example, you can access the variables in customize.json
using the following placeholders: <<[ .cus.foo ]>>
and <<[ .cus.nesting.is.ok ]>>
. If you don't like the prefix .cus
, you can change it to whatever you desire: -c <context-name>=./customize.json
.
gomplate
has many other data source strategies and utility methods. I recommend checking out the utility's documentation (which is excellent).
Finally, the customize
script uses the "implode" pattern (most people know it from Create React App) where files are moved to their correct places in the file structure and scaffolding is deleted. I use this especially for the .github
directory because I don't want a templatized version of Github Actions running whenever commits are made to the template repo. I also tend to have two README.md
documents. One describes how to use the template repo, and the other is the templatized version of the README.md
that will remain after customize
is ran.
Conclusion
Github Template Repositories are a great tool for allowing developers to clone and customize repositories, but the process has some deficiencies that can be addressed very simply with a templating technology and some bash
. The customize
script (above) is what we use at Peachjar for customizing new microservice and microapp projects from our "starter kits" (all Github Template Repositories). Hopefully, this will inspire you to create similar template repositories for use within your own project or organization.
You might also be interested in these articles...
Stumbling my way through the great wastelands of enterprise software development.