{"id":15209233,"url":"https://github.com/hopplajs/hoppla","last_synced_at":"2025-10-29T15:30:29.567Z","repository":{"id":44981655,"uuid":"134168744","full_name":"hopplajs/hoppla","owner":"hopplajs","description":"EJS and HJSON based scaffolding","archived":false,"fork":false,"pushed_at":"2022-01-15T02:56:42.000Z","size":142,"stargazers_count":5,"open_issues_count":3,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-02T01:51:14.239Z","etag":null,"topics":["cli","ejs","hjson","scaffolding"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hopplajs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-20T16:47:36.000Z","updated_at":"2021-11-13T23:50:36.000Z","dependencies_parsed_at":"2022-09-26T17:20:41.764Z","dependency_job_id":null,"html_url":"https://github.com/hopplajs/hoppla","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopplajs%2Fhoppla","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopplajs%2Fhoppla/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopplajs%2Fhoppla/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopplajs%2Fhoppla/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hopplajs","download_url":"https://codeload.github.com/hopplajs/hoppla/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238840720,"owners_count":19539601,"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":["cli","ejs","hjson","scaffolding"],"created_at":"2024-09-28T07:22:26.566Z","updated_at":"2025-10-29T15:30:24.258Z","avatar_url":"https://github.com/hopplajs.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hoppla\nEJS and HJSON based scaffolding. A simple tool and library to automate the process of setting up new projects or project parts with templates.\n\nSummary:\n* Works similar to `cp -r`\n* No overwrite by default\n* Files suffixed with `.hop.ejs` parsed by EJS\n* Directly access JSON/HJSON Data in the files\n* No cli modal boilerplate\n* Customize with own javascript\n\n## Cli usage\n```\nOptions:\n  --help                 Show help                                     [boolean]\n  --version              Show version number                           [boolean]\n  -t, --template         Path to template folder             [string] [required]\n  -d, --destination      Path to destination folder      [string] [default: \".\"]\n  -i, --input            HJSON input data                               [string]\n  -f, --force            Overwrites existing files                     [boolean]\n  --ed, --ejs-delimiter  Which EJS delimiter to use      [string] [default: \"%\"]\n```\n\n## Installation\n`npm install hoppla`\n\n## Basics\nHoppla is a tool to quickly set up new folder structures in your projects. The templates can be automated with EJS and JS. Instead of using complicated cli dialogs to provide data for your templates, the data can be directly provided by HJSON or JSON.\n\nThe core of hoppla works similar to `cp -r`. It merges the template contents into the destination, will however by default not overwrite files. \n\n* Template `templates/example` content: `helloworld.txt`\n* Example cli execution: `npx hoppla -t ./templates/example -d .`\n* Result: `./helloworld.txt`\n\n## EJS / input\n\nBy default all files suffixed with `.hop.ejs` will be parsed with EJS. Other files will be copied as is.\nThe data specified in the input cli option will be used as EJS data and therefore can be accessed in the template files.\n\nExample content of `templates/example/helloworld.txt.hop.ejs`:\n```ejs\nHello \u003c%= input.userName %\u003e\n\n\u003c% if (input.userAge \u003c 18) { %\u003e\n  Sorry but you are too young.\n\u003c% } %\u003e\n```\n\nThis file will be scaffolded with the name `helloworld.txt` (hoppla removes the `.hop.ejs` suffix).\n\n## Anatomy of a template\nA template is just a folder with content that will be copied recursively to a destination.\n   \n### Configuration\nThere are three places to configure your template:\n\n### global per template\nYou can add a file with the name `hopplaconfig` to the root of your template. This file will not be copied to the destination. The contents of the file is a HSJON or JSON object which can have the following options:\n\n```js\n{\n  // Default input data used for EJS. Input data provided with the cli option will be merged over the input data specified here.\n  input: {\n    aVariable: 'defaultValue'\n  }\n\n  // Files and folders matching these globs will not be copied to the destination\n  // Keep in mind: this gets checked before the files will be renamed (in case there is a hopplaconfig fileName).\n  // Think it like this: if you have an excluded file, its hopplaconfig is ignored too!\n  excludeGlobs: [ '**/tmp', 'TODOS.md' ] \n\n  // Files matching these globs will not be parsed with EJS\n  // By default only files suffixed with \".hop.ejs\" are parsed with EJS!\n  rawGlobs: [ '**/*.png', '**/*.zip' ]\n\n  // Custom javascript which will be executed at the start of hoppla, after the tmp directory is created and still empty\n  init: 'console.log(\"Hello world\")'\n\n  // Custom javascript which will be executed, before the template files are copied from the tmp directory to the destination\n  prepare: 'console.log(\"How are you?\")'\n\n  // Custom javascript which will be executed at the end of hoppla, after the template files are copied to the destination\n  finalize: 'console.log(\"Goodby\")'\n}\n```\n\n### local inline per file\nComing back to the helloworld.txt.hop.ejs file from the Basics chapter.\nIts content can look like this:\n\n```\n###hopplaconfig {\n  // Create the file in the destination with a custom filename.\n  // The string will be joined with the parent folder name before interpretation, making it viable\n  // for various tricks like:\n  // '.' (a folder would in this case copy its contents into its parent)\n  // 'newFolderX/newFolderY/hello.txt' (the file would be copied into the completely new folders newFolderX/newFolderY)\n  fileName: 'hello.\u003c%= input.userName %\u003e.txt',\n  // Can be set to \"true\" and the file will not be copied to the destination\n  exclude: false,\n  // Copy file as is / not EJS output\n  raw: false,\n  // Custom javascript hook (have a look at the specific chapter)\n  generate: 'console.log(hoppla.input.userName); return hoppla.generate(hoppla.input);'\n} hopplaconfig###\nHello \u003c%= input.userName %\u003e\n```\n\nThe ###hopplaconfig hopplaconfig### block is not included in the destination output.\n\n### local separate config per file\nEvery file in the template accepts a second file with the name `filename.hopplaconfig`. `filename` is the filename of the file to configure (without the `.hop.ejs` suffix). So the hopplaconfig filename for our example `helloworld.txt.hop.ejs` is `helloworld.txt.hopplaconfig`\n\nThis is espacially useful for folders and binary files which cannot be configured inline.\n\nThe content of the separate config file is JSON or HJSON. The options are the same as if you use the inline configuration.\n\n## Custom javascript\nInside of your custom javascript you have access to a `hoppla` variable which is an object with several properties:\n\n### prepare / finalize\nCustomize the template with javascript at the start and end of the hoppla process.\n\n```js\n{\n  input: { hello: 'world' }\n  prepare: 'console.log(\"prepareHopplaObj\", hoppla)'\n  /*\n   * Output: \n   * 'prepareHopplaObj' {\n   *   input: { hello: 'world' },\n   *   template: '/home/ubuntu/projects/templates/helloworld',\n   *   tmp: '/home/ubuntu/projects/templates/new-helloworld/tmp-hoppla/helloworld'\n   *   destination: '/home/ubuntu/projects/new-helloworld'\n   *   require: Function\n   * }\n   */\n  finalize: 'console.log(\"finalizeHopplaObj\", hoppla)'\n  /*\n   * Output: \n   * 'finalizeHopplaObj' {\n   *   error: Error\n   *   input: { hello: 'world' },\n   *   template: '/home/ubuntu/projects/templates/helloworld',\n   *   destination: '/home/ubuntu/projects/new-helloworld'\n   *   require: Function\n   * }\n   */\n}\n```\n\n### generate\nThis is a hook where you can add file specific javascript. In the js context is a hoppla object with a generate function.\nEverytime this generate function is called, it will create a copy of the file (counter.txt in the example) and interpret \nthe new copy  with the input you specified as the first argument of hoppla.generate.\nOnly the generated copies will be finally copied to the destination!\n\nThis allows you to not only add extra input variables from js for a single file but also to generate \nmultiple copies of the file with new file names.\n\nExample:\n\ncounter.txt:\n```\n###hopplaconfig {\n  fileName: 'count.\u003c%= input.count %\u003e.txt'\n  // Always return the promise from hoppla.generate!\n  generate: 'return hoppla.require('tpl-helpers/hello.js')(hoppla)'\n} ###hopplaconfig\nCounting \u003c%= input.count %\u003e\n```\n\ntpl-helpers/hello.js:\n```js\nmodule.exports = function(hoppla) {\n  console.log('generateHopplaObj', hoppla);\n  /*\n   * Output:\n   * 'generateHopplaObj' {\n   *   generate: Function,\n   *   input: { userName: 'john' }\n   *   require: Function\n   * }\n   */\n\n  var promise = Promise.resolve();\n  for(var i = 0; i \u003c 3; i++) {\n    promise = promise.then(() =\u003e {\n      input.count = i;\n      // Creates a temporary copy of counter.txt which will be parsed with the new input\n      // The copy will NOT recursively interpret the generate =)\n      // hoppla.generate is asynchronous and returns a Promise!\n      return hoppla.generate(input);  \n    })\n  }\n\n  return promise;\n}\n```\n\nDestination result:\n* count.0.txt\n* count.1.txt\n* count.2.txt\n\n**If you use the generate option, you also have to use the hoppla.generate function atleast once. Otherwise the file would not be copied to the destination.**\n\n#### Asynchronous javascript\nYou can just return a promise in custom-js options:\n\n```js\n{\n  prepare: 'return Promise.resolve().then(() =\u003e { console.log(\"async\") })'\n}\n```\n\n#### hoppla.require\nUse this to require other javascript files from your template. The path is relative to the template directory.\n\n```js\n{\n  excludeGlobs: [ 'tpl-helpers' ]\n  prepare: 'hoppla.require(\"tpl-helpers/prepare.js\")()'\n}\n```\n\n#### hoppla.error\nOnly exists in the hoppla object of finalize. If hoppla somewhere throwed an error, it will be accessible in `hoppla.error`. This allows you to add sensible logic to your custom javascript:\n\n```js\n{\n  finalize: 'if (hoppla.error) console.log(\"Please restart windows\")'\n}\n```\n\n#### hoppla.call\nOnly exists in the hoppla object of prepare/finalize. Use this to call separate hoppla-js processes. Check out the \"Call hoppla from javascript\" chapter for more details.\n\n## Call hoppla from javascript\nInstead of using the cli for hoppla, you also can instead require and call it with javascript like in this example:\n\n```js\nconst { hoppla } = require('hoppla');\nPromise.resolve()\n  .then(() =\u003e {\n    return hoppla({\n      input: {\n        userName: 'john'\n      },\n      template: 'folder/to/template',\n      destination: 'folder/to/destination'\n      // force: true,\n      // ejsOptions: { customEjsOptions... }\n    })\n  })\n  .then(() =\u003e {\n    // hoppla is done\n  })\n```\n\n## Manually use hoppla's recursive directory copy method\nYou can use copyRecursive, to recursive merge two directories together. Files only existing in the destination will be kept there.\n\n```js\nconst path = require('path');\nconst { copyRecursive } = require('hoppla');\n\n// Merges proj/your-src-dir into proj/your-dest-dir\ncopyRecursive({\n  source: path.join(__dirname, 'your-src-dir'),\n  destination: path.join(__dirname, 'your-dest-dir'),\n  force: true, // overwrite files, default: false\n  silent: true, // only output errors, default: false\n  baseDir: '/', // logs are relative to this dir, default: your-dest-dir/..\n  exclude: [\n    '**/.git'\n  ]\n})\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhopplajs%2Fhoppla","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhopplajs%2Fhoppla","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhopplajs%2Fhoppla/lists"}