rollup-plugin-replace-shebang 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -32
- package/dist/index.cjs +202 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +84 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +84 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +174 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -103
- package/es/index.mjs +0 -59
- package/lib/index.js +0 -65
- package/typings/base.d.ts +0 -9
- package/typings/index.d.ts +0 -17
package/README.md
CHANGED
|
@@ -1,46 +1,144 @@
|
|
|
1
|
-
<div style="text-align: center;" align="center">
|
|
2
|
-
|
|
3
1
|
# rollup-plugin-replace-shebang
|
|
4
2
|
|
|
5
|
-
一个自动替换`shebang`的`rollup`插件
|
|
6
|
-
|
|
7
3
|
[![NPM version][npm-image]][npm-url]
|
|
8
|
-
[![Codacy Badge][codacy-image]][codacy-url]
|
|
9
|
-
[![Test coverage][codecov-image]][codecov-url]
|
|
10
4
|
[![npm download][download-image]][download-url]
|
|
11
|
-
[![gzip][gzip-image]][gzip-url]
|
|
12
5
|
[![License][license-image]][license-url]
|
|
13
6
|
|
|
14
|
-
|
|
7
|
+
A Rollup plugin that preserves and relocates shebang (`#!`) to the output bundle.
|
|
8
|
+
|
|
9
|
+
[简体中文](./README_CN.md) | [Documentation](https://saqqdy.github.io/rollup-plugin-replace-shebang/)
|
|
10
|
+
|
|
11
|
+
## Background
|
|
12
|
+
|
|
13
|
+
When building CLI tools with Rollup, the shebang (`#!/usr/bin/env node`) at the top of your entry file gets removed during bundling. This plugin ensures your shebang is preserved in the final output.
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
## Online Examples
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
Try the plugin online with StackBlitz:
|
|
18
|
+
|
|
19
|
+
| Example | Description | Link |
|
|
20
|
+
|---------|-------------|------|
|
|
21
|
+
| rollup-v2 | Rollup 2.x with template variables | [Open in StackBlitz](https://stackblitz.com/github/saqqdy/rollup-plugin-replace-shebang/tree/master/examples/rollup-v2) |
|
|
22
|
+
| rollup-v4 | Rollup 4.x multi-file project | [Open in StackBlitz](https://stackblitz.com/github/saqqdy/rollup-plugin-replace-shebang/tree/master/examples/rollup-v4) |
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
19
25
|
|
|
20
26
|
```bash
|
|
21
|
-
#
|
|
22
|
-
|
|
27
|
+
# pnpm
|
|
28
|
+
pnpm add -D rollup-plugin-replace-shebang
|
|
23
29
|
|
|
24
|
-
#
|
|
25
|
-
|
|
30
|
+
# npm
|
|
31
|
+
npm install -D rollup-plugin-replace-shebang
|
|
26
32
|
```
|
|
27
33
|
|
|
28
|
-
##
|
|
34
|
+
## Usage
|
|
29
35
|
|
|
30
36
|
```js
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
import replaceShebang from 'rollup-plugin-replace-shebang'
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
input: 'src/cli.js',
|
|
41
|
+
output: {
|
|
42
|
+
file: 'dist/cli.js',
|
|
43
|
+
format: 'es'
|
|
44
|
+
},
|
|
45
|
+
plugins: [
|
|
46
|
+
replaceShebang({
|
|
47
|
+
shebang: '#!/usr/bin/env node',
|
|
48
|
+
skipBackslash: true,
|
|
49
|
+
chmod: true
|
|
37
50
|
})
|
|
38
|
-
]
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Options
|
|
56
|
+
|
|
57
|
+
| Option | Type | Default | Description |
|
|
58
|
+
|--------|------|---------|-------------|
|
|
59
|
+
| `shebang` | `string` | Original shebang | Custom shebang to prepend. Supports template variables: `${name}`, `${version}` |
|
|
60
|
+
| `skipBackslash` | `boolean` | `false` | Preserve `\u005c` escape sequences |
|
|
61
|
+
| `preserve` | `boolean` | `false` | Preserve original shebang without modification |
|
|
62
|
+
| `chmod` | `boolean` | `false` | Auto set executable permission (chmod +x) on output files |
|
|
63
|
+
| `include` | `string \| string[]` | `['**/*.js', '**/*.ts', ...]` | Files to include |
|
|
64
|
+
| `exclude` | `string \| string[]` | `['node_modules/**']` | Files to exclude |
|
|
65
|
+
| `warnOnMultiple` | `boolean` | `true` | Warn when multiple files have shebangs |
|
|
66
|
+
|
|
67
|
+
### Template Variables
|
|
68
|
+
|
|
69
|
+
The `shebang` option supports template variables:
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
replaceShebang({
|
|
73
|
+
shebang: '#!/usr/bin/env node # ${name} v${version}'
|
|
74
|
+
})
|
|
75
|
+
// Output: #!/usr/bin/env node # rollup-plugin-replace-shebang v2.0.0
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Include/Exclude Patterns
|
|
79
|
+
|
|
80
|
+
The plugin supports various glob patterns for filtering files:
|
|
81
|
+
|
|
82
|
+
| Pattern | Example | Matches |
|
|
83
|
+
|---------|---------|---------|
|
|
84
|
+
| Exact match | `src/cli.ts` | Only `src/cli.ts` |
|
|
85
|
+
| Extension match | `**/*.ts` | Any `.ts` file in any directory |
|
|
86
|
+
| Prefix match | `src/**` | Any file under `src/` |
|
|
87
|
+
| Directory match | `**/node_modules/**` | Any file in `node_modules` anywhere |
|
|
88
|
+
| Wildcard match | `src/*.test.ts` | `src/foo.test.ts` but not `src/sub/foo.test.ts` |
|
|
89
|
+
| Plain string | `node_modules` | Any path containing `node_modules` |
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
replaceShebang({
|
|
93
|
+
include: ['src/cli.ts', 'src/bin/*.ts'],
|
|
94
|
+
exclude: ['node_modules/**', '**/*.test.ts']
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How it works
|
|
99
|
+
|
|
100
|
+
1. **Transform phase**: Extracts and removes shebang from source files
|
|
101
|
+
2. **RenderChunk phase**: Prepends shebang to the output bundle
|
|
102
|
+
3. **WriteBundle phase**: Optionally sets executable permissions
|
|
103
|
+
|
|
104
|
+
## Plugin API
|
|
105
|
+
|
|
106
|
+
The plugin exposes an API for external access:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
const plugin = replaceShebang()
|
|
110
|
+
|
|
111
|
+
// Get shebang for a specific file
|
|
112
|
+
plugin.api.getShebang('/path/to/file.js')
|
|
113
|
+
|
|
114
|
+
// Get all shebangs
|
|
115
|
+
plugin.api.getAllShebangs()
|
|
39
116
|
```
|
|
40
117
|
|
|
41
|
-
##
|
|
118
|
+
## Example
|
|
119
|
+
|
|
120
|
+
**Input (src/cli.js):**
|
|
121
|
+
```js
|
|
122
|
+
#!/usr/bin/env node
|
|
123
|
+
import { program } from 'commander'
|
|
124
|
+
|
|
125
|
+
program.parse()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Output (dist/cli.js):**
|
|
129
|
+
```js
|
|
130
|
+
#!/usr/bin/env node
|
|
131
|
+
// bundled code...
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Requirements
|
|
135
|
+
|
|
136
|
+
- Rollup >= 2.0.0
|
|
137
|
+
- Node.js >= 12
|
|
138
|
+
|
|
139
|
+
## Feedback
|
|
42
140
|
|
|
43
|
-
|
|
141
|
+
Feel free to submit an [Issue](https://github.com/saqqdy/rollup-plugin-replace-shebang/issues) for bugs or suggestions.
|
|
44
142
|
|
|
45
143
|
## License
|
|
46
144
|
|
|
@@ -48,15 +146,7 @@ Please open an issue [here](https://github.com/saqqdy/rollup-plugin-replace-sheb
|
|
|
48
146
|
|
|
49
147
|
[npm-image]: https://img.shields.io/npm/v/rollup-plugin-replace-shebang.svg?style=flat-square
|
|
50
148
|
[npm-url]: https://npmjs.org/package/rollup-plugin-replace-shebang
|
|
51
|
-
[codacy-image]: https://app.codacy.com/project/badge/Grade/f70d4880e4ad4f40aa970eb9ee9d0696
|
|
52
|
-
[codacy-url]: https://www.codacy.com/gh/saqqdy/rollup-plugin-replace-shebang/dashboard?utm_source=github.com&utm_medium=referral&utm_content=saqqdy/rollup-plugin-replace-shebang&utm_campaign=Badge_Grade
|
|
53
|
-
[codecov-image]: https://img.shields.io/codecov/c/github/saqqdy/rollup-plugin-replace-shebang.svg?style=flat-square
|
|
54
|
-
[codecov-url]: https://codecov.io/github/saqqdy/rollup-plugin-replace-shebang?branch=master
|
|
55
149
|
[download-image]: https://img.shields.io/npm/dm/rollup-plugin-replace-shebang.svg?style=flat-square
|
|
56
150
|
[download-url]: https://npmjs.org/package/rollup-plugin-replace-shebang
|
|
57
|
-
[gzip-image]: http://img.badgesize.io/https://unpkg.com/rollup-plugin-replace-shebang/lib/index.js?compression=gzip&label=gzip%20size:%20JS
|
|
58
|
-
[gzip-url]: http://img.badgesize.io/https://unpkg.com/rollup-plugin-replace-shebang/lib/index.js?compression=gzip&label=gzip%20size:%20JS
|
|
59
151
|
[license-image]: https://img.shields.io/badge/License-MIT-yellow.svg
|
|
60
152
|
[license-url]: LICENSE
|
|
61
|
-
[sonar-image]: https://sonarcloud.io/api/project_badges/quality_gate?project=saqqdy_rollup-plugin-replace-shebang
|
|
62
|
-
[sonar-url]: https://sonarcloud.io/dashboard?id=saqqdy_rollup-plugin-replace-shebang
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* rollup-plugin-replace-shebang v2.0.0
|
|
3
|
+
* (c) 2021-present saqqdy <https://github.com/saqqdy>
|
|
4
|
+
* Released under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
//#region \0rolldown/runtime.js
|
|
7
|
+
var __create = Object.create;
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
12
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
16
|
+
key = keys[i];
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
18
|
+
__defProp(to, key, {
|
|
19
|
+
get: ((k) => from[k]).bind(null, key),
|
|
20
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
28
|
+
value: mod,
|
|
29
|
+
enumerable: true
|
|
30
|
+
}) : target, mod));
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
let magic_string = require("magic-string");
|
|
34
|
+
magic_string = __toESM(magic_string);
|
|
35
|
+
|
|
36
|
+
//#region src/index.ts
|
|
37
|
+
const PACKAGE_NAME = "rollup-plugin-replace-shebang";
|
|
38
|
+
const PACKAGE_VERSION = "2.0.0";
|
|
39
|
+
/**
|
|
40
|
+
* Validates if a string is a valid shebang.
|
|
41
|
+
* Valid shebangs start with #! followed by a path.
|
|
42
|
+
*/
|
|
43
|
+
function validateShebang(shebang) {
|
|
44
|
+
return /^#!\/[^\s]*\S/.test(shebang);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Creates a filter function from include/exclude patterns.
|
|
48
|
+
*/
|
|
49
|
+
function createFilter(include, exclude) {
|
|
50
|
+
if (!include && !exclude) return (id) => !id.includes("node_modules");
|
|
51
|
+
const includePatterns = include ? Array.isArray(include) ? include : [include] : null;
|
|
52
|
+
const excludePatterns = exclude ? Array.isArray(exclude) ? exclude : [exclude] : null;
|
|
53
|
+
/**
|
|
54
|
+
* Check if a pattern matches the given id
|
|
55
|
+
*/
|
|
56
|
+
function matchPattern(pattern, id) {
|
|
57
|
+
if (id === pattern) return true;
|
|
58
|
+
if (pattern.startsWith("**/*.") && id.endsWith(pattern.slice(4))) return true;
|
|
59
|
+
if (pattern.endsWith("/**") && id.startsWith(pattern.slice(0, -3))) return true;
|
|
60
|
+
if (pattern.startsWith("**/") && pattern.endsWith("/**")) {
|
|
61
|
+
const segment = pattern.slice(3, -3);
|
|
62
|
+
return id.includes(`/${segment}/`) || id.startsWith(`${segment}/`);
|
|
63
|
+
}
|
|
64
|
+
if (pattern.includes("*")) return patternToRegex(pattern).test(id);
|
|
65
|
+
return id.includes(`/${pattern}/`) || id.includes(pattern);
|
|
66
|
+
}
|
|
67
|
+
return (id) => {
|
|
68
|
+
if (excludePatterns) {
|
|
69
|
+
for (const pattern of excludePatterns) if (matchPattern(pattern, id)) return false;
|
|
70
|
+
}
|
|
71
|
+
if (includePatterns) {
|
|
72
|
+
for (const pattern of includePatterns) if (matchPattern(pattern, id)) return true;
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Converts a glob pattern to RegExp.
|
|
80
|
+
*/
|
|
81
|
+
function patternToRegex(pattern) {
|
|
82
|
+
const regex = pattern.replace(/\*\*\//g, "<<DOUBLE_STAR_SLASH>>").replace(/\*\*/g, "<<DOUBLE_STAR>>").replace(/\*/g, "[^/]*").replace(/<<DOUBLE_STAR_SLASH>>/g, "(.*\\/)?").replace(/<<DOUBLE_STAR>>/g, ".*").replace(/\?/g, "[^/]").replace(/\./g, "\\.").replace(/\//g, "\\/");
|
|
83
|
+
return new RegExp(`^${regex}$`);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolves template variables in shebang string.
|
|
87
|
+
*/
|
|
88
|
+
function resolveTemplateVars(shebang, vars) {
|
|
89
|
+
return shebang.replace(/\$\{(\w+)\}/g, (_, key) => vars[key] || "");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A Rollup plugin that preserves and relocates shebang (#!) to the output bundle.
|
|
93
|
+
*
|
|
94
|
+
* During the build process, shebangs are removed from individual modules and
|
|
95
|
+
* re-added to the final bundle, ensuring CLI tools work correctly.
|
|
96
|
+
*
|
|
97
|
+
* @param options - Plugin configuration options
|
|
98
|
+
* @returns A Rollup plugin instance
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```js
|
|
102
|
+
* import replaceShebang from 'rollup-plugin-replace-shebang'
|
|
103
|
+
*
|
|
104
|
+
* export default {
|
|
105
|
+
* plugins: [
|
|
106
|
+
* replaceShebang({
|
|
107
|
+
* shebang: '#!/usr/bin/env node',
|
|
108
|
+
* skipBackslash: true,
|
|
109
|
+
* chmod: true,
|
|
110
|
+
* include: ['src/cli.ts'],
|
|
111
|
+
* exclude: ['node_modules/**']
|
|
112
|
+
* })
|
|
113
|
+
* ]
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
function replaceShebangPlugin(options = {}) {
|
|
118
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
119
|
+
const filter = createFilter(options.include, options.exclude);
|
|
120
|
+
let hasWarnedMultiple = false;
|
|
121
|
+
const transform = (code, moduleID) => {
|
|
122
|
+
if (!filter(moduleID)) return null;
|
|
123
|
+
if (options.preserve) return null;
|
|
124
|
+
let shebang, newCode = code.replace(/^#![^\n]*/, (match) => {
|
|
125
|
+
shebang = match;
|
|
126
|
+
return "";
|
|
127
|
+
});
|
|
128
|
+
if (options.skipBackslash) newCode = newCode.replace(/(\\u005c|\\\\)/g, () => "__u005c__");
|
|
129
|
+
if (!shebang) return null;
|
|
130
|
+
if (!validateShebang(shebang)) console.warn(`[${PACKAGE_NAME}] Invalid shebang format: "${shebang}" in ${moduleID}`);
|
|
131
|
+
const normalizedID = moduleID.replace(/\?.+$/, "");
|
|
132
|
+
contextMap.set(normalizedID, {
|
|
133
|
+
shebang,
|
|
134
|
+
id: normalizedID
|
|
135
|
+
});
|
|
136
|
+
if (options.warnOnMultiple !== false && contextMap.size > 1 && !hasWarnedMultiple) {
|
|
137
|
+
console.warn(`[${PACKAGE_NAME}] Multiple files with shebang detected. Only the entry file's shebang will be used in output.`);
|
|
138
|
+
hasWarnedMultiple = true;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
code: newCode,
|
|
142
|
+
map: null
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
const renderChunk = (code, chunk, { sourcemap }) => {
|
|
146
|
+
var _chunk$facadeModuleId, _options$shebang;
|
|
147
|
+
const moduleID = (_chunk$facadeModuleId = chunk.facadeModuleId) === null || _chunk$facadeModuleId === void 0 ? void 0 : _chunk$facadeModuleId.replace(/\?.+$/, "");
|
|
148
|
+
if (!moduleID) return null;
|
|
149
|
+
const info = contextMap.get(moduleID);
|
|
150
|
+
if (!info) return null;
|
|
151
|
+
let finalCode = code, resolvedShebang = (_options$shebang = options.shebang) !== null && _options$shebang !== void 0 ? _options$shebang : info.shebang;
|
|
152
|
+
if (options.skipBackslash) finalCode = finalCode.replace(/__u005c__/g, () => "\\u005c");
|
|
153
|
+
resolvedShebang = resolveTemplateVars(resolvedShebang, {
|
|
154
|
+
name: PACKAGE_NAME,
|
|
155
|
+
version: PACKAGE_VERSION
|
|
156
|
+
});
|
|
157
|
+
const ms = new magic_string.default(finalCode);
|
|
158
|
+
ms.prepend(`${resolvedShebang}\n`);
|
|
159
|
+
return {
|
|
160
|
+
code: ms.toString(),
|
|
161
|
+
map: sourcemap ? ms.generateMap({ hires: true }) : null
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
const buildEnd = () => {};
|
|
165
|
+
const writeBundle = async (outputOptions, bundle) => {
|
|
166
|
+
contextMap.clear();
|
|
167
|
+
if (options.chmod) {
|
|
168
|
+
for (const fileName of Object.keys(bundle)) if (fileName.endsWith(".js") || fileName.endsWith(".mjs") || fileName.endsWith(".cjs")) {
|
|
169
|
+
const chunk = bundle[fileName];
|
|
170
|
+
if (chunk && "code" in chunk) {
|
|
171
|
+
if (chunk.code.startsWith("#!")) {
|
|
172
|
+
const fs = await import("node:fs/promises");
|
|
173
|
+
const path = await import("node:path");
|
|
174
|
+
const outputPath = outputOptions.file ? path.resolve(outputOptions.file) : path.join(outputOptions.dir || "dist", fileName);
|
|
175
|
+
try {
|
|
176
|
+
await fs.chmod(outputPath, 493);
|
|
177
|
+
} catch {}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
return {
|
|
184
|
+
name: "replace-shebang",
|
|
185
|
+
version: PACKAGE_VERSION,
|
|
186
|
+
transform,
|
|
187
|
+
renderChunk,
|
|
188
|
+
buildEnd,
|
|
189
|
+
writeBundle,
|
|
190
|
+
api: {
|
|
191
|
+
getShebang: (id) => {
|
|
192
|
+
var _contextMap$get;
|
|
193
|
+
return (_contextMap$get = contextMap.get(id)) === null || _contextMap$get === void 0 ? void 0 : _contextMap$get.shebang;
|
|
194
|
+
},
|
|
195
|
+
getAllShebangs: () => new Map(contextMap)
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
//#endregion
|
|
201
|
+
module.exports = replaceShebangPlugin;
|
|
202
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["MagicString"],"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin, RenderChunkHook, TransformHook } from 'rollup'\nimport MagicString from 'magic-string'\n\nexport interface Options {\n\t/**\n\t * Custom shebang to prepend to the output.\n\t * Defaults to the original shebang from the source file.\n\t * Supports template variables: ${name}, ${version}\n\t */\n\tshebang?: string\n\t/**\n\t * Whether to skip backslash escape processing.\n\t * When true, preserves `\\u005c` escape sequences.\n\t * @default false\n\t */\n\tskipBackslash?: boolean\n\t/**\n\t * Preserve the original shebang without modification.\n\t * @default false\n\t */\n\tpreserve?: boolean\n\t/**\n\t * Automatically set executable permission (chmod +x) on output files.\n\t * Only works when output is a file, not stdout.\n\t * @default false\n\t */\n\tchmod?: boolean\n\t/**\n\t * Include pattern for files to process.\n\t * @default [\"**\\/*.js\", \"**\\/*.mjs\", \"**\\/*.ts\", \"**\\/*.mts\", \"**\\/*.cjs\", \"**\\/*.cts\"]\n\t */\n\tinclude?: string | string[]\n\t/**\n\t * Exclude pattern for files to skip.\n\t * @default ['node_modules/**']\n\t */\n\texclude?: string | string[]\n\t/**\n\t * Warn when multiple entry files have shebangs.\n\t * @default true\n\t */\n\twarnOnMultiple?: boolean\n}\n\ninterface ModuleInfo {\n\tshebang: string\n\tid: string\n}\n\ninterface PluginApi {\n\tgetShebang: (id: string) => string | undefined\n\tgetAllShebangs: () => Map<string, ModuleInfo>\n}\n\n// Package info for template variables\ndeclare const __PACKAGE_NAME__: string\ndeclare const __PACKAGE_VERSION__: string\n\nconst PACKAGE_NAME = 'rollup-plugin-replace-shebang'\nconst PACKAGE_VERSION = '2.0.0'\n\n/**\n * Validates if a string is a valid shebang.\n * Valid shebangs start with #! followed by a path.\n */\nfunction validateShebang(shebang: string): boolean {\n\treturn /^#!\\/[^\\s]*\\S/.test(shebang)\n}\n\n/**\n * Creates a filter function from include/exclude patterns.\n */\nfunction createFilter(\n\tinclude: string | string[] | undefined,\n\texclude: string | string[] | undefined\n): (id: string) => boolean {\n\t// If no include/exclude specified, process all files except node_modules\n\tif (!include && !exclude) {\n\t\treturn (id: string) => !id.includes('node_modules')\n\t}\n\n\t// Simple string matching for include/exclude\n\tconst includePatterns = include ? (Array.isArray(include) ? include : [include]) : null\n\tconst excludePatterns = exclude ? (Array.isArray(exclude) ? exclude : [exclude]) : null\n\n\t/**\n\t * Check if a pattern matches the given id\n\t */\n\tfunction matchPattern(pattern: string, id: string): boolean {\n\t\t// Exact match\n\t\tif (id === pattern) return true\n\t\t// Extension match (e.g., **/*.ts)\n\t\tif (pattern.startsWith('**/*.') && id.endsWith(pattern.slice(4))) return true\n\t\t// Prefix match (e.g., src/**)\n\t\tif (pattern.endsWith('/**') && id.startsWith(pattern.slice(0, -3))) return true\n\t\t// Directory contains match (e.g., **/node_modules/**)\n\t\tif (pattern.startsWith('**/') && pattern.endsWith('/**')) {\n\t\t\tconst segment = pattern.slice(3, -3)\n\t\t\treturn id.includes(`/${segment}/`) || id.startsWith(`${segment}/`)\n\t\t}\n\t\t// Wildcard match\n\t\tif (pattern.includes('*')) {\n\t\t\treturn patternToRegex(pattern).test(id)\n\t\t}\n\t\t// Plain string match - check if id contains the pattern as a path segment\n\t\treturn id.includes(`/${pattern}/`) || id.includes(pattern)\n\t}\n\n\treturn (id: string) => {\n\t\t// Check exclude first\n\t\tif (excludePatterns) {\n\t\t\tfor (const pattern of excludePatterns) {\n\t\t\t\tif (matchPattern(pattern, id)) return false\n\t\t\t}\n\t\t}\n\n\t\t// Check include\n\t\tif (includePatterns) {\n\t\t\tfor (const pattern of includePatterns) {\n\t\t\t\tif (matchPattern(pattern, id)) return true\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\n\t\treturn true\n\t}\n}\n\n/**\n * Converts a glob pattern to RegExp.\n */\nfunction patternToRegex(pattern: string): RegExp {\n\t// Handle **/ at the start (matches zero or more directories)\n\t// **/*.js should match: test.js, src/test.js, src/dir/test.js\n\tconst regex = pattern\n\t\t.replace(/\\*\\*\\//g, '<<DOUBLE_STAR_SLASH>>')\n\t\t.replace(/\\*\\*/g, '<<DOUBLE_STAR>>')\n\t\t.replace(/\\*/g, '[^/]*')\n\t\t.replace(/<<DOUBLE_STAR_SLASH>>/g, '(.*\\\\/)?')\n\t\t.replace(/<<DOUBLE_STAR>>/g, '.*')\n\t\t.replace(/\\?/g, '[^/]')\n\t\t.replace(/\\./g, '\\\\.')\n\t\t.replace(/\\//g, '\\\\/')\n\treturn new RegExp(`^${regex}$`)\n}\n\n/**\n * Resolves template variables in shebang string.\n */\nfunction resolveTemplateVars(shebang: string, vars: Record<string, string>): string {\n\treturn shebang.replace(/\\$\\{(\\w+)\\}/g, (_, key) => vars[key] || '')\n}\n\n/**\n * A Rollup plugin that preserves and relocates shebang (#!) to the output bundle.\n *\n * During the build process, shebangs are removed from individual modules and\n * re-added to the final bundle, ensuring CLI tools work correctly.\n *\n * @param options - Plugin configuration options\n * @returns A Rollup plugin instance\n *\n * @example\n * ```js\n * import replaceShebang from 'rollup-plugin-replace-shebang'\n *\n * export default {\n * plugins: [\n * replaceShebang({\n * shebang: '#!/usr/bin/env node',\n * skipBackslash: true,\n * chmod: true,\n * include: ['src/cli.ts'],\n * exclude: ['node_modules/**']\n * })\n * ]\n * }\n * ```\n */\nexport default function replaceShebangPlugin(options: Options = {}): Plugin & { api: PluginApi } {\n\tconst contextMap = new Map<string, ModuleInfo>()\n\tconst filter = createFilter(options.include, options.exclude)\n\tlet hasWarnedMultiple = false\n\n\tconst transform: TransformHook = (code, moduleID) => {\n\t\t// Skip if not matching filter\n\t\tif (!filter(moduleID)) return null\n\n\t\t// Skip if preserve mode\n\t\tif (options.preserve) return null\n\n\t\tlet shebang: string | undefined,\n\t\t\tnewCode = code.replace(/^#![^\\n]*/, match => {\n\t\t\t\tshebang = match\n\t\t\t\treturn ''\n\t\t\t})\n\n\t\tif (options.skipBackslash) {\n\t\t\tnewCode = newCode.replace(/(\\\\u005c|\\\\\\\\)/g, () => '__u005c__')\n\t\t}\n\n\t\tif (!shebang) return null\n\n\t\t// Validate shebang\n\t\tif (!validateShebang(shebang)) {\n\t\t\tconsole.warn(`[${PACKAGE_NAME}] Invalid shebang format: \"${shebang}\" in ${moduleID}`)\n\t\t}\n\n\t\t// Normalize moduleID by stripping query parameters\n\t\tconst normalizedID = moduleID.replace(/\\?.+$/, '')\n\t\tcontextMap.set(normalizedID, { shebang, id: normalizedID })\n\n\t\t// Warn on multiple shebangs\n\t\tif (options.warnOnMultiple !== false && contextMap.size > 1 && !hasWarnedMultiple) {\n\t\t\tconsole.warn(\n\t\t\t\t`[${PACKAGE_NAME}] Multiple files with shebang detected. ` +\n\t\t\t\t\t`Only the entry file's shebang will be used in output.`\n\t\t\t)\n\t\t\thasWarnedMultiple = true\n\t\t}\n\n\t\treturn { code: newCode, map: null }\n\t}\n\n\tconst renderChunk: RenderChunkHook = (code, chunk, { sourcemap }) => {\n\t\tconst moduleID = chunk.facadeModuleId?.replace(/\\?.+$/, '')\n\t\tif (!moduleID) return null\n\n\t\tconst info = contextMap.get(moduleID)\n\t\tif (!info) return null\n\n\t\tlet finalCode = code,\n\t\t\tresolvedShebang = options.shebang ?? info.shebang\n\t\tif (options.skipBackslash) {\n\t\t\tfinalCode = finalCode.replace(/__u005c__/g, () => '\\\\u005c')\n\t\t}\n\t\tresolvedShebang = resolveTemplateVars(resolvedShebang, {\n\t\t\tname: PACKAGE_NAME,\n\t\t\tversion: PACKAGE_VERSION\n\t\t})\n\n\t\tconst ms = new MagicString(finalCode)\n\t\tms.prepend(`${resolvedShebang}\\n`)\n\n\t\treturn {\n\t\t\tcode: ms.toString(),\n\t\t\tmap: sourcemap ? ms.generateMap({ hires: true }) : null\n\t\t}\n\t}\n\n\tconst buildEnd = (): void => {\n\t\t// Note: Don't clear contextMap here, renderChunk still needs it\n\t\t// Clear it in writeBundle instead\n\t}\n\n\tconst writeBundle: Plugin['writeBundle'] = async (outputOptions, bundle) => {\n\t\t// Clear contextMap after all chunks are written\n\t\tcontextMap.clear()\n\n\t\t// Handle chmod option\n\t\tif (options.chmod) {\n\t\t\tfor (const fileName of Object.keys(bundle)) {\n\t\t\t\tif (fileName.endsWith('.js') || fileName.endsWith('.mjs') || fileName.endsWith('.cjs')) {\n\t\t\t\t\tconst chunk = bundle[fileName]\n\t\t\t\t\tif (chunk && 'code' in chunk) {\n\t\t\t\t\t\t// Check if this file has a shebang\n\t\t\t\t\t\tif (chunk.code.startsWith('#!')) {\n\t\t\t\t\t\t\tconst fs = await import('node:fs/promises')\n\t\t\t\t\t\t\tconst path = await import('node:path')\n\t\t\t\t\t\t\tconst outputPath = outputOptions.file\n\t\t\t\t\t\t\t\t? path.resolve(outputOptions.file)\n\t\t\t\t\t\t\t\t: path.join(outputOptions.dir || 'dist', fileName)\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait fs.chmod(outputPath, 0o755)\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Ignore chmod errors (e.g., on Windows)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Plugin API for external access\n\tconst api: PluginApi = {\n\t\tgetShebang: (id: string) => contextMap.get(id)?.shebang,\n\t\tgetAllShebangs: () => new Map(contextMap)\n\t}\n\n\treturn {\n\t\tname: 'replace-shebang',\n\t\tversion: PACKAGE_VERSION,\n\t\ttransform,\n\t\trenderChunk,\n\t\tbuildEnd,\n\t\twriteBundle,\n\t\tapi\n\t}\n}\n\n// Export types\nexport type { PluginApi }\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
import { Plugin } from "rollup";
|
|
3
|
+
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
interface Options {
|
|
6
|
+
/**
|
|
7
|
+
* Custom shebang to prepend to the output.
|
|
8
|
+
* Defaults to the original shebang from the source file.
|
|
9
|
+
* Supports template variables: ${name}, ${version}
|
|
10
|
+
*/
|
|
11
|
+
shebang?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Whether to skip backslash escape processing.
|
|
14
|
+
* When true, preserves `\u005c` escape sequences.
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
skipBackslash?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Preserve the original shebang without modification.
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
preserve?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Automatically set executable permission (chmod +x) on output files.
|
|
25
|
+
* Only works when output is a file, not stdout.
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
chmod?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Include pattern for files to process.
|
|
31
|
+
* @default ["**\/*.js", "**\/*.mjs", "**\/*.ts", "**\/*.mts", "**\/*.cjs", "**\/*.cts"]
|
|
32
|
+
*/
|
|
33
|
+
include?: string | string[];
|
|
34
|
+
/**
|
|
35
|
+
* Exclude pattern for files to skip.
|
|
36
|
+
* @default ['node_modules/**']
|
|
37
|
+
*/
|
|
38
|
+
exclude?: string | string[];
|
|
39
|
+
/**
|
|
40
|
+
* Warn when multiple entry files have shebangs.
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
warnOnMultiple?: boolean;
|
|
44
|
+
}
|
|
45
|
+
interface ModuleInfo {
|
|
46
|
+
shebang: string;
|
|
47
|
+
id: string;
|
|
48
|
+
}
|
|
49
|
+
interface PluginApi {
|
|
50
|
+
getShebang: (id: string) => string | undefined;
|
|
51
|
+
getAllShebangs: () => Map<string, ModuleInfo>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A Rollup plugin that preserves and relocates shebang (#!) to the output bundle.
|
|
55
|
+
*
|
|
56
|
+
* During the build process, shebangs are removed from individual modules and
|
|
57
|
+
* re-added to the final bundle, ensuring CLI tools work correctly.
|
|
58
|
+
*
|
|
59
|
+
* @param options - Plugin configuration options
|
|
60
|
+
* @returns A Rollup plugin instance
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```js
|
|
64
|
+
* import replaceShebang from 'rollup-plugin-replace-shebang'
|
|
65
|
+
*
|
|
66
|
+
* export default {
|
|
67
|
+
* plugins: [
|
|
68
|
+
* replaceShebang({
|
|
69
|
+
* shebang: '#!/usr/bin/env node',
|
|
70
|
+
* skipBackslash: true,
|
|
71
|
+
* chmod: true,
|
|
72
|
+
* include: ['src/cli.ts'],
|
|
73
|
+
* exclude: ['node_modules/**']
|
|
74
|
+
* })
|
|
75
|
+
* ]
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
declare function replaceShebangPlugin(options?: Options): Plugin & {
|
|
80
|
+
api: PluginApi;
|
|
81
|
+
};
|
|
82
|
+
//#endregion
|
|
83
|
+
export { Options, type PluginApi, replaceShebangPlugin as default };
|
|
84
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;UAGiB,OAAA;;AAAjB;;;;EAMC,OAAA;EAMA;;;;;EAAA,aAAA;EA0Bc;;AACd;;EAtBA,QAAA;EAyBA;;AACE;;;EApBF,KAAA;EAwBA;;;;EAnBA,OAAA;EAoB4C;;AAAA;;EAf5C,OAAA;EA+IqD;;;;EA1IrD,cAAA;AAAA;AAAA,UAGS,UAAA;EACT,OAAA;EACA,EAAA;AAAA;AAAA,UAGS,SAAA;EACT,UAAA,GAAa,EAAA;EACb,cAAA,QAAsB,GAAA,SAAY,UAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgIX,oBAAA,CAAqB,OAAA,GAAS,OAAA,GAAe,MAAA;EAAW,GAAA,EAAK,SAAA;AAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
import { Plugin } from "rollup";
|
|
3
|
+
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
interface Options {
|
|
6
|
+
/**
|
|
7
|
+
* Custom shebang to prepend to the output.
|
|
8
|
+
* Defaults to the original shebang from the source file.
|
|
9
|
+
* Supports template variables: ${name}, ${version}
|
|
10
|
+
*/
|
|
11
|
+
shebang?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Whether to skip backslash escape processing.
|
|
14
|
+
* When true, preserves `\u005c` escape sequences.
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
skipBackslash?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Preserve the original shebang without modification.
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
preserve?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Automatically set executable permission (chmod +x) on output files.
|
|
25
|
+
* Only works when output is a file, not stdout.
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
chmod?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Include pattern for files to process.
|
|
31
|
+
* @default ["**\/*.js", "**\/*.mjs", "**\/*.ts", "**\/*.mts", "**\/*.cjs", "**\/*.cts"]
|
|
32
|
+
*/
|
|
33
|
+
include?: string | string[];
|
|
34
|
+
/**
|
|
35
|
+
* Exclude pattern for files to skip.
|
|
36
|
+
* @default ['node_modules/**']
|
|
37
|
+
*/
|
|
38
|
+
exclude?: string | string[];
|
|
39
|
+
/**
|
|
40
|
+
* Warn when multiple entry files have shebangs.
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
warnOnMultiple?: boolean;
|
|
44
|
+
}
|
|
45
|
+
interface ModuleInfo {
|
|
46
|
+
shebang: string;
|
|
47
|
+
id: string;
|
|
48
|
+
}
|
|
49
|
+
interface PluginApi {
|
|
50
|
+
getShebang: (id: string) => string | undefined;
|
|
51
|
+
getAllShebangs: () => Map<string, ModuleInfo>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A Rollup plugin that preserves and relocates shebang (#!) to the output bundle.
|
|
55
|
+
*
|
|
56
|
+
* During the build process, shebangs are removed from individual modules and
|
|
57
|
+
* re-added to the final bundle, ensuring CLI tools work correctly.
|
|
58
|
+
*
|
|
59
|
+
* @param options - Plugin configuration options
|
|
60
|
+
* @returns A Rollup plugin instance
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```js
|
|
64
|
+
* import replaceShebang from 'rollup-plugin-replace-shebang'
|
|
65
|
+
*
|
|
66
|
+
* export default {
|
|
67
|
+
* plugins: [
|
|
68
|
+
* replaceShebang({
|
|
69
|
+
* shebang: '#!/usr/bin/env node',
|
|
70
|
+
* skipBackslash: true,
|
|
71
|
+
* chmod: true,
|
|
72
|
+
* include: ['src/cli.ts'],
|
|
73
|
+
* exclude: ['node_modules/**']
|
|
74
|
+
* })
|
|
75
|
+
* ]
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
declare function replaceShebangPlugin(options?: Options): Plugin & {
|
|
80
|
+
api: PluginApi;
|
|
81
|
+
};
|
|
82
|
+
//#endregion
|
|
83
|
+
export { Options, type PluginApi, replaceShebangPlugin as default };
|
|
84
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;UAGiB,OAAA;;AAAjB;;;;EAMC,OAAA;EAMA;;;;;EAAA,aAAA;EA0Bc;;AACd;;EAtBA,QAAA;EAyBA;;AACE;;;EApBF,KAAA;EAwBA;;;;EAnBA,OAAA;EAoB4C;;AAAA;;EAf5C,OAAA;EA+IqD;;;;EA1IrD,cAAA;AAAA;AAAA,UAGS,UAAA;EACT,OAAA;EACA,EAAA;AAAA;AAAA,UAGS,SAAA;EACT,UAAA,GAAa,EAAA;EACb,cAAA,QAAsB,GAAA,SAAY,UAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgIX,oBAAA,CAAqB,OAAA,GAAS,OAAA,GAAe,MAAA;EAAW,GAAA,EAAK,SAAA;AAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* rollup-plugin-replace-shebang v2.0.0
|
|
3
|
+
* (c) 2021-present saqqdy <https://github.com/saqqdy>
|
|
4
|
+
* Released under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
import MagicString from "magic-string";
|
|
7
|
+
|
|
8
|
+
//#region src/index.ts
|
|
9
|
+
const PACKAGE_NAME = "rollup-plugin-replace-shebang";
|
|
10
|
+
const PACKAGE_VERSION = "2.0.0";
|
|
11
|
+
/**
|
|
12
|
+
* Validates if a string is a valid shebang.
|
|
13
|
+
* Valid shebangs start with #! followed by a path.
|
|
14
|
+
*/
|
|
15
|
+
function validateShebang(shebang) {
|
|
16
|
+
return /^#!\/[^\s]*\S/.test(shebang);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates a filter function from include/exclude patterns.
|
|
20
|
+
*/
|
|
21
|
+
function createFilter(include, exclude) {
|
|
22
|
+
if (!include && !exclude) return (id) => !id.includes("node_modules");
|
|
23
|
+
const includePatterns = include ? Array.isArray(include) ? include : [include] : null;
|
|
24
|
+
const excludePatterns = exclude ? Array.isArray(exclude) ? exclude : [exclude] : null;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a pattern matches the given id
|
|
27
|
+
*/
|
|
28
|
+
function matchPattern(pattern, id) {
|
|
29
|
+
if (id === pattern) return true;
|
|
30
|
+
if (pattern.startsWith("**/*.") && id.endsWith(pattern.slice(4))) return true;
|
|
31
|
+
if (pattern.endsWith("/**") && id.startsWith(pattern.slice(0, -3))) return true;
|
|
32
|
+
if (pattern.startsWith("**/") && pattern.endsWith("/**")) {
|
|
33
|
+
const segment = pattern.slice(3, -3);
|
|
34
|
+
return id.includes(`/${segment}/`) || id.startsWith(`${segment}/`);
|
|
35
|
+
}
|
|
36
|
+
if (pattern.includes("*")) return patternToRegex(pattern).test(id);
|
|
37
|
+
return id.includes(`/${pattern}/`) || id.includes(pattern);
|
|
38
|
+
}
|
|
39
|
+
return (id) => {
|
|
40
|
+
if (excludePatterns) {
|
|
41
|
+
for (const pattern of excludePatterns) if (matchPattern(pattern, id)) return false;
|
|
42
|
+
}
|
|
43
|
+
if (includePatterns) {
|
|
44
|
+
for (const pattern of includePatterns) if (matchPattern(pattern, id)) return true;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Converts a glob pattern to RegExp.
|
|
52
|
+
*/
|
|
53
|
+
function patternToRegex(pattern) {
|
|
54
|
+
const regex = pattern.replace(/\*\*\//g, "<<DOUBLE_STAR_SLASH>>").replace(/\*\*/g, "<<DOUBLE_STAR>>").replace(/\*/g, "[^/]*").replace(/<<DOUBLE_STAR_SLASH>>/g, "(.*\\/)?").replace(/<<DOUBLE_STAR>>/g, ".*").replace(/\?/g, "[^/]").replace(/\./g, "\\.").replace(/\//g, "\\/");
|
|
55
|
+
return new RegExp(`^${regex}$`);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolves template variables in shebang string.
|
|
59
|
+
*/
|
|
60
|
+
function resolveTemplateVars(shebang, vars) {
|
|
61
|
+
return shebang.replace(/\$\{(\w+)\}/g, (_, key) => vars[key] || "");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A Rollup plugin that preserves and relocates shebang (#!) to the output bundle.
|
|
65
|
+
*
|
|
66
|
+
* During the build process, shebangs are removed from individual modules and
|
|
67
|
+
* re-added to the final bundle, ensuring CLI tools work correctly.
|
|
68
|
+
*
|
|
69
|
+
* @param options - Plugin configuration options
|
|
70
|
+
* @returns A Rollup plugin instance
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```js
|
|
74
|
+
* import replaceShebang from 'rollup-plugin-replace-shebang'
|
|
75
|
+
*
|
|
76
|
+
* export default {
|
|
77
|
+
* plugins: [
|
|
78
|
+
* replaceShebang({
|
|
79
|
+
* shebang: '#!/usr/bin/env node',
|
|
80
|
+
* skipBackslash: true,
|
|
81
|
+
* chmod: true,
|
|
82
|
+
* include: ['src/cli.ts'],
|
|
83
|
+
* exclude: ['node_modules/**']
|
|
84
|
+
* })
|
|
85
|
+
* ]
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
function replaceShebangPlugin(options = {}) {
|
|
90
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
91
|
+
const filter = createFilter(options.include, options.exclude);
|
|
92
|
+
let hasWarnedMultiple = false;
|
|
93
|
+
const transform = (code, moduleID) => {
|
|
94
|
+
if (!filter(moduleID)) return null;
|
|
95
|
+
if (options.preserve) return null;
|
|
96
|
+
let shebang, newCode = code.replace(/^#![^\n]*/, (match) => {
|
|
97
|
+
shebang = match;
|
|
98
|
+
return "";
|
|
99
|
+
});
|
|
100
|
+
if (options.skipBackslash) newCode = newCode.replace(/(\\u005c|\\\\)/g, () => "__u005c__");
|
|
101
|
+
if (!shebang) return null;
|
|
102
|
+
if (!validateShebang(shebang)) console.warn(`[${PACKAGE_NAME}] Invalid shebang format: "${shebang}" in ${moduleID}`);
|
|
103
|
+
const normalizedID = moduleID.replace(/\?.+$/, "");
|
|
104
|
+
contextMap.set(normalizedID, {
|
|
105
|
+
shebang,
|
|
106
|
+
id: normalizedID
|
|
107
|
+
});
|
|
108
|
+
if (options.warnOnMultiple !== false && contextMap.size > 1 && !hasWarnedMultiple) {
|
|
109
|
+
console.warn(`[${PACKAGE_NAME}] Multiple files with shebang detected. Only the entry file's shebang will be used in output.`);
|
|
110
|
+
hasWarnedMultiple = true;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
code: newCode,
|
|
114
|
+
map: null
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
const renderChunk = (code, chunk, { sourcemap }) => {
|
|
118
|
+
var _chunk$facadeModuleId, _options$shebang;
|
|
119
|
+
const moduleID = (_chunk$facadeModuleId = chunk.facadeModuleId) === null || _chunk$facadeModuleId === void 0 ? void 0 : _chunk$facadeModuleId.replace(/\?.+$/, "");
|
|
120
|
+
if (!moduleID) return null;
|
|
121
|
+
const info = contextMap.get(moduleID);
|
|
122
|
+
if (!info) return null;
|
|
123
|
+
let finalCode = code, resolvedShebang = (_options$shebang = options.shebang) !== null && _options$shebang !== void 0 ? _options$shebang : info.shebang;
|
|
124
|
+
if (options.skipBackslash) finalCode = finalCode.replace(/__u005c__/g, () => "\\u005c");
|
|
125
|
+
resolvedShebang = resolveTemplateVars(resolvedShebang, {
|
|
126
|
+
name: PACKAGE_NAME,
|
|
127
|
+
version: PACKAGE_VERSION
|
|
128
|
+
});
|
|
129
|
+
const ms = new MagicString(finalCode);
|
|
130
|
+
ms.prepend(`${resolvedShebang}\n`);
|
|
131
|
+
return {
|
|
132
|
+
code: ms.toString(),
|
|
133
|
+
map: sourcemap ? ms.generateMap({ hires: true }) : null
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
const buildEnd = () => {};
|
|
137
|
+
const writeBundle = async (outputOptions, bundle) => {
|
|
138
|
+
contextMap.clear();
|
|
139
|
+
if (options.chmod) {
|
|
140
|
+
for (const fileName of Object.keys(bundle)) if (fileName.endsWith(".js") || fileName.endsWith(".mjs") || fileName.endsWith(".cjs")) {
|
|
141
|
+
const chunk = bundle[fileName];
|
|
142
|
+
if (chunk && "code" in chunk) {
|
|
143
|
+
if (chunk.code.startsWith("#!")) {
|
|
144
|
+
const fs = await import("node:fs/promises");
|
|
145
|
+
const path = await import("node:path");
|
|
146
|
+
const outputPath = outputOptions.file ? path.resolve(outputOptions.file) : path.join(outputOptions.dir || "dist", fileName);
|
|
147
|
+
try {
|
|
148
|
+
await fs.chmod(outputPath, 493);
|
|
149
|
+
} catch {}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
name: "replace-shebang",
|
|
157
|
+
version: PACKAGE_VERSION,
|
|
158
|
+
transform,
|
|
159
|
+
renderChunk,
|
|
160
|
+
buildEnd,
|
|
161
|
+
writeBundle,
|
|
162
|
+
api: {
|
|
163
|
+
getShebang: (id) => {
|
|
164
|
+
var _contextMap$get;
|
|
165
|
+
return (_contextMap$get = contextMap.get(id)) === null || _contextMap$get === void 0 ? void 0 : _contextMap$get.shebang;
|
|
166
|
+
},
|
|
167
|
+
getAllShebangs: () => new Map(contextMap)
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
//#endregion
|
|
173
|
+
export { replaceShebangPlugin as default };
|
|
174
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin, RenderChunkHook, TransformHook } from 'rollup'\nimport MagicString from 'magic-string'\n\nexport interface Options {\n\t/**\n\t * Custom shebang to prepend to the output.\n\t * Defaults to the original shebang from the source file.\n\t * Supports template variables: ${name}, ${version}\n\t */\n\tshebang?: string\n\t/**\n\t * Whether to skip backslash escape processing.\n\t * When true, preserves `\\u005c` escape sequences.\n\t * @default false\n\t */\n\tskipBackslash?: boolean\n\t/**\n\t * Preserve the original shebang without modification.\n\t * @default false\n\t */\n\tpreserve?: boolean\n\t/**\n\t * Automatically set executable permission (chmod +x) on output files.\n\t * Only works when output is a file, not stdout.\n\t * @default false\n\t */\n\tchmod?: boolean\n\t/**\n\t * Include pattern for files to process.\n\t * @default [\"**\\/*.js\", \"**\\/*.mjs\", \"**\\/*.ts\", \"**\\/*.mts\", \"**\\/*.cjs\", \"**\\/*.cts\"]\n\t */\n\tinclude?: string | string[]\n\t/**\n\t * Exclude pattern for files to skip.\n\t * @default ['node_modules/**']\n\t */\n\texclude?: string | string[]\n\t/**\n\t * Warn when multiple entry files have shebangs.\n\t * @default true\n\t */\n\twarnOnMultiple?: boolean\n}\n\ninterface ModuleInfo {\n\tshebang: string\n\tid: string\n}\n\ninterface PluginApi {\n\tgetShebang: (id: string) => string | undefined\n\tgetAllShebangs: () => Map<string, ModuleInfo>\n}\n\n// Package info for template variables\ndeclare const __PACKAGE_NAME__: string\ndeclare const __PACKAGE_VERSION__: string\n\nconst PACKAGE_NAME = 'rollup-plugin-replace-shebang'\nconst PACKAGE_VERSION = '2.0.0'\n\n/**\n * Validates if a string is a valid shebang.\n * Valid shebangs start with #! followed by a path.\n */\nfunction validateShebang(shebang: string): boolean {\n\treturn /^#!\\/[^\\s]*\\S/.test(shebang)\n}\n\n/**\n * Creates a filter function from include/exclude patterns.\n */\nfunction createFilter(\n\tinclude: string | string[] | undefined,\n\texclude: string | string[] | undefined\n): (id: string) => boolean {\n\t// If no include/exclude specified, process all files except node_modules\n\tif (!include && !exclude) {\n\t\treturn (id: string) => !id.includes('node_modules')\n\t}\n\n\t// Simple string matching for include/exclude\n\tconst includePatterns = include ? (Array.isArray(include) ? include : [include]) : null\n\tconst excludePatterns = exclude ? (Array.isArray(exclude) ? exclude : [exclude]) : null\n\n\t/**\n\t * Check if a pattern matches the given id\n\t */\n\tfunction matchPattern(pattern: string, id: string): boolean {\n\t\t// Exact match\n\t\tif (id === pattern) return true\n\t\t// Extension match (e.g., **/*.ts)\n\t\tif (pattern.startsWith('**/*.') && id.endsWith(pattern.slice(4))) return true\n\t\t// Prefix match (e.g., src/**)\n\t\tif (pattern.endsWith('/**') && id.startsWith(pattern.slice(0, -3))) return true\n\t\t// Directory contains match (e.g., **/node_modules/**)\n\t\tif (pattern.startsWith('**/') && pattern.endsWith('/**')) {\n\t\t\tconst segment = pattern.slice(3, -3)\n\t\t\treturn id.includes(`/${segment}/`) || id.startsWith(`${segment}/`)\n\t\t}\n\t\t// Wildcard match\n\t\tif (pattern.includes('*')) {\n\t\t\treturn patternToRegex(pattern).test(id)\n\t\t}\n\t\t// Plain string match - check if id contains the pattern as a path segment\n\t\treturn id.includes(`/${pattern}/`) || id.includes(pattern)\n\t}\n\n\treturn (id: string) => {\n\t\t// Check exclude first\n\t\tif (excludePatterns) {\n\t\t\tfor (const pattern of excludePatterns) {\n\t\t\t\tif (matchPattern(pattern, id)) return false\n\t\t\t}\n\t\t}\n\n\t\t// Check include\n\t\tif (includePatterns) {\n\t\t\tfor (const pattern of includePatterns) {\n\t\t\t\tif (matchPattern(pattern, id)) return true\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\n\t\treturn true\n\t}\n}\n\n/**\n * Converts a glob pattern to RegExp.\n */\nfunction patternToRegex(pattern: string): RegExp {\n\t// Handle **/ at the start (matches zero or more directories)\n\t// **/*.js should match: test.js, src/test.js, src/dir/test.js\n\tconst regex = pattern\n\t\t.replace(/\\*\\*\\//g, '<<DOUBLE_STAR_SLASH>>')\n\t\t.replace(/\\*\\*/g, '<<DOUBLE_STAR>>')\n\t\t.replace(/\\*/g, '[^/]*')\n\t\t.replace(/<<DOUBLE_STAR_SLASH>>/g, '(.*\\\\/)?')\n\t\t.replace(/<<DOUBLE_STAR>>/g, '.*')\n\t\t.replace(/\\?/g, '[^/]')\n\t\t.replace(/\\./g, '\\\\.')\n\t\t.replace(/\\//g, '\\\\/')\n\treturn new RegExp(`^${regex}$`)\n}\n\n/**\n * Resolves template variables in shebang string.\n */\nfunction resolveTemplateVars(shebang: string, vars: Record<string, string>): string {\n\treturn shebang.replace(/\\$\\{(\\w+)\\}/g, (_, key) => vars[key] || '')\n}\n\n/**\n * A Rollup plugin that preserves and relocates shebang (#!) to the output bundle.\n *\n * During the build process, shebangs are removed from individual modules and\n * re-added to the final bundle, ensuring CLI tools work correctly.\n *\n * @param options - Plugin configuration options\n * @returns A Rollup plugin instance\n *\n * @example\n * ```js\n * import replaceShebang from 'rollup-plugin-replace-shebang'\n *\n * export default {\n * plugins: [\n * replaceShebang({\n * shebang: '#!/usr/bin/env node',\n * skipBackslash: true,\n * chmod: true,\n * include: ['src/cli.ts'],\n * exclude: ['node_modules/**']\n * })\n * ]\n * }\n * ```\n */\nexport default function replaceShebangPlugin(options: Options = {}): Plugin & { api: PluginApi } {\n\tconst contextMap = new Map<string, ModuleInfo>()\n\tconst filter = createFilter(options.include, options.exclude)\n\tlet hasWarnedMultiple = false\n\n\tconst transform: TransformHook = (code, moduleID) => {\n\t\t// Skip if not matching filter\n\t\tif (!filter(moduleID)) return null\n\n\t\t// Skip if preserve mode\n\t\tif (options.preserve) return null\n\n\t\tlet shebang: string | undefined,\n\t\t\tnewCode = code.replace(/^#![^\\n]*/, match => {\n\t\t\t\tshebang = match\n\t\t\t\treturn ''\n\t\t\t})\n\n\t\tif (options.skipBackslash) {\n\t\t\tnewCode = newCode.replace(/(\\\\u005c|\\\\\\\\)/g, () => '__u005c__')\n\t\t}\n\n\t\tif (!shebang) return null\n\n\t\t// Validate shebang\n\t\tif (!validateShebang(shebang)) {\n\t\t\tconsole.warn(`[${PACKAGE_NAME}] Invalid shebang format: \"${shebang}\" in ${moduleID}`)\n\t\t}\n\n\t\t// Normalize moduleID by stripping query parameters\n\t\tconst normalizedID = moduleID.replace(/\\?.+$/, '')\n\t\tcontextMap.set(normalizedID, { shebang, id: normalizedID })\n\n\t\t// Warn on multiple shebangs\n\t\tif (options.warnOnMultiple !== false && contextMap.size > 1 && !hasWarnedMultiple) {\n\t\t\tconsole.warn(\n\t\t\t\t`[${PACKAGE_NAME}] Multiple files with shebang detected. ` +\n\t\t\t\t\t`Only the entry file's shebang will be used in output.`\n\t\t\t)\n\t\t\thasWarnedMultiple = true\n\t\t}\n\n\t\treturn { code: newCode, map: null }\n\t}\n\n\tconst renderChunk: RenderChunkHook = (code, chunk, { sourcemap }) => {\n\t\tconst moduleID = chunk.facadeModuleId?.replace(/\\?.+$/, '')\n\t\tif (!moduleID) return null\n\n\t\tconst info = contextMap.get(moduleID)\n\t\tif (!info) return null\n\n\t\tlet finalCode = code,\n\t\t\tresolvedShebang = options.shebang ?? info.shebang\n\t\tif (options.skipBackslash) {\n\t\t\tfinalCode = finalCode.replace(/__u005c__/g, () => '\\\\u005c')\n\t\t}\n\t\tresolvedShebang = resolveTemplateVars(resolvedShebang, {\n\t\t\tname: PACKAGE_NAME,\n\t\t\tversion: PACKAGE_VERSION\n\t\t})\n\n\t\tconst ms = new MagicString(finalCode)\n\t\tms.prepend(`${resolvedShebang}\\n`)\n\n\t\treturn {\n\t\t\tcode: ms.toString(),\n\t\t\tmap: sourcemap ? ms.generateMap({ hires: true }) : null\n\t\t}\n\t}\n\n\tconst buildEnd = (): void => {\n\t\t// Note: Don't clear contextMap here, renderChunk still needs it\n\t\t// Clear it in writeBundle instead\n\t}\n\n\tconst writeBundle: Plugin['writeBundle'] = async (outputOptions, bundle) => {\n\t\t// Clear contextMap after all chunks are written\n\t\tcontextMap.clear()\n\n\t\t// Handle chmod option\n\t\tif (options.chmod) {\n\t\t\tfor (const fileName of Object.keys(bundle)) {\n\t\t\t\tif (fileName.endsWith('.js') || fileName.endsWith('.mjs') || fileName.endsWith('.cjs')) {\n\t\t\t\t\tconst chunk = bundle[fileName]\n\t\t\t\t\tif (chunk && 'code' in chunk) {\n\t\t\t\t\t\t// Check if this file has a shebang\n\t\t\t\t\t\tif (chunk.code.startsWith('#!')) {\n\t\t\t\t\t\t\tconst fs = await import('node:fs/promises')\n\t\t\t\t\t\t\tconst path = await import('node:path')\n\t\t\t\t\t\t\tconst outputPath = outputOptions.file\n\t\t\t\t\t\t\t\t? path.resolve(outputOptions.file)\n\t\t\t\t\t\t\t\t: path.join(outputOptions.dir || 'dist', fileName)\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait fs.chmod(outputPath, 0o755)\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Ignore chmod errors (e.g., on Windows)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Plugin API for external access\n\tconst api: PluginApi = {\n\t\tgetShebang: (id: string) => contextMap.get(id)?.shebang,\n\t\tgetAllShebangs: () => new Map(contextMap)\n\t}\n\n\treturn {\n\t\tname: 'replace-shebang',\n\t\tversion: PACKAGE_VERSION,\n\t\ttransform,\n\t\trenderChunk,\n\t\tbuildEnd,\n\t\twriteBundle,\n\t\tapi\n\t}\n}\n\n// Export types\nexport type { PluginApi }\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,104 +1,79 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"hashbang",
|
|
81
|
-
"saqqdy"
|
|
82
|
-
],
|
|
83
|
-
"pnpm": {
|
|
84
|
-
"peerDependencyRules": {
|
|
85
|
-
"ignoreMissing": [
|
|
86
|
-
"tslib",
|
|
87
|
-
"webpack"
|
|
88
|
-
],
|
|
89
|
-
"allowedVersions": {
|
|
90
|
-
"eslint": "^8.0.0"
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
"license": "MIT",
|
|
95
|
-
"author": "saqqdy <saqqdy@qq.com> (https://github.com/saqqdy)",
|
|
96
|
-
"repository": {
|
|
97
|
-
"type": "git",
|
|
98
|
-
"url": "git+https://github.com/saqqdy/rollup-plugin-replace-shebang.git"
|
|
99
|
-
},
|
|
100
|
-
"bugs": {
|
|
101
|
-
"url": "https://github.com/saqqdy/rollup-plugin-replace-shebang/issues"
|
|
102
|
-
},
|
|
103
|
-
"homepage": "https://github.com/saqqdy/rollup-plugin-replace-shebang#readme"
|
|
104
|
-
}
|
|
2
|
+
"name": "rollup-plugin-replace-shebang",
|
|
3
|
+
"description": "A Rollup plugin that preserves and relocates shebang to the output bundle.",
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.mts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": {
|
|
12
|
+
"import": "./dist/index.d.mts",
|
|
13
|
+
"require": "./dist/index.d.cts"
|
|
14
|
+
},
|
|
15
|
+
"import": "./dist/index.mjs",
|
|
16
|
+
"require": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=12"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"magic-string": "^0.30.21"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@eslint-sets/eslint-config": "^6.2.0",
|
|
30
|
+
"@types/node": "^24.12.0",
|
|
31
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
32
|
+
"eslint": "^9.39.4",
|
|
33
|
+
"rollup": "^4.59.1",
|
|
34
|
+
"tsdown": "^0.21.4",
|
|
35
|
+
"tsx": "^4.21.0",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vitepress": "^1.6.4",
|
|
38
|
+
"vitest": "^3.2.4"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"rollup": ">=2.0.0"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"rollup",
|
|
45
|
+
"rollup-plugin",
|
|
46
|
+
"shebang",
|
|
47
|
+
"hashbang",
|
|
48
|
+
"cli",
|
|
49
|
+
"bundler"
|
|
50
|
+
],
|
|
51
|
+
"sideEffects": false,
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"author": "saqqdy <https://github.com/saqqdy>",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/saqqdy/rollup-plugin-replace-shebang.git"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/saqqdy/rollup-plugin-replace-shebang/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/saqqdy/rollup-plugin-replace-shebang#readme",
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsdown",
|
|
64
|
+
"dev": "tsdown --watch",
|
|
65
|
+
"docs:dev": "vitepress dev docs",
|
|
66
|
+
"docs:build": "vitepress build docs",
|
|
67
|
+
"docs:preview": "vitepress preview docs",
|
|
68
|
+
"deploy": "sh scripts/deploy.sh",
|
|
69
|
+
"test": "vitest run",
|
|
70
|
+
"test:watch": "vitest",
|
|
71
|
+
"test:coverage": "vitest run --coverage",
|
|
72
|
+
"test:compat": "npx tsx scripts/test-compat.ts",
|
|
73
|
+
"pub": "tscjs scripts/publish",
|
|
74
|
+
"unpub": "tscjs scripts/unpublish",
|
|
75
|
+
"sync": "tscjs scripts/sync",
|
|
76
|
+
"lint": "eslint --fix",
|
|
77
|
+
"dist": "pnpm run lint && pnpm run build"
|
|
78
|
+
}
|
|
79
|
+
}
|
package/es/index.mjs
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import MagicString from 'magic-string';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 一个自动替换shebang的rollup插件
|
|
5
|
-
*
|
|
6
|
-
* @param options - 配置参数
|
|
7
|
-
* @returns Plugin - 插件
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
function replaceStringPlugin(options = {}) {
|
|
11
|
-
const contextMap = new Map();
|
|
12
|
-
return {
|
|
13
|
-
name: 'replace-shebang',
|
|
14
|
-
|
|
15
|
-
transform(code, moduleID) {
|
|
16
|
-
let shebang;
|
|
17
|
-
code = code.replace(/^#![^\n]*/, match => (shebang = match, ''));
|
|
18
|
-
|
|
19
|
-
if (options.skipBackslash) {
|
|
20
|
-
code = code.replace(/(\\u005c|\\\\)/g, () => '__u005c__');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (!shebang) return null;
|
|
24
|
-
contextMap.set(moduleID, {
|
|
25
|
-
shebang
|
|
26
|
-
});
|
|
27
|
-
return {
|
|
28
|
-
code,
|
|
29
|
-
map: null
|
|
30
|
-
};
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
renderChunk(code, chunk, {
|
|
34
|
-
sourcemap
|
|
35
|
-
}) {
|
|
36
|
-
const moduleID = chunk.facadeModuleId.replace(/\?.+$/, '');
|
|
37
|
-
const {
|
|
38
|
-
shebang
|
|
39
|
-
} = contextMap.get(moduleID) || {};
|
|
40
|
-
if (!shebang) return null;
|
|
41
|
-
|
|
42
|
-
if (options.skipBackslash) {
|
|
43
|
-
code = code.replace(/__u005c__/g, () => '\\u005c');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const ms = new MagicString(code);
|
|
47
|
-
ms.prepend(`${options.shebang || shebang}\n`);
|
|
48
|
-
return {
|
|
49
|
-
code: ms.toString(),
|
|
50
|
-
map: sourcemap ? ms.generateMap({
|
|
51
|
-
hires: true
|
|
52
|
-
}) : null
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export { replaceStringPlugin as default };
|
package/lib/index.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var MagicString = require('magic-string');
|
|
4
|
-
|
|
5
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
6
|
-
|
|
7
|
-
var MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString);
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 一个自动替换shebang的rollup插件
|
|
11
|
-
*
|
|
12
|
-
* @param options - 配置参数
|
|
13
|
-
* @returns Plugin - 插件
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
function replaceStringPlugin(options = {}) {
|
|
17
|
-
const contextMap = new Map();
|
|
18
|
-
return {
|
|
19
|
-
name: 'replace-shebang',
|
|
20
|
-
|
|
21
|
-
transform(code, moduleID) {
|
|
22
|
-
let shebang;
|
|
23
|
-
code = code.replace(/^#![^\n]*/, match => (shebang = match, ''));
|
|
24
|
-
|
|
25
|
-
if (options.skipBackslash) {
|
|
26
|
-
code = code.replace(/(\\u005c|\\\\)/g, () => '__u005c__');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!shebang) return null;
|
|
30
|
-
contextMap.set(moduleID, {
|
|
31
|
-
shebang
|
|
32
|
-
});
|
|
33
|
-
return {
|
|
34
|
-
code,
|
|
35
|
-
map: null
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
renderChunk(code, chunk, {
|
|
40
|
-
sourcemap
|
|
41
|
-
}) {
|
|
42
|
-
const moduleID = chunk.facadeModuleId.replace(/\?.+$/, '');
|
|
43
|
-
const {
|
|
44
|
-
shebang
|
|
45
|
-
} = contextMap.get(moduleID) || {};
|
|
46
|
-
if (!shebang) return null;
|
|
47
|
-
|
|
48
|
-
if (options.skipBackslash) {
|
|
49
|
-
code = code.replace(/__u005c__/g, () => '\\u005c');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const ms = new MagicString__default["default"](code);
|
|
53
|
-
ms.prepend(`${options.shebang || shebang}\n`);
|
|
54
|
-
return {
|
|
55
|
-
code: ms.toString(),
|
|
56
|
-
map: sourcemap ? ms.generateMap({
|
|
57
|
-
hires: true
|
|
58
|
-
}) : null
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
module.exports = replaceStringPlugin;
|
package/typings/base.d.ts
DELETED
package/typings/index.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { Plugin as Plugin_2 } from 'rollup';
|
|
2
|
-
|
|
3
|
-
export declare interface Options {
|
|
4
|
-
shebang?: string;
|
|
5
|
-
skipBackslash?: boolean;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 一个自动替换shebang的rollup插件
|
|
10
|
-
*
|
|
11
|
-
* @param options - 配置参数
|
|
12
|
-
* @returns Plugin - 插件
|
|
13
|
-
*/
|
|
14
|
-
declare function replaceStringPlugin(options?: Options): Plugin_2;
|
|
15
|
-
export default replaceStringPlugin;
|
|
16
|
-
|
|
17
|
-
export { }
|