├── .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 | [](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 |
--------------------------------------------------------------------------------