loreanvictor / tmplr

Automate Code Scaffolding
MIT License
27 stars 0 forks source link

Support for local/private templates #11

Closed nino-magazinovic closed 1 year ago

nino-magazinovic commented 1 year ago

Hi,

Is it possible to create a project from local templates (templates/*) that are not published in a seperate repository, but are part of a monorepo (pnpm workspace using NX package based approach).

Idea is to have a command like Vite pnpm create vite my-vue-app --template vue where the template can be either remote repository or local file path, it will create a folder with the provided name (can be exposed as default npm package name) and will copy content from local template or checkout (using degit) from remote repisitory.

Vite community templates are only supported using degit but without any project scaffolding as per documentation https://vitejs.dev/guide/#community-templates.

Im happy for tmplr and realy found it via the degit issue https://github.com/Rich-Harris/degit/issues/30, it is a good fit to scaffold a package based NX workspace and created seperate feature request https://github.com/nrwl/nx/issues/17979.

loreanvictor commented 1 year ago

I'm not sure if I understand exactly what you need, but:

run: ./templates/some-recipe.yml

Which can be combined with [`choices`](https://github.com/loreanvictor/tmplr/tree/main#choices) or [`prompt`](https://github.com/loreanvictor/tmplr/tree/main#prompt) to receive some parameters from the user and then fetch and run the desired template based on that input. You can even combine it with [`use`](https://github.com/loreanvictor/tmplr/tree/main#use) in case you want to run a remote repository, and you can check whether you need to run a local or a remote repository using the [`matches` pipe](https://github.com/loreanvictor/tmplr/tree/main#pipes).

What you currently can't do, but should be easy to add the support for:

- Allow running a local recipe other than `.tmplr.yml`, e.g. something like this:
```bash
tmplr -r ./templates/some-other-template.yml
nino-magazinovic commented 1 year ago

Yes, subdirectories are per degit support.

My thoughts were manly on private repositories and on the missing feature of Vite template to scaffold the project when we have to use custom templates (not the one provided by Vite)

For example Vite has out of the box templates https://vitejs.dev/guide/#scaffolding-your-first-vite-project that can be scaffoled like: pnpm create vite my-react-app --template react-ts

but in case of custom Vite template and together with tmplr we have to make it into three commands:

mkdir my-react-app
cd my-react-app
npx tmplr owner/repo

Idea was to have a tmplr starter that can handle above in a single command like: pnpm create tmplr my-react-app owner/repo or for local template pnpm create tmplr my-react-app ./templates/react-app

Starter will create directory my-react-app, cd into it, copy/checkout the project and run the tmplr recipe.

Like nextjs starter or any other https://petermekhaeil.com/how-to-build-an-npx-starter-template/ but with the ability to use any repo or local directory.

loreanvictor commented 1 year ago

Doing those 3 in one command line is already supported (though unfortunately, not documented yet). You can do what you specified in one command like this:

npx tmplr owner/repo -d my-react-app
nino-magazinovic commented 1 year ago

Thanks a lot for the single command,

so it would be possible to use both paremters -d and -r to run private templates in the same monorepo without the usage of degit and have the desired behavior explained above?

loreanvictor commented 1 year ago

it would be, though some modifications would be required. right now, template recipes can only operate in their given scope: when you receive them from some remote repository, the content of the repository is cloned to the working directory, and when you run them locally (without passing any arguments), then they are executed within the given working directory.

to emulate similar behavior and without requiring weird new mechanism for access control, the -r option would need to copy the contents of a local directory to the working directory, overriding existing files if need be (similar to fetching from remote), and then run the local .tmplr.yml file.

all that said, until such -r option is implemented (I would welcome a PR on it as well as I'm super overwhelmed with other stuff atm 😅), you can setup monorepo management from local templates with the existing features too. for example, if you put a .tmplr.yml recipe like this at the root of your project:

steps:
  - read: name
    prompt: What is the package name?

  - read: template
    prompt: Which template you want to use?
    choices:
      - first
      - second

  - run: './templates/{{template}}/.tmplr.yml'
    with:
      name: '{{name}}'
      target:
        path: './packages/{{name}}'

where you have templates/first/.tmplr.yml and templates/second/.tmplr.yml, each being something like this:

steps:
  # this makes the name variable accessible in copied files
  - read: name
    from: args.name

  - copy: README.md
    to:
      path: '{{args.target}}/README.md'

then you would be able to create new packages in packages folder using existing templates from templates folder. even if you need to do this in a non-interactive way, you can achieve this by using environment variables instead of asking the user (which is not as pretty, but will get the job done).

I have setup an example sandbox for this particular use case. You can fork it, open a terminal and run npx tmplr to see the described scenario in action.

nino-magazinovic commented 1 year ago

Thanks a lot for the solution. Suggestion can be part of the documentation.

loreanvictor commented 1 year ago

further reflection on implementation: while allowing filesystems to also read from some out-of-scope addresses (predetermined on construction), and effectively separating read-scope from write-scope, this is a fundamental change with a lot of side effects, some perhaps involving security issues.

the leaner method would be to merely copy the contents of given folder into working directory, basically treating local files as another source. this can be done before any recipe is executed, removing the need to increase the execution scope of recipes in any way. additionally, this allows local templates to perfectly mirror remote templates, which should make it easier to write and maintain them.

this solution would perhaps look something like this:

npx tmplr file:./templates/first-template -d ./packages/my-new-package
loreanvictor commented 1 year ago

using local templates is supported from 0.2.13 onwards:

tmplr local:/my/template