├── .gitignore ├── test ├── fixtures │ └── single.js ├── _register.js ├── _helpers.ts ├── issues │ ├── 77.ts │ └── 2.ts └── test.ts ├── .babelrc ├── tslint.json ├── rollup.config.js ├── tsconfig.json ├── README.md ├── CHANGELOG.md ├── src └── index.ts ├── package.json └── .circleci └── config.yml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | -------------------------------------------------------------------------------- /test/fixtures/single.js: -------------------------------------------------------------------------------- 1 | console.log('run single...'); 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "targets": { "node": 10 } 7 | } 8 | ], 9 | "@babel/typescript" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/_register.js: -------------------------------------------------------------------------------- 1 | require('@babel/register')({ 2 | extensions: ['.js', '.ts'], 3 | // These patterns are relative to the project directory (where the `package.json` file lives): 4 | ignore: ['node_modules/*', 'test/*'] 5 | }); 6 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "quotemark": [true, "single"], 5 | "max-line-length": [true, 100], 6 | "interface-name": [true, "never-prefix"] 7 | }, 8 | "jsRules": true 9 | } 10 | -------------------------------------------------------------------------------- /test/_helpers.ts: -------------------------------------------------------------------------------- 1 | import * as rollup from 'rollup'; 2 | import shebang from '../src/index'; 3 | 4 | export function bundleSingle(options?: object) { 5 | return rollup.rollup({ 6 | input: ['./test/fixtures/single.js'], 7 | plugins: [ 8 | shebang({ 9 | include: 'single.js', 10 | ...options, 11 | }), 12 | ], 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/issues/77.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { bundleSingle } from '../_helpers'; 3 | 4 | test('should throw with invalid shebang', async (t) => { 5 | const bundle = await bundleSingle({ 6 | shebang() { // eslint-disable no-empty 7 | }, 8 | }); 9 | 10 | const error = await t.throwsAsync(bundle.generate({ format: 'cjs' }), { 11 | instanceOf: Error, 12 | }) as { [key: string]: any }; 13 | 14 | t.is(error.code, 'PLUGIN_ERROR'); 15 | t.is(error.hook, 'renderChunk'); 16 | t.is(error.plugin, 'shebang'); 17 | t.regex(error.message, /shebang.*option/i); 18 | }); 19 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { builtinModules } from 'module'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import babel from 'rollup-plugin-babel'; 4 | import { dependencies } from './package.json'; 5 | 6 | const extensions = ['.js', '.ts']; 7 | 8 | export default { 9 | external: builtinModules.concat(Object.keys(dependencies)), 10 | input: './src/index.ts', 11 | output: { 12 | dir: './out/', 13 | format: 'cjs', 14 | sourcemap: true, 15 | }, 16 | plugins: [ 17 | resolve({ extensions }), 18 | babel({ extensions, include: ['src/**/*'] }), 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { "*": ["types/*"] }, 5 | "allowSyntheticDefaultImports": true, 6 | "noFallthroughCasesInSwitch": true, 7 | "noUnusedParameters": true, 8 | "noImplicitReturns": true, 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "noUnusedLocals": true, 12 | "noImplicitAny": true, 13 | "declarationDir": "out/types", 14 | "declaration": true, 15 | "target": "es2015", 16 | "module": "es2015", 17 | "strict": true, 18 | "resolveJsonModule": true 19 | }, 20 | "include": [ 21 | "src/**/*" 22 | ], 23 | "exclude": [ 24 | "node_modules", 25 | "out" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /test/issues/2.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { OutputChunk } from 'rollup'; 3 | import { bundleSingle } from '../_helpers'; 4 | 5 | test('should not add sourcemap if not wanted', async (t) => { 6 | // create a bundle 7 | const bundle = await bundleSingle(); 8 | 9 | // generate code 10 | const checkMap = async (sourcemap: boolean | 'inline', check: (output: OutputChunk) => void) => { 11 | const { output: [output] } = await bundle.generate({ 12 | format: 'cjs', 13 | sourcemap, 14 | }); 15 | 16 | t.is(output.fileName, 'single.js'); 17 | check(output); 18 | }; 19 | 20 | await checkMap(true, (chunk) => t.truthy(chunk.map)); 21 | await checkMap(false, (chunk) => t.falsy(chunk.map)); 22 | await checkMap('inline', (chunk) => t.truthy(chunk.map)); 23 | }); 24 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { bundleSingle } from './_helpers'; 3 | 4 | test('should add shebang to rendered chunk', async (t) => { 5 | // create a bundle 6 | const bundle = await bundleSingle(); 7 | 8 | // generate code 9 | const { output: [output] } = await bundle.generate({ 10 | format: 'cjs', 11 | }); 12 | 13 | t.is(output.fileName, 'single.js'); 14 | t.true(output.code.startsWith('#!/usr/bin/env node')); 15 | }); 16 | 17 | test('should add shebang to rendered chunk, using a function', async (t) => { 18 | // create a bundle 19 | const bundle = await bundleSingle({ 20 | shebang() { 21 | return '#!/usr/bin/env ts-node'; 22 | }, 23 | }); 24 | 25 | // generate code 26 | const { output: [output] } = await bundle.generate({ 27 | format: 'cjs', 28 | }); 29 | 30 | t.is(output.fileName, 'single.js'); 31 | t.true(output.code.startsWith('#!/usr/bin/env ts-node')); 32 | }); 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rollup-plugin-add-shebang 2 | 3 | > Rollup plugin that adds shebangs to output files 4 | 5 | [![CircleCI](https://circleci.com/gh/ls-age/rollup-plugin-add-shebang/tree/master.svg?style=svg)](https://circleci.com/gh/ls-age/rollup-plugin-add-shebang/tree/master) 6 | 7 | ## Installation 8 | 9 | As usual, run `npm install --save-dev rollup-plugin-add-shebang`. 10 | 11 | ## Usage 12 | 13 | Inside your *rollup.config.js* to which files a shebang should be added: 14 | 15 | ```javascript 16 | // rollup.config.js 17 | import shebang from 'rollup-plugin-add-shebang'; 18 | 19 | export default { 20 | ... 21 | plugins: [ 22 | shebang({ 23 | // A single or an array of filename patterns. Defaults to ['**/cli.js', '**/bin.js']. 24 | include: 'out/cli.js' 25 | // you could also 'exclude' here 26 | // or specify a special shebang (or a function returning one) using the 'shebang' option 27 | }), 28 | ], 29 | ... 30 | }; 31 | ``` 32 | 33 | ## Advantages over other shebang plugins 34 | 35 | - You don't have to add shebangs to your source files 36 | - It works when using code splitting 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.3.1 (2019-07-15) 3 | 4 | 5 | ### Bug Fixes 6 | 7 | * Throw error with invalid shebang option ([#80](https://github.com/ls-age/rollup-plugin-add-shebang/issues/80)) ([7ea57c4](https://github.com/ls-age/rollup-plugin-add-shebang/commits/7ea57c4)) 8 | 9 | 10 | 11 | 12 | 13 | # 0.3.0 (2019-04-12) 14 | 15 | 16 | ### Features 17 | 18 | * Allow shebang option to be a function ([#14](https://github.com/ls-age/rollup-plugin-add-shebang/issues/14)) ([d699db8](https://github.com/ls-age/rollup-plugin-add-shebang/commits/d699db8)) 19 | 20 | 21 | 22 | 23 | 24 | ## 0.1.1 (2019-03-16) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * Generate sourcemaps only if needed ([#4](https://github.com/ls-age/rollup-plugin-add-shebang/issues/4)) ([5ca7303](https://github.com/ls-age/rollup-plugin-add-shebang/commits/5ca7303)), closes [#2](https://github.com/ls-age/rollup-plugin-add-shebang/issues/2) 30 | 31 | 32 | 33 | 34 | 35 | # 0.1.0 (2019-03-11) 36 | 37 | 38 | ### Features 39 | 40 | * Initial release ([e9d0407](https://github.com/ls-age/rollup-plugin-add-shebang/commits/e9d0407)) 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import MagicString from 'magic-string'; 2 | import { join, resolve } from 'path'; 3 | import rollup from 'rollup'; 4 | import { createFilter } from 'rollup-pluginutils'; 5 | 6 | interface Options { 7 | include?: string | string[]; 8 | exclude?: string | string[]; 9 | shebang?: string | (() => string); 10 | } 11 | 12 | const plugin: rollup.PluginImpl = ({ 13 | include = ['**/cli.js', '**/bin.js'], 14 | exclude, 15 | shebang: shebangOption = '#!/usr/bin/env node', 16 | }: Options = {}) => { 17 | const filter = createFilter(include, exclude) as (fileName: string) => boolean; 18 | 19 | return { 20 | name: 'shebang', 21 | renderChunk(code, { fileName }, { dir, file, sourcemap }) { 22 | const outPath = dir ? 23 | join(dir, fileName) : 24 | file || fileName; 25 | 26 | if (!filter(resolve(outPath))) { return null; } 27 | 28 | const shebang = typeof shebangOption === 'function' ? shebangOption() : shebangOption; 29 | 30 | if (!shebang) { 31 | throw new Error('Invalid `shebang` option: Should be a string or a function returning one'); 32 | } 33 | 34 | const prefix = `${shebang} 35 | 36 | `; 37 | 38 | if (!sourcemap) { return `${prefix}${code}`; } 39 | 40 | const s = new MagicString(code); 41 | 42 | s.prepend(prefix); 43 | 44 | return { 45 | code: s.toString(), 46 | // Need to cast version, as it's declared string 47 | // See: https://github.com/Rich-Harris/magic-string/pull/155 48 | // (already merged, will be in next release) 49 | map: s.generateMap({ hires: true }), 50 | }; 51 | }, 52 | }; 53 | }; 54 | 55 | export default plugin; 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollup-plugin-add-shebang", 3 | "version": "0.3.1", 4 | "description": "Rollup plugin that adds shebangs to output files", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/ls-age/rollup-plugin-add-shebang.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/ls-age/rollup-plugin-add-shebang/issues" 12 | }, 13 | "homepage": "https://github.com/ls-age/rollup-plugin-add-shebang#readme", 14 | "author": "Lukas Hechenberger ", 15 | "main": "out/index.js", 16 | "types": "out/types/index.d.ts", 17 | "directories": { 18 | "test": "test" 19 | }, 20 | "scripts": { 21 | "build": "run-p build:*", 22 | "build:js": "rollup -c", 23 | "build:types": "tsc --emitDeclarationOnly", 24 | "lint": "tslint -c tslint.json src/**/*.ts ./test/{,**/}*.ts rollup.config.js", 25 | "test": "ava", 26 | "type-check": "tsc --noEmit" 27 | }, 28 | "keywords": [ 29 | "rollup", 30 | "plugin", 31 | "add", 32 | "shebang", 33 | "cli" 34 | ], 35 | "dependencies": { 36 | "magic-string": "^0.25.3", 37 | "rollup-pluginutils": "^2.8.1" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "7.21.8", 41 | "@babel/preset-env": "7.21.5", 42 | "@babel/preset-typescript": "7.21.5", 43 | "@babel/register": "7.21.0", 44 | "ava": "2.4.0", 45 | "npm-run-all": "4.1.5", 46 | "rollup": "2.79.1", 47 | "rollup-plugin-babel": "4.4.0", 48 | "rollup-plugin-node-resolve": "5.2.0", 49 | "tslint": "6.1.3", 50 | "typescript": "5.0.4" 51 | }, 52 | "ava": { 53 | "babel": { 54 | "extensions": [ 55 | "ts" 56 | ] 57 | }, 58 | "require": [ 59 | "./test/_register.js" 60 | ] 61 | }, 62 | "renovate": { 63 | "extends": [ 64 | "@ls-age:automergeDev" 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | docker: 3 | - image: circleci/node:10 4 | 5 | git-login: &git-login 6 | name: Setting up git user 7 | command: git config --global user.email ci@ls-age.com && git config --global user.name "ls-age CI" 8 | 9 | npm-login: &npm-login 10 | name: Logging in to npm 11 | command: echo "$NPM_TOKEN" > ~/.npmrc 12 | 13 | version: 2 14 | jobs: 15 | install-deps: 16 | <<: *defaults 17 | steps: 18 | - checkout 19 | - restore_cache: 20 | keys: 21 | - v1-npm-cache-{{ checksum "package-lock.json" }} 22 | - run: 23 | <<: *git-login 24 | - run: 25 | name: Installing npm dependencies 26 | command: npm ci 27 | - run: 28 | <<: *git-login 29 | - save_cache: 30 | key: v1-npm-cache-{{ checksum "package-lock.json" }} 31 | paths: 32 | - ~/.npm 33 | - persist_to_workspace: 34 | root: . 35 | paths: 36 | - . 37 | 38 | build: 39 | <<: *defaults 40 | steps: 41 | - attach_workspace: 42 | at: . 43 | - run: 44 | name: Transpiling source code 45 | command: npm run build 46 | - persist_to_workspace: 47 | root: . 48 | paths: 49 | - out 50 | 51 | lint-source: 52 | <<: *defaults 53 | steps: 54 | - attach_workspace: 55 | at: . 56 | - run: 57 | name: Linting source files 58 | command: npm run lint 59 | 60 | type-check: 61 | <<: *defaults 62 | steps: 63 | - attach_workspace: 64 | at: . 65 | - run: 66 | name: Checking typing 67 | command: npm run type-check 68 | 69 | test: 70 | <<: *defaults 71 | steps: 72 | - attach_workspace: 73 | at: . 74 | - run: 75 | name: Running tests 76 | command: npm run test 77 | 78 | deploy: 79 | <<: *defaults 80 | steps: 81 | - checkout 82 | - add_ssh_keys 83 | - attach_workspace: 84 | at: . 85 | - run: 86 | <<: *git-login 87 | - run: 88 | <<: *npm-login 89 | - run: 90 | name: Deploying changes 91 | command: 92 | npx @ls-age/bump-version release --gh-token $RELEASE_GITHUB_TOKEN 93 | 94 | workflows: 95 | version: 2 96 | 97 | test-and-deploy: 98 | jobs: 99 | - install-deps 100 | - build: 101 | requires: 102 | - install-deps 103 | - lint-source: 104 | requires: 105 | - install-deps 106 | - type-check: 107 | requires: 108 | - install-deps 109 | - test: 110 | requires: 111 | - install-deps 112 | - deploy: 113 | requires: 114 | - build 115 | - lint-source 116 | - type-check 117 | - test 118 | filters: 119 | branches: 120 | only: 121 | - master 122 | - beta 123 | --------------------------------------------------------------------------------