├── .changeset └── config.json ├── .eslintrc.cjs ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierrc ├── .yarn └── releases │ └── yarn-4.0.2.cjs ├── .yarnrc.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── README.zh.md ├── lerna.json ├── package.json ├── packages ├── node-plop │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── build-scripts │ │ └── clean.js │ ├── package.json │ ├── src │ │ ├── actions │ │ │ ├── _common-action-add-file.js │ │ │ ├── _common-action-interface-check.js │ │ │ ├── _common-action-utils.js │ │ │ ├── add.js │ │ │ ├── addMany.js │ │ │ ├── append.js │ │ │ ├── index.js │ │ │ └── modify.js │ │ ├── baked-in-helpers.js │ │ ├── fs-promise-proxy.js │ │ ├── generator-runner.js │ │ ├── index.js │ │ ├── node-plop.js │ │ └── prompt-bypass.js │ ├── tests │ │ ├── abort-on-fail │ │ │ └── abort-on-fail.spec.js │ │ ├── action-data-cleanup │ │ │ └── action-data-cleanup.spec.js │ │ ├── action-force-add │ │ │ └── action-force-add.spec.js │ │ ├── add-action-binary-file │ │ │ ├── add-action-binary-file.spec.js │ │ │ └── plop-logo.png │ │ ├── add-action-executable-file │ │ │ ├── add-action-executable-file.spec.js │ │ │ └── plop-templates │ │ │ │ └── add.sh │ │ ├── add-action-failure │ │ │ └── add-action-failure.spec.js │ │ ├── add-action-no-template │ │ │ └── add-action-no-template.spec.js │ │ ├── add-action-skip-function │ │ │ └── add-action-skip-function.spec.js │ │ ├── add-action-transform-function │ │ │ └── add-action-transform-function.spec.js │ │ ├── addMany-action-transform-function │ │ │ ├── addMany-action-transform-function.spec.js │ │ │ ├── file1.txt.hbs │ │ │ └── file2.txt.hbs │ │ ├── addMany-dynamic-template-file │ │ │ ├── addMany-dynamic-template-file.spec.js │ │ │ ├── plop-templates │ │ │ │ ├── bar-chart │ │ │ │ │ ├── helpers │ │ │ │ │ │ ├── not-included.txt │ │ │ │ │ │ └── {{dashCase name}}.js │ │ │ │ │ ├── {{dashCase name}}-bar-ctrl.js │ │ │ │ │ └── {{dashCase name}}-bar-tmpl.html │ │ │ │ └── line-chart │ │ │ │ │ ├── {{dashCase name}}-line-ctrl.js │ │ │ │ │ └── {{dashCase name}}-line-tmpl.html │ │ │ └── plopfile.js │ │ ├── addMany-executable-file │ │ │ ├── addMany-executable-file.spec.js │ │ │ ├── plop-templates │ │ │ │ └── {{dashCase executableName}}.sh │ │ │ └── plopfile.js │ │ ├── addMany-multiple-files │ │ │ ├── addMany-multiple-files.spec.js │ │ │ ├── plop-templates │ │ │ │ ├── .gitignore │ │ │ │ ├── add.txt │ │ │ │ ├── another-add.txt │ │ │ │ ├── components │ │ │ │ │ ├── logic │ │ │ │ │ │ ├── {{dashCase name}}-ctrl.js │ │ │ │ │ │ ├── {{dashCase name}}-plop-logo.png │ │ │ │ │ │ ├── {{dashCase name}}-tmpl.html │ │ │ │ │ │ └── {{dashCase name}}-view.js.hbs │ │ │ │ │ └── tests │ │ │ │ │ │ └── {{dashCase name}}._test.js │ │ │ │ └── nested-folder │ │ │ │ │ ├── a-nested-add.txt │ │ │ │ │ ├── another-nested-add.txt │ │ │ │ │ └── my-name-is-{{dashCase name}}.txt │ │ │ └── plopfile.js │ │ ├── addMany-non-verbose │ │ │ ├── addMany-non-verbose.spec.js │ │ │ ├── plop-templates │ │ │ │ ├── .gitignore │ │ │ │ ├── add.txt │ │ │ │ ├── another-add.txt │ │ │ │ ├── components │ │ │ │ │ ├── logic │ │ │ │ │ │ ├── {{dashCase name}}-ctrl.js │ │ │ │ │ │ ├── {{dashCase name}}-plop-logo.png │ │ │ │ │ │ └── {{dashCase name}}-tmpl.html │ │ │ │ │ └── tests │ │ │ │ │ │ └── {{dashCase name}}._test.js │ │ │ │ └── nested-folder │ │ │ │ │ ├── a-nested-add.txt │ │ │ │ │ ├── another-nested-add.txt │ │ │ │ │ └── my-name-is-{{dashCase name}}.txt │ │ │ └── plopfile.js │ │ ├── addMany-strip-extensions │ │ │ ├── addMany-strip-extensions.spec.js │ │ │ ├── plop-templates │ │ │ │ ├── remove-all │ │ │ │ │ ├── my-view._test.js │ │ │ │ │ └── my-view._test.js.hbs │ │ │ │ ├── remove-dotfile-hbs │ │ │ │ │ ├── .eslintrc.cjs.hbs │ │ │ │ │ └── .gitignore.hbs │ │ │ │ └── remove-hbs │ │ │ │ │ ├── {{dashCase name}}-my-view._test.js │ │ │ │ │ └── {{dashCase name}}-my-view.js.hbs │ │ │ └── plopfile.js │ │ ├── append-empty │ │ │ ├── append-empty.spec.js │ │ │ └── plopfile.js │ │ ├── append │ │ │ ├── append.spec.js │ │ │ ├── package.json │ │ │ ├── plop-templates │ │ │ │ └── list.txt │ │ │ └── plopfile.js │ │ ├── basic-no-plopfile │ │ │ └── basic-no-plopfile.spec.js │ │ ├── basic-plopfile │ │ │ ├── basic-plopfile.spec.js │ │ │ ├── package.json │ │ │ ├── plop-templates │ │ │ │ ├── add.txt │ │ │ │ ├── change-me.txt │ │ │ │ └── part.txt │ │ │ └── plopfile.js │ │ ├── custom-data-in-actions │ │ │ ├── custom-data-in-actions.spec.js │ │ │ ├── plop-templates │ │ │ │ └── who-loves-who.txt │ │ │ └── plopfile.js │ │ ├── dynamic-actions │ │ │ ├── dynamic-actions.spec.js │ │ │ ├── plop-templates │ │ │ │ ├── burger.txt │ │ │ │ └── potatoes.txt │ │ │ └── plopfile.js │ │ ├── dynamic-prompts │ │ │ ├── dynamic-prompts.spec.js │ │ │ └── plopfile.js │ │ ├── dynamic-template-file │ │ │ ├── dynamic-template-file.spec.js │ │ │ ├── plop-templates │ │ │ │ ├── bar-chart.txt │ │ │ │ ├── change-me.txt │ │ │ │ └── line-chart.txt │ │ │ └── plopfile.js │ │ ├── esm-plopfile │ │ │ ├── esm-plopfile.spec.js │ │ │ ├── package.json │ │ │ ├── plop-templates │ │ │ │ ├── add.txt │ │ │ │ ├── change-me.txt │ │ │ │ └── part.txt │ │ │ ├── plopfile-cjs.js │ │ │ ├── plopfile-cjs.mjs │ │ │ ├── plopfile.cjs │ │ │ ├── plopfile.js │ │ │ └── plopfile.mjs │ │ ├── force-del-outside-cwd │ │ │ ├── force-del-outside-cwd.spec.js │ │ │ └── sub │ │ │ │ └── plopfile.js │ │ ├── generator-name-and-prompts │ │ │ └── generator-name-and-prompts.spec.js │ │ ├── get-generator-list │ │ │ └── get-generator-list.spec.js │ │ ├── helpers │ │ │ └── path.js │ │ ├── imported-custom-action │ │ │ ├── custom-action.js │ │ │ └── imported-custom-action.spec.js │ │ ├── invalid-generator-names │ │ │ └── invalid-generator-names.spec.js │ │ ├── lifecycle-hooks │ │ │ └── lifecycle-hooks.spec.js │ │ ├── load-assets-from-pack │ │ │ ├── load-assets-from-pack.spec.js │ │ │ └── plopfile.js │ │ ├── load-assets-from-plopfile │ │ │ ├── load-assets-from-plopfile.spec.js │ │ │ └── plopfile.js │ │ ├── load-nested-plopfile-generators │ │ │ ├── load-nested-plopfile-generators.spec.js │ │ │ ├── nested │ │ │ │ ├── nested-plopfile.js │ │ │ │ └── plop-templates │ │ │ │ │ └── nested-test.txt │ │ │ ├── plop-templates │ │ │ │ └── test.txt │ │ │ └── plopfile.js │ │ ├── missing-action-path │ │ │ └── missing-action-path.spec.js │ │ ├── modify-action-transform-function │ │ │ └── modify-action-transform-function.spec.js │ │ ├── prompt-bypass-checkbox │ │ │ └── prompt-bypass-checkbox.spec.js │ │ ├── prompt-bypass-confirm │ │ │ └── prompt-bypass-confirm.spec.js │ │ ├── prompt-bypass-list │ │ │ └── prompt-bypass-list.spec.js │ │ ├── prompt-bypass-mixed │ │ │ └── prompt-bypass-mixed.spec.js │ │ ├── prompt-bypass-validate │ │ │ └── prompt-bypass-validate.spec.js │ │ └── set-generator-returns-generator │ │ │ └── set-generator-returns-generator.ava.js │ ├── tsconfig.json │ ├── types │ │ ├── index.d.ts │ │ ├── test.ts │ │ └── tsconfig.json │ └── vite.config.ts └── plop │ ├── .gitattributes │ ├── .gitignore │ ├── .nycrc │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ └── plop.js │ ├── package.json │ ├── scripts │ ├── postpublish.js │ └── prepublishOnly.js │ ├── src │ ├── bypass.js │ ├── console-out.js │ ├── input-processing.js │ ├── plop.d.ts │ └── plop.js │ ├── tests │ ├── __snapshots__ │ │ └── input-processing.spec.js.snap │ ├── action-failure.spec.js │ ├── actions.spec.js │ ├── config │ │ └── setup.js │ ├── esm.spec.js │ ├── examples │ │ ├── action-failure │ │ │ ├── package.json │ │ │ └── plopfile.js │ │ ├── add-action │ │ │ ├── output │ │ │ │ └── .gitkeep │ │ │ ├── package.json │ │ │ ├── plopfile.js │ │ │ └── templates │ │ │ │ ├── to-add-change.txt │ │ │ │ └── to-add.txt │ │ ├── cjs-js │ │ │ ├── package.json │ │ │ └── plopfile.js │ │ ├── cjs │ │ │ ├── package.json │ │ │ └── plopfile.cjs │ │ ├── esm │ │ │ ├── package.json │ │ │ └── plopfile.js │ │ ├── javascript │ │ │ ├── package.json │ │ │ ├── plopfile.js │ │ │ └── templates │ │ │ │ ├── burger.txt │ │ │ │ ├── change-me.txt │ │ │ │ ├── part.txt │ │ │ │ ├── potatoes.txt │ │ │ │ └── temp.txt │ │ ├── mjs │ │ │ ├── package.json │ │ │ └── plopfile.mjs │ │ ├── prompt-only │ │ │ ├── package.json │ │ │ └── plopfile.js │ │ ├── typescript │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ ├── plopfile.ts │ │ │ └── tsconfig.json │ │ └── wrap-plop │ │ │ ├── index.js │ │ │ ├── output │ │ │ └── .gitkeep │ │ │ ├── package.json │ │ │ ├── plopfile.cjs │ │ │ └── templates │ │ │ └── to-add.txt │ ├── file-helper.js │ ├── input-processing.spec.js │ ├── render.js │ ├── typescript.spec.js │ └── wrapper.spec.js │ └── vite.config.ts ├── plop-load.md ├── plop-templates ├── node-plop-test.js └── plop-test.js ├── plopfile.js ├── tsconfig.json ├── turbo.json └── yarn.lock /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "plopjs/plop" }], 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const ts = { 2 | files: ["**/*.ts"], 3 | extends: [ 4 | "plugin:prettier/recommended", 5 | "plugin:@typescript-eslint/eslint-recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | ], 8 | parser: "@typescript-eslint/parser", 9 | parserOptions: { 10 | ecmaVersion: 2018, 11 | sourceType: "module", 12 | project: "./tsconfig.json", 13 | tsconfigRootDir: __dirname, 14 | allowImportExportEverywhere: true, 15 | }, 16 | plugins: ["@typescript-eslint"], 17 | rules: { 18 | "@typescript-eslint/no-explicit-any": "off", 19 | "@typescript-eslint/no-empty-function": "off", 20 | "@typescript-eslint/no-var-requires": "off", 21 | "@typescript-eslint/no-unused-vars": "off", 22 | "prefer-const": "off" 23 | }, 24 | }; 25 | 26 | module.exports = { 27 | env: { 28 | commonjs: true, 29 | es6: true, 30 | node: true, 31 | jest: true, 32 | }, 33 | parserOptions: { 34 | sourceType: "module", 35 | ecmaVersion: 2021, 36 | allowImportExportEverywhere: true, 37 | }, 38 | extends: ["plugin:prettier/recommended"], 39 | rules: { 40 | // https://github.com/plopjs/plop/issues/288 41 | "linebreak-style": ["error", "unix"], 42 | }, 43 | overrides: [ts], 44 | }; 45 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | jobs: 11 | test: 12 | env: 13 | CI: true 14 | strategy: 15 | matrix: 16 | os: [ ubuntu-latest, windows-latest ] 17 | node: [ 18, 20 ] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: ${{ matrix.node }} 25 | cache: yarn 26 | 27 | - name: Install Dependencies 28 | run: yarn install --frozen-lockfile 29 | 30 | - name: Test 31 | run: yarn test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | .vscode/ 4 | .nyc_output/ 5 | .turbo/ 6 | .eslintcache 7 | yarn-error.log 8 | 9 | .pnp.* 10 | .yarn/* 11 | !.yarn/patches 12 | !.yarn/plugins 13 | !.yarn/releases 14 | !.yarn/sdks 15 | !.yarn/versions 16 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "semi": true 4 | } 5 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | nmHoistingLimits: workspaces 6 | 7 | nodeLinker: node-modules 8 | 9 | yarnPath: .yarn/releases/yarn-4.0.2.cjs 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | For each change made (anything not marked as a "chore"), run the following command: 2 | ``` 3 | yarn changeset add 4 | ``` 5 | 6 | This will allow you to do the following: 7 | 8 | 1) Select impacted packages 9 | 2) Add a related message 10 | 3) Notify what type of package bump is required (major, minor, patch) 11 | 12 | ------- 13 | 14 | # Releasing 15 | 16 | When a maintainer is ready to publish, run the following command to generate a changelog: 17 | 18 | ``` 19 | yarn changeset version 20 | ``` 21 | 22 | You should then verify, using your Git CLI or Git GUI, that the CHANGELOG is correct, everything is up-to-date, make a commit, and that you're ready to release! 23 | 24 | Then, once you've ran `version`, run the following command to publish to NPM: 25 | 26 | ``` 27 | yarn changeset publish 28 | ``` 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Andrew Worcester 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "npmClient": "yarn", 6 | "useWorkspaces": true, 7 | "version": "0.0.0" 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-monorepo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "workspaces": { 6 | "packages": [ 7 | "packages/*" 8 | ] 9 | }, 10 | "type": "module", 11 | "scripts": { 12 | "test": "turbo run test --parallel", 13 | "format": "eslint -c .eslintrc.cjs --fix ./", 14 | "prepare": "husky install", 15 | "plop": "node ./packages/plop/bin/plop.js" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/plopjs/plop/issues" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/plopjs/plop.git" 23 | }, 24 | "homepage": "https://plopjs.com", 25 | "license": "MIT", 26 | "engines": { 27 | "node": ">=18" 28 | }, 29 | "devDependencies": { 30 | "@changesets/changelog-github": "^0.5.0", 31 | "@changesets/cli": "^2.27.1", 32 | "@typescript-eslint/eslint-plugin": "^6.15.0", 33 | "@typescript-eslint/parser": "^6.15.0", 34 | "eslint": "^8.56.0", 35 | "eslint-config-prettier": "^9.1.0", 36 | "eslint-plugin-prettier": "^5.1.1", 37 | "husky": "^8.0.3", 38 | "lint-staged": "^15.2.0", 39 | "prettier": "^3.1.1", 40 | "turbo": "^1.11.2", 41 | "typescript": "^5.3.3" 42 | }, 43 | "lint-staged": { 44 | "*.js": "eslint --cache --fix" 45 | }, 46 | "packageManager": "yarn@4.0.2" 47 | } 48 | -------------------------------------------------------------------------------- /packages/node-plop/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Dependency directory 12 | node_modules 13 | 14 | # Optional npm cache directory 15 | .npm 16 | 17 | # Compiled src 18 | lib 19 | 20 | # Test mocks 21 | tests/*-mock/src 22 | 23 | # IDE files 24 | .idea -------------------------------------------------------------------------------- /packages/node-plop/.npmignore: -------------------------------------------------------------------------------- 1 | tests 2 | .*rc 3 | build-scripts 4 | plopfile.js 5 | plop-templates 6 | -------------------------------------------------------------------------------- /packages/node-plop/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # node-plop 2 | 3 | ## 0.32.0 4 | 5 | ### Minor Changes 6 | 7 | - [#396](https://github.com/plopjs/plop/pull/396) [`a22e33f`](https://github.com/plopjs/plop/commit/a22e33f416340352e83a1e9c0d470baf2aff1c4b) Thanks [@crutchcorn](https://github.com/crutchcorn)! - Drop support for Node 12, 14, & 16. Update all dependencies 8 | 9 | ## 0.31.1 10 | 11 | ### Patch Changes 12 | 13 | - Append action should now allow handlebars for template path 14 | 15 | * Fix addMany dotfile extension stripping 16 | 17 | - Fix Inquirer TypeScript typings 18 | 19 | * Fix empty checkboxes not bypassing properly 20 | 21 | ## 0.31.0 22 | 23 | ### Minor Changes 24 | 25 | - [#333](https://github.com/plopjs/plop/pull/333) [`d6176cc`](https://github.com/plopjs/plop/commit/d6176cce4ee57dfc18ad1c86ec467444e966567e) Thanks [@RobinKnipe](https://github.com/RobinKnipe)! - Added shorthand to load all Plop assets at once #333 26 | 27 | ## 0.30.1 28 | 29 | ### Patch Changes 30 | 31 | - Moved to monorepo 32 | -------------------------------------------------------------------------------- /packages/node-plop/README.md: -------------------------------------------------------------------------------- 1 | Node-Plop 2 | ====== 3 | 4 | [![npm](https://img.shields.io/npm/v/node-plop.svg)](https://www.npmjs.com/package/node-plop) 5 | [![GitHub actions](https://img.shields.io/github/workflow/status/plopjs/node-plop/test)](https://github.com/plopjs/node-plop/actions/workflows/test.yml) 6 | 7 | This is an early publication of the plop core logic being removed from the CLI tool. Main purpose for this is to make it easier for others to automate code generation through processes and tools OTHER than the command line. This also makes it easier to test the code functionality of PLOP without needing to test via the CLI interface. 8 | 9 | This is the backend code that drives the plop CLI tool using node-plop. 10 | 11 | ``` javascript 12 | import nodePlop from 'node-plop'; 13 | // load an instance of plop from a plopfile 14 | const plop = await nodePlop(`./path/to/plopfile.js`); 15 | // get a generator by name 16 | const basicAdd = plop.getGenerator('basic-add'); 17 | 18 | // run all the generator actions using the data specified 19 | basicAdd.runActions({name: 'this is a test'}).then(function (results) { 20 | // do something after the actions have run 21 | }); 22 | ``` 23 | -------------------------------------------------------------------------------- /packages/node-plop/build-scripts/clean.js: -------------------------------------------------------------------------------- 1 | import { deleteSync } from "del"; 2 | 3 | deleteSync("./lib"); 4 | -------------------------------------------------------------------------------- /packages/node-plop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-plop", 3 | "version": "0.32.0", 4 | "description": "programmatic plopping for fun and profit", 5 | "main": "src/index.js", 6 | "type": "module", 7 | "types": "types/index.d.ts", 8 | "scripts": { 9 | "test": "npm run test:js && npm run test:typings", 10 | "test:typings": "dtslint types --localTs ../../node_modules/typescript/lib --expectOnly", 11 | "test:js": "vitest run", 12 | "test-watch": "vitest watch", 13 | "develop": "npm run test-watch" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/plopjs/plop.git", 18 | "directory": "packages/node-plop" 19 | }, 20 | "keywords": [ 21 | "plop", 22 | "generator", 23 | "scaffolding", 24 | "node", 25 | "programmatic", 26 | "automation" 27 | ], 28 | "author": "Andrew Worcester (http://amwmedia.com)", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/plopjs/plop/issues" 32 | }, 33 | "engines": { 34 | "node": ">=18" 35 | }, 36 | "devDependencies": { 37 | "@types/inquirer-autocomplete-prompt": "^3.0.3", 38 | "@types/node": "^20.10.5", 39 | "dtslint": "^4.2.1", 40 | "plop-pack-fancy-comments": "^0.2.1", 41 | "typescript": "^5.3.3", 42 | "vitest": "^1.1.0" 43 | }, 44 | "dependencies": { 45 | "@types/inquirer": "^9.0.7", 46 | "change-case": "^5.3.0", 47 | "del": "^7.1.0", 48 | "globby": "^14.0.0", 49 | "handlebars": "^4.7.8", 50 | "inquirer": "^9.2.12", 51 | "isbinaryfile": "^5.0.0", 52 | "lodash.get": "^4.4.2", 53 | "mkdirp": "^3.0.1", 54 | "resolve": "^1.22.8", 55 | "title-case": "^4.2.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/node-plop/src/actions/_common-action-add-file.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { deleteAsync } from "del"; 3 | import { 4 | getRenderedTemplate, 5 | getTransformedTemplate, 6 | makeDestPath, 7 | throwStringifiedError, 8 | getRelativeToBasePath, 9 | } from "./_common-action-utils.js"; 10 | import { isBinaryFileSync } from "isbinaryfile"; 11 | import * as fspp from "../fs-promise-proxy.js"; 12 | 13 | export default async function addFile(data, cfg, plop) { 14 | const fileDestPath = makeDestPath(data, cfg, plop); 15 | const { force, skipIfExists = false } = cfg; 16 | try { 17 | // check path 18 | let destExists = await fspp.fileExists(fileDestPath); 19 | 20 | // if we are forcing and the file already exists, delete the file 21 | if (force === true && destExists) { 22 | await deleteAsync([fileDestPath], { force }); 23 | destExists = false; 24 | } 25 | 26 | // we can't create files where one already exists 27 | if (destExists) { 28 | if (skipIfExists) { 29 | return `[SKIPPED] ${fileDestPath} (exists)`; 30 | } 31 | throw `File already exists\n -> ${fileDestPath}`; 32 | } else { 33 | await fspp.makeDir(path.dirname(fileDestPath)); 34 | 35 | const absTemplatePath = 36 | (cfg.templateFile && 37 | path.resolve(plop.getPlopfilePath(), cfg.templateFile)) || 38 | null; 39 | 40 | if (absTemplatePath != null && isBinaryFileSync(absTemplatePath)) { 41 | const rawTemplate = await fspp.readFileRaw(cfg.templateFile); 42 | await fspp.writeFileRaw(fileDestPath, rawTemplate); 43 | } else { 44 | const renderedTemplate = await getRenderedTemplate(data, cfg, plop); 45 | 46 | const transformedTemplate = await getTransformedTemplate( 47 | renderedTemplate, 48 | data, 49 | cfg, 50 | ); 51 | 52 | await fspp.writeFile(fileDestPath, transformedTemplate); 53 | } 54 | 55 | // keep the executable flags 56 | if (absTemplatePath != null) { 57 | const sourceStats = await fspp.stat(absTemplatePath); 58 | const destStats = await fspp.stat(fileDestPath); 59 | const executableFlags = 60 | sourceStats.mode & 61 | (fspp.constants.S_IXUSR | 62 | fspp.constants.S_IXGRP | 63 | fspp.constants.S_IXOTH); 64 | await fspp.chmod(fileDestPath, destStats.mode | executableFlags); 65 | } 66 | } 67 | 68 | // return the added file path (relative to the destination path) 69 | return getRelativeToBasePath(fileDestPath, plop); 70 | } catch (err) { 71 | throwStringifiedError(err); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/node-plop/src/actions/_common-action-interface-check.js: -------------------------------------------------------------------------------- 1 | export default function ( 2 | action, 3 | { checkPath = true, checkAbortOnFail = true } = {}, 4 | ) { 5 | // it's not even an object, you fail! 6 | if (typeof action !== "object") { 7 | return `Invalid action object: ${JSON.stringify(action)}`; 8 | } 9 | 10 | const { path, abortOnFail } = action; 11 | 12 | if (checkPath && (typeof path !== "string" || path.length === 0)) { 13 | return `Invalid path "${path}"`; 14 | } 15 | 16 | // abortOnFail is optional, but if it's provided it needs to be a Boolean 17 | if ( 18 | checkAbortOnFail && 19 | abortOnFail !== undefined && 20 | typeof abortOnFail !== "boolean" 21 | ) { 22 | return `Invalid value for abortOnFail (${abortOnFail} is not a Boolean)`; 23 | } 24 | 25 | if ("transform" in action && typeof action.transform !== "function") { 26 | return `Invalid value for transform (${typeof action.transform} is not a function)`; 27 | } 28 | 29 | if ( 30 | action.type === "modify" && 31 | !("pattern" in action) && 32 | !("transform" in action) 33 | ) { 34 | return "Invalid modify action (modify must have a pattern or transform function)"; 35 | } 36 | 37 | if ("skip" in action && typeof action.skip !== "function") { 38 | return `Invalid value for skip (${typeof action.skip} is not a function)`; 39 | } 40 | 41 | return true; 42 | } 43 | -------------------------------------------------------------------------------- /packages/node-plop/src/actions/_common-action-utils.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import * as fspp from "../fs-promise-proxy.js"; 3 | 4 | const getFullData = (data, cfg) => Object.assign({}, cfg.data, data); 5 | 6 | export const normalizePath = (path) => { 7 | return !path.sep || path.sep === "\\" ? path.replace(/\\/g, "/") : path; 8 | }; 9 | 10 | export const makeDestPath = (data, cfg, plop) => { 11 | return path.resolve( 12 | plop.getDestBasePath(), 13 | plop.renderString(normalizePath(cfg.path) || "", getFullData(data, cfg)), 14 | ); 15 | }; 16 | 17 | export function getRenderedTemplatePath(data, cfg, plop) { 18 | if (cfg.templateFile) { 19 | const absTemplatePath = path.resolve( 20 | plop.getPlopfilePath(), 21 | cfg.templateFile, 22 | ); 23 | return plop.renderString( 24 | normalizePath(absTemplatePath), 25 | getFullData(data, cfg), 26 | ); 27 | } 28 | return null; 29 | } 30 | 31 | export async function getTemplate(data, cfg, plop) { 32 | const makeTmplPath = (p) => path.resolve(plop.getPlopfilePath(), p); 33 | 34 | let { template } = cfg; 35 | 36 | if (cfg.templateFile) { 37 | template = await fspp.readFile(makeTmplPath(cfg.templateFile)); 38 | } 39 | if (template == null) { 40 | template = ""; 41 | } 42 | 43 | return template; 44 | } 45 | 46 | export async function getRenderedTemplate(data, cfg, plop) { 47 | const template = await getTemplate(data, cfg, plop); 48 | 49 | return plop.renderString(template, getFullData(data, cfg)); 50 | } 51 | 52 | export const getRelativeToBasePath = (filePath, plop) => 53 | filePath.replace(path.resolve(plop.getDestBasePath()), ""); 54 | 55 | export const throwStringifiedError = (err) => { 56 | if (typeof err === "string") { 57 | throw err; 58 | } else { 59 | throw err.message || JSON.stringify(err); 60 | } 61 | }; 62 | 63 | export async function getTransformedTemplate(template, data, cfg) { 64 | // transform() was already typechecked at runtime in interface check 65 | if ("transform" in cfg) { 66 | const result = await cfg.transform(template, data); 67 | 68 | if (typeof result !== "string") 69 | throw new TypeError( 70 | `Invalid return value for transform (${JSON.stringify( 71 | result, 72 | )} is not a string)`, 73 | ); 74 | 75 | return result; 76 | } else { 77 | return template; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/node-plop/src/actions/add.js: -------------------------------------------------------------------------------- 1 | import actionInterfaceTest from "./_common-action-interface-check.js"; 2 | import addFile from "./_common-action-add-file.js"; 3 | import { getRenderedTemplatePath } from "./_common-action-utils.js"; 4 | 5 | export default async function (data, cfg, plop) { 6 | const interfaceTestResult = actionInterfaceTest(cfg); 7 | if (interfaceTestResult !== true) { 8 | throw interfaceTestResult; 9 | } 10 | 11 | cfg.templateFile = getRenderedTemplatePath(data, cfg, plop); 12 | 13 | return await addFile(data, cfg, plop); 14 | } 15 | -------------------------------------------------------------------------------- /packages/node-plop/src/actions/addMany.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs"; 3 | import { globbySync } from "globby"; 4 | import actionInterfaceTest from "./_common-action-interface-check.js"; 5 | import addFile from "./_common-action-add-file.js"; 6 | import { normalizePath } from "./_common-action-utils.js"; 7 | 8 | const defaultConfig = { 9 | verbose: true, 10 | stripExtensions: ["hbs"], 11 | }; 12 | 13 | export default async function (data, userConfig, plop) { 14 | // shallow-merge default config and input config 15 | const cfg = Object.assign({}, defaultConfig, userConfig); 16 | // check the common action interface attributes. skip path check because it's NA 17 | const interfaceTestResult = actionInterfaceTest(cfg, { checkPath: false }); 18 | if (interfaceTestResult !== true) { 19 | throw interfaceTestResult; 20 | } 21 | // check that destination (instead of path) is a string value 22 | const dest = cfg.destination; 23 | if (typeof dest !== "string" || dest.length === 0) { 24 | throw `Invalid destination "${dest}"`; 25 | } 26 | 27 | if (cfg.base) { 28 | cfg.base = plop.renderString(cfg.base, data); 29 | } 30 | 31 | if (typeof cfg.templateFiles === "function") { 32 | cfg.templateFiles = cfg.templateFiles(); 33 | } 34 | 35 | cfg.templateFiles = [] 36 | .concat(cfg.templateFiles) // Ensure `cfg.templateFiles` is an array, even if a string is passed. 37 | .map((file) => plop.renderString(file, data)); // render the paths as hbs templates 38 | 39 | const templateFiles = resolveTemplateFiles( 40 | cfg.templateFiles, 41 | cfg.base, 42 | cfg.globOptions, 43 | plop, 44 | ); 45 | 46 | const filesAdded = []; 47 | for (let templateFile of templateFiles) { 48 | const absTemplatePath = path.resolve(plop.getPlopfilePath(), templateFile); 49 | const fileCfg = Object.assign({}, cfg, { 50 | path: stripExtensions( 51 | cfg.stripExtensions, 52 | resolvePath(cfg.destination, templateFile, cfg.base), 53 | ), 54 | templateFile: absTemplatePath, 55 | }); 56 | const addedPath = await addFile(data, fileCfg, plop); 57 | filesAdded.push(addedPath); 58 | } 59 | 60 | const summary = `${filesAdded.length} files added`; 61 | if (!cfg.verbose) return summary; 62 | else return `${summary}\n -> ${filesAdded.join("\n -> ")}`; 63 | } 64 | 65 | function resolveTemplateFiles(templateFilesGlob, basePath, globOptions, plop) { 66 | globOptions = Object.assign({ cwd: plop.getPlopfilePath() }, globOptions); 67 | return globbySync( 68 | templateFilesGlob, 69 | Object.assign({ braceExpansion: false }, globOptions), 70 | ) 71 | .filter(isUnder(basePath)) 72 | .filter(isAbsoluteOrRelativeFileTo(plop.getPlopfilePath())); 73 | } 74 | function isAbsoluteOrRelativeFileTo(relativePath) { 75 | const isFile = (file) => fs.existsSync(file) && fs.lstatSync(file).isFile(); 76 | return (file) => isFile(file) || isFile(path.join(relativePath, file)); 77 | } 78 | 79 | function isUnder(basePath = "") { 80 | return (path) => path.startsWith(basePath); 81 | } 82 | 83 | function resolvePath(destination, file, rootPath) { 84 | return normalizePath( 85 | path.join(destination, dropFileRootPath(file, rootPath)), 86 | ); 87 | } 88 | 89 | function dropFileRootPath(file, rootPath) { 90 | return rootPath ? file.replace(rootPath, "") : dropFileRootFolder(file); 91 | } 92 | 93 | function dropFileRootFolder(file) { 94 | const fileParts = path.normalize(file).split(path.sep); 95 | fileParts.shift(); 96 | 97 | return fileParts.join(path.sep); 98 | } 99 | 100 | function stripExtensions(shouldStrip, fileName) { 101 | const maybeFile = path.parse(fileName); 102 | 103 | if ( 104 | Array.isArray(shouldStrip) && 105 | !shouldStrip.map((item) => `.${item}`).includes(maybeFile.ext) 106 | ) 107 | return fileName; 108 | 109 | return path.parse(maybeFile.name).ext !== "" || maybeFile.name.startsWith(".") 110 | ? path.join(maybeFile.dir, maybeFile.name) 111 | : fileName; 112 | } 113 | -------------------------------------------------------------------------------- /packages/node-plop/src/actions/append.js: -------------------------------------------------------------------------------- 1 | import * as fspp from "../fs-promise-proxy.js"; 2 | 3 | import { 4 | getRenderedTemplate, 5 | getRenderedTemplatePath, 6 | makeDestPath, 7 | throwStringifiedError, 8 | getRelativeToBasePath, 9 | } from "./_common-action-utils.js"; 10 | 11 | import actionInterfaceTest from "./_common-action-interface-check.js"; 12 | 13 | const doAppend = async function (data, cfg, plop, fileData) { 14 | const stringToAppend = await getRenderedTemplate(data, cfg, plop); 15 | // if the appended string should be unique (default), 16 | // remove any occurence of it (but only if pattern would match) 17 | 18 | const { separator = "\n" } = cfg; 19 | if (cfg.unique !== false) { 20 | // only remove after "pattern", so that we remove not too much accidentally 21 | const parts = fileData.split(cfg.pattern); 22 | const lastPart = parts[parts.length - 1]; 23 | const lastPartWithoutDuplicates = lastPart.replace( 24 | new RegExp(separator + stringToAppend, "g"), 25 | "", 26 | ); 27 | fileData = fileData.replace(lastPart, lastPartWithoutDuplicates); 28 | } 29 | 30 | // add the appended string to the end of the "fileData" if "pattern" 31 | // was not provided, i.e. null or false 32 | if (!cfg.pattern) { 33 | // make sure to add a "separator" if "fileData" is not empty 34 | if (fileData.length > 0) { 35 | fileData += separator; 36 | } 37 | return fileData + stringToAppend; 38 | } 39 | 40 | return fileData.replace(cfg.pattern, "$&" + separator + stringToAppend); 41 | }; 42 | 43 | export default async function (data, cfg, plop) { 44 | const interfaceTestResult = actionInterfaceTest(cfg); 45 | if (interfaceTestResult !== true) { 46 | throw interfaceTestResult; 47 | } 48 | const fileDestPath = makeDestPath(data, cfg, plop); 49 | try { 50 | // check path 51 | const pathExists = await fspp.fileExists(fileDestPath); 52 | if (!pathExists) { 53 | throw "File does not exist"; 54 | } else { 55 | let fileData = await fspp.readFile(fileDestPath); 56 | cfg.templateFile = getRenderedTemplatePath(data, cfg, plop); 57 | fileData = await doAppend(data, cfg, plop, fileData); 58 | await fspp.writeFile(fileDestPath, fileData); 59 | } 60 | return getRelativeToBasePath(fileDestPath, plop); 61 | } catch (err) { 62 | throwStringifiedError(err); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/node-plop/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import add from "./add.js"; 2 | import addMany from "./addMany.js"; 3 | import modify from "./modify.js"; 4 | import append from "./append.js"; 5 | 6 | export { add, addMany, modify, append }; 7 | -------------------------------------------------------------------------------- /packages/node-plop/src/actions/modify.js: -------------------------------------------------------------------------------- 1 | import * as fspp from "../fs-promise-proxy.js"; 2 | import { 3 | getRenderedTemplate, 4 | makeDestPath, 5 | throwStringifiedError, 6 | getRelativeToBasePath, 7 | getRenderedTemplatePath, 8 | getTransformedTemplate, 9 | } from "./_common-action-utils.js"; 10 | 11 | import actionInterfaceTest from "./_common-action-interface-check.js"; 12 | 13 | export default async function (data, cfg, plop) { 14 | const interfaceTestResult = actionInterfaceTest(cfg); 15 | if (interfaceTestResult !== true) { 16 | throw interfaceTestResult; 17 | } 18 | const fileDestPath = makeDestPath(data, cfg, plop); 19 | try { 20 | // check path 21 | const pathExists = await fspp.fileExists(fileDestPath); 22 | 23 | if (!pathExists) { 24 | throw "File does not exist"; 25 | } else { 26 | let fileData = await fspp.readFile(fileDestPath); 27 | cfg.templateFile = getRenderedTemplatePath(data, cfg, plop); 28 | const replacement = await getRenderedTemplate(data, cfg, plop); 29 | 30 | if (typeof cfg.pattern === "string" || cfg.pattern instanceof RegExp) { 31 | fileData = fileData.replace(cfg.pattern, replacement); 32 | } 33 | 34 | const transformed = await getTransformedTemplate(fileData, data, cfg); 35 | await fspp.writeFile(fileDestPath, transformed); 36 | } 37 | return getRelativeToBasePath(fileDestPath, plop); 38 | } catch (err) { 39 | throwStringifiedError(err); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/node-plop/src/baked-in-helpers.js: -------------------------------------------------------------------------------- 1 | import { 2 | camelCase, 3 | snakeCase, 4 | dotCase, 5 | pathCase, 6 | sentenceCase, 7 | constantCase, 8 | kebabCase, 9 | pascalCase, 10 | } from "change-case"; 11 | import { titleCase } from "title-case"; 12 | 13 | export default { 14 | camelCase: camelCase, 15 | snakeCase: snakeCase, 16 | dotCase: dotCase, 17 | pathCase: pathCase, 18 | lowerCase: (str) => str.toUpperCase(), 19 | upperCase: (str) => str.toLowerCase(), 20 | sentenceCase: sentenceCase, 21 | constantCase: constantCase, 22 | titleCase: titleCase, 23 | 24 | dashCase: kebabCase, 25 | kabobCase: kebabCase, 26 | kebabCase: kebabCase, 27 | 28 | properCase: pascalCase, 29 | pascalCase: pascalCase, 30 | }; 31 | -------------------------------------------------------------------------------- /packages/node-plop/src/fs-promise-proxy.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { mkdirp } from "mkdirp"; 3 | 4 | export const makeDir = mkdirp; 5 | export const readdir = fs.promises.readdir; 6 | export const stat = fs.promises.stat; 7 | export const chmod = fs.promises.chmod; 8 | export const readFile = (path) => fs.promises.readFile(path, "utf8"); 9 | export const writeFile = (path, data) => 10 | fs.promises.writeFile(path, data, "utf8"); 11 | export const readFileRaw = (path) => fs.promises.readFile(path, null); 12 | export const writeFileRaw = (path, data) => 13 | fs.promises.writeFile(path, data, null); 14 | export const fileExists = (path) => 15 | fs.promises.access(path).then( 16 | () => true, 17 | () => false, 18 | ); 19 | 20 | export const constants = fs.constants; 21 | -------------------------------------------------------------------------------- /packages/node-plop/src/index.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "./node-plop.js"; 2 | 3 | /** 4 | * Main node-plop module 5 | * 6 | * @param {string} plopfilePath - The absolute path to the plopfile we are interested in working with 7 | * @param {object} plopCfg - A config object to be passed into the plopfile when it's executed 8 | * @returns {object} the node-plop API for the plopfile requested 9 | */ 10 | export default nodePlop; 11 | -------------------------------------------------------------------------------- /packages/node-plop/tests/abort-on-fail/abort-on-fail.spec.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | 3 | describe("abort-on-fail", () => { 4 | let plop; 5 | beforeEach(async () => { 6 | plop = await nodePlop(); 7 | }); 8 | 9 | ///// 10 | // if an action has no path, the action should fail 11 | // 12 | test("Check that abortOnFail:true prevents future actions", async function () { 13 | plop.setGenerator("abort-on-fail-true", { 14 | actions: [{ abortOnFail: true }, {}], 15 | }); 16 | 17 | const results = await plop 18 | .getGenerator("abort-on-fail-true") 19 | .runActions({}); 20 | const { changes, failures } = results; 21 | 22 | expect(changes.length).toBe(0); 23 | expect(failures.length).toBe(2); 24 | expect(failures[0].error).toBe("Invalid action (#1)"); 25 | expect(failures[1].error).toBe("Aborted due to previous action failure"); 26 | }); 27 | 28 | test("Check that abortOnFail:false does not prevent future actions", async function () { 29 | plop.setGenerator("abort-on-fail-false", { 30 | actions: [{ abortOnFail: false }, {}], 31 | }); 32 | 33 | const results = await plop 34 | .getGenerator("abort-on-fail-false") 35 | .runActions({}); 36 | const { changes, failures } = results; 37 | 38 | expect(changes.length).toBe(0); 39 | expect(failures.length).toBe(2); 40 | expect(failures[1].error).not.toBe( 41 | "Aborted due to previous action failure", 42 | ); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/node-plop/tests/action-data-cleanup/action-data-cleanup.spec.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | import { setupMockPath } from "../helpers/path.js"; 3 | import { normalizePath } from "../../src/actions/_common-action-utils.js"; 4 | 5 | const { clean, testSrcPath } = setupMockPath(import.meta.url); 6 | 7 | describe("action-data-cleanup", () => { 8 | let plop; 9 | beforeEach(async () => { 10 | plop = await nodePlop(); 11 | }); 12 | 13 | afterEach(clean); 14 | 15 | // Make sure that props added by the action's data attr are cleaned up 16 | // after the action executes 17 | 18 | test("Action data cleanup", async function () { 19 | const actions = ["one", "two", "three"].map((fName) => ({ 20 | type: "add", 21 | template: "", 22 | path: `${testSrcPath}//{{fName}}-{{unchanged}}.txt`, 23 | data: { fName, unchanged: `${fName}-unchanged` }, 24 | })); 25 | const g = plop.setGenerator("", { actions }); 26 | const { changes, failures } = await g.runActions({ 27 | unchanged: "unchanged", 28 | }); 29 | const addedFiles = changes 30 | .map((c) => normalizePath(c.path).split("/").slice(-1)) 31 | .join("|"); 32 | expect(addedFiles).toBe( 33 | "one-unchanged.txt|two-unchanged.txt|three-unchanged.txt", 34 | ); 35 | expect(changes.length).toBe(3); 36 | expect(failures.length).toBe(0); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/node-plop/tests/action-force-add/action-force-add.spec.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | import * as fspp from "../../src/fs-promise-proxy.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | 5 | const { clean, testSrcPath } = setupMockPath(import.meta.url); 6 | 7 | describe("action-force-add", function () { 8 | afterEach(clean); 9 | 10 | ///// 11 | // global and local "force" flag that can be used to push through failures 12 | // 13 | 14 | test("Action force add (global force)", async function () { 15 | const plop = await nodePlop("", { force: true }); 16 | const filePath = `${testSrcPath}/test.txt`; 17 | const gen = plop.setGenerator("Gen", { 18 | actions: [ 19 | { 20 | type: "add", 21 | template: "initial", 22 | path: filePath, 23 | }, 24 | { 25 | type: "add", 26 | template: "overwrite", 27 | path: filePath, 28 | }, 29 | { 30 | type: "add", 31 | template: "success", 32 | path: filePath, 33 | force: false, // will not be respected due to global flag 34 | }, 35 | ], 36 | }); 37 | 38 | const { changes, failures } = await gen.runActions({}); 39 | const content = await fspp.readFile(filePath); 40 | 41 | expect(changes.length).toBe(3); 42 | expect(failures.length).toBe(0); 43 | expect(await fspp.fileExists(filePath)).toBeTruthy(); 44 | expect(content).toBe("success"); 45 | }); 46 | 47 | test("Action force add (local action force)", async function () { 48 | const plop = await nodePlop(); 49 | const filePath = `${testSrcPath}/test2.txt`; 50 | const gen = plop.setGenerator("Gen", { 51 | actions: [ 52 | { 53 | type: "add", 54 | template: "initial", 55 | path: filePath, 56 | }, 57 | { 58 | type: "add", 59 | template: "failure", 60 | path: filePath, 61 | abortOnFail: false, 62 | }, 63 | { 64 | type: "add", 65 | template: "success", 66 | path: filePath, 67 | force: true, 68 | }, 69 | ], 70 | }); 71 | 72 | const { changes, failures } = await gen.runActions({}); 73 | const content = await fspp.readFile(filePath); 74 | 75 | expect(changes.length).toBe(2); 76 | expect(failures.length).toBe(1); 77 | expect(await fspp.fileExists(filePath)).toBeTruthy(); 78 | expect(content).toBe("success"); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /packages/node-plop/tests/add-action-binary-file/add-action-binary-file.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | 6 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 7 | 8 | describe("add-action-binary-file", () => { 9 | afterEach(clean); 10 | 11 | let plop; 12 | beforeEach(async () => { 13 | plop = await nodePlop(); 14 | }); 15 | 16 | ///// 17 | // 18 | // 19 | 20 | test("Add action does not fail on binary file", async function () { 21 | plop.setGenerator("addBinary", { 22 | actions: [ 23 | { 24 | type: "add", 25 | path: `${testSrcPath}/{{dashCase name}}-plop-logo.png`, 26 | templateFile: `${mockPath}/plop-logo.png`, 27 | }, 28 | ], 29 | }); 30 | 31 | const filePath = path.resolve(testSrcPath, "test-plop-logo.png"); 32 | await plop.getGenerator("addBinary").runActions({ name: "test" }); 33 | expect(fs.existsSync(filePath)).toBeTruthy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/node-plop/tests/add-action-binary-file/plop-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/add-action-binary-file/plop-logo.png -------------------------------------------------------------------------------- /packages/node-plop/tests/add-action-executable-file/add-action-executable-file.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import nodePlop from "../../src/index.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | 7 | describe("add-action-executable-file", () => { 8 | afterEach(clean); 9 | 10 | let plop; 11 | beforeEach(async () => { 12 | plop = await nodePlop(); 13 | }); 14 | 15 | test("Add action keeps the executable flag", async function () { 16 | if (process.platform !== "win32") { 17 | plop.setGenerator("addExecutable", { 18 | actions: [ 19 | { 20 | type: "add", 21 | path: `${testSrcPath}/added.sh`, 22 | templateFile: `${mockPath}/plop-templates/add.sh`, 23 | }, 24 | ], 25 | }); 26 | 27 | await plop.getGenerator("addExecutable").runActions(); 28 | const destStats = fs.statSync(`${testSrcPath}/added.sh`); 29 | expect(destStats.mode & fs.constants.S_IXUSR).toBe(fs.constants.S_IXUSR); 30 | } else { 31 | // Windows, skip 32 | // eslint-disable-next-line no-constant-condition 33 | if (true) { 34 | expect(true).toBe(true); 35 | return; 36 | } 37 | plop.setGenerator("addExecutable", { 38 | actions: [ 39 | { 40 | type: "add", 41 | path: `${testSrcPath}/added.sh`, 42 | templateFile: `${mockPath}/plop-templates/add.sh`, 43 | }, 44 | ], 45 | }); 46 | 47 | await plop.getGenerator("addExecutable").runActions(); 48 | 49 | const destStats = fs.statSync(`${testSrcPath}/added.sh`); 50 | expect(destStats.mode & fs.constants.S_IXUSR).toBe(fs.constants.S_IXUSR); 51 | } 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/node-plop/tests/add-action-executable-file/plop-templates/add.sh: -------------------------------------------------------------------------------- 1 | ls -------------------------------------------------------------------------------- /packages/node-plop/tests/add-action-failure/add-action-failure.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | 6 | const { clean, testSrcPath } = setupMockPath(import.meta.url); 7 | 8 | describe("add-action-failure", () => { 9 | afterEach(clean); 10 | 11 | let plop; 12 | let baseAction; 13 | let actionAdd; 14 | let actionAddWithSkip; 15 | beforeEach(async () => { 16 | plop = await nodePlop(); 17 | 18 | baseAction = { 19 | type: "add", 20 | template: "{{name}}", 21 | path: `${testSrcPath}/{{name}}.txt`, 22 | }; 23 | actionAdd = plop.setGenerator("add-action", { 24 | actions: [baseAction], 25 | }); 26 | actionAddWithSkip = plop.setGenerator("add-action-skip-exists-true", { 27 | actions: [Object.assign({}, baseAction, { skipIfExists: true })], 28 | }); 29 | }); 30 | 31 | test("Check that the file is created", async function () { 32 | const filePath = path.resolve(testSrcPath, "test1.txt"); 33 | const result = await actionAdd.runActions({ name: "test1" }); 34 | expect(result.changes.length).toBe(1); 35 | expect(result.failures.length).toBe(0); 36 | expect(fs.existsSync(filePath)).toBeTruthy(); 37 | }); 38 | 39 | test("If run twice, should fail due to file already exists", async function () { 40 | const filePath = path.resolve(testSrcPath, "test2.txt"); 41 | // add the test file 42 | const result = await actionAdd.runActions({ name: "test2" }); 43 | expect(result.changes.length).toBe(1); 44 | expect(result.failures.length).toBe(0); 45 | expect(fs.existsSync(filePath)).toBeTruthy(); 46 | // try to add it again 47 | const result2 = await actionAdd.runActions({ name: "test2" }); 48 | expect(result2.changes.length).toBe(0); 49 | expect(result2.failures.length).toBe(1); 50 | expect(fs.existsSync(filePath)).toBeTruthy(); 51 | }); 52 | 53 | test("If skipIfExists is true, it should not fail", async function () { 54 | const filePath = path.resolve(testSrcPath, "test3.txt"); 55 | // add the test file 56 | const result = await actionAdd.runActions({ name: "test3" }); 57 | expect(result.changes.length).toBe(1); 58 | expect(result.failures.length).toBe(0); 59 | expect(fs.existsSync(filePath)).toBeTruthy(); 60 | // try to add it again 61 | const result2 = await actionAddWithSkip.runActions({ name: "test3" }); 62 | expect(result2.changes.length).toBe(1); 63 | expect(result2.failures.length).toBe(0); 64 | expect(fs.existsSync(filePath)).toBeTruthy(); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/node-plop/tests/add-action-no-template/add-action-no-template.spec.js: -------------------------------------------------------------------------------- 1 | import * as fspp from "../../src/fs-promise-proxy.js"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | 6 | const { clean, testSrcPath } = setupMockPath(import.meta.url); 7 | 8 | describe("add-action-no-template", () => { 9 | afterEach(clean); 10 | 11 | let plop; 12 | beforeEach(async () => { 13 | plop = await nodePlop(); 14 | }); 15 | 16 | test("Check that an empty file has been created", async function () { 17 | plop.setGenerator("no-template", { 18 | actions: [ 19 | { 20 | type: "add", 21 | path: `${testSrcPath}/{{dashCase name}}.txt`, 22 | }, 23 | ], 24 | }); 25 | 26 | const name = "no-template"; 27 | const results = await plop.getGenerator(name).runActions({ name }); 28 | const { changes, failures } = results; 29 | const filePath = path.resolve(testSrcPath, `${name}.txt`); 30 | const content = await fspp.readFile(filePath); 31 | 32 | expect(changes.length).toBe(1); 33 | expect(failures.length).toBe(0); 34 | expect(await fspp.fileExists(filePath)).toBeTruthy(); 35 | expect(content).toBe(""); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-action-transform-function/file1.txt.hbs: -------------------------------------------------------------------------------- 1 | file1 {{dataProp}} -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-action-transform-function/file2.txt.hbs: -------------------------------------------------------------------------------- 1 | file2 {{dataProp}} -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-dynamic-template-file/addMany-dynamic-template-file.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | 4 | import nodePlop from "../../src/index.js"; 5 | import { setupMockPath } from "../helpers/path.js"; 6 | 7 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 8 | 9 | describe("addMany-dynamic-template-file", function () { 10 | afterAll(clean); 11 | 12 | let plop; 13 | let dynamicTemplateAddMany; 14 | let multipleAddsResult; 15 | 16 | beforeAll(async () => { 17 | plop = await nodePlop(`${mockPath}/plopfile.js`); 18 | dynamicTemplateAddMany = plop.getGenerator("dynamic-template-add-many"); 19 | multipleAddsResult = await dynamicTemplateAddMany.runActions({ 20 | name: "John Doe", 21 | kind: "BarChart", 22 | }); 23 | }); 24 | 25 | test("Check that all files have been created", () => { 26 | const expectedFiles = [ 27 | "john-doe-bar-chart/john-doe-bar-ctrl.js", 28 | "john-doe-bar-chart/john-doe-bar-tmpl.html", 29 | "john-doe-bar-chart/helpers/john-doe.js", 30 | ]; 31 | expectedFiles.map((file) => { 32 | const filePath = path.resolve(testSrcPath, file); 33 | expect(fs.existsSync(filePath)).toBe(true); 34 | }); 35 | 36 | expect( 37 | multipleAddsResult.changes[0].path.includes( 38 | `${expectedFiles.length} files added`, 39 | ), 40 | ).toBe(true); 41 | }); 42 | 43 | test("Test the content of the rendered file", () => { 44 | const filePath = path.resolve( 45 | testSrcPath, 46 | "john-doe-bar-chart/john-doe-bar-tmpl.html", 47 | ); 48 | const content = fs.readFileSync(filePath).toString(); 49 | 50 | expect(content.includes("name: John Doe")).toBe(true); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-dynamic-template-file/plop-templates/bar-chart/helpers/not-included.txt: -------------------------------------------------------------------------------- 1 | This file should not be created. 2 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-dynamic-template-file/plop-templates/bar-chart/helpers/{{dashCase name}}.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-dynamic-template-file/plop-templates/bar-chart/helpers/{{dashCase name}}.js -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-dynamic-template-file/plop-templates/bar-chart/{{dashCase name}}-bar-ctrl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-dynamic-template-file/plop-templates/bar-chart/{{dashCase name}}-bar-ctrl.js -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-dynamic-template-file/plop-templates/bar-chart/{{dashCase name}}-bar-tmpl.html: -------------------------------------------------------------------------------- 1 | this is a bar chart 2 | name: {{name}} 3 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-dynamic-template-file/plop-templates/line-chart/{{dashCase name}}-line-ctrl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-dynamic-template-file/plop-templates/line-chart/{{dashCase name}}-line-ctrl.js -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-dynamic-template-file/plop-templates/line-chart/{{dashCase name}}-line-tmpl.html: -------------------------------------------------------------------------------- 1 | this is a line chart 2 | name: {{name}} 3 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-dynamic-template-file/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | // setGenerator creates a generator that can be run with "plop generatorName" 3 | plop.setGenerator("dynamic-template-add-many", { 4 | description: "adds multiple files using dynamic templates", 5 | prompts: [ 6 | { 7 | type: "input", 8 | name: "name", 9 | message: "What is your name?", 10 | validate: function (value) { 11 | if (/.+/.test(value)) { 12 | return true; 13 | } 14 | return "name is required"; 15 | }, 16 | }, 17 | { 18 | type: "list", 19 | name: "kind", 20 | message: "What kind of widget do you want to create?", 21 | choices: ["LineChart", "BarChart"], 22 | }, 23 | ], 24 | actions: [ 25 | { 26 | type: "addMany", 27 | destination: "src/{{dashCase name}}-{{dashCase kind}}/", 28 | templateFiles: [ 29 | "plop-templates/{{dashCase kind}}/*", 30 | "plop-templates/{{dashCase kind}}/helpers/\\{{dashCase name}}.js", 31 | ], 32 | base: "plop-templates/{{dashCase kind}}", 33 | abortOnFail: true, 34 | }, 35 | ], 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-executable-file/addMany-executable-file.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import nodePlop from "../../src/index.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | 7 | describe("addMany-executable-file", function () { 8 | afterEach(clean); 9 | 10 | let plop; 11 | let executableFlagAddMany; 12 | let res; 13 | 14 | beforeAll(async () => { 15 | plop = await nodePlop(`${mockPath}/plopfile.js`); 16 | executableFlagAddMany = plop.getGenerator("executable-flag-add-many"); 17 | res = executableFlagAddMany.runActions({ executableName: "ls command" }); 18 | await res; 19 | }); 20 | 21 | if (process.platform !== "win32") { 22 | test("addMany action keeps the executable flag", () => { 23 | const destStats = fs.statSync(`${testSrcPath}/ls-command.sh`); 24 | expect(destStats.mode & fs.constants.S_IXUSR).toBe(fs.constants.S_IXUSR); 25 | }); 26 | } else { 27 | test.skip("[windows] addMany action keeps the executable flag", (t) => { 28 | const destStats = fs.statSync(`${testSrcPath}/ls-command.sh`); 29 | expect(destStats.mode & fs.constants.S_IXUSR).toBe(fs.constants.S_IXUSR); 30 | }); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-executable-file/plop-templates/{{dashCase executableName}}.sh: -------------------------------------------------------------------------------- 1 | ls -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-executable-file/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | // setGenerator creates a generator that can be run with "plop generatorName" 3 | plop.setGenerator("executable-flag-add-many", { 4 | description: "adds multiple files from a glob", 5 | prompts: [ 6 | { 7 | type: "input", 8 | name: "executableName", 9 | message: "Name of the executable?", 10 | validate: function (value) { 11 | if (/.+/.test(value)) { 12 | return true; 13 | } 14 | return "name is required"; 15 | }, 16 | }, 17 | ], 18 | actions: [ 19 | { 20 | type: "addMany", 21 | destination: "src/", 22 | templateFiles: "plop-templates/**/*", 23 | abortOnFail: true, 24 | }, 25 | ], 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/addMany-multiple-files.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | 6 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 7 | 8 | describe("addMany-multiple-files", function () { 9 | afterEach(clean); 10 | 11 | let multipleAddsResult; 12 | let plop; 13 | 14 | beforeEach(async () => { 15 | plop = await nodePlop(`${mockPath}/plopfile.js`); 16 | const multipleAdds = plop.getGenerator("multiple-adds"); 17 | multipleAddsResult = await multipleAdds.runActions({ name: "John Doe" }); 18 | }); 19 | 20 | test("Check that all files have been created", () => { 21 | const expectedFiles = [ 22 | "john-doe/add.txt", 23 | "john-doe/another-add.txt", 24 | "john-doe/nested-folder/a-nested-add.txt", 25 | "john-doe/nested-folder/another-nested-add.txt", 26 | "john-doe/nested-folder/my-name-is-john-doe.txt", 27 | ]; 28 | expectedFiles.map((file) => { 29 | const filePath = path.resolve(testSrcPath, file); 30 | expect(fs.existsSync(filePath)).toBe(true); 31 | }); 32 | 33 | expect( 34 | multipleAddsResult.changes[0].path.includes( 35 | `${expectedFiles.length} files added`, 36 | ), 37 | ).toBe(true); 38 | }); 39 | 40 | test("Check that all files have been when using a templateFiles array", () => { 41 | const expectedFiles = [ 42 | "array-john-doe/add.txt", 43 | "array-john-doe/another-add.txt", 44 | "array-john-doe/nested-folder/a-nested-add.txt", 45 | "array-john-doe/nested-folder/another-nested-add.txt", 46 | "array-john-doe/nested-folder/my-name-is-john-doe.txt", 47 | ]; 48 | expectedFiles.map((file) => { 49 | const filePath = path.resolve(testSrcPath, file); 50 | expect(fs.existsSync(filePath)).toBe(true); 51 | }); 52 | 53 | expect( 54 | multipleAddsResult.changes[0].path.includes( 55 | `${expectedFiles.length} files added`, 56 | ), 57 | ).toBe(true); 58 | }); 59 | 60 | test("Check that the base path is chopped from templateFiles path", () => { 61 | const expectedFiles = [ 62 | "base-john-doe/a-nested-add.txt", 63 | "base-john-doe/another-nested-add.txt", 64 | "base-john-doe/my-name-is-john-doe.txt", 65 | ]; 66 | expectedFiles.map((file) => { 67 | const filePath = path.resolve(testSrcPath, file); 68 | expect(fs.existsSync(filePath)).toBe(true); 69 | }); 70 | 71 | const expectedNotCreatedFiles = [ 72 | "base-john-doe/add.txt", 73 | "base-john-doe/another-add.txt", 74 | "base-john-doe/plop-templates/add.txt", 75 | "base-john-doe/plop-templates/another-add.txt", 76 | ]; 77 | expectedNotCreatedFiles.map((file) => { 78 | const filePath = path.resolve(testSrcPath, file); 79 | expect(fs.existsSync(filePath)).toBe(false); 80 | }); 81 | }); 82 | 83 | test("Test the content of the rendered file add.txt", () => { 84 | const filePath = path.resolve(testSrcPath, "john-doe/add.txt"); 85 | const content = fs.readFileSync(filePath).toString(); 86 | 87 | expect(content.includes("name: John Doe")).toBe(true); 88 | }); 89 | 90 | test("Test the content of the rendered file in nested folder", () => { 91 | const filePath = path.resolve( 92 | testSrcPath, 93 | "john-doe/nested-folder/a-nested-add.txt", 94 | ); 95 | const content = fs.readFileSync(filePath).toString(); 96 | 97 | expect(content.includes("constant name: JOHN_DOE")).toBe(true); 98 | }); 99 | 100 | test("Test the base value is used to decide which files are created", () => { 101 | const expectedCreatedFiles = [ 102 | "components/john-doe-ctrl.js", 103 | "components/john-doe-tmpl.html", 104 | "components/john-doe-plop-logo.png", 105 | ]; 106 | expectedCreatedFiles.map((file) => { 107 | const filePath = path.resolve(testSrcPath, file); 108 | expect(fs.existsSync(filePath)).toBe(true); 109 | }); 110 | 111 | const expectedNotCreatedFiles = [ 112 | "components/logic/john-doe-ctrl.js", 113 | "components/logic/john-doe-tmpl.html", 114 | "components/tests/john-doe._test.js", 115 | "components/john-doe._test.js", 116 | ]; 117 | expectedNotCreatedFiles.map((file) => { 118 | const filePath = path.resolve(testSrcPath, file); 119 | expect(fs.existsSync(filePath)).toBe(false); 120 | }); 121 | }); 122 | 123 | test("Check that all files including dot have been created", () => { 124 | const expectedFiles = [ 125 | "john-doe-dot/.gitignore", 126 | "john-doe-dot/add.txt", 127 | "john-doe-dot/another-add.txt", 128 | ]; 129 | expectedFiles.map((file) => { 130 | const filePath = path.resolve(testSrcPath, file); 131 | expect(fs.existsSync(filePath)).toBe(true); 132 | }); 133 | expect( 134 | multipleAddsResult.changes[4].path.includes( 135 | `${expectedFiles.length} files added`, 136 | ), 137 | ).toBe(true); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-multiple-files/plop-templates/.gitignore -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/add.txt: -------------------------------------------------------------------------------- 1 | name: {{titleCase name}} 2 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/another-add.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-multiple-files/plop-templates/another-add.txt -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/components/logic/{{dashCase name}}-ctrl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-multiple-files/plop-templates/components/logic/{{dashCase name}}-ctrl.js -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/components/logic/{{dashCase name}}-plop-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-multiple-files/plop-templates/components/logic/{{dashCase name}}-plop-logo.png -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/components/logic/{{dashCase name}}-tmpl.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-multiple-files/plop-templates/components/logic/{{dashCase name}}-tmpl.html -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/components/logic/{{dashCase name}}-view.js.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-multiple-files/plop-templates/components/logic/{{dashCase name}}-view.js.hbs -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/components/tests/{{dashCase name}}._test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-multiple-files/plop-templates/components/tests/{{dashCase name}}._test.js -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/nested-folder/a-nested-add.txt: -------------------------------------------------------------------------------- 1 | constant name: {{constantCase name}} 2 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/nested-folder/another-nested-add.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-multiple-files/plop-templates/nested-folder/another-nested-add.txt -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plop-templates/nested-folder/my-name-is-{{dashCase name}}.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-multiple-files/plop-templates/nested-folder/my-name-is-{{dashCase name}}.txt -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-multiple-files/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | // setGenerator creates a generator that can be run with "plop generatorName" 3 | plop.setGenerator("multiple-adds", { 4 | description: "adds multiple files from a glob", 5 | prompts: [ 6 | { 7 | type: "input", 8 | name: "name", 9 | message: "What is your name?", 10 | validate: function (value) { 11 | if (/.+/.test(value)) { 12 | return true; 13 | } 14 | return "name is required"; 15 | }, 16 | }, 17 | ], 18 | actions: [ 19 | { 20 | type: "addMany", 21 | destination: "src/{{dashCase name}}/", 22 | templateFiles: "plop-templates/**/*.txt", 23 | abortOnFail: true, 24 | }, 25 | { 26 | type: "addMany", 27 | destination: "src/base-{{dashCase name}}/", 28 | templateFiles: "plop-templates/**/*.txt", 29 | base: "plop-templates/nested-folder/", 30 | abortOnFail: true, 31 | }, 32 | { 33 | type: "addMany", 34 | destination: "src/components", 35 | templateFiles: "plop-templates/components/**/*", 36 | base: "plop-templates/components/logic", 37 | abortOnFail: true, 38 | }, 39 | { 40 | type: "addMany", 41 | destination: "src/array-{{dashCase name}}/", 42 | templateFiles: [ 43 | "plop-templates/*.txt", 44 | "plop-templates/nested-folder/*.txt", 45 | ], 46 | base: "plop-templates/", 47 | abortOnFail: true, 48 | }, 49 | { 50 | type: "addMany", 51 | destination: "src/{{dashCase name}}-dot/", 52 | templateFiles: () => "plop-templates/*", 53 | globOptions: { dot: true }, 54 | abortOnFail: true, 55 | }, 56 | ], 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/addMany-non-verbose.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | 6 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 7 | 8 | describe("addMany-non-verbose", function () { 9 | afterEach(clean); 10 | 11 | let plop; 12 | beforeEach(async () => { 13 | plop = await nodePlop(`${mockPath}/plopfile.js`); 14 | }); 15 | 16 | test("Check that all files have been created", async function () { 17 | const multipleAddsResult = await plop 18 | .getGenerator("multiple-adds") 19 | .runActions({ name: "John Doe" }); 20 | 21 | const expectedFiles = [ 22 | "john-doe/add.txt", 23 | "john-doe/another-add.txt", 24 | "john-doe/nested-folder/a-nested-add.txt", 25 | "john-doe/nested-folder/another-nested-add.txt", 26 | "john-doe/nested-folder/my-name-is-john-doe.txt", 27 | ]; 28 | 29 | expectedFiles.forEach((file) => { 30 | const filePath = path.resolve(testSrcPath, file); 31 | expect(fs.existsSync(filePath)).toBe(true); 32 | }); 33 | 34 | // has the summary line 35 | expect(multipleAddsResult.changes[0].path.includes("5 files added")).toBe( 36 | true, 37 | ); 38 | // does not have additional lines 39 | expect(multipleAddsResult.changes[0].path.includes("\n")).toBe(false); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-non-verbose/plop-templates/.gitignore -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/add.txt: -------------------------------------------------------------------------------- 1 | name: {{titleCase name}} 2 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/another-add.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-non-verbose/plop-templates/another-add.txt -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/components/logic/{{dashCase name}}-ctrl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-non-verbose/plop-templates/components/logic/{{dashCase name}}-ctrl.js -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/components/logic/{{dashCase name}}-plop-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-non-verbose/plop-templates/components/logic/{{dashCase name}}-plop-logo.png -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/components/logic/{{dashCase name}}-tmpl.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-non-verbose/plop-templates/components/logic/{{dashCase name}}-tmpl.html -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/components/tests/{{dashCase name}}._test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-non-verbose/plop-templates/components/tests/{{dashCase name}}._test.js -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/nested-folder/a-nested-add.txt: -------------------------------------------------------------------------------- 1 | constant name: {{constantCase name}} 2 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/nested-folder/another-nested-add.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-non-verbose/plop-templates/nested-folder/another-nested-add.txt -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plop-templates/nested-folder/my-name-is-{{dashCase name}}.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-non-verbose/plop-templates/nested-folder/my-name-is-{{dashCase name}}.txt -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-non-verbose/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | // setGenerator creates a generator that can be run with "plop generatorName" 3 | plop.setGenerator("multiple-adds", { 4 | description: "adds multiple files from a glob", 5 | prompts: [ 6 | { 7 | type: "input", 8 | name: "name", 9 | message: "What is your name?", 10 | validate: function (value) { 11 | if (/.+/.test(value)) { 12 | return true; 13 | } 14 | return "name is required"; 15 | }, 16 | }, 17 | ], 18 | actions: [ 19 | { 20 | type: "addMany", 21 | destination: "src/{{dashCase name}}/", 22 | templateFiles: "plop-templates/**/*.txt", 23 | abortOnFail: true, 24 | verbose: false, 25 | }, 26 | ], 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-strip-extensions/addMany-strip-extensions.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | 7 | describe("addMany-strip-extensions", function () { 8 | afterEach(clean); 9 | 10 | let plop; 11 | let multipleAdds; 12 | beforeEach(async () => { 13 | plop = await nodePlop(`${mockPath}/plopfile.js`); 14 | multipleAdds = plop.getGenerator("add-many-strip-extensions"); 15 | await multipleAdds.runActions({ name: "John Doe" }); 16 | }); 17 | 18 | test("Check that all files generated without hbs extension", () => { 19 | const nonSpecPath = path.resolve( 20 | testSrcPath, 21 | "remove-hbs/john-doe-my-view.js", 22 | ); 23 | const specPath = path.resolve( 24 | testSrcPath, 25 | "remove-hbs/john-doe-my-view._test.js", 26 | ); 27 | 28 | expect(fs.existsSync(nonSpecPath)).toBe(true); 29 | expect(fs.existsSync(specPath)).toBe(true); 30 | }); 31 | 32 | test("Check that all files generated with all extensions removed", () => { 33 | const nonSpecPath = path.resolve(testSrcPath, "remove-all/my-view._test"); 34 | const specPath = path.resolve(testSrcPath, "remove-all/my-view._test.js"); 35 | 36 | expect(fs.existsSync(nonSpecPath)).toBe(true); 37 | expect(fs.existsSync(specPath)).toBe(true); 38 | }); 39 | 40 | test("Check that dot files generated without hbs extension", () => { 41 | const dotPath = path.resolve(testSrcPath, "remove-dotfile-hbs/.gitignore"); 42 | const dotPathWithExtension = path.resolve( 43 | testSrcPath, 44 | "remove-dotfile-hbs/.eslintrc.cjs", 45 | ); 46 | 47 | expect(fs.existsSync(dotPath)).toBe(true); 48 | expect(fs.existsSync(dotPathWithExtension)).toBe(true); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-all/my-view._test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-all/my-view._test.js -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-all/my-view._test.js.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-all/my-view._test.js.hbs -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-dotfile-hbs/.eslintrc.cjs.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-dotfile-hbs/.eslintrc.cjs.hbs -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-dotfile-hbs/.gitignore.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-dotfile-hbs/.gitignore.hbs -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-hbs/{{dashCase name}}-my-view._test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-hbs/{{dashCase name}}-my-view._test.js -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-hbs/{{dashCase name}}-my-view.js.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/node-plop/tests/addMany-strip-extensions/plop-templates/remove-hbs/{{dashCase name}}-my-view.js.hbs -------------------------------------------------------------------------------- /packages/node-plop/tests/addMany-strip-extensions/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | // setGenerator creates a generator that can be run with "plop generatorName" 3 | plop.setGenerator("add-many-strip-extensions", { 4 | description: "adds multiple files removing extensions", 5 | prompts: [ 6 | { 7 | type: "input", 8 | name: "name", 9 | message: "What is your name?", 10 | validate: function (value) { 11 | if (/.+/.test(value)) { 12 | return true; 13 | } 14 | return "name is required"; 15 | }, 16 | }, 17 | ], 18 | actions: [ 19 | { 20 | type: "addMany", 21 | destination: "src/", 22 | stripExtensions: ["hbs"], 23 | templateFiles: "plop-templates/remove-hbs/*", 24 | abortOnFail: true, 25 | }, 26 | { 27 | type: "addMany", 28 | destination: "src/", 29 | stripExtensions: true, 30 | templateFiles: "plop-templates/remove-all/*", 31 | abortOnFail: true, 32 | }, 33 | { 34 | type: "addMany", 35 | destination: "src/", 36 | stripExtensions: ["hbs"], 37 | templateFiles: "plop-templates/remove-dotfile-hbs/*", 38 | abortOnFail: true, 39 | globOptions: { dot: true }, 40 | }, 41 | ], 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /packages/node-plop/tests/append-empty/append-empty.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | 7 | describe("append-empty", function () { 8 | afterEach(clean); 9 | 10 | let plop; 11 | let makeList; 12 | let appendToList; 13 | 14 | beforeEach(async () => { 15 | plop = await nodePlop(`${mockPath}/plopfile.js`); 16 | makeList = plop.getGenerator("make-list"); 17 | appendToList = plop.getGenerator("append-to-list"); 18 | }); 19 | 20 | test("Check if entry will be appended", async function () { 21 | await makeList.runActions({ listName: "test" }); 22 | await appendToList.runActions({ listName: "test", name: "Marco" }); 23 | await appendToList.runActions({ listName: "test", name: "Polo" }); 24 | const filePath = path.resolve(testSrcPath, "test.txt"); 25 | const content = fs.readFileSync(filePath).toString(); 26 | 27 | expect(content).toBe("Marco\nPolo"); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/node-plop/tests/append-empty/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("make-list", { 3 | actions: [ 4 | { 5 | type: "add", 6 | path: "src/{{listName}}.txt", 7 | template: "", 8 | }, 9 | ], 10 | }); 11 | 12 | plop.setGenerator("append-to-list", { 13 | description: "adds entry to a list", 14 | actions: [ 15 | { 16 | type: "append", 17 | path: "src/{{listName}}.txt", 18 | template: "{{name}}", 19 | }, 20 | ], 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /packages/node-plop/tests/append/append.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | 6 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 7 | 8 | describe("append", function () { 9 | afterEach(clean); 10 | 11 | let plop; 12 | let makeList; 13 | let appendToList; 14 | 15 | beforeEach(async () => { 16 | plop = await nodePlop(`${mockPath}/plopfile.js`); 17 | makeList = plop.getGenerator("make-list"); 18 | appendToList = plop.getGenerator("append-to-list"); 19 | }); 20 | 21 | test("Check if list has been created", async function () { 22 | await makeList.runActions({ listName: "test" }); 23 | const filePath = path.resolve(testSrcPath, "test.txt"); 24 | expect(fs.existsSync(filePath)).toBe(true); 25 | }); 26 | 27 | test("Check if entry will be appended", async function () { 28 | await makeList.runActions({ listName: "list1" }); 29 | await appendToList.runActions({ 30 | listName: "list1", 31 | name: "Marco", 32 | allowDuplicates: false, 33 | }); 34 | await appendToList.runActions({ 35 | listName: "list1", 36 | name: "Polo", 37 | allowDuplicates: false, 38 | }); 39 | const filePath = path.resolve(testSrcPath, "list1.txt"); 40 | const content = fs.readFileSync(filePath).toString(); 41 | 42 | expect((content.match(/Marco1/g) || []).length).toBe(1); 43 | expect((content.match(/Polo1/g) || []).length).toBe(1); 44 | expect((content.match(/Marco2/g) || []).length).toBe(1); 45 | expect((content.match(/Polo2/g) || []).length).toBe(1); 46 | }); 47 | 48 | test("Check if duplicates get filtered", async function () { 49 | await makeList.runActions({ listName: "list2" }); 50 | 51 | await appendToList.runActions({ 52 | listName: "list2", 53 | name: "Marco", 54 | allowDuplicates: false, 55 | }); 56 | await appendToList.runActions({ 57 | listName: "list2", 58 | name: "Polo", 59 | allowDuplicates: false, 60 | }); 61 | await appendToList.runActions({ 62 | listName: "list2", 63 | name: "Marco", 64 | allowDuplicates: false, 65 | }); 66 | const filePath = path.resolve(testSrcPath, "list2.txt"); 67 | const content = fs.readFileSync(filePath).toString(); 68 | 69 | expect((content.match(/Marco1/g) || []).length).toBe(1); 70 | expect((content.match(/Polo1/g) || []).length).toBe(1); 71 | expect((content.match(/Marco2/g) || []).length).toBe(1); 72 | expect((content.match(/Polo2/g) || []).length).toBe(1); 73 | }); 74 | 75 | test("Check if duplicates are kept, if allowed", async function () { 76 | await makeList.runActions({ listName: "list3" }); 77 | await appendToList.runActions({ 78 | listName: "list3", 79 | name: "Marco", 80 | allowDuplicates: true, 81 | }); 82 | await appendToList.runActions({ 83 | listName: "list3", 84 | name: "Polo", 85 | allowDuplicates: true, 86 | }); 87 | await appendToList.runActions({ 88 | listName: "list3", 89 | name: "Marco", 90 | allowDuplicates: true, 91 | }); 92 | const filePath = path.resolve(testSrcPath, "list3.txt"); 93 | const content = fs.readFileSync(filePath).toString(); 94 | 95 | expect((content.match(/Marco1/g) || []).length).toBe(2); 96 | expect((content.match(/Polo1/g) || []).length).toBe(1); 97 | expect((content.match(/Marco2/g) || []).length).toBe(2); 98 | expect((content.match(/Polo2/g) || []).length).toBe(1); 99 | }); 100 | 101 | test("Check if duplicates are only removed below the pattern", async function () { 102 | await makeList.runActions({ listName: "list4" }); 103 | 104 | await appendToList.runActions({ 105 | listName: "list4", 106 | name: "Plop", 107 | allowDuplicates: false, 108 | }); 109 | await appendToList.runActions({ 110 | listName: "list4", 111 | name: "Polo", 112 | allowDuplicates: false, 113 | }); 114 | await appendToList.runActions({ 115 | listName: "list4", 116 | name: "Plop", 117 | allowDuplicates: false, 118 | }); 119 | const filePath = path.resolve(testSrcPath, "list4.txt"); 120 | const content = fs.readFileSync(filePath).toString(); 121 | 122 | expect((content.match(/Plop1/g) || []).length).toBe(1); 123 | expect((content.match(/Polo1/g) || []).length).toBe(1); 124 | expect((content.match(/Plop2/g) || []).length).toBe(1); 125 | expect((content.match(/Polo2/g) || []).length).toBe(1); 126 | 127 | // there's a plop at the top 128 | expect((content.match(/Plop/g) || []).length).toBe(3); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /packages/node-plop/tests/append/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "append-test", 3 | "private": true, 4 | "type": "module", 5 | "config": { 6 | "nested": [ 7 | "basic-plopfile-test-propertyPath-value-index-0", 8 | "basic-plopfile-test-propertyPath-value-index-1" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/node-plop/tests/append/plop-templates/list.txt: -------------------------------------------------------------------------------- 1 | Don't remove me: Plop 2 | 3 | -- APPEND ITEMS HERE -- 4 | 5 | /* APPEND OTHER ITEMS HERE */ 6 | 7 | +++++++++++++++++++++++++++++++++++++++ 8 | -------------------------------------------------------------------------------- /packages/node-plop/tests/append/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("make-list", { 3 | prompts: [ 4 | { 5 | type: "input", 6 | name: "listName", 7 | message: "What's the list name?", 8 | validate: function (value) { 9 | if (/.+/.test(value)) { 10 | return true; 11 | } 12 | return "name is required"; 13 | }, 14 | }, 15 | ], 16 | actions: [ 17 | { 18 | type: "add", 19 | path: "src/{{listName}}.txt", 20 | templateFile: "plop-templates/list.txt", 21 | }, 22 | ], 23 | }); 24 | 25 | plop.setGenerator("append-to-list", { 26 | description: "adds entry to a list", 27 | prompts: [ 28 | { 29 | type: "input", 30 | name: "listName", 31 | message: "What's the list name?", 32 | validate: function (value) { 33 | if (/.+/.test(value)) { 34 | return true; 35 | } 36 | return "name is required"; 37 | }, 38 | }, 39 | { 40 | type: "input", 41 | name: "name", 42 | message: "What is your name?", 43 | validate: function (value) { 44 | if (/.+/.test(value)) { 45 | return true; 46 | } 47 | return "name is required"; 48 | }, 49 | }, 50 | { 51 | type: "confirm", 52 | name: "allowDuplicates", 53 | message: "Allow Duplicates?", 54 | }, 55 | ], 56 | actions: ({ allowDuplicates }) => [ 57 | { 58 | type: "append", 59 | path: "src/{{listName}}.txt", 60 | pattern: /-- APPEND ITEMS HERE --/gi, 61 | template: "😻 name: {{name}}1", 62 | unique: !allowDuplicates, 63 | }, 64 | { 65 | type: "append", 66 | path: "src/{{listName}}.txt", 67 | pattern: "/* APPEND OTHER ITEMS HERE */", 68 | template: "🔥 name: {{name}}2", 69 | unique: !allowDuplicates, 70 | }, 71 | ], 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /packages/node-plop/tests/basic-no-plopfile/basic-no-plopfile.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | const { clean, testSrcPath } = setupMockPath(import.meta.url); 6 | 7 | describe("basic-no-plopfile", function () { 8 | afterEach(clean); 9 | 10 | let plop; 11 | 12 | beforeEach(async () => { 13 | plop = await nodePlop(); 14 | const name = "basic test name"; 15 | plop.setHelper("uCase", (txt) => txt.toUpperCase()); 16 | plop.setGenerator("basic-add-no-plopfile", { 17 | description: "adds a file using a template", 18 | prompts: [ 19 | { 20 | type: "input", 21 | name: "name", 22 | message: "What is your name?", 23 | validate: function (value) { 24 | if (/.+/.test(value)) { 25 | return true; 26 | } 27 | return "name is required"; 28 | }, 29 | }, 30 | ], 31 | actions: [ 32 | { 33 | type: "add", 34 | path: `${testSrcPath}/{{dashCase name}}.txt`, 35 | template: "{{uCase name}}", 36 | }, 37 | ], 38 | }); 39 | 40 | const basicAdd = plop.getGenerator("basic-add-no-plopfile"); 41 | await basicAdd.runActions({ name }); 42 | }); 43 | 44 | test("Check that the file has been created", () => { 45 | const filePath = path.resolve(testSrcPath, "basic-test-name.txt"); 46 | 47 | expect(fs.existsSync(filePath)).toBe(true); 48 | }); 49 | 50 | test("Test the content of the rendered file", () => { 51 | const filePath = path.resolve(testSrcPath, "basic-test-name.txt"); 52 | const content = fs.readFileSync(filePath).toString(); 53 | 54 | expect(content === "BASIC TEST NAME").toBe(true); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/node-plop/tests/basic-plopfile/basic-plopfile.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | 7 | describe("basic-plopfile", function () { 8 | afterAll(clean); 9 | 10 | let plop; 11 | let basicAdd; 12 | beforeAll(async () => { 13 | plop = await nodePlop(`${mockPath}/plopfile.js`); 14 | }); 15 | 16 | test("Check that the file has been created", async () => { 17 | basicAdd = plop.getGenerator("basic-add"); 18 | await basicAdd.runActions({ name: "this is a test", age: "21" }); 19 | 20 | const filePath = path.resolve(testSrcPath, "this-is-a-test.txt"); 21 | expect(fs.existsSync(filePath)).toBe(true); 22 | }); 23 | 24 | test("Test the content of the rendered file this-is-a-test.txt", async () => { 25 | basicAdd = plop.getGenerator("basic-add"); 26 | await basicAdd.runActions({ name: "this is a test", age: "21" }); 27 | 28 | const filePath = path.resolve(testSrcPath, "this-is-a-test.txt"); 29 | const content = fs.readFileSync(filePath).toString(); 30 | 31 | expect(content.includes("name: this is a test")).toBe(true); 32 | expect(content.includes("upperCase: THIS_IS_A_TEST")).toBe(true); 33 | }); 34 | 35 | test("Test the content of the rendered file _THIS_IS_A_TEST.txt", async () => { 36 | const filePath = path.resolve(testSrcPath, "_THIS_IS_A_TEST.txt"); 37 | const content = fs.readFileSync(filePath).toString(); 38 | 39 | expect(content.includes("inline template: this is a test")).toBe(true); 40 | expect(content.includes("test: basic-plopfile-test")).toBe(true); 41 | expect( 42 | content.includes( 43 | "propertyPathTest: basic-plopfile-test-propertyPath-value-index-1", 44 | ), 45 | ).toBe(true); 46 | }); 47 | 48 | test("Test the content of the rendered file change-me.txt", async () => { 49 | basicAdd = plop.getGenerator("basic-add"); 50 | await basicAdd.runActions({ name: "this is a test", age: "21" }); 51 | 52 | const filePath = path.resolve(testSrcPath, "change-me.txt"); 53 | const content = fs.readFileSync(filePath).toString(); 54 | 55 | expect(content.includes("this is a test: 21")).toBe(true); 56 | expect( 57 | content.includes("this is prepended! replaced => this-is-a-test: 21"), 58 | ).toBe(true); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /packages/node-plop/tests/basic-plopfile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic-plopfile-test", 3 | "private": true, 4 | "type": "module", 5 | "config": { 6 | "nested": [ 7 | "basic-plopfile-test-propertyPath-value-index-0", 8 | "basic-plopfile-test-propertyPath-value-index-1" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/node-plop/tests/basic-plopfile/plop-templates/add.txt: -------------------------------------------------------------------------------- 1 | add test 2 | name: {{name}} 3 | upperCase: {{constantCase name}} 4 | {{> salutation}} 5 | -------------------------------------------------------------------------------- /packages/node-plop/tests/basic-plopfile/plop-templates/change-me.txt: -------------------------------------------------------------------------------- 1 | the modify option in the test plop should add lines below for each run. 2 | Use modify for things like adding script references to an HTML file. 3 | 4 | -- APPEND ITEMS HERE -- 5 | 6 | +++++++++++++++++++++++++++++++++++++++ 7 | 8 | -- PREPEND ITEMS HERE -- 9 | -------------------------------------------------------------------------------- /packages/node-plop/tests/basic-plopfile/plop-templates/part.txt: -------------------------------------------------------------------------------- 1 | this is prepended! ## replace name here ##: {{age}} 2 | $1 -------------------------------------------------------------------------------- /packages/node-plop/tests/basic-plopfile/plopfile.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { join } from "path"; 3 | 4 | export default function (plop) { 5 | /////// 6 | // helpers are passed through to handlebars and made 7 | // available for use in the generator templates 8 | // 9 | 10 | // adds 4 dashes around some text (yes es6/es2015 is supported) 11 | plop.setHelper("dashAround", function (text) { 12 | return "---- " + text + " ----"; 13 | }); 14 | // plop.setHelper('dashAround', (text) => '---- ' + text + ' ----'); 15 | 16 | // formats an array of options like you would write 17 | // it if you were speaking (one, two, and three) 18 | plop.setHelper("wordJoin", function (words) { 19 | return words.join(", ").replace(/(:?.*),/, "$1, and"); 20 | }); 21 | 22 | // greet the user using a partial 23 | plop.setPartial( 24 | "salutation", 25 | "my name is {{ properCase name }} and I am {{ age }}.", 26 | ); 27 | 28 | // setGenerator creates a generator that can be run with "plop generatorName" 29 | plop.setGenerator("basic-add", { 30 | description: "adds a file using a template", 31 | prompts: [ 32 | { 33 | type: "input", 34 | name: "name", 35 | message: "What is your name?", 36 | validate: function (value) { 37 | if (/.+/.test(value)) { 38 | return true; 39 | } 40 | return "name is required"; 41 | }, 42 | }, 43 | { 44 | type: "input", 45 | name: "age", 46 | message: "How old are you?", 47 | validate: function (value) { 48 | var digitsOnly = /\d+/; 49 | if (digitsOnly.test(value)) { 50 | return true; 51 | } 52 | return "Invalid age! Must be a number genius!"; 53 | }, 54 | }, 55 | ], 56 | actions: [ 57 | { 58 | type: "add", 59 | path: "src/{{dashCase name}}.txt", 60 | templateFile: "plop-templates/add.txt", 61 | abortOnFail: true, 62 | }, 63 | { 64 | type: "add", 65 | path: "src/_{{constantCase name}}.txt", 66 | template: 67 | 'test: {{pkg "name"}}\npropertyPathTest: {{pkg "config.nested[1]"}}\ninline template: {{name}}', 68 | abortOnFail: true, 69 | }, 70 | function customAction(answers) { 71 | // move the current working directory to the plop file path 72 | // this allows this action to work even when the generator is 73 | // executed from inside a subdirectory 74 | 75 | var plopFilePath = plop.getPlopfilePath(); 76 | 77 | // custom function can be synchronous or async (by returning a promise) 78 | var copiedMsg = "hey {{name}}, I copied change-me.txt for you", 79 | changeFile = "change-me.txt", 80 | toPath = join(plopFilePath, "src", changeFile), 81 | fromPath = join(plopFilePath, "plop-templates", changeFile); 82 | 83 | // you can use plop.renderString to render templates 84 | copiedMsg = plop.renderString(copiedMsg, answers); 85 | 86 | if (fs.existsSync(toPath)) { 87 | fs.unlinkSync(toPath); 88 | } 89 | 90 | fs.writeFileSync(toPath, fs.readFileSync(fromPath)); 91 | return copiedMsg; 92 | }, 93 | { 94 | type: "modify", 95 | path: "src/change-me.txt", 96 | pattern: /(-- APPEND ITEMS HERE --)/gi, 97 | template: "$1\r\n{{name}}: {{age}}", 98 | }, 99 | { 100 | type: "modify", 101 | path: "src/change-me.txt", 102 | pattern: /(-- PREPEND ITEMS HERE --)/gi, 103 | templateFile: "plop-templates/part.txt", 104 | }, 105 | { 106 | type: "modify", 107 | path: "src/change-me.txt", 108 | pattern: /## replace name here ##/gi, 109 | template: "replaced => {{dashCase name}}", 110 | }, 111 | ], 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /packages/node-plop/tests/custom-data-in-actions/custom-data-in-actions.spec.js: -------------------------------------------------------------------------------- 1 | import * as fspp from "../../src/fs-promise-proxy.js"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | 7 | describe("custom-data-in-actions", function () { 8 | afterEach(clean); 9 | 10 | let plop; 11 | let customData; 12 | 13 | beforeEach(async () => { 14 | plop = await nodePlop(`${mockPath}/plopfile.js`); 15 | customData = plop.getGenerator("custom-data-in-actions"); 16 | }); 17 | 18 | test("Check that custom data is in template", async function () { 19 | await customData.runActions({}); 20 | const file = path.resolve(testSrcPath, "Frodo-loves-who.txt"); 21 | const content = await fspp.readFile(file); 22 | expect(content).toBe("Frodo loves Gandalf"); 23 | }); 24 | 25 | test("Check that data is overridden", async function () { 26 | await customData.runActions({ name: "Sauron" }); 27 | const filePath = path.resolve(testSrcPath, "Sauron-loves-who.txt"); 28 | const greetSubjectPath = path.resolve(testSrcPath, "hola-world.txt"); 29 | 30 | expect(await fspp.readFile(filePath)).toBe("Sauron loves Gandalf"); 31 | expect(await fspp.fileExists(greetSubjectPath)).toBe(true); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/node-plop/tests/custom-data-in-actions/plop-templates/who-loves-who.txt: -------------------------------------------------------------------------------- 1 | {{name}} loves {{otherName}} -------------------------------------------------------------------------------- /packages/node-plop/tests/custom-data-in-actions/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | // test with dynamic actions, regarding responses to prompts 3 | plop.setGenerator("custom-data-in-actions", { 4 | description: "A test that shows how to append data in action definitions", 5 | prompts: [ 6 | { 7 | type: "input", 8 | name: "name", 9 | message: "What is your name?", 10 | }, 11 | ], 12 | actions: [ 13 | { 14 | type: "add", 15 | path: "src/{{name}}-loves-who.txt", 16 | templateFile: "plop-templates/who-loves-who.txt", 17 | data: { 18 | name: "Frodo", 19 | otherName: "Gandalf", 20 | }, 21 | }, 22 | function (data) { 23 | data.subject = "world"; 24 | return "custom action added data"; 25 | }, 26 | { 27 | type: "add", 28 | path: "src/{{greeting}}-{{subject}}.txt", 29 | templateFile: "plop-templates/who-loves-who.txt", 30 | data: () => ({ greeting: "hola" }), 31 | }, 32 | ], 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-actions/dynamic-actions.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | 7 | describe("dynamic-actions", function () { 8 | afterEach(clean); 9 | 10 | let plop; 11 | let dynamicActions; 12 | beforeEach(async () => { 13 | plop = await nodePlop(`${mockPath}/plopfile.js`); 14 | dynamicActions = plop.getGenerator("dynamic-actions"); 15 | }); 16 | 17 | test("Check that the potato-man-burger.txt file has been created", async () => { 18 | await dynamicActions.runActions({ name: "potato man", yesPotatoes: true }); 19 | const burgerFilePath = path.resolve(testSrcPath, "potato-man-burger.txt"); 20 | const potatoFilePath = path.resolve(testSrcPath, "potato-man-burger.txt"); 21 | 22 | // both files should have been created 23 | expect(fs.existsSync(burgerFilePath)).toBe(true); 24 | expect(fs.existsSync(potatoFilePath)).toBe(true); 25 | }); 26 | 27 | test("Check that the file potato-hater-burger.txt", async () => { 28 | await dynamicActions.runActions({ 29 | name: "potato hater", 30 | yesPotatoes: false, 31 | }); 32 | const burgerFilePath = path.resolve(testSrcPath, "potato-hater-burger.txt"); 33 | const potatoFilePath = path.resolve( 34 | testSrcPath, 35 | "potato-hater-potatoes.txt", 36 | ); 37 | 38 | // only the burger file should have been created 39 | expect(fs.existsSync(burgerFilePath)).toBe(true); 40 | expect(fs.existsSync(potatoFilePath)).toBe(false); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-actions/plop-templates/burger.txt: -------------------------------------------------------------------------------- 1 | your burger is here {{name}} 2 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-actions/plop-templates/potatoes.txt: -------------------------------------------------------------------------------- 1 | I like potatoes 2 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-actions/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | // test with dynamic actions, regarding responses to prompts 3 | plop.setGenerator("dynamic-actions", { 4 | description: "another test using an actions function", 5 | prompts: [ 6 | { 7 | type: "input", 8 | name: "name", 9 | message: "What is your name?", 10 | validate: function (value) { 11 | if (/.+/.test(value)) { 12 | return true; 13 | } 14 | return "name is required"; 15 | }, 16 | }, 17 | { 18 | type: "confirm", 19 | name: "yesPotatoes", 20 | message: "Do you want potatoes with your burger?", 21 | }, 22 | ], 23 | actions: function (data) { 24 | var actions = [ 25 | { 26 | type: "add", 27 | path: "src/{{dashCase name}}-burger.txt", 28 | templateFile: "plop-templates/burger.txt", 29 | abortOnFail: true, 30 | }, 31 | ]; 32 | 33 | if (data.yesPotatoes) { 34 | actions.push({ 35 | type: "add", 36 | path: "src/{{dashCase name}}-potatoes.txt", 37 | templateFile: "plop-templates/potatoes.txt", 38 | abortOnFail: true, 39 | }); 40 | } 41 | 42 | return actions; 43 | }, 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-prompts/dynamic-prompts.spec.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | import { setupMockPath } from "../helpers/path.js"; 3 | const { clean, mockPath } = setupMockPath(import.meta.url); 4 | 5 | describe("dynamic-prompts", function () { 6 | afterEach(clean); 7 | 8 | let plop, dynamicPrompts; 9 | 10 | beforeEach(async () => { 11 | plop = await nodePlop(`${mockPath}/plopfile.js`); 12 | dynamicPrompts = plop.getGenerator("dynamic-prompt"); 13 | }); 14 | 15 | test("If prompt is provided as a function, runPrompts() should call it", async function () { 16 | const result = await dynamicPrompts.runPrompts(); 17 | expect(result.promptFunctionCalled).toBe(true); 18 | }); 19 | 20 | test("If prompt is provided as a function, runPrompts() should be called with inquirer instance", async function () { 21 | const result = await dynamicPrompts.runPrompts(); 22 | expect(result.promptArgs[0]).toBe(plop.inquirer); 23 | }); 24 | 25 | test("Prompt can be a function that synchronously returns answers", async function () { 26 | const dynPromptSync = plop.setGenerator("dynamic-prompt-sync", { 27 | prompts: () => ({ promptFunctionCalled: true }), 28 | }); 29 | const result = await dynPromptSync.runPrompts(); 30 | expect(result.promptFunctionCalled).toBe(true); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-prompts/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | // test with dynamic actions, regarding responses to prompts 3 | plop.setGenerator("dynamic-prompt", { 4 | description: "A test using a dynamic prompt defined by a function", 5 | prompts: function () { 6 | return Promise.resolve({ 7 | promptArgs: arguments, 8 | promptFunctionCalled: true, 9 | }); 10 | }, 11 | actions: function () { 12 | return []; 13 | }, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-template-file/dynamic-template-file.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | 7 | describe("dynamic-template-file", function () { 8 | afterEach(clean); 9 | 10 | let plop; 11 | let dynamicTemplateAdd; 12 | beforeEach(async () => { 13 | plop = await nodePlop(`${mockPath}/plopfile.js`); 14 | dynamicTemplateAdd = plop.getGenerator("dynamic-template-add"); 15 | await dynamicTemplateAdd.runActions({ 16 | name: "this is a test", 17 | kind: "LineChart", 18 | }); 19 | }); 20 | 21 | test("Check that the file has been created", () => { 22 | const filePath = path.resolve(testSrcPath, "this-is-a-test.txt"); 23 | expect(fs.existsSync(filePath)).toBe(true); 24 | }); 25 | 26 | test("Test file this-is-a-test.txt has been rendered from line-chart.txt", () => { 27 | const filePath = path.resolve(testSrcPath, "this-is-a-test.txt"); 28 | const content = fs.readFileSync(filePath).toString(); 29 | 30 | expect(content.includes("this is a line chart")).toBe(true); 31 | expect(content.includes("name: this is a test")).toBe(true); 32 | }); 33 | 34 | test("Test file change-me.txt has been updated with line-chart.txt", () => { 35 | const filePath = path.resolve(testSrcPath, "change-me.txt"); 36 | const content = fs.readFileSync(filePath).toString(); 37 | 38 | expect(content.includes("this is a line chart")).toBe(true); 39 | expect(content.includes("name: this is a test")).toBe(true); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-template-file/plop-templates/bar-chart.txt: -------------------------------------------------------------------------------- 1 | this is a bar chart 2 | name: {{name}} 3 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-template-file/plop-templates/change-me.txt: -------------------------------------------------------------------------------- 1 | -- APPEND ITEMS HERE -- 2 | 3 | +++++++++++++++++++++++++++++++++++++++ 4 | 5 | -- PREPEND ITEMS HERE -- 6 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-template-file/plop-templates/line-chart.txt: -------------------------------------------------------------------------------- 1 | this is a line chart 2 | name: {{name}} 3 | -------------------------------------------------------------------------------- /packages/node-plop/tests/dynamic-template-file/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("dynamic-template-add", { 3 | description: "adds a file using a dynamic template", 4 | prompts: [ 5 | { 6 | type: "input", 7 | name: "name", 8 | message: "What is your name?", 9 | validate: function (value) { 10 | if (/.+/.test(value)) { 11 | return true; 12 | } 13 | return "name is required"; 14 | }, 15 | }, 16 | { 17 | type: "list", 18 | name: "kind", 19 | message: "What kind of widget do you want to create?", 20 | choices: ["LineChart", "BarChart"], 21 | }, 22 | ], 23 | actions: [ 24 | { 25 | type: "add", 26 | path: "src/{{dashCase name}}.txt", 27 | templateFile: "plop-templates/{{dashCase kind}}.txt", 28 | abortOnFail: true, 29 | }, 30 | { 31 | type: "add", 32 | path: "src/change-me.txt", 33 | templateFile: "plop-templates/change-me.txt", 34 | }, 35 | { 36 | type: "append", 37 | path: "src/change-me.txt", 38 | pattern: /(-- APPEND ITEMS HERE --)/gi, 39 | templateFile: "plop-templates/{{dashCase kind}}.txt", 40 | }, 41 | { 42 | type: "modify", 43 | path: "src/change-me.txt", 44 | pattern: /(-- APPEND ITEMS HERE --)/gi, 45 | templateFile: "plop-templates/{{dashCase kind}}.txt", 46 | }, 47 | ], 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/esm-plopfile.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | 6 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 7 | 8 | describe("esm-plopfile", function () { 9 | afterEach(clean); 10 | 11 | test("Check that ESM default loads", async () => { 12 | const plop = await nodePlop(`${mockPath}/plopfile.js`); 13 | const basicAdd = plop.getGenerator("basic-add"); 14 | await basicAdd.runActions({ name: "this is a test", age: "21" }); 15 | 16 | const filePath = path.resolve(testSrcPath, "this-is-a-test.txt"); 17 | expect(fs.existsSync(filePath)).toBe(true); 18 | }); 19 | 20 | test("Check that MJS loads", async () => { 21 | const plop = await nodePlop(`${mockPath}/plopfile.mjs`); 22 | const basicAdd = plop.getGenerator("basic-add"); 23 | await basicAdd.runActions({ name: "this is a test", age: "21" }); 24 | 25 | const filePath = path.resolve(testSrcPath, "this-is-a-test.txt"); 26 | expect(fs.existsSync(filePath)).toBe(true); 27 | }); 28 | 29 | test("Check that CJS loads", async () => { 30 | const plop = await nodePlop(`${mockPath}/plopfile.cjs`); 31 | const basicAdd = plop.getGenerator("basic-add"); 32 | await basicAdd.runActions({ name: "this is a test", age: "21" }); 33 | 34 | const filePath = path.resolve(testSrcPath, "this-is-a-test.txt"); 35 | expect(fs.existsSync(filePath)).toBe(true); 36 | }); 37 | 38 | // TODO: Add these back once the following is fixed: 39 | // @see https://github.com/vitest-dev/vitest/issues/326 40 | test.skip("Check that CJS doesn't load", async () => { 41 | await expect(nodePlop(`${mockPath}/plopfile-cjs.js`)).rejects.toThrow(); 42 | }); 43 | 44 | test.skip("Check that incorrect (CJS) JS file doesn't load", async () => { 45 | await expect(nodePlop(`${mockPath}/plopfile-cjs.js`)).rejects.toThrow(); 46 | }); 47 | 48 | test.skip("Check that incorrect MJS doesn't load", async () => { 49 | await expect(nodePlop(`${mockPath}/plopfile-cjs.mjs`)).rejects.toThrow(); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic-plopfile-test", 3 | "private": true, 4 | "type": "module", 5 | "config": { 6 | "nested": [ 7 | "basic-plopfile-test-propertyPath-value-index-0", 8 | "basic-plopfile-test-propertyPath-value-index-1" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/plop-templates/add.txt: -------------------------------------------------------------------------------- 1 | add test 2 | name: {{name}} 3 | upperCase: {{constantCase name}} 4 | {{> salutation}} 5 | -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/plop-templates/change-me.txt: -------------------------------------------------------------------------------- 1 | the modify option in the test plop should add lines below for each run. 2 | Use modify for things like adding script references to an HTML file. 3 | 4 | -- APPEND ITEMS HERE -- 5 | 6 | +++++++++++++++++++++++++++++++++++++++ 7 | 8 | -- PREPEND ITEMS HERE -- 9 | -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/plop-templates/part.txt: -------------------------------------------------------------------------------- 1 | this is prepended! ## replace name here ##: {{age}} 2 | $1 -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/plopfile-cjs.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const { join } = require("path"); 3 | 4 | module.exports = function (plop) { 5 | /////// 6 | // helpers are passed through to handlebars and made 7 | // available for use in the generator templates 8 | // 9 | 10 | // adds 4 dashes around some text (yes es6/es2015 is supported) 11 | plop.setHelper("dashAround", function (text) { 12 | return "---- " + text + " ----"; 13 | }); 14 | // plop.setHelper('dashAround', (text) => '---- ' + text + ' ----'); 15 | 16 | // formats an array of options like you would write 17 | // it if you were speaking (one, two, and three) 18 | plop.setHelper("wordJoin", function (words) { 19 | return words.join(", ").replace(/(:?.*),/, "$1, and"); 20 | }); 21 | 22 | // greet the user using a partial 23 | plop.setPartial( 24 | "salutation", 25 | "my name is {{ properCase name }} and I am {{ age }}.", 26 | ); 27 | 28 | // setGenerator creates a generator that can be run with "plop generatorName" 29 | plop.setGenerator("basic-add", { 30 | description: "adds a file using a template", 31 | prompts: [ 32 | { 33 | type: "input", 34 | name: "name", 35 | message: "What is your name?", 36 | validate: function (value) { 37 | if (/.+/.test(value)) { 38 | return true; 39 | } 40 | return "name is required"; 41 | }, 42 | }, 43 | { 44 | type: "input", 45 | name: "age", 46 | message: "How old are you?", 47 | validate: function (value) { 48 | var digitsOnly = /\d+/; 49 | if (digitsOnly.test(value)) { 50 | return true; 51 | } 52 | return "Invalid age! Must be a number genius!"; 53 | }, 54 | }, 55 | ], 56 | actions: [ 57 | { 58 | type: "add", 59 | path: "src/{{dashCase name}}.txt", 60 | templateFile: "plop-templates/add.txt", 61 | abortOnFail: true, 62 | }, 63 | { 64 | type: "add", 65 | path: "src/_{{constantCase name}}.txt", 66 | template: 67 | 'test: {{pkg "name"}}\npropertyPathTest: {{pkg "config.nested[1]"}}\ninline template: {{name}}', 68 | abortOnFail: true, 69 | }, 70 | function customAction(answers) { 71 | // move the current working directory to the plop file path 72 | // this allows this action to work even when the generator is 73 | // executed from inside a subdirectory 74 | 75 | var plopFilePath = plop.getPlopfilePath(); 76 | 77 | // custom function can be synchronous or async (by returning a promise) 78 | var copiedMsg = "hey {{name}}, I copied change-me.txt for you", 79 | changeFile = "change-me.txt", 80 | toPath = join(plopFilePath, "src", changeFile), 81 | fromPath = join(plopFilePath, "plop-templates", changeFile); 82 | 83 | // you can use plop.renderString to render templates 84 | copiedMsg = plop.renderString(copiedMsg, answers); 85 | 86 | if (fs.existsSync(toPath)) { 87 | fs.unlinkSync(toPath); 88 | } 89 | 90 | fs.writeFileSync(toPath, fs.readFileSync(fromPath)); 91 | return copiedMsg; 92 | }, 93 | { 94 | type: "modify", 95 | path: "src/change-me.txt", 96 | pattern: /(-- APPEND ITEMS HERE --)/gi, 97 | template: "$1\r\n{{name}}: {{age}}", 98 | }, 99 | { 100 | type: "modify", 101 | path: "src/change-me.txt", 102 | pattern: /(-- PREPEND ITEMS HERE --)/gi, 103 | templateFile: "plop-templates/part.txt", 104 | }, 105 | { 106 | type: "modify", 107 | path: "src/change-me.txt", 108 | pattern: /## replace name here ##/gi, 109 | template: "replaced => {{dashCase name}}", 110 | }, 111 | ], 112 | }); 113 | }; 114 | -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/plopfile-cjs.mjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const {join} = require('path'); 3 | 4 | module.exports = function (plop) { 5 | /////// 6 | // helpers are passed through to handlebars and made 7 | // available for use in the generator templates 8 | // 9 | 10 | // adds 4 dashes around some text (yes es6/es2015 is supported) 11 | plop.setHelper('dashAround', function (text) { return '---- ' + text + ' ----'; }); 12 | // plop.setHelper('dashAround', (text) => '---- ' + text + ' ----'); 13 | 14 | // formats an array of options like you would write 15 | // it if you were speaking (one, two, and three) 16 | plop.setHelper('wordJoin', function (words) { 17 | return words.join(', ').replace(/(:?.*),/, '$1, and'); 18 | }); 19 | 20 | // greet the user using a partial 21 | plop.setPartial('salutation', 'my name is {{ properCase name }} and I am {{ age }}.'); 22 | 23 | 24 | // setGenerator creates a generator that can be run with "plop generatorName" 25 | plop.setGenerator('basic-add', { 26 | description: 'adds a file using a template', 27 | prompts: [ 28 | { 29 | type: 'input', 30 | name: 'name', 31 | message: 'What is your name?', 32 | validate: function (value) { 33 | if ((/.+/).test(value)) { return true; } 34 | return 'name is required'; 35 | } 36 | }, { 37 | type: 'input', 38 | name: 'age', 39 | message: 'How old are you?', 40 | validate: function (value) { 41 | var digitsOnly = /\d+/; 42 | if (digitsOnly.test(value)) { return true; } 43 | return 'Invalid age! Must be a number genius!'; 44 | } 45 | } 46 | ], 47 | actions: [ 48 | { 49 | type: 'add', 50 | path: 'src/{{dashCase name}}.txt', 51 | templateFile: 'plop-templates/add.txt', 52 | abortOnFail: true 53 | }, { 54 | type: 'add', 55 | path: 'src/_{{constantCase name}}.txt', 56 | template: 'test: {{pkg "name"}}\npropertyPathTest: {{pkg "config.nested[1]"}}\ninline template: {{name}}', 57 | abortOnFail: true 58 | }, 59 | function customAction(answers) { 60 | // move the current working directory to the plop file path 61 | // this allows this action to work even when the generator is 62 | // executed from inside a subdirectory 63 | 64 | var plopFilePath = plop.getPlopfilePath(); 65 | 66 | // custom function can be synchronous or async (by returning a promise) 67 | var copiedMsg = 'hey {{name}}, I copied change-me.txt for you', 68 | changeFile = 'change-me.txt', 69 | toPath = join(plopFilePath, 'src', changeFile), 70 | fromPath = join(plopFilePath, 'plop-templates', changeFile); 71 | 72 | // you can use plop.renderString to render templates 73 | copiedMsg = plop.renderString(copiedMsg, answers); 74 | 75 | if (fs.existsSync(toPath)) { 76 | fs.unlinkSync(toPath); 77 | } 78 | 79 | fs.writeFileSync(toPath, fs.readFileSync(fromPath)); 80 | return copiedMsg; 81 | },{ 82 | type: 'modify', 83 | path: 'src/change-me.txt', 84 | pattern: /(-- APPEND ITEMS HERE --)/gi, 85 | template: '$1\r\n{{name}}: {{age}}' 86 | },{ 87 | type: 'modify', 88 | path: 'src/change-me.txt', 89 | pattern: /(-- PREPEND ITEMS HERE --)/gi, 90 | templateFile: 'plop-templates/part.txt' 91 | },{ 92 | type: 'modify', 93 | path: 'src/change-me.txt', 94 | pattern: /## replace name here ##/gi, 95 | template: 'replaced => {{dashCase name}}' 96 | } 97 | ] 98 | }); 99 | }; 100 | -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/plopfile.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const {join} = require('path'); 3 | 4 | module.exports = function (plop) { 5 | /////// 6 | // helpers are passed through to handlebars and made 7 | // available for use in the generator templates 8 | // 9 | 10 | // adds 4 dashes around some text (yes es6/es2015 is supported) 11 | plop.setHelper('dashAround', function (text) { return '---- ' + text + ' ----'; }); 12 | // plop.setHelper('dashAround', (text) => '---- ' + text + ' ----'); 13 | 14 | // formats an array of options like you would write 15 | // it if you were speaking (one, two, and three) 16 | plop.setHelper('wordJoin', function (words) { 17 | return words.join(', ').replace(/(:?.*),/, '$1, and'); 18 | }); 19 | 20 | // greet the user using a partial 21 | plop.setPartial('salutation', 'my name is {{ properCase name }} and I am {{ age }}.'); 22 | 23 | 24 | // setGenerator creates a generator that can be run with "plop generatorName" 25 | plop.setGenerator('basic-add', { 26 | description: 'adds a file using a template', 27 | prompts: [ 28 | { 29 | type: 'input', 30 | name: 'name', 31 | message: 'What is your name?', 32 | validate: function (value) { 33 | if ((/.+/).test(value)) { return true; } 34 | return 'name is required'; 35 | } 36 | }, { 37 | type: 'input', 38 | name: 'age', 39 | message: 'How old are you?', 40 | validate: function (value) { 41 | var digitsOnly = /\d+/; 42 | if (digitsOnly.test(value)) { return true; } 43 | return 'Invalid age! Must be a number genius!'; 44 | } 45 | } 46 | ], 47 | actions: [ 48 | { 49 | type: 'add', 50 | path: 'src/{{dashCase name}}.txt', 51 | templateFile: 'plop-templates/add.txt', 52 | abortOnFail: true 53 | }, { 54 | type: 'add', 55 | path: 'src/_{{constantCase name}}.txt', 56 | template: 'test: {{pkg "name"}}\npropertyPathTest: {{pkg "config.nested[1]"}}\ninline template: {{name}}', 57 | abortOnFail: true 58 | }, 59 | function customAction(answers) { 60 | // move the current working directory to the plop file path 61 | // this allows this action to work even when the generator is 62 | // executed from inside a subdirectory 63 | 64 | var plopFilePath = plop.getPlopfilePath(); 65 | 66 | // custom function can be synchronous or async (by returning a promise) 67 | var copiedMsg = 'hey {{name}}, I copied change-me.txt for you', 68 | changeFile = 'change-me.txt', 69 | toPath = join(plopFilePath, 'src', changeFile), 70 | fromPath = join(plopFilePath, 'plop-templates', changeFile); 71 | 72 | // you can use plop.renderString to render templates 73 | copiedMsg = plop.renderString(copiedMsg, answers); 74 | 75 | if (fs.existsSync(toPath)) { 76 | fs.unlinkSync(toPath); 77 | } 78 | 79 | fs.writeFileSync(toPath, fs.readFileSync(fromPath)); 80 | return copiedMsg; 81 | },{ 82 | type: 'modify', 83 | path: 'src/change-me.txt', 84 | pattern: /(-- APPEND ITEMS HERE --)/gi, 85 | template: '$1\r\n{{name}}: {{age}}' 86 | },{ 87 | type: 'modify', 88 | path: 'src/change-me.txt', 89 | pattern: /(-- PREPEND ITEMS HERE --)/gi, 90 | templateFile: 'plop-templates/part.txt' 91 | },{ 92 | type: 'modify', 93 | path: 'src/change-me.txt', 94 | pattern: /## replace name here ##/gi, 95 | template: 'replaced => {{dashCase name}}' 96 | } 97 | ] 98 | }); 99 | }; 100 | -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/plopfile.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { join } from "path"; 3 | 4 | export default function (plop) { 5 | /////// 6 | // helpers are passed through to handlebars and made 7 | // available for use in the generator templates 8 | // 9 | 10 | // adds 4 dashes around some text (yes es6/es2015 is supported) 11 | plop.setHelper("dashAround", function (text) { 12 | return "---- " + text + " ----"; 13 | }); 14 | // plop.setHelper('dashAround', (text) => '---- ' + text + ' ----'); 15 | 16 | // formats an array of options like you would write 17 | // it if you were speaking (one, two, and three) 18 | plop.setHelper("wordJoin", function (words) { 19 | return words.join(", ").replace(/(:?.*),/, "$1, and"); 20 | }); 21 | 22 | // greet the user using a partial 23 | plop.setPartial( 24 | "salutation", 25 | "my name is {{ properCase name }} and I am {{ age }}.", 26 | ); 27 | 28 | // setGenerator creates a generator that can be run with "plop generatorName" 29 | plop.setGenerator("basic-add", { 30 | description: "adds a file using a template", 31 | prompts: [ 32 | { 33 | type: "input", 34 | name: "name", 35 | message: "What is your name?", 36 | validate: function (value) { 37 | if (/.+/.test(value)) { 38 | return true; 39 | } 40 | return "name is required"; 41 | }, 42 | }, 43 | { 44 | type: "input", 45 | name: "age", 46 | message: "How old are you?", 47 | validate: function (value) { 48 | var digitsOnly = /\d+/; 49 | if (digitsOnly.test(value)) { 50 | return true; 51 | } 52 | return "Invalid age! Must be a number genius!"; 53 | }, 54 | }, 55 | ], 56 | actions: [ 57 | { 58 | type: "add", 59 | path: "src/{{dashCase name}}.txt", 60 | templateFile: "plop-templates/add.txt", 61 | abortOnFail: true, 62 | }, 63 | ], 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /packages/node-plop/tests/esm-plopfile/plopfile.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import {join} from 'path'; 3 | 4 | export default function (plop) { 5 | /////// 6 | // helpers are passed through to handlebars and made 7 | // available for use in the generator templates 8 | // 9 | 10 | // adds 4 dashes around some text (yes es6/es2015 is supported) 11 | plop.setHelper('dashAround', function (text) { return '---- ' + text + ' ----'; }); 12 | // plop.setHelper('dashAround', (text) => '---- ' + text + ' ----'); 13 | 14 | // formats an array of options like you would write 15 | // it if you were speaking (one, two, and three) 16 | plop.setHelper('wordJoin', function (words) { 17 | return words.join(', ').replace(/(:?.*),/, '$1, and'); 18 | }); 19 | 20 | // greet the user using a partial 21 | plop.setPartial('salutation', 'my name is {{ properCase name }} and I am {{ age }}.'); 22 | 23 | 24 | // setGenerator creates a generator that can be run with "plop generatorName" 25 | plop.setGenerator('basic-add', { 26 | description: 'adds a file using a template', 27 | prompts: [ 28 | { 29 | type: 'input', 30 | name: 'name', 31 | message: 'What is your name?', 32 | validate: function (value) { 33 | if ((/.+/).test(value)) { return true; } 34 | return 'name is required'; 35 | } 36 | }, { 37 | type: 'input', 38 | name: 'age', 39 | message: 'How old are you?', 40 | validate: function (value) { 41 | var digitsOnly = /\d+/; 42 | if (digitsOnly.test(value)) { return true; } 43 | return 'Invalid age! Must be a number genius!'; 44 | } 45 | } 46 | ], 47 | actions: [ 48 | { 49 | type: 'add', 50 | path: 'src/{{dashCase name}}.txt', 51 | templateFile: 'plop-templates/add.txt', 52 | abortOnFail: true 53 | }, { 54 | type: 'add', 55 | path: 'src/_{{constantCase name}}.txt', 56 | template: 'test: {{pkg "name"}}\npropertyPathTest: {{pkg "config.nested[1]"}}\ninline template: {{name}}', 57 | abortOnFail: true 58 | }, 59 | function customAction(answers) { 60 | // move the current working directory to the plop file path 61 | // this allows this action to work even when the generator is 62 | // executed from inside a subdirectory 63 | 64 | var plopFilePath = plop.getPlopfilePath(); 65 | 66 | // custom function can be synchronous or async (by returning a promise) 67 | var copiedMsg = 'hey {{name}}, I copied change-me.txt for you', 68 | changeFile = 'change-me.txt', 69 | toPath = join(plopFilePath, 'src', changeFile), 70 | fromPath = join(plopFilePath, 'plop-templates', changeFile); 71 | 72 | // you can use plop.renderString to render templates 73 | copiedMsg = plop.renderString(copiedMsg, answers); 74 | 75 | if (fs.existsSync(toPath)) { 76 | fs.unlinkSync(toPath); 77 | } 78 | 79 | fs.writeFileSync(toPath, fs.readFileSync(fromPath)); 80 | return copiedMsg; 81 | },{ 82 | type: 'modify', 83 | path: 'src/change-me.txt', 84 | pattern: /(-- APPEND ITEMS HERE --)/gi, 85 | template: '$1\r\n{{name}}: {{age}}' 86 | },{ 87 | type: 'modify', 88 | path: 'src/change-me.txt', 89 | pattern: /(-- PREPEND ITEMS HERE --)/gi, 90 | templateFile: 'plop-templates/part.txt' 91 | },{ 92 | type: 'modify', 93 | path: 'src/change-me.txt', 94 | pattern: /## replace name here ##/gi, 95 | template: 'replaced => {{dashCase name}}' 96 | } 97 | ] 98 | }); 99 | }; 100 | -------------------------------------------------------------------------------- /packages/node-plop/tests/force-del-outside-cwd/force-del-outside-cwd.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import nodePlop from "../../src/index.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 5 | 6 | describe("force-del-outside-cwd", function () { 7 | afterEach(clean); 8 | 9 | let plop; 10 | beforeEach(async () => { 11 | plop = await nodePlop(`${mockPath}/sub/plopfile.js`); 12 | }); 13 | 14 | // chdir doesn't like to work in modern versions of ava (or many other test frameworks) 15 | // EG: process.chdir() is not supported in workers 16 | // We should rewrite this test 17 | test.skip("Force del outside cwd test", async function () { 18 | process.chdir(`${mockPath}/sub`); 19 | fs.mkdirSync(testSrcPath); 20 | fs.writeFileSync(testSrcPath + "/test.txt", "init content"); 21 | const testGen = plop.getGenerator("test"); 22 | const { changes } = await testGen.runActions(); 23 | const content = fs.readFileSync(testSrcPath + "/test.txt", "utf8"); 24 | expect(changes.length).toBe(1); 25 | expect(content).toBe("test content"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/node-plop/tests/force-del-outside-cwd/sub/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("test", { 3 | actions: [ 4 | { 5 | type: "add", 6 | path: "../src/test.txt", 7 | template: "test content", 8 | force: true, 9 | }, 10 | ], 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/node-plop/tests/generator-name-and-prompts/generator-name-and-prompts.spec.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | import { setupMockPath } from "../helpers/path.js"; 3 | const { clean } = setupMockPath(import.meta.url); 4 | 5 | describe("generator-name-and-prompts", function () { 6 | afterEach(clean); 7 | 8 | /////// 9 | // generator name should be defaulted 10 | // runPrompts should reject if there are no prompts 11 | // 12 | 13 | let plop; 14 | beforeEach(async () => { 15 | plop = await nodePlop(); 16 | 17 | plop.setGenerator("", {}); 18 | plop.setGenerator("bad-actions-function", { 19 | actions: () => { 20 | "bad actions output"; 21 | }, 22 | }); 23 | }); 24 | 25 | test("generator should not be able to run promps if it has none", async function () { 26 | const generatorOne = plop.getGenerator("generator-1"); 27 | expect(typeof generatorOne).toBe("object"); 28 | 29 | await expect(() => generatorOne.runPrompts()).rejects.toThrow( 30 | "generator-1 has no prompts", 31 | ); 32 | }); 33 | 34 | test("generator should not be able to run actions if it has none", async function () { 35 | const generatorOne = plop.getGenerator("generator-1"); 36 | expect(typeof generatorOne).toBe("object"); 37 | 38 | await expect(() => generatorOne.runActions()).rejects.toThrow( 39 | "generator-1 has no actions", 40 | ); 41 | }); 42 | 43 | test("generator should not be able to run invalid actions data", async function () { 44 | const generatorBadActions = plop.getGenerator("bad-actions-function"); 45 | 46 | await expect(() => generatorBadActions.runActions()).rejects.toThrow( 47 | "bad-actions-function has no actions", 48 | ); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/node-plop/tests/get-generator-list/get-generator-list.spec.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | import { setupMockPath } from "../helpers/path.js"; 3 | const { clean } = setupMockPath(import.meta.url); 4 | 5 | describe("get-generator-list", function () { 6 | afterEach(clean); 7 | 8 | let plop; 9 | beforeEach(async () => { 10 | plop = await nodePlop(); 11 | }); 12 | 13 | ///// 14 | // if an action has no path, the action should fail 15 | // 16 | 17 | test("set generator should return the generator object", function () { 18 | plop.setGenerator("one", {}); 19 | plop.setGenerator("two", {}); 20 | plop.setGenerator("three", {}); 21 | 22 | const list = plop.getGeneratorList().map((g) => g.name); 23 | 24 | expect(list.includes("one")).toBe(true); 25 | expect(list.includes("two")).toBe(true); 26 | expect(list.includes("three")).toBe(true); 27 | expect(list.includes("four")).toBe(false); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/node-plop/tests/helpers/path.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { normalizePath } from "../../src/actions/_common-action-utils.js"; 3 | import { deleteAsync } from "del"; 4 | import * as fspp from "../../src/fs-promise-proxy.js"; 5 | import { fileURLToPath } from "node:url"; 6 | 7 | /** 8 | * @param {string} importMetaUrl 9 | */ 10 | export function setupMockPath(importMetaUrl) { 11 | const __dirname = path.dirname(fileURLToPath(importMetaUrl)); 12 | const mockPath = normalizePath(__dirname); 13 | const testSrcPath = path.resolve(mockPath, "src"); 14 | 15 | async function clean() { 16 | // remove the src folder 17 | await deleteAsync([normalizePath(testSrcPath)], { force: true }); 18 | 19 | try { 20 | const mockIsEmpty = (await fspp.readdir(mockPath)).length === 0; 21 | if (mockIsEmpty) { 22 | await deleteAsync([mockPath], { force: true }); 23 | } 24 | } catch (err) { 25 | // there was no mock directory to remove 26 | } 27 | } 28 | 29 | return { 30 | mockPath, 31 | testSrcPath, 32 | clean, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /packages/node-plop/tests/imported-custom-action/custom-action.js: -------------------------------------------------------------------------------- 1 | import { deleteAsync } from "del"; 2 | import * as fspp from "../../src/fs-promise-proxy.js"; 3 | import { normalizePath } from "../../src/actions/_common-action-utils.js"; 4 | 5 | export default async function (data, cfg /*, plop*/) { 6 | const removeFilePath = cfg.path; 7 | if (await fspp.fileExists(removeFilePath)) { 8 | return await deleteAsync([normalizePath(removeFilePath)]); 9 | } else { 10 | throw `Path does not exist ${removeFilePath}`; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/node-plop/tests/imported-custom-action/imported-custom-action.spec.js: -------------------------------------------------------------------------------- 1 | import { fileExists } from "../../src/fs-promise-proxy.js"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | import { pathToFileURL } from "url"; 7 | 8 | describe("imported-custom-action", function () { 9 | afterEach(clean); 10 | 11 | ///// 12 | // imported custom actions should execute 13 | // 14 | 15 | let customAction; 16 | beforeEach(async () => { 17 | customAction = ( 18 | await import( 19 | pathToFileURL(path.resolve(mockPath, "custom-action.js")).href 20 | ) 21 | ).default; 22 | }); 23 | 24 | test("imported custom action should execute correctly", async function () { 25 | const plop = await nodePlop(); 26 | const testFilePath = path.resolve(testSrcPath, "test.txt"); 27 | plop.setActionType("custom-del", customAction); 28 | 29 | // add the file 30 | const addTestFile = { type: "add", path: testFilePath }; 31 | // remove the file 32 | const deleteTestFile = { type: "custom-del", path: testFilePath }; 33 | 34 | const generator = plop.setGenerator("", { 35 | actions: [addTestFile, deleteTestFile], 36 | }); 37 | 38 | expect(typeof plop.getActionType("custom-del")).toBe("function"); 39 | 40 | const results = await generator.runActions({}); 41 | const testFileExists = await fileExists(testFilePath); 42 | 43 | expect(results.failures.length).toBe(0); 44 | expect(results.changes.length).toBe(2); 45 | expect(testFileExists).toBe(false); 46 | }); 47 | 48 | test("imported custom action can throw errors", async function () { 49 | const plop = await nodePlop(); 50 | const testFilePath = path.resolve(testSrcPath, "test2.txt"); 51 | plop.setActionType("custom-del", customAction); 52 | 53 | // remove the file 54 | const deleteTestFile = { type: "custom-del", path: testFilePath }; 55 | 56 | // remove a file that doesn't exist (should error) 57 | const generator = plop.setGenerator("", { actions: [deleteTestFile] }); 58 | const results = await generator.runActions({}); 59 | 60 | expect(results.failures.length).toBe(1); 61 | expect(results.failures[0].error.startsWith("Path does not exist")).toBe( 62 | true, 63 | ); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/node-plop/tests/invalid-generator-names/invalid-generator-names.spec.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | import { setupMockPath } from "../helpers/path.js"; 3 | const { clean } = setupMockPath(import.meta.url); 4 | 5 | describe("invalid-generator-names", function () { 6 | afterEach(clean); 7 | 8 | let plop; 9 | beforeEach(async () => { 10 | plop = await nodePlop(); 11 | }); 12 | 13 | test("Invalid generator names test", function () { 14 | plop.setGenerator("test"); 15 | expect(() => plop.getGenerator("error")).toThrowError( 16 | 'Generator "error" does not exist.', 17 | ); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/node-plop/tests/lifecycle-hooks/lifecycle-hooks.spec.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | import { setupMockPath } from "../helpers/path.js"; 3 | const { clean } = setupMockPath(import.meta.url); 4 | 5 | describe("lifecycle-hooks", function () { 6 | afterEach(clean); 7 | 8 | const errAction = () => { 9 | throw Error(""); 10 | }; 11 | 12 | // onSuccess and onFailure Lifecycle hooks 13 | test("Lifecycle hooks test (onSuccess, onFailure)", async function () { 14 | const plop = await nodePlop(); 15 | const onSuccess = () => onSuccess.called++; 16 | onSuccess.called = 0; 17 | const onFailure = () => onFailure.called++; 18 | onFailure.called = 0; 19 | 20 | await plop 21 | .setGenerator("", { actions: [() => "yes", errAction] }) 22 | .runActions({}, { onSuccess, onFailure }); 23 | 24 | expect(onSuccess.called).toBe(1); 25 | expect(onFailure.called).toBe(1); 26 | }); 27 | 28 | test("Lifecycle hooks negative scenario test (onSuccess)", async function () { 29 | const plop = await nodePlop(); 30 | const onSuccess = () => onSuccess.called++; 31 | onSuccess.called = 0; 32 | const onFailure = () => onFailure.called++; 33 | onFailure.called = 0; 34 | 35 | await plop 36 | .setGenerator("", { actions: [errAction, errAction] }) 37 | .runActions({}, { onSuccess, onFailure }); 38 | 39 | expect(onSuccess.called).toBe(0); 40 | expect(onFailure.called).toBe(2); 41 | }); 42 | 43 | test("Lifecycle hooks negative scenario test (onFailure)", async function () { 44 | const plop = await nodePlop(); 45 | const onSuccess = () => onSuccess.called++; 46 | onSuccess.called = 0; 47 | const onFailure = () => onFailure.called++; 48 | onFailure.called = 0; 49 | 50 | await plop 51 | .setGenerator("", { actions: [() => "yes", () => "yes"] }) 52 | .runActions({}, { onSuccess, onFailure }); 53 | 54 | expect(onSuccess.called).toBe(2); 55 | expect(onFailure.called).toBe(0); 56 | }); 57 | 58 | test("Lifecycle hook test (onComment)", async function () { 59 | const plop = await nodePlop(); 60 | const onSuccess = () => onSuccess.called++; 61 | onSuccess.called = 0; 62 | const onFailure = () => onFailure.called++; 63 | onFailure.called = 0; 64 | const onComment = () => onComment.called++; 65 | onComment.called = 0; 66 | 67 | await plop 68 | .setGenerator("", { actions: ["yes", () => "yes", errAction, "yes"] }) 69 | .runActions({}, { onSuccess, onFailure, onComment }); 70 | 71 | expect(onSuccess.called).toBe(1); 72 | expect(onFailure.called).toBe(1); 73 | expect(onComment.called).toBe(1); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /packages/node-plop/tests/load-assets-from-pack/load-assets-from-pack.spec.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import nodePlop from "../../src/index.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | const { clean, mockPath } = setupMockPath(import.meta.url); 5 | 6 | describe("load-assets-from-pack", function () { 7 | afterEach(clean); 8 | 9 | const packModuleName = "plop-pack-fancy-comments"; 10 | const plopfilePath = path.join(mockPath, "plopfile.js"); 11 | 12 | ///// 13 | // test the various ways to import all or part of a node module 14 | // 15 | 16 | test("plop.load should use the default include definition set by the pack", async function () { 17 | const plop = await nodePlop(); 18 | await plop.load(packModuleName); 19 | 20 | expect(plop.getHelperList().includes("js-multi-line-header")).toBe(true); 21 | expect(plop.getGeneratorList().length).toBe(0); 22 | expect(plop.getHelperList().length > 0).toBe(true); 23 | expect(plop.getPartialList().length).toBe(0); 24 | }); 25 | 26 | test("plop.load should include all generators by default", async function () { 27 | const plop = await nodePlop(); 28 | await plop.load([packModuleName], { prefix: "html-" }); 29 | 30 | expect(plop.getHelperList().includes("html-multi-line-header")).toBe(true); 31 | expect(plop.getGeneratorList().length).toBe(0); 32 | expect(plop.getHelperList().length > 0).toBe(true); 33 | expect(plop.getPartialList().length).toBe(0); 34 | }); 35 | 36 | test("plop.load should work with mixed types (packs and files)", async function () { 37 | const plop = await nodePlop(); 38 | await plop.load([packModuleName, plopfilePath]); 39 | 40 | expect(plop.getHelperList().includes("js-multi-line-header")).toBe(true); 41 | expect(plop.getGeneratorList().length).toBe(3); 42 | expect(plop.getHelperList().length > 0).toBe(true); 43 | expect(plop.getPartialList().length).toBe(0); 44 | }); 45 | 46 | test("plop.load should allow consumer to override config", async function () { 47 | const plop = await nodePlop(); 48 | await plop.load([packModuleName, plopfilePath], { prefix: "test-" }); 49 | 50 | expect(plop.getHelperList().includes("test-multi-line-header")).toBe(true); 51 | expect( 52 | plop 53 | .getGeneratorList() 54 | .map((g) => g.name) 55 | .includes("test-generator1"), 56 | ).toBe(true); 57 | expect(plop.getGeneratorList().length).toBe(3); 58 | expect(plop.getHelperList().length > 0).toBe(true); 59 | expect(plop.getPartialList().length).toBe(0); 60 | }); 61 | 62 | test("plop.load should allow consumer to override include definition", async function () { 63 | const plop = await nodePlop(); 64 | await plop.load([packModuleName, plopfilePath], null, { helpers: true }); 65 | 66 | expect(plop.getGeneratorList().length).toBe(0); 67 | expect(plop.getHelperList().length > 0).toBe(true); 68 | expect(plop.getHelperList().includes("js-multi-line-header")).toBe(true); 69 | expect(plop.getPartialList().length).toBe(0); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/node-plop/tests/load-assets-from-pack/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop, config = {}) { 2 | const cfg = Object.assign({ prefix: "" }, config); 3 | 4 | // adds 4 dashes around some text (yes es6/es2015 is supported) 5 | plop.setHelper(`${cfg.prefix}helper1`, (t) => `helper 1: ${t}`); 6 | plop.setHelper(`${cfg.prefix}helper2`, (t) => `helper 2: ${t}`); 7 | plop.setHelper(`${cfg.prefix}helper3`, (t) => `helper 3: ${t}`); 8 | 9 | plop.setPartial(`${cfg.prefix}partial1`, "partial 1: {{name}}"); 10 | plop.setPartial(`${cfg.prefix}partial2`, "partial 2: {{name}}"); 11 | plop.setPartial(`${cfg.prefix}partial3`, "partial 3: {{name}}"); 12 | 13 | // setGenerator creates a generator that can be run with "plop generatorName" 14 | plop.setGenerator(`${cfg.prefix}generator1`, { 15 | actions: [{ type: "add", path: "src/{{name}}.txt", template: "" }], 16 | }); 17 | plop.setGenerator(`${cfg.prefix}generator2`, { 18 | actions: [{ type: "add", path: "src/{{name}}.txt", template: "" }], 19 | }); 20 | plop.setGenerator(`${cfg.prefix}generator3`, { 21 | actions: [{ type: "add", path: "src/{{name}}.txt", template: "" }], 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /packages/node-plop/tests/load-assets-from-plopfile/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop, config = {}) { 2 | const cfg = Object.assign({ prefix: "" }, config); 3 | 4 | plop.setHelper(`${cfg.prefix}helper1`, (t) => `helper 1: ${t}`); 5 | plop.setHelper(`${cfg.prefix}helper2`, (t) => `helper 2: ${t}`); 6 | plop.setHelper(`${cfg.prefix}helper3`, (t) => `helper 3: ${t}`); 7 | 8 | plop.setPartial(`${cfg.prefix}partial1`, "partial 1: {{name}}"); 9 | plop.setPartial(`${cfg.prefix}partial2`, "partial 2: {{name}}"); 10 | plop.setPartial(`${cfg.prefix}partial3`, "partial 3: {{name}}"); 11 | 12 | plop.setActionType(`${cfg.prefix}actionType1`, () => "test"); 13 | 14 | const generatorObject = { 15 | actions: [{ type: "add", path: "src/{{name}}.txt" }], 16 | }; 17 | plop.setGenerator(`${cfg.prefix}generator1`, generatorObject); 18 | plop.setGenerator(`${cfg.prefix}generator2`, generatorObject); 19 | plop.setGenerator(`${cfg.prefix}generator3`, generatorObject); 20 | } 21 | -------------------------------------------------------------------------------- /packages/node-plop/tests/load-nested-plopfile-generators/load-nested-plopfile-generators.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import nodePlop from "../../src/index.js"; 4 | import { setupMockPath } from "../helpers/path.js"; 5 | const { clean, testSrcPath, mockPath } = setupMockPath(import.meta.url); 6 | 7 | describe("load-nested-plopfile-generators", function () { 8 | afterAll(clean); 9 | 10 | let plop; 11 | beforeAll(async () => { 12 | plop = await nodePlop(`${mockPath}/plopfile.js`); 13 | }); 14 | 15 | ///// 16 | // if an action has no path, the action should fail 17 | // 18 | 19 | test("nested generator should add file to main directory", async function () { 20 | const filePath = path.resolve(testSrcPath, "nested-nestman.txt"); 21 | const generator = plop.getGenerator("basic-nested"); 22 | expect(typeof generator.runPrompts).toBe("function"); 23 | expect(typeof generator.runActions).toBe("function"); 24 | expect(generator.name).toBe("basic-nested"); 25 | 26 | const results = await generator.runActions({ name: "Nestman" }); 27 | expect(results.changes.length).toBe(1); 28 | expect(results.failures.length).toBe(0); 29 | expect(fs.existsSync(filePath)).toBe(true); 30 | }); 31 | 32 | test("nested generator should not override existing helpers", async function () { 33 | const filePath = path.resolve(testSrcPath, "addman.txt"); 34 | const generator = plop.getGenerator("basic-add"); 35 | expect(typeof generator.runPrompts).toBe("function"); 36 | expect(typeof generator.runActions).toBe("function"); 37 | expect(generator.name).toBe("basic-add"); 38 | 39 | const results = await generator.runActions({ name: "Addman" }).then(); 40 | expect(results.changes.length).toBe(1); 41 | expect(results.failures.length).toBe(0); 42 | expect(fs.existsSync(filePath)).toBe(true); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/node-plop/tests/load-nested-plopfile-generators/nested/nested-plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | // adds 4 dashes around some text (yes es6/es2015 is supported) 3 | plop.setHelper("surround", (text) => "##### " + text + " #####"); 4 | 5 | // setGenerator creates a generator that can be run with "plop generatorName" 6 | plop.setGenerator("basic-nested", { 7 | actions: [ 8 | { 9 | type: "add", 10 | path: "src/nested-{{dashCase name}}.txt", 11 | templateFile: "plop-templates/nested-test.txt", 12 | }, 13 | ], 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/node-plop/tests/load-nested-plopfile-generators/nested/plop-templates/nested-test.txt: -------------------------------------------------------------------------------- 1 | nested {{name}} 2 | {{surround name}} 3 | -------------------------------------------------------------------------------- /packages/node-plop/tests/load-nested-plopfile-generators/plop-templates/test.txt: -------------------------------------------------------------------------------- 1 | testing {{name}} 2 | Surround {{surround name}} 3 | -------------------------------------------------------------------------------- /packages/node-plop/tests/load-nested-plopfile-generators/plopfile.js: -------------------------------------------------------------------------------- 1 | export default async function (plop) { 2 | // adds 4 dashes around some text (yes es6/es2015 is supported) 3 | plop.setHelper("surround", (text) => "---- " + text + " ----"); 4 | 5 | // setGenerator creates a generator that can be run with "plop generatorName" 6 | plop.setGenerator("basic-add", { 7 | actions: [ 8 | { 9 | type: "add", 10 | path: "src/{{dashCase name}}.txt", 11 | templateFile: "plop-templates/test.txt", 12 | }, 13 | ], 14 | }); 15 | 16 | await plop.load("./nested/nested-plopfile.js"); 17 | } 18 | -------------------------------------------------------------------------------- /packages/node-plop/tests/missing-action-path/missing-action-path.spec.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | import { setupMockPath } from "../helpers/path.js"; 3 | const { clean } = setupMockPath(import.meta.url); 4 | 5 | describe("missing-action-path", function () { 6 | afterEach(clean); 7 | 8 | let plop; 9 | beforeAll(async () => { 10 | plop = await nodePlop(); 11 | }); 12 | 13 | beforeEach(() => { 14 | plop.setGenerator("no-path", { 15 | actions: [ 16 | { type: "add", template: "{{name}}", abortOnFail: false }, 17 | { type: "add", path: "", template: "{{name}}" }, 18 | ], 19 | }); 20 | }); 21 | 22 | test("Check that the file has been created", async function () { 23 | const name = "no path"; 24 | const results = await plop.getGenerator("no-path").runActions({ name }); 25 | const { changes, failures } = results; 26 | 27 | expect(changes.length).toBe(0); 28 | expect(failures.length).toBe(2); 29 | expect(failures[0].error).toBe('Invalid path "undefined"'); 30 | expect(failures[1].error).toBe('Invalid path ""'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/node-plop/tests/prompt-bypass-checkbox/prompt-bypass-checkbox.spec.js: -------------------------------------------------------------------------------- 1 | import promptBypass from "../../src/prompt-bypass.js"; 2 | import nodePlop from "../../src/index.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | const { clean } = setupMockPath(import.meta.url); 5 | 6 | describe("prompt-bypass-checkbox", function () { 7 | afterEach(clean); 8 | 9 | let plop; 10 | beforeEach(async () => { 11 | plop = await nodePlop(); 12 | }); 13 | 14 | const prompts = [ 15 | { 16 | type: "checkbox", 17 | name: "checkbox", 18 | message: "checkboxMsg", 19 | choices: ["one", { key: "t", value: "two" }, { name: "three" }], 20 | }, 21 | ]; 22 | 23 | test("verify good bypass input", async function () { 24 | const [, allAnswersByValue] = await promptBypass( 25 | prompts, 26 | ["one,two,three"], 27 | plop, 28 | ); 29 | expect(Array.isArray(allAnswersByValue.checkbox)).toBe(true); 30 | expect(JSON.stringify(allAnswersByValue.checkbox)).toBe( 31 | '["one","two","three"]', 32 | ); 33 | 34 | const [, someAnswersByValue] = await promptBypass( 35 | prompts, 36 | ["one,three"], 37 | plop, 38 | ); 39 | expect(Array.isArray(someAnswersByValue.checkbox)).toBe(true); 40 | expect(JSON.stringify(someAnswersByValue.checkbox)).toBe('["one","three"]'); 41 | 42 | const [, allAnswersByIndex] = await promptBypass(prompts, ["0,1,2"], plop); 43 | expect(Array.isArray(allAnswersByIndex.checkbox)).toBe(true); 44 | expect(JSON.stringify(allAnswersByIndex.checkbox)).toBe( 45 | '["one","two","three"]', 46 | ); 47 | 48 | const [, someAnswersByIndex] = await promptBypass(prompts, ["0,2"], plop); 49 | expect(Array.isArray(someAnswersByIndex.checkbox)).toBe(true); 50 | expect(JSON.stringify(someAnswersByIndex.checkbox)).toBe('["one","three"]'); 51 | 52 | const [, allAnswersByMixed] = await promptBypass( 53 | prompts, 54 | ["0,t,three"], 55 | plop, 56 | ); 57 | expect(Array.isArray(allAnswersByMixed.checkbox)).toBe(true); 58 | expect(JSON.stringify(allAnswersByMixed.checkbox)).toBe( 59 | '["one","two","three"]', 60 | ); 61 | 62 | const [, someAnswersByMixed] = await promptBypass( 63 | prompts, 64 | ["0,three"], 65 | plop, 66 | ); 67 | expect(Array.isArray(someAnswersByMixed.checkbox)).toBe(true); 68 | expect(JSON.stringify(someAnswersByMixed.checkbox)).toBe('["one","three"]'); 69 | 70 | const [, noAnswers] = await promptBypass(prompts, [""], plop); 71 | expect(Array.isArray(noAnswers.checkbox)).toBe(true); 72 | expect(JSON.stringify(noAnswers.checkbox)).toBe("[]"); 73 | }); 74 | 75 | test("verify bad bypass input", async function () { 76 | await expect(() => 77 | promptBypass(prompts, ["one,four"]).reject.toThrow({ is: plop }), 78 | ); 79 | await expect(() => 80 | promptBypass(prompts, ["four"]).reject.toThrow({ is: plop }), 81 | ); 82 | await expect(() => 83 | promptBypass(prompts, ["3"]).reject.toThrow({ is: plop }), 84 | ); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/node-plop/tests/prompt-bypass-confirm/prompt-bypass-confirm.spec.js: -------------------------------------------------------------------------------- 1 | import promptBypass from "../../src/prompt-bypass.js"; 2 | import nodePlop from "../../src/index.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | const { clean } = setupMockPath(import.meta.url); 5 | 6 | describe("prompt-bypass-confirm", function () { 7 | afterEach(clean); 8 | 9 | let plop; 10 | beforeEach(async () => { 11 | plop = await nodePlop(); 12 | }); 13 | 14 | const prompts = [ 15 | { type: "confirm", name: "confirm1", message: "confirmMsg1" }, 16 | { type: "confirm", name: "confirm2", message: "confirmMsg2" }, 17 | { type: "confirm", name: "confirm3", message: "confirmMsg3" }, 18 | { type: "confirm", name: "confirm4", message: "confirmMsg4" }, 19 | ]; 20 | 21 | test("verify good bypass input", async function () { 22 | const [, isTrue] = await promptBypass( 23 | prompts, 24 | ["y", "true", "yes", "t"], 25 | plop, 26 | ); 27 | expect(isTrue.confirm1).toBe(true); 28 | expect(isTrue.confirm2).toBe(true); 29 | expect(isTrue.confirm3).toBe(true); 30 | expect(isTrue.confirm4).toBe(true); 31 | 32 | const [, isTrueCap] = await promptBypass( 33 | prompts, 34 | ["Y", "True", "YES", "T"], 35 | plop, 36 | ); 37 | expect(isTrueCap.confirm1).toBe(true); 38 | expect(isTrueCap.confirm2).toBe(true); 39 | expect(isTrueCap.confirm3).toBe(true); 40 | expect(isTrueCap.confirm4).toBe(true); 41 | 42 | const [, notTrue] = await promptBypass( 43 | prompts, 44 | ["n", "false", "no", "n"], 45 | plop, 46 | ); 47 | expect(notTrue.confirm1).toBe(false); 48 | expect(notTrue.confirm2).toBe(false); 49 | expect(notTrue.confirm3).toBe(false); 50 | expect(notTrue.confirm4).toBe(false); 51 | 52 | const [, notTrueCap] = await promptBypass( 53 | prompts, 54 | ["N", "False", "NO", "N"], 55 | plop, 56 | ); 57 | expect(notTrueCap.confirm1).toBe(false); 58 | expect(notTrueCap.confirm2).toBe(false); 59 | expect(notTrueCap.confirm3).toBe(false); 60 | expect(notTrueCap.confirm4).toBe(false); 61 | }); 62 | 63 | test("verify bad bypass input", async function () { 64 | await expect(() => 65 | promptBypass([prompts[0]], ["asdf"], { is: plop }), 66 | ).rejects.toThrow(); 67 | await expect(() => 68 | promptBypass([prompts[0]], ["1"], { is: plop }), 69 | ).rejects.toThrow(); 70 | await expect(() => 71 | promptBypass([prompts[0]], ["0"], { is: plop }), 72 | ).rejects.toThrow(); 73 | await expect(() => 74 | promptBypass([prompts[0]], ["no way"], { is: plop }), 75 | ).rejects.toThrow(); 76 | await expect(() => 77 | promptBypass([prompts[0]], ["NOOOOOO"], { is: plop }), 78 | ).rejects.toThrow(); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /packages/node-plop/tests/prompt-bypass-list/prompt-bypass-list.spec.js: -------------------------------------------------------------------------------- 1 | import promptBypass from "../../src/prompt-bypass.js"; 2 | import nodePlop from "../../src/index.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | const { clean } = setupMockPath(import.meta.url); 5 | 6 | describe("prompt-bypass-list", function () { 7 | afterEach(clean); 8 | 9 | let plop; 10 | beforeEach(async () => { 11 | plop = await nodePlop(); 12 | }); 13 | 14 | const prompts = [ 15 | { 16 | type: "list", 17 | name: "list", 18 | message: "listMsg", 19 | choices: [ 20 | "eh", 21 | { key: "b", value: "bee" }, 22 | { name: "c", value: "see" }, 23 | { value: "d" }, 24 | { name: "e" }, 25 | { key: "f", name: "ff", value: { prop: "value" } }, 26 | ], 27 | }, 28 | ]; 29 | 30 | test("verify good bypass input", async function () { 31 | const [, byValue] = await promptBypass(prompts, ["eh"], plop); 32 | expect(byValue.list).toBe("eh"); 33 | 34 | const [, byKey] = await promptBypass(prompts, ["b"], plop); 35 | expect(byKey.list).toBe("bee"); 36 | 37 | const [, byName] = await promptBypass(prompts, ["c"], plop); 38 | expect(byName.list).toBe("see"); 39 | 40 | const [, byValueProp] = await promptBypass(prompts, ["d"], plop); 41 | expect(byValueProp.list).toBe("d"); 42 | 43 | const [, byNameNoValue] = await promptBypass(prompts, ["e"], plop); 44 | expect(byNameNoValue.list).toBe("e"); 45 | 46 | const [, byIndexValue] = await promptBypass(prompts, ["0"], plop); 47 | expect(byIndexValue.list).toBe("eh"); 48 | 49 | const [, byIndexKey] = await promptBypass(prompts, ["1"], plop); 50 | expect(byIndexKey.list).toBe("bee"); 51 | 52 | const [, byIndexName] = await promptBypass(prompts, ["2"], plop); 53 | expect(byIndexName.list).toBe("see"); 54 | 55 | const [, byIndexValueProp] = await promptBypass(prompts, ["3"], plop); 56 | expect(byIndexValueProp.list).toBe("d"); 57 | 58 | const [, byIndexNameNoValue] = await promptBypass(prompts, ["4"], plop); 59 | expect(byIndexNameNoValue.list).toBe("e"); 60 | 61 | const [, byIndexNumber] = await promptBypass(prompts, [4], plop); 62 | expect(byIndexNumber.list).toBe("e"); 63 | 64 | const [, byIndexNumberObject] = await promptBypass(prompts, [5], plop); 65 | expect(byIndexNumberObject.list).toEqual({ prop: "value" }); 66 | 67 | const [, byKeyObject] = await promptBypass(prompts, "f", plop); 68 | expect(byKeyObject.list).toEqual({ prop: "value" }); 69 | 70 | const [, byNameObject] = await promptBypass(prompts, "ff", plop); 71 | expect(byNameObject.list).toEqual({ prop: "value" }); 72 | }); 73 | 74 | test("verify bad bypass input", async function () { 75 | await expect(() => 76 | promptBypass(prompts, ["asdf"], { is: plop }), 77 | ).rejects.toThrow(); 78 | await expect(() => 79 | promptBypass(prompts, ["6"], { is: plop }), 80 | ).rejects.toThrow(); 81 | await expect(() => 82 | promptBypass(prompts, [6], { is: plop }), 83 | ).rejects.toThrow(); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /packages/node-plop/tests/prompt-bypass-mixed/prompt-bypass-mixed.spec.js: -------------------------------------------------------------------------------- 1 | import promptBypass from "../../src/prompt-bypass.js"; 2 | import nodePlop from "../../src/index.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | const { clean } = setupMockPath(import.meta.url); 5 | 6 | describe("prompt-bypass-mixed", function () { 7 | afterEach(clean); 8 | 9 | let plop; 10 | beforeEach(async () => { 11 | plop = await nodePlop(); 12 | }); 13 | 14 | const prompts = [ 15 | { 16 | type: "list", 17 | name: "list", 18 | message: "listMsg", 19 | choices: ["a", "B", "c"], 20 | }, 21 | { type: "input", name: "input", message: "inputMsg" }, 22 | { 23 | type: "input", 24 | name: "filter", 25 | message: "filterMsg", 26 | filter: () => "filter applied", 27 | }, 28 | { 29 | type: "input", 30 | name: "filter2", 31 | message: "filterMsg2", 32 | filter: () => "t needed", 33 | }, 34 | { 35 | type: "input", 36 | name: "conditional", 37 | message: "conditionalMsg", 38 | when: () => false, 39 | }, 40 | ]; 41 | 42 | test("verify good bypass input", async function () { 43 | const [promptsAfterBypassOne, bypassOne] = await promptBypass( 44 | prompts, 45 | ["0"], 46 | plop, 47 | ); 48 | expect(bypassOne.list).toBe("a"); 49 | expect(promptsAfterBypassOne.length).toBe(5); 50 | expect(promptsAfterBypassOne[0].type).toBe(undefined); 51 | 52 | const [promptsAfterBypassTwo, bypassTwo] = await promptBypass( 53 | prompts, 54 | ["b", "something"], 55 | plop, 56 | ); 57 | expect(bypassTwo.list).toBe("B"); 58 | expect(bypassTwo.input).toBe("something"); 59 | expect(promptsAfterBypassTwo.length).toBe(4); 60 | 61 | const [promptsAfterBypassThree, bypassThree] = await promptBypass( 62 | prompts, 63 | ["b", "something", "something filtered"], 64 | plop, 65 | ); 66 | expect(bypassThree.list).toBe("B"); 67 | expect(bypassThree.input).toBe("something"); 68 | expect(bypassThree.filter).toBe("filter applied"); 69 | expect(promptsAfterBypassThree.length).toBe(3); 70 | 71 | //check correct parameters passed to inquirer function 72 | prompts[3].filter = (input, answers) => { 73 | expect(input).toBe("unimportant"); 74 | expect(answers.list).toBe("B"); 75 | expect(answers.input).toBe("something"); 76 | expect(answers.filter).toBe("filter applied"); 77 | return answers.list; 78 | }; 79 | const [promptsAfterBypassFour, bypassFour] = await promptBypass( 80 | prompts, 81 | ["b", "something", "something filtered", "unimportant"], 82 | plop, 83 | ); 84 | expect(bypassFour.list).toBe("B"); 85 | expect(bypassFour.input).toBe("something"); 86 | expect(bypassFour.filter).toBe("filter applied"); 87 | expect(bypassFour.filter2).toBe("B"); 88 | expect(promptsAfterBypassFour.length).toBe(2); 89 | }); 90 | 91 | test("verify bad bypass input", async function () { 92 | // can't bypass conditional prompts 93 | await expect(() => 94 | promptBypass( 95 | prompts, 96 | [ 97 | "a", 98 | "something", 99 | "something filtered", 100 | "unimportant", 101 | "something else", 102 | ], 103 | { is: plop }, 104 | ), 105 | ).rejects.toThrow(); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /packages/node-plop/tests/prompt-bypass-validate/prompt-bypass-validate.spec.js: -------------------------------------------------------------------------------- 1 | import promptBypass from "../../src/prompt-bypass.js"; 2 | import nodePlop from "../../src/index.js"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | const { clean } = setupMockPath(import.meta.url); 5 | 6 | describe("prompt-bypass-validate", function () { 7 | afterEach(clean); 8 | let plop; 9 | beforeEach(async () => { 10 | plop = await nodePlop(); 11 | }); 12 | 13 | const prompts = [ 14 | { 15 | type: "input", 16 | name: "input", 17 | message: "inputMsg", 18 | validate: (value) => (value === "invalid" ? "Is invalid" : true), 19 | }, 20 | { 21 | type: "input", 22 | name: "dependent-input", 23 | message: "dependent-inputMsg", 24 | }, 25 | ]; 26 | 27 | test("verify valid bypass input", async function () { 28 | const [, isValid] = await promptBypass(prompts, ["valid"], plop); 29 | expect(isValid.input).toBe("valid"); 30 | }); 31 | 32 | test("verify valid bypass input with access to answers", async function () { 33 | const promptsCopy = [...prompts]; 34 | promptsCopy[1].validate = (value, answers) => { 35 | expect(answers.input).toBe("valid"); 36 | return !!value; 37 | }; 38 | const [, isValid] = await promptBypass( 39 | promptsCopy, 40 | ["valid", "also valid"], 41 | plop, 42 | ); 43 | expect(isValid.input).toBe("valid"); 44 | expect(isValid["dependent-input"]).toBe("also valid"); 45 | }); 46 | 47 | test("verify valid bypass async input with access to answers", async function () { 48 | const promptsCopy = [...prompts]; 49 | promptsCopy[1].validate = async (value, answers) => { 50 | expect(answers.input).toBe("valid"); 51 | return !!value; 52 | }; 53 | const [, isValid] = await promptBypass( 54 | promptsCopy, 55 | ["valid", "also valid"], 56 | plop, 57 | ); 58 | expect(isValid.input).toBe("valid"); 59 | expect(isValid["dependent-input"]).toBe("also valid"); 60 | }); 61 | 62 | test("verify bad bypass input", async function () { 63 | await expect(() => 64 | promptBypass(prompts, ["invalid"], { is: plop }), 65 | ).rejects.toThrow(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/node-plop/tests/set-generator-returns-generator/set-generator-returns-generator.ava.js: -------------------------------------------------------------------------------- 1 | import nodePlop from "../../src/index.js"; 2 | import { setupMockPath } from "../helpers/path.js"; 3 | const { clean } = setupMockPath(import.meta.url); 4 | 5 | describe("set-generator-returns-generator", function () { 6 | afterEach(clean); 7 | let plop; 8 | beforeEach(async () => { 9 | plop = await nodePlop(); 10 | }); 11 | 12 | ///// 13 | // if an action has no path, the action should fail 14 | // 15 | 16 | test("set generator should return the generator object", function () { 17 | const generator = plop.setGenerator("name", {}); 18 | 19 | expect(typeof generator.runPrompts).toBe("function"); 20 | expect(typeof generator.runActions).toBe("function"); 21 | expect(generator.name).toBe("name"); 22 | }); 23 | 24 | test("set generator without name should return the generator object", function () { 25 | const generator = plop.setGenerator("", {}); 26 | 27 | expect(typeof generator.runPrompts).toBe("function"); 28 | expect(typeof generator.runActions).toBe("function"); 29 | expect(generator.name.startsWith("generator-")).toBe(true); 30 | }); 31 | 32 | test("set generator with null name should return the generator object", function () { 33 | const generator = plop.setGenerator(null, {}); 34 | 35 | expect(typeof generator.runPrompts).toBe("function"); 36 | expect(typeof generator.runActions).toBe("function"); 37 | expect(generator.name.startsWith("generator-")).toBe(true); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/node-plop/tsconfig.json: -------------------------------------------------------------------------------- 1 | // Used by eslint for linting 2 | { 3 | "extends": "./types/tsconfig.json", 4 | // We're just using this for index.d.ts 5 | "include": [ 6 | "types/index.d.ts", 7 | "types/test.ts" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/node-plop/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["ESNext"], 5 | "noImplicitAny": true, 6 | "noImplicitThis": true, 7 | "strictNullChecks": true, 8 | "strictFunctionTypes": true, 9 | "noEmit": true, 10 | "target": "ES2020", 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "moduleResolution": "Node" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/node-plop/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Configure Vitest (https://vitest.dev/config) 4 | 5 | import { defineConfig } from "vite"; 6 | 7 | export default defineConfig({ 8 | test: { 9 | /* for example, use global to avoid globals imports (describe, test, expect): */ 10 | globals: true, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/plop/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /packages/plop/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.sublime-* 3 | example/folder/ 4 | example/change-me.txt 5 | .idea 6 | .vscode 7 | .DS_STORE 8 | .nyc_output 9 | coverage 10 | instrumented/ 11 | .eslintcache 12 | -------------------------------------------------------------------------------- /packages/plop/.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "reporter": [ 4 | "text", 5 | "text-summary", 6 | "lcov" 7 | ], 8 | "include": [ 9 | "bin/**/*.js", 10 | "src/**/*.js" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/plop/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # plop 2 | 3 | ## 4.0.1 4 | 5 | ### Patch Changes 6 | 7 | - [#408](https://github.com/plopjs/plop/pull/408) [`49c0029`](https://github.com/plopjs/plop/commit/49c00296b478efa5a212458ae1781acc93a16fa8) Thanks [@rznzippy](https://github.com/rznzippy)! - Adds --no-progress flag that disables the progress bar 8 | 9 | ## 4.0.0 10 | 11 | ### Major Changes 12 | 13 | - [#396](https://github.com/plopjs/plop/pull/396) [`a22e33f`](https://github.com/plopjs/plop/commit/a22e33f416340352e83a1e9c0d470baf2aff1c4b) Thanks [@crutchcorn](https://github.com/crutchcorn)! - Support TypeScript config files OOTB. Drop support for Node 12, 14, & 16. Update all deps. 14 | 15 | ### Patch Changes 16 | 17 | - Updated dependencies [[`a22e33f`](https://github.com/plopjs/plop/commit/a22e33f416340352e83a1e9c0d470baf2aff1c4b)]: 18 | - node-plop@0.32.0 19 | 20 | ## 3.1.2 21 | 22 | ### Patch Changes 23 | 24 | - Append action should now allow handlebars for template path 25 | 26 | * Fix addMany dotfile extension stripping 27 | 28 | - Fix Inquirer TypeScript typings 29 | 30 | * Fix empty checkboxes not bypassing properly 31 | 32 | * Updated dependencies []: 33 | - node-plop@0.31.1 34 | 35 | ## 3.1.1 36 | 37 | ### Patch Changes 38 | 39 | - Export PlopGeneratorConfig TypeScript type 40 | 41 | ## 3.1.0 42 | 43 | ### Minor Changes 44 | 45 | - [#333](https://github.com/plopjs/plop/pull/333) [`d6176cc`](https://github.com/plopjs/plop/commit/d6176cce4ee57dfc18ad1c86ec467444e966567e) Thanks [@RobinKnipe](https://github.com/RobinKnipe)! - Added shorthand to load all Plop assets at once #333 46 | 47 | ### Patch Changes 48 | 49 | - Updated dependencies [[`d6176cc`](https://github.com/plopjs/plop/commit/d6176cce4ee57dfc18ad1c86ec467444e966567e)]: 50 | - node-plop@0.31.0 51 | 52 | ## 3.0.6 53 | 54 | ### Patch Changes 55 | 56 | - Moved to monorepo 57 | 58 | - Updated dependencies []: 59 | - node-plop@0.30.1 60 | -------------------------------------------------------------------------------- /packages/plop/README.md: -------------------------------------------------------------------------------- 1 | This is the main source code for the `plop` package. 2 | 3 | For documentation, please refer to [our website](https://plopjs.com/) or [our main README at the root of the repository](../../README.md). 4 | -------------------------------------------------------------------------------- /packages/plop/bin/plop.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const args = process.argv.slice(2); 3 | import { Plop, run } from "../src/plop.js"; 4 | import minimist from "minimist"; 5 | const argv = minimist(args); 6 | 7 | Plop.prepare( 8 | { 9 | cwd: argv.cwd, 10 | preload: argv.preload || [], 11 | configPath: argv.plopfile, 12 | completion: argv.completion, 13 | }, 14 | function (env) { 15 | Plop.execute(env, run); 16 | }, 17 | ); 18 | -------------------------------------------------------------------------------- /packages/plop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop", 3 | "version": "4.0.1", 4 | "description": "Micro-generator framework that makes it easy for an entire team to create files with a level of uniformity", 5 | "main": "./src/plop.js", 6 | "type": "module", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/plopjs/plop.git", 10 | "directory": "packages/plop" 11 | }, 12 | "keywords": [ 13 | "generator", 14 | "scaffolding", 15 | "yeoman", 16 | "make", 17 | "build", 18 | "generate", 19 | "gen", 20 | "plop" 21 | ], 22 | "author": "Andrew Worcester (http://amwmedia.com)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/plopjs/plop/issues" 26 | }, 27 | "scripts": { 28 | "test": "npm run test:instrument && npm run vitest && nyc report", 29 | "test:instrument": "nyc instrument ./bin ./instrumented/bin && nyc instrument ./src ./instrumented/src && cp package.json ./instrumented", 30 | "vitest": "vitest run", 31 | "prepublishOnly": "node ./scripts/prepublishOnly.js", 32 | "postpublish": "node ./scripts/postpublish.js" 33 | }, 34 | "devDependencies": { 35 | "cli-testing-library": "^2.0.2", 36 | "inquirer-directory": "^2.2.0", 37 | "nyc": "^15.1.0", 38 | "plop-pack-fancy-comments": "^0.2.1", 39 | "queue-microtask": "^1.2.3", 40 | "vitest": "^1.1.0" 41 | }, 42 | "homepage": "https://plopjs.com", 43 | "dependencies": { 44 | "@types/liftoff": "^4.0.3", 45 | "chalk": "^5.3.0", 46 | "interpret": "^3.1.1", 47 | "liftoff": "^4.0.0", 48 | "minimist": "^1.2.8", 49 | "node-plop": "^0.32.0", 50 | "ora": "^8.0.0", 51 | "v8flags": "^4.0.1" 52 | }, 53 | "engines": { 54 | "node": ">=18" 55 | }, 56 | "preferGlobal": true, 57 | "bin": "./bin/plop.js" 58 | } 59 | -------------------------------------------------------------------------------- /packages/plop/scripts/postpublish.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from "fs"; 4 | import * as path from "path"; 5 | import * as url from "url"; 6 | 7 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 8 | 9 | const exists = fs.existsSync("./README.md"); 10 | 11 | if (exists) { 12 | // Restore version of README previously there. 13 | fs.copyFileSync( 14 | path.join(__dirname, "./README.md"), 15 | path.join(__dirname, "../README.md"), 16 | ); 17 | 18 | fs.unlinkSync(path.join(__dirname, "./README.md")); 19 | } 20 | -------------------------------------------------------------------------------- /packages/plop/scripts/prepublishOnly.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from "fs"; 4 | import * as path from "path"; 5 | import * as url from "url"; 6 | 7 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 8 | 9 | // Keep backup of temporary README 10 | fs.copyFileSync( 11 | path.join(__dirname, "../README.md"), 12 | path.join(__dirname, "./README.md"), 13 | ); 14 | 15 | // Move main README 16 | fs.copyFileSync( 17 | path.join(__dirname, "../../../README.md"), 18 | path.join(__dirname, "../README.md"), 19 | ); 20 | -------------------------------------------------------------------------------- /packages/plop/src/bypass.js: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import * as out from "./console-out.js"; 3 | 4 | export { combineBypassData }; 5 | 6 | /** 7 | * Combine different types of bypass data 8 | * @param generator - The generator object involved 9 | * @param bypassArr - The array of overwritten properties 10 | * @param plopArgV - The original args passed to plop without using names 11 | */ 12 | function combineBypassData(generator, bypassArr, plopArgV) { 13 | // skip bypass if prompts is a function 14 | if (typeof generator.prompts === "function") { 15 | return []; 16 | } 17 | 18 | // Get named prompts that are passed to the command line 19 | const promptNames = generator.prompts.map((prompt) => prompt.name); 20 | // Check if bypassArr is too long for promptNames 21 | if (bypassArr.length > promptNames.length) { 22 | console.error( 23 | chalk.red("[PLOP] ") + 24 | 'Too many bypass arguments passed for "' + 25 | generator.name + 26 | '"', 27 | ); 28 | out.getHelpMessage(generator); 29 | process.exit(1); 30 | } 31 | 32 | let namedBypassArr = []; 33 | if (Object.keys(plopArgV).length > 0) { 34 | // Let's make sure we made no whoopsy-poos (AKA passing incorrect inputs) 35 | let errors = false; 36 | Object.keys(plopArgV).forEach((arg) => { 37 | if (!promptNames.find((name) => name === arg) && arg !== "_") { 38 | console.error( 39 | chalk.red("[PLOP] ") + 40 | '"' + 41 | arg + 42 | '"' + 43 | ' is an invalid argument for "' + 44 | generator.name + 45 | '"', 46 | ); 47 | errors = true; 48 | } 49 | }); 50 | if (errors) { 51 | out.getHelpMessage(generator); 52 | process.exit(1); 53 | } 54 | namedBypassArr = promptNames.map((name) => 55 | plopArgV[name] !== undefined ? plopArgV[name] : undefined, 56 | ); 57 | } 58 | 59 | // merge the bypass data with named bypass values 60 | const mergedBypass = mergeArrays(bypassArr, namedBypassArr); 61 | // clean up `undefined` values 62 | return mergedBypass.map((v) => (v === undefined ? "_" : v)); 63 | } 64 | 65 | function mergeArrays(baseArr, overlay) { 66 | const length = Math.max(baseArr.length, overlay.length); 67 | return new Array(length) 68 | .fill() 69 | .map((v, i) => (overlay[i] !== undefined ? overlay[i] : baseArr[i])); 70 | } 71 | -------------------------------------------------------------------------------- /packages/plop/src/console-out.js: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import nodePlop from "node-plop"; 3 | import fs from "node:fs"; 4 | 5 | const defaultChoosingMessage = 6 | chalk.blue("[PLOP]") + " Please choose a generator."; 7 | 8 | function getHelpMessage(generator) { 9 | const maxLen = Math.max( 10 | ...generator.prompts.map((prompt) => prompt.name.length), 11 | ); 12 | console.log( 13 | [ 14 | "", 15 | chalk.bold("Options:"), 16 | ...generator.prompts.map( 17 | (prompt) => 18 | " --" + 19 | prompt.name + 20 | " ".repeat(maxLen - prompt.name.length + 2) + 21 | chalk.dim(prompt.help ? prompt.help : prompt.message), 22 | ), 23 | ].join("\n"), 24 | ); 25 | } 26 | 27 | async function chooseOptionFromList(plopList, message) { 28 | const plop = await nodePlop(); 29 | const generator = plop.setGenerator("choose", { 30 | prompts: [ 31 | { 32 | type: "list", 33 | name: "generator", 34 | message: message || defaultChoosingMessage, 35 | choices: plopList.map(function (p) { 36 | return { 37 | name: 38 | p.name + chalk.gray(!!p.description ? " - " + p.description : ""), 39 | value: p.name, 40 | }; 41 | }), 42 | }, 43 | ], 44 | }); 45 | return generator.runPrompts().then((results) => results.generator); 46 | } 47 | 48 | function displayHelpScreen() { 49 | console.log( 50 | [ 51 | "", 52 | chalk.bold("Usage:"), 53 | " $ plop " + 54 | chalk.dim("Select from a list of available generators"), 55 | " $ plop " + 56 | chalk.dim("Run a generator registered under that name"), 57 | " $ plop [input] " + 58 | chalk.dim("Run the generator with input data to bypass prompts"), 59 | "", 60 | chalk.bold("Options:"), 61 | " -h, --help " + chalk.dim("Show this help display"), 62 | " -t, --show-type-names " + 63 | chalk.dim("Show type names instead of abbreviations"), 64 | " -i, --init " + chalk.dim("Generate a basic plopfile.js"), 65 | " --init-ts " + chalk.dim("Generate a basic plopfile.ts"), 66 | " -v, --version " + chalk.dim("Print current version"), 67 | " -f, --force " + chalk.dim("Run the generator forcefully"), 68 | "", 69 | chalk.dim(" ------------------------------------------------------"), 70 | chalk.dim(" ⚠ danger waits for those who venture below the line"), 71 | "", 72 | chalk.dim(" --plopfile Path to the plopfile"), 73 | chalk.dim( 74 | " --cwd Directory from which relative paths are calculated against while locating the plopfile", 75 | ), 76 | chalk.dim( 77 | " --preload String or array of modules to require before running plop", 78 | ), 79 | chalk.dim( 80 | " --dest Output to this directory instead of the plopfile's parent directory", 81 | ), 82 | chalk.dim(" --no-progress Disable the progress bar"), 83 | "", 84 | chalk.bold("Examples:"), 85 | " $ " + chalk.blue("plop"), 86 | " $ " + chalk.blue("plop component"), 87 | " $ " + chalk.blue('plop component "name of component"'), 88 | "", 89 | ].join("\n"), 90 | ); 91 | } 92 | 93 | function createInitPlopfile(force = false, useTypescript = false) { 94 | var initString = (() => { 95 | if (useTypescript) { 96 | return ( 97 | "import type { NodePlopAPI } from 'plop'\n" + 98 | "\n" + 99 | "export default async function (plop: NodePlopAPI) {\n" + 100 | "\n" + 101 | "}\n" + 102 | "\n" 103 | ); 104 | } else { 105 | return ( 106 | "export default function (plop) {\n\n" + 107 | "\tplop.setGenerator('basics', {\n" + 108 | "\t\tdescription: 'this is a skeleton plopfile',\n" + 109 | "\t\tprompts: [],\n" + 110 | "\t\tactions: []\n" + 111 | "\t});\n\n" + 112 | "};" 113 | ); 114 | } 115 | })(); 116 | 117 | [`js`, `cjs`, `ts`].forEach((ext) => { 118 | const name = `plopfile.${ext}`; 119 | if (fs.existsSync(process.cwd() + `/${name}`) && force === false) { 120 | throw Error(`"${name}" already exists at this location.`); 121 | } 122 | }); 123 | 124 | const outExt = useTypescript ? `ts` : `js`; 125 | fs.writeFileSync(process.cwd() + `/plopfile.${outExt}`, initString); 126 | } 127 | 128 | const typeDisplay = { 129 | function: chalk.yellow("->"), 130 | add: chalk.green("++"), 131 | addMany: chalk.green("+!"), 132 | modify: `${chalk.green("+")}${chalk.red("-")}`, 133 | append: chalk.green("_+"), 134 | skip: chalk.green("--"), 135 | }; 136 | const typeMap = (name, noMap) => { 137 | const dimType = chalk.dim(name); 138 | return noMap ? dimType : typeDisplay[name] || dimType; 139 | }; 140 | 141 | export { 142 | chooseOptionFromList, 143 | displayHelpScreen, 144 | createInitPlopfile, 145 | typeMap, 146 | getHelpMessage, 147 | }; 148 | -------------------------------------------------------------------------------- /packages/plop/src/input-processing.js: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import minimist from "minimist"; 3 | import * as out from "./console-out.js"; 4 | import { createRequire } from "node:module"; 5 | const require = createRequire(import.meta.url); 6 | const globalPkg = require("../package.json"); 7 | const args = process.argv.slice(2); 8 | const argv = minimist(args); 9 | 10 | /** 11 | * Parses the user input to identify the generator to run and any bypass data 12 | * @param plop - The plop context 13 | * @param passArgsBeforeDashes - Should we pass args before `--` to the generator API 14 | */ 15 | function getBypassAndGenerator(plop, passArgsBeforeDashes) { 16 | // See if there are args to pass to generator 17 | const eoaIndex = args.indexOf("--"); 18 | const { plopArgV, eoaArg } = passArgsBeforeDashes 19 | ? { plopArgV: argv } 20 | : eoaIndex === -1 21 | ? { plopArgV: [] } 22 | : { 23 | plopArgV: minimist(args.slice(eoaIndex + 1, args.length)), 24 | eoaArg: args[eoaIndex + 1], 25 | }; 26 | 27 | // locate the generator name based on input and take the rest of the 28 | // user's input as prompt bypass data to be passed into the generator 29 | let generatorName = ""; 30 | let bypassArr = []; 31 | 32 | const generatorNames = plop.getGeneratorList().map((v) => v.name); 33 | for (let i = 0; i < argv._.length; i++) { 34 | const nameTest = 35 | (generatorName.length ? generatorName + " " : "") + argv._[i]; 36 | if (listHasOptionThatStartsWith(generatorNames, nameTest)) { 37 | generatorName = nameTest; 38 | } else { 39 | let index = argv._.findIndex((arg) => arg === eoaArg); 40 | // If can't find index, slice until the very end - allowing all `_` to be passed 41 | index = index !== -1 ? index : argv._.length; 42 | // Force `'_'` to become undefined in nameless bypassArr 43 | bypassArr = argv._.slice(i, index).map((arg) => 44 | /^_+$/.test(arg) ? undefined : arg, 45 | ); 46 | break; 47 | } 48 | } 49 | 50 | return { generatorName, bypassArr, plopArgV }; 51 | } 52 | 53 | function listHasOptionThatStartsWith(list, prefix) { 54 | return list.some(function (txt) { 55 | return txt.indexOf(prefix) === 0; 56 | }); 57 | } 58 | 59 | /** 60 | * Handles all basic argument flags 61 | * @param env - Values parsed by Liftoff 62 | */ 63 | function handleArgFlags(env) { 64 | // Make sure that we're not overwriting `help`, `init,` or `version` args in generators 65 | if (argv._.length === 0) { 66 | // handle request for usage and options 67 | if (argv.help || argv.h) { 68 | out.displayHelpScreen(); 69 | process.exit(0); 70 | } 71 | 72 | // handle request for initializing a new plopfile 73 | if (argv.init || argv.i || argv[`init-ts`]) { 74 | const force = argv.force === true || argv.f === true || false; 75 | try { 76 | out.createInitPlopfile(force, !!argv[`init-ts`]); 77 | process.exit(0); 78 | } catch (err) { 79 | console.error(chalk.red("[PLOP] ") + err.message); 80 | process.exit(1); 81 | } 82 | } 83 | 84 | // handle request for version number 85 | if (argv.version || argv.v) { 86 | const localVersion = env.modulePackage.version; 87 | if (localVersion !== globalPkg.version && localVersion != null) { 88 | console.log(chalk.yellow("CLI version"), globalPkg.version); 89 | console.log(chalk.yellow("Local version"), localVersion); 90 | } else { 91 | console.log(globalPkg.version); 92 | } 93 | process.exit(0); 94 | } 95 | } 96 | 97 | // abort if there's no plopfile found 98 | if (env.configPath == null) { 99 | console.error(chalk.red("[PLOP] ") + "No plopfile found"); 100 | out.displayHelpScreen(); 101 | process.exit(1); 102 | } 103 | } 104 | 105 | export { getBypassAndGenerator, handleArgFlags }; 106 | -------------------------------------------------------------------------------- /packages/plop/src/plop.d.ts: -------------------------------------------------------------------------------- 1 | import * as Liftoff from "liftoff"; 2 | 3 | export { 4 | ActionConfig, 5 | ActionType, 6 | AddActionConfig, 7 | AddManyActionConfig, 8 | AppendActionConfig, 9 | CustomActionFunction, 10 | ModifyActionConfig, 11 | PlopCfg, 12 | PlopGenerator, 13 | NodePlopAPI, 14 | PlopGeneratorConfig, 15 | Actions 16 | } from "node-plop"; 17 | 18 | export const Plop: Liftoff; 19 | export const run: ( 20 | env: Liftoff.LiftoffEnv, 21 | _: any, 22 | passArgsBeforeDashes: boolean, 23 | ) => Promise; 24 | -------------------------------------------------------------------------------- /packages/plop/tests/__snapshots__/input-processing.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`should show help information on help flag 1`] = ` 4 | " 5 | Usage: 6 | $ plop Select from a list of available generators 7 | $ plop Run a generator registered under that name 8 | $ plop [input] Run the generator with input data to bypass prompts 9 | 10 | Options: 11 | -h, --help Show this help display 12 | -t, --show-type-names Show type names instead of abbreviations 13 | -i, --init Generate a basic plopfile.js 14 | --init-ts Generate a basic plopfile.ts 15 | -v, --version Print current version 16 | -f, --force Run the generator forcefully 17 | 18 | ------------------------------------------------------ 19 | ⚠ danger waits for those who venture below the line 20 | 21 | --plopfile Path to the plopfile 22 | --cwd Directory from which relative paths are calculated against while locating the plopfile 23 | --preload String or array of modules to require before running plop 24 | --dest Output to this directory instead of the plopfile's parent directory 25 | --no-progress Disable the progress bar 26 | 27 | Examples: 28 | $ plop 29 | $ plop component 30 | $ plop component "name of component" 31 | " 32 | `; 33 | -------------------------------------------------------------------------------- /packages/plop/tests/action-failure.spec.js: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from "node:path"; 2 | import { waitFor } from "cli-testing-library"; 3 | import { renderPlop } from "./render.js"; 4 | 5 | import { fileURLToPath } from "node:url"; 6 | 7 | const __dirname = dirname(fileURLToPath(import.meta.url)); 8 | 9 | test("should exit with code 1 when failed actions", async () => { 10 | const { findByText, userEvent } = await renderPlop([], { 11 | cwd: resolve(__dirname, "./examples/action-failure"), 12 | }); 13 | expect(await findByText("What is your name?")).toBeInTheConsole(); 14 | await userEvent.keyboard("Joe"); 15 | expect(await findByText("Joe")).toBeInTheConsole(); 16 | await userEvent.keyboard("[Enter]"); 17 | const actionOutput = await findByText("Action failed"); 18 | await waitFor(() => 19 | expect(actionOutput.hasExit()).toStrictEqual({ exitCode: 1 }), 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/plop/tests/actions.spec.js: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from "node:path"; 2 | import { waitFor } from "cli-testing-library"; 3 | import * as fs from "node:fs"; 4 | import { renderPlop } from "./render.js"; 5 | import { getFileHelper } from "./file-helper.js"; 6 | const { getFilePath } = getFileHelper(); 7 | import { fileURLToPath } from "node:url"; 8 | 9 | const __dirname = dirname(fileURLToPath(import.meta.url)); 10 | 11 | test("Plop to add and rename files", async () => { 12 | const expectedFilePath = await getFilePath( 13 | "./examples/add-action/output/new-output.txt", 14 | ); 15 | 16 | const { findByText, userEvent } = await renderPlop(["addAndNameFile"], { 17 | cwd: resolve(__dirname, "./examples/add-action"), 18 | }); 19 | 20 | expect(await findByText("What should the file name be?")).toBeInTheConsole(); 21 | 22 | await userEvent.keyboard("new-output"); 23 | await userEvent.keyboard("[Enter]"); 24 | 25 | await waitFor(() => fs.promises.stat(expectedFilePath)); 26 | 27 | const data = fs.readFileSync(expectedFilePath, "utf8"); 28 | 29 | expect(data).toMatch(/Hello/); 30 | }); 31 | 32 | test("Plop to add and change file contents", async () => { 33 | const expectedFilePath = await getFilePath( 34 | "./examples/add-action/output/new-output.txt", 35 | ); 36 | 37 | const { findByText, userEvent } = await renderPlop(["addAndChangeFile"], { 38 | cwd: resolve(__dirname, "./examples/add-action"), 39 | }); 40 | 41 | expect(await findByText("What's your name?")).toBeInTheConsole(); 42 | 43 | await userEvent.keyboard("Corbin"); 44 | await userEvent.keyboard("[Enter]"); 45 | 46 | await waitFor(() => fs.promises.stat(expectedFilePath)); 47 | 48 | const data = await fs.promises.readFile(expectedFilePath, "utf8"); 49 | 50 | expect(data).toMatch(/Hi Corbin!/); 51 | }); 52 | 53 | test.todo("Test modify"); 54 | test.todo("Test append"); 55 | test.todo("Test built-in helpers"); 56 | test.todo("Test custom helpers"); 57 | -------------------------------------------------------------------------------- /packages/plop/tests/config/setup.js: -------------------------------------------------------------------------------- 1 | // require("cli-testing-library/extend-expect"); 2 | 3 | import { configure } from "cli-testing-library"; 4 | import "cli-testing-library/extend-expect"; 5 | 6 | configure({ 7 | asyncUtilTimeout: 8000, 8 | renderAwaitTime: 4000, 9 | errorDebounceTimeout: 4000, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/plop/tests/esm.spec.js: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from "node:path"; 2 | import { renderPlop } from "./render.js"; 3 | 4 | import { fileURLToPath } from "node:url"; 5 | 6 | const __dirname = dirname(fileURLToPath(import.meta.url)); 7 | 8 | test("should load ESM file", async () => { 9 | const { findByText, userEvent } = await renderPlop([], { 10 | cwd: resolve(__dirname, "./examples/esm"), 11 | }); 12 | expect(await findByText("What is your name?")).toBeInTheConsole(); 13 | await userEvent.keyboard("Joe"); 14 | expect(await findByText("Joe")).toBeInTheConsole(); 15 | await userEvent.keyboard("[Enter]"); 16 | }); 17 | 18 | test("should load MJS file", async () => { 19 | const { findByText, userEvent } = await renderPlop([], { 20 | cwd: resolve(__dirname, "./examples/mjs"), 21 | }); 22 | expect(await findByText("What is your name?")).toBeInTheConsole(); 23 | await userEvent.keyboard("Joe"); 24 | expect(await findByText("Joe")).toBeInTheConsole(); 25 | await userEvent.keyboard("[Enter]"); 26 | }); 27 | 28 | test("should load CJS file", async () => { 29 | const { findByText, userEvent } = await renderPlop([], { 30 | cwd: resolve(__dirname, "./examples/cjs"), 31 | }); 32 | expect(await findByText("What is your name?")).toBeInTheConsole(); 33 | await userEvent.keyboard("Joe"); 34 | expect(await findByText("Joe")).toBeInTheConsole(); 35 | await userEvent.keyboard("[Enter]"); 36 | }); 37 | 38 | test("should load JS module='commonjs' file", async () => { 39 | const { findByText, userEvent } = await renderPlop([], { 40 | cwd: resolve(__dirname, "./examples/cjs-js"), 41 | }); 42 | expect(await findByText("What is your name?")).toBeInTheConsole(); 43 | await userEvent.keyboard("Joe"); 44 | expect(await findByText("Joe")).toBeInTheConsole(); 45 | await userEvent.keyboard("[Enter]"); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/action-failure/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example-action-failure", 3 | "type": "module", 4 | "engines": { 5 | "node": ">=18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/action-failure/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("test", { 3 | description: "this is a test", 4 | prompts: [ 5 | { 6 | type: "input", 7 | name: "name", 8 | message: "What is your name?", 9 | }, 10 | ], 11 | actions: [ 12 | () => { 13 | throw new Error("Action failed"); 14 | }, 15 | ], 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/add-action/output/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/plop/tests/examples/add-action/output/.gitkeep -------------------------------------------------------------------------------- /packages/plop/tests/examples/add-action/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example-add-action", 3 | "type": "module", 4 | "engines": { 5 | "node": ">=18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/add-action/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("addAndNameFile", { 3 | description: "Name that file", 4 | prompts: [ 5 | { 6 | type: "input", 7 | name: "fileName", 8 | message: "What should the file name be?", 9 | }, 10 | ], 11 | actions: [ 12 | { 13 | type: "add", 14 | path: "./output/{{fileName}}.txt", 15 | templateFile: "./templates/to-add.txt", 16 | }, 17 | ], 18 | }); 19 | 20 | plop.setGenerator("addAndChangeFile", { 21 | description: "Name that file", 22 | prompts: [ 23 | { 24 | type: "input", 25 | name: "name", 26 | message: "What's your name?", 27 | }, 28 | ], 29 | actions: [ 30 | { 31 | type: "add", 32 | path: "./output/new-output.txt", 33 | templateFile: "./templates/to-add-change.txt", 34 | }, 35 | ], 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/add-action/templates/to-add-change.txt: -------------------------------------------------------------------------------- 1 | Hi {{name}}! 2 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/add-action/templates/to-add.txt: -------------------------------------------------------------------------------- 1 | Hello 2 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/cjs-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example-prompts-only", 3 | "type": "commonjs", 4 | "engines": { 5 | "node": ">=18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/cjs-js/plopfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (plop) { 2 | plop.setGenerator("test", { 3 | description: "this is a test", 4 | prompts: [ 5 | { 6 | type: "input", 7 | name: "name", 8 | message: "What is your name?", 9 | validate: function (value) { 10 | if (/.+/.test(value)) { 11 | return true; 12 | } 13 | return "name is required"; 14 | }, 15 | }, 16 | { 17 | type: "checkbox", 18 | name: "toppings", 19 | message: "What pizza toppings do you like?", 20 | choices: [ 21 | { name: "Cheese", value: "cheese", checked: true }, 22 | { name: "Pepperoni", value: "pepperoni" }, 23 | { name: "Pineapple", value: "pineapple" }, 24 | { name: "Mushroom", value: "mushroom" }, 25 | { name: "Bacon", value: "bacon", checked: true }, 26 | ], 27 | }, 28 | ], 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example-prompts-only", 3 | "type": "module", 4 | "engines": { 5 | "node": ">=18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/cjs/plopfile.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function (plop) { 2 | plop.setGenerator("test", { 3 | description: "this is a test", 4 | prompts: [ 5 | { 6 | type: "input", 7 | name: "name", 8 | message: "What is your name?", 9 | validate: function (value) { 10 | if (/.+/.test(value)) { 11 | return true; 12 | } 13 | return "name is required"; 14 | }, 15 | }, 16 | { 17 | type: "checkbox", 18 | name: "toppings", 19 | message: "What pizza toppings do you like?", 20 | choices: [ 21 | { name: "Cheese", value: "cheese", checked: true }, 22 | { name: "Pepperoni", value: "pepperoni" }, 23 | { name: "Pineapple", value: "pineapple" }, 24 | { name: "Mushroom", value: "mushroom" }, 25 | { name: "Bacon", value: "bacon", checked: true }, 26 | ], 27 | }, 28 | ], 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example-prompts-only", 3 | "type": "module", 4 | "engines": { 5 | "node": ">=18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/esm/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("test", { 3 | description: "this is a test", 4 | prompts: [ 5 | { 6 | type: "input", 7 | name: "name", 8 | message: "What is your name?", 9 | validate: function (value) { 10 | if (/.+/.test(value)) { 11 | return true; 12 | } 13 | return "name is required"; 14 | }, 15 | }, 16 | { 17 | type: "checkbox", 18 | name: "toppings", 19 | message: "What pizza toppings do you like?", 20 | choices: [ 21 | { name: "Cheese", value: "cheese", checked: true }, 22 | { name: "Pepperoni", value: "pepperoni" }, 23 | { name: "Pineapple", value: "pineapple" }, 24 | { name: "Mushroom", value: "mushroom" }, 25 | { name: "Bacon", value: "bacon", checked: true }, 26 | ], 27 | }, 28 | ], 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example", 3 | "type": "module", 4 | "engines": { 5 | "node": ">=18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/javascript/templates/burger.txt: -------------------------------------------------------------------------------- 1 | {{ header 'Hello Burger Lover!' }} 2 | 3 | Here's your burger {{ properCase name }}! 4 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/javascript/templates/change-me.txt: -------------------------------------------------------------------------------- 1 | the modify option in the test plop should add lines below for each run. 2 | Use modify for things like adding script references to an HTML file. 3 | 4 | -- APPEND ITEMS HERE -- 5 | 6 | +++++++++++++++++++++++++++++++++++++++ 7 | 8 | -- PREPEND ITEMS HERE -- 9 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/javascript/templates/part.txt: -------------------------------------------------------------------------------- 1 | this is prepended! ## replace name here ##: {{age}} 2 | $1 -------------------------------------------------------------------------------- /packages/plop/tests/examples/javascript/templates/potatoes.txt: -------------------------------------------------------------------------------- 1 | Well {{ properCase name }}, it seems you asked for potatoes. 2 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/javascript/templates/temp.txt: -------------------------------------------------------------------------------- 1 | {{ dashAround (properCase name) }} 2 | {{> salutation greeting="Hello there" }} 3 | 4 | {{#if toppings}} 5 | on my pizza I like {{ wordJoin toppings }} 6 | {{else}} 7 | I don't like any toppings on my pizza (not human) 8 | {{/if}} 9 | 10 | generated by {{ pkg 'name' }} 11 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/mjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example-prompts-only", 3 | "type": "module", 4 | "engines": { 5 | "node": ">=18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/mjs/plopfile.mjs: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("test", { 3 | description: "this is a test", 4 | prompts: [ 5 | { 6 | type: "input", 7 | name: "name", 8 | message: "What is your name?", 9 | validate: function (value) { 10 | if (/.+/.test(value)) { 11 | return true; 12 | } 13 | return "name is required"; 14 | }, 15 | }, 16 | { 17 | type: "checkbox", 18 | name: "toppings", 19 | message: "What pizza toppings do you like?", 20 | choices: [ 21 | { name: "Cheese", value: "cheese", checked: true }, 22 | { name: "Pepperoni", value: "pepperoni" }, 23 | { name: "Pineapple", value: "pineapple" }, 24 | { name: "Mushroom", value: "mushroom" }, 25 | { name: "Bacon", value: "bacon", checked: true }, 26 | ], 27 | }, 28 | ], 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/prompt-only/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example-prompts-only", 3 | "type": "module", 4 | "engines": { 5 | "node": ">=18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/prompt-only/plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("test", { 3 | description: "this is a test", 4 | prompts: [ 5 | { 6 | type: "input", 7 | name: "name", 8 | message: "What is your name?", 9 | }, 10 | { 11 | type: "checkbox", 12 | name: "toppings", 13 | message: "What pizza toppings do you like?", 14 | choices: [ 15 | { name: "Cheese", value: "cheese", checked: true }, 16 | { name: "Pepperoni", value: "pepperoni" }, 17 | { name: "Pineapple", value: "pineapple" }, 18 | { name: "Mushroom", value: "mushroom" }, 19 | { name: "Bacon", value: "bacon", checked: true }, 20 | ], 21 | }, 22 | ], 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.js 2 | **/*.map 3 | .nyc_output/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example", 3 | "dependencies": { 4 | "typescript": "^5.2.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/typescript/plopfile.ts: -------------------------------------------------------------------------------- 1 | import { NodePlopAPI } from "plop"; 2 | 3 | module.exports = function (plop: NodePlopAPI) { 4 | plop.setGenerator("test", { 5 | description: "this is a test", 6 | prompts: [ 7 | { 8 | type: "input", 9 | name: "name", 10 | message: "What is your name?", 11 | validate: function (value) { 12 | if (/.+/.test(value)) { 13 | return true; 14 | } 15 | return "name is required"; 16 | }, 17 | }, 18 | { 19 | type: "checkbox", 20 | name: "toppings", 21 | message: "What pizza toppings do you like?", 22 | choices: [ 23 | { name: "Cheese", value: "cheese", checked: true }, 24 | { name: "Pepperoni", value: "pepperoni" }, 25 | { name: "Pineapple", value: "pineapple" }, 26 | { name: "Mushroom", value: "mushroom" }, 27 | { name: "Bacon", value: "bacon", checked: true }, 28 | ], 29 | }, 30 | ], 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Required 4 | "module": "esnext", 5 | "esModuleInterop": true, 6 | "moduleResolution": "node", 7 | // Not required 8 | "strict": true, 9 | "baseUrl": "./", 10 | "paths": { 11 | "plop": ["../../src/plop.d.ts"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/wrap-plop/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import path from "node:path"; 3 | import minimist from "minimist"; 4 | import { Plop, run } from "../../../instrumented/src/plop.js"; 5 | 6 | const args = process.argv.slice(2); 7 | const argv = minimist(args); 8 | import { fileURLToPath } from "node:url"; 9 | 10 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 11 | 12 | Plop.prepare( 13 | { 14 | cwd: argv.cwd, 15 | preload: argv.preload || [], 16 | // In order for `plop` to always pick up the `plopfile.js` despite the CWD, you must use `__dirname` 17 | configPath: path.join(__dirname, "plopfile.cjs"), 18 | completion: argv.completion, 19 | // This will merge the `plop` argv and the generator argv. 20 | // This means that you don't need to use `--` anymore 21 | }, 22 | function (env) { 23 | Plop.execute(env, function (env) { 24 | return run(env, undefined, true); 25 | }); 26 | }, 27 | ); 28 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/wrap-plop/output/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plopjs/plop/e0122279d1376ee62604acbbff1e76a88935b1af/packages/plop/tests/examples/wrap-plop/output/.gitkeep -------------------------------------------------------------------------------- /packages/plop/tests/examples/wrap-plop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-example-wrap", 3 | "type": "module", 4 | "engines": { 5 | "node": ">=18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/wrap-plop/plopfile.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function (plop) { 2 | plop.setGenerator("test", { 3 | description: "this is a test", 4 | prompts: [ 5 | { 6 | type: "input", 7 | name: "name", 8 | message: "What is your name?", 9 | validate: function (value) { 10 | if (/.+/.test(value)) { 11 | return true; 12 | } 13 | return "name is required"; 14 | }, 15 | }, 16 | { 17 | type: "checkbox", 18 | name: "toppings", 19 | message: "What pizza toppings do you like?", 20 | choices: [ 21 | { name: "Cheese", value: "cheese", checked: true }, 22 | { name: "Pepperoni", value: "pepperoni" }, 23 | { name: "Pineapple", value: "pineapple" }, 24 | { name: "Mushroom", value: "mushroom" }, 25 | { name: "Bacon", value: "bacon", checked: true }, 26 | ], 27 | }, 28 | ], 29 | actions: [ 30 | { 31 | type: "add", 32 | path: "./output/added.txt", 33 | templateFile: "./templates/to-add.txt", 34 | }, 35 | ], 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /packages/plop/tests/examples/wrap-plop/templates/to-add.txt: -------------------------------------------------------------------------------- 1 | Hello 2 | -------------------------------------------------------------------------------- /packages/plop/tests/file-helper.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { resolve, dirname } from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | 5 | const __dirname = dirname(fileURLToPath(import.meta.url)); 6 | 7 | export const getFileHelper = () => { 8 | let cleanupFile = null; 9 | 10 | afterEach(() => { 11 | if (!cleanupFile) return; 12 | try { 13 | fs.unlinkSync(cleanupFile); 14 | } catch (e) {} 15 | cleanupFile = null; 16 | }); 17 | 18 | const getFilePath = async (path) => { 19 | const expectedFilePath = resolve(__dirname, path); 20 | 21 | cleanupFile = expectedFilePath; 22 | try { 23 | await fs.promises.unlink(cleanupFile); 24 | } catch (e) {} 25 | 26 | return expectedFilePath; 27 | }; 28 | 29 | return { 30 | getFilePath, 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/plop/tests/render.js: -------------------------------------------------------------------------------- 1 | import { render } from "cli-testing-library"; 2 | import { resolve, dirname } from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | 5 | const __dirname = dirname(fileURLToPath(import.meta.url)); 6 | 7 | /** 8 | * @param {String} script 9 | * @param {Array} args 10 | * @param {Object} opts 11 | */ 12 | export function renderScript(script, args = [], opts = {}) { 13 | const { cwd = __dirname } = opts; 14 | 15 | return render( 16 | resolve(__dirname, "../node_modules/.bin/nyc"), 17 | ["--silent", "node", script, ...args], 18 | { 19 | cwd, 20 | spawnOpts: { 21 | env: { ...process.env, NODE_ENV: "test" }, 22 | }, 23 | }, 24 | ); 25 | } 26 | 27 | /** 28 | * @param {Array} args 29 | * @param {Object} opts 30 | */ 31 | export function renderPlop(args = [], opts = {}) { 32 | return renderScript( 33 | resolve(__dirname, "../instrumented/bin/plop.js"), 34 | args, 35 | opts, 36 | ); 37 | } 38 | 39 | export * from "cli-testing-library"; 40 | -------------------------------------------------------------------------------- /packages/plop/tests/typescript.spec.js: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from "node:path"; 2 | import { renderScript } from "./render.js"; 3 | import { fileURLToPath } from "node:url"; 4 | 5 | const __dirname = dirname(fileURLToPath(import.meta.url)); 6 | 7 | const renderWrapper = (...props) => { 8 | return renderScript( 9 | resolve(__dirname, "./examples/wrap-plop/index.js"), 10 | ...props, 11 | ); 12 | }; 13 | 14 | test("support typescript out of the box", async () => { 15 | const { findByText } = await renderWrapper([""], { 16 | cwd: resolve(__dirname, "./examples/typescript"), 17 | }); 18 | 19 | expect(await findByText("What is your name?")).toBeInTheConsole(); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/plop/tests/wrapper.spec.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { resolve, dirname } from "node:path"; 3 | import { waitFor } from "cli-testing-library"; 4 | import { renderScript } from "./render.js"; 5 | import { getFileHelper } from "./file-helper.js"; 6 | const { getFilePath } = getFileHelper(); 7 | import { fileURLToPath } from "node:url"; 8 | 9 | const __dirname = dirname(fileURLToPath(import.meta.url)); 10 | 11 | const renderWrapper = (...props) => { 12 | return renderScript( 13 | resolve(__dirname, "./examples/wrap-plop/index.js"), 14 | ...props, 15 | ); 16 | }; 17 | 18 | test("wrapper should show version on v flag", async () => { 19 | const { findByText } = await renderWrapper(["-v"]); 20 | 21 | expect(await findByText(/^[\w\.-]+$/)).toBeInTheConsole(); 22 | }); 23 | 24 | test("wrapper should prompts", async () => { 25 | const { findByText, fireEvent } = await renderWrapper([""], { 26 | cwd: resolve(__dirname, "./examples/wrap-plop"), 27 | }); 28 | 29 | expect(await findByText("What is your name?")).toBeInTheConsole(); 30 | }); 31 | 32 | test("wrapper should bypass prompts with index", async () => { 33 | const { findByText, queryByText, fireEvent } = await renderWrapper( 34 | ["Corbin"], 35 | { 36 | cwd: resolve(__dirname, "./examples/wrap-plop"), 37 | }, 38 | ); 39 | 40 | expect(await queryByText("What is your name?")).not.toBeInTheConsole(); 41 | expect( 42 | await findByText("What pizza toppings do you like?"), 43 | ).toBeInTheConsole(); 44 | }); 45 | 46 | test("wrapper should bypass prompts with name", async () => { 47 | const { findByText, queryByText, fireEvent } = await renderWrapper( 48 | ["--name", "Corbin"], 49 | { 50 | cwd: resolve(__dirname, "./examples/wrap-plop"), 51 | }, 52 | ); 53 | 54 | expect(await queryByText("What is your name?")).not.toBeInTheConsole(); 55 | expect( 56 | await findByText("What pizza toppings do you like?"), 57 | ).toBeInTheConsole(); 58 | }); 59 | 60 | test("can run actions (add)", async () => { 61 | const expectedFilePath = await getFilePath( 62 | "./examples/wrap-plop/output/added.txt", 63 | ); 64 | 65 | const { fireEvent } = await renderWrapper(["Test", "Cheese"], { 66 | cwd: resolve(__dirname, "./examples/wrap-plop"), 67 | }); 68 | 69 | await waitFor(() => fs.promises.stat(expectedFilePath)); 70 | 71 | const data = fs.readFileSync(expectedFilePath, "utf8"); 72 | 73 | expect(data).toMatch(/Hello/); 74 | }); 75 | -------------------------------------------------------------------------------- /packages/plop/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Configure Vitest (https://vitest.dev/config) 4 | 5 | import { defineConfig } from "vite"; 6 | 7 | export default defineConfig({ 8 | test: { 9 | globals: true, 10 | setupFiles: ["./tests/config/setup.js"], 11 | testTimeout: 10000, 12 | hookTimeout: 10000, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /plop-load.md: -------------------------------------------------------------------------------- 1 | plop.load 2 | ========= 3 | 4 | `plop.load` can be used to load generators, actionTypes, helpers, and partials from a other plopfiles that are in your project or on NPM (`plop-pack`). `plop.load` executes async when used within es6 modules and requires `async/await`. Example code below. 5 | 6 | ### plop.load([targets](#targets), [[config](#config)], [[include](#include)]) 7 | 8 | #### targets 9 | - **type:** `String` or `Array` 10 | - **required** 11 | 12 | `targets` is the location or locations of the plopfile(s) to be loaded. These locations can be file paths (absolute or relative to the current plopfile) or the node module name of the `plop-pack` that should be loaded. 13 | 14 | #### config 15 | - **type:** `Object` 16 | - **optional** 17 | 18 | `config` is an object that can be passed to the plopfile or `plop-pack` when they are run. This allows the consumer of the plopfile or `plop-pack` to configure certain aspects of its functionality. To know what properties should be in this object, see the documentation provided by the author. 19 | 20 | #### include 21 | - **type:** `Object` or `Boolean` 22 | - **default:** `{ generators:true, helpers:false, partials:false, actionTypes:false }` 23 | - **optional** 24 | 25 | If `include` is `true` all assets from the target will be included (none if `false`). Otherwise, `include` should be an object that can contain 4 properties (`generators`, `helpers`, `partials`, and `actionTypes`). Each of these properties should have an [`IncludeDefinition`](#Interface-IncludeDefinition) as its value. Most of the time this object is not needed because the plopfile or `plop-pack` is able to specify a default [`IncludeDefinition`](#Interface-IncludeDefinition) to be used. 26 | 27 | #### Interface `IncludeDefinition` 28 | - **Boolean:** `true` will include all assets, `false` will include non of them. 29 | - **Array:** a list of asset names that should be included. any assets that don't match a name in this list will be skipped. 30 | - **Object:** the include object allows the consumer to rename assets when for use in their own plopfile. the property name in this object is the asset name, the value is the name that will be given to the asset when loaded. 31 | 32 | ## Examples 33 | *Usage in es6 modules* 34 | ```javascript 35 | // plop.load has to be within an async function 36 | export default async function(plop) { 37 | // Use await to actually load the external files 38 | await plop.load('./plopfiles/component.js`); 39 | } 40 | ``` 41 | *load via a path* 42 | ```javascript 43 | // loads all 5 generators, no helpers, actionTypes or partials (even if they exist) 44 | plop.load('./plopfiles/component.js'); 45 | ``` 46 | *load all assets from a path* 47 | ```javascript 48 | // loads all 5 generators, all helpers, actionTypes and partials (if they exist) 49 | plop.load('./plopfiles/component.js', {}, true); 50 | ``` 51 | *load via a path with a custom include config* 52 | ```javascript 53 | // loads all helpers, no generators, actionTypes or partials (even if they exist) 54 | plop.load('./plopfiles/component.js', {}, { helpers: true }); 55 | ``` 56 | *load via a path with a limited include* 57 | ```javascript 58 | // loads only the "js-header" helper, no generators, actionTypes or partials (even if they exist) 59 | plop.load('./plopfiles/component.js', {}, { helpers: ['js-header'] }); 60 | ``` 61 | *load via a path with config object* 62 | ```javascript 63 | // the component.js module will receive the config object and do something 64 | // example: it could use it to alter which templates it will use (es6) 65 | // and prefix all generator names with 'foo' 66 | plop.load('./plopfiles/component.js', { es6: true, namePrefix: 'foo' }); 67 | ``` 68 | *load via [npm module](https://www.npmjs.com/package/plop-pack-fancy-comments)* 69 | > this module [configures a default include definition](https://github.com/amwmedia/plop-pack-fancy-comments/blob/master/index.js#L13) 70 | 71 | ```javascript 72 | // loads all 3 helpers 73 | plop.load('plop-pack-fancy-comments'); 74 | ``` 75 | *load via npm module with a renaming include config* 76 | ```javascript 77 | // loads only the header helper, no generators, actionTypes or partials (even if they exist) 78 | // within the plopfile and templates, the "js-header" helper is referenced as "titleComment" 79 | plop.load('plop-pack-fancy-comments', {}, { helpers: {'js-header': 'titleComment'} }); 80 | ``` 81 | *load via npm module AND path* 82 | ```javascript 83 | // uses the default include config for each item 84 | plop.load([ 85 | 'plop-pack-fancy-comments', 86 | './plopfiles/component.js' 87 | ]); 88 | ``` 89 | -------------------------------------------------------------------------------- /plop-templates/node-plop-test.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { setupMockPath } from "../helpers/path.js"; 4 | import { normalizePath } from "../../src/actions/_common-action-utils.js"; 5 | 6 | const { clean, testSrcPath } = setupMockPath(import.meta.url); 7 | 8 | describe("{{sentenceCase name}}", () => { 9 | const plop = nodePlop(); 10 | 11 | ///// 12 | // 13 | // 14 | 15 | test("{{sentenceCase name}} async test", async function (t) { 16 | const results = await somethingAsync(); 17 | }); 18 | 19 | test("{{sentenceCase name}} test", function (t) {}); 20 | }); 21 | -------------------------------------------------------------------------------- /plop-templates/plop-test.js: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from "node:path"; 2 | import { renderPlop } from "./render.js"; 3 | 4 | import { fileURLToPath } from "node:url"; 5 | 6 | const __dirname = dirname(fileURLToPath(import.meta.url)); 7 | 8 | test("should load ESM file", async () => { 9 | const { findByText, userEvent } = await renderPlop([], { 10 | cwd: resolve(__dirname, "./examples/esm"), 11 | }); 12 | expect(await findByText("What is your name?")).toBeInTheConsole(); 13 | await userEvent.keyboard("Joe"); 14 | expect(await findByText("Joe")).toBeInTheConsole(); 15 | await userEvent.keyboard("[Enter]"); 16 | }); 17 | -------------------------------------------------------------------------------- /plopfile.js: -------------------------------------------------------------------------------- 1 | export default function (plop) { 2 | plop.setGenerator("node-plop-test", { 3 | prompts: [ 4 | { 5 | type: "input", 6 | name: "name", 7 | message: function () { 8 | return "test name"; 9 | }, 10 | validate: function (value) { 11 | if (/.+/.test(value)) { 12 | return true; 13 | } 14 | return "test name is required"; 15 | }, 16 | }, 17 | ], 18 | actions: [ 19 | { 20 | type: "add", 21 | path: "packages/node-plop/tests/{{dashCase name}}/{{dashCase name}}.spec.js", 22 | templateFile: "plop-templates/node-plop-test.js", 23 | }, 24 | ], 25 | }); 26 | 27 | plop.setGenerator("plop-test", { 28 | prompts: [ 29 | { 30 | type: "input", 31 | name: "name", 32 | message: function () { 33 | return "test name"; 34 | }, 35 | validate: function (value) { 36 | if (/.+/.test(value)) { 37 | return true; 38 | } 39 | return "test name is required"; 40 | }, 41 | }, 42 | ], 43 | actions: [ 44 | { 45 | type: "add", 46 | path: "packages/plop/tests/{{dashCase name}}.spec.js", 47 | templateFile: "plop-templates/plop-test.js", 48 | }, 49 | ], 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // Used by eslint for linting 2 | { 3 | "extends": "./packages/node-plop/types/tsconfig.json", 4 | } 5 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "pipeline": { 4 | "test": { 5 | "outputs": ["instrumented/**", "coverage/**", ".nyc_output/**"] 6 | } 7 | } 8 | } 9 | --------------------------------------------------------------------------------