@nodesecure/js-x-ray 7.1.1 → 7.3.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 CHANGED
@@ -223,6 +223,8 @@ interface RuntimeOptions {
223
223
  module?: boolean;
224
224
  removeHTMLComments?: boolean;
225
225
  isMinified?: boolean;
226
+ initialize?: (sourceFile: SourceFile) => void;
227
+ finalize?: (sourceFile: SourceFile) => void;
226
228
  }
227
229
  ```
228
230
 
@@ -256,6 +258,8 @@ interface RuntimeFileOptions {
256
258
  module?: boolean;
257
259
  removeHTMLComments?: boolean;
258
260
  packageName?: string;
261
+ initialize?: (sourceFile: SourceFile) => void;
262
+ finalize?: (sourceFile: SourceFile) => void;
259
263
  }
260
264
  ```
261
265
 
@@ -303,7 +307,7 @@ $ yarn add @nodesecure/estree-ast-util
303
307
  ## Contributors ✨
304
308
 
305
309
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
306
- [![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors-)
310
+ [![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-)
307
311
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
308
312
 
309
313
  Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -335,6 +339,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
335
339
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/FredGuiou"><img src="https://avatars.githubusercontent.com/u/99122562?v=4?s=100" width="100px;" alt="FredGuiou"/><br /><sub><b>FredGuiou</b></sub></a><br /><a href="https://github.com/NodeSecure/js-x-ray/commits?author=FredGuiou" title="Documentation">📖</a> <a href="https://github.com/NodeSecure/js-x-ray/commits?author=FredGuiou" title="Code">💻</a></td>
336
340
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/madina0801"><img src="https://avatars.githubusercontent.com/u/101329759?v=4?s=100" width="100px;" alt="Madina"/><br /><sub><b>Madina</b></sub></a><br /><a href="https://github.com/NodeSecure/js-x-ray/commits?author=madina0801" title="Code">💻</a></td>
337
341
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/sairuss7"><img src="https://avatars.githubusercontent.com/u/87803528?v=4?s=100" width="100px;" alt="SairussDev"/><br /><sub><b>SairussDev</b></sub></a><br /><a href="https://github.com/NodeSecure/js-x-ray/commits?author=sairuss7" title="Code">💻</a></td>
342
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/fless-lab"><img src="https://avatars.githubusercontent.com/u/71844440?v=4?s=100" width="100px;" alt="Abdou-Raouf ATARMLA"/><br /><sub><b>Abdou-Raouf ATARMLA</b></sub></a><br /><a href="https://github.com/NodeSecure/js-x-ray/commits?author=fless-lab" title="Code">💻</a></td>
338
343
  </tr>
339
344
  </tbody>
340
345
  </table>
package/index.js CHANGED
@@ -8,6 +8,14 @@ function runASTAnalysis(
8
8
  str,
9
9
  options = Object.create(null)
10
10
  ) {
11
+ process.emitWarning(
12
+ 'The runASTAnalysis API is deprecated and will be removed in v8. Please use the AstAnalyser class instead.',
13
+ {
14
+ code: 'DeprecationWarning',
15
+ detail: 'The runASTAnalysis API is deprecated and will be removed in v8. Please use the AstAnalyser class instead.'
16
+ }
17
+ );
18
+
11
19
  const {
12
20
  customParser = new JsSourceParser(),
13
21
  customProbes = [],
@@ -28,6 +36,14 @@ async function runASTAnalysisOnFile(
28
36
  pathToFile,
29
37
  options = {}
30
38
  ) {
39
+ process.emitWarning(
40
+ 'The runASTAnalysisOnFile API is deprecated and will be removed in v8. Please use the AstAnalyser class instead.',
41
+ {
42
+ code: 'DeprecationWarning',
43
+ detail: 'The runASTAnalysisOnFile API is deprecated and will be removed in v8. Please use the AstAnalyser class instead.'
44
+ }
45
+ );
46
+
31
47
  const {
32
48
  customProbes = [],
33
49
  customParser = new JsSourceParser(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodesecure/js-x-ray",
3
- "version": "7.1.1",
3
+ "version": "7.3.0",
4
4
  "description": "JavaScript AST XRay analysis",
5
5
  "type": "module",
6
6
  "exports": "./index.js",
@@ -56,10 +56,10 @@
56
56
  "devDependencies": {
57
57
  "@nodesecure/eslint-config": "^1.6.0",
58
58
  "@types/node": "^20.6.2",
59
- "c8": "^9.0.0",
59
+ "c8": "^10.1.2",
60
60
  "cross-env": "^7.0.3",
61
61
  "eslint": "^9.0.0",
62
- "glob": "^10.3.4",
62
+ "glob": "^11.0.0",
63
63
  "iterator-matcher": "^2.1.0",
64
64
  "pkg-ok": "^3.0.0"
65
65
  }
@@ -1,137 +1,207 @@
1
- // Import Node.js Dependencies
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
-
5
- // Import Third-party Dependencies
6
- import { walk } from "estree-walker";
7
- import isMinified from "is-minified-code";
8
-
9
- // Import Internal Dependencies
10
- import { SourceFile } from "./SourceFile.js";
11
- import { isOneLineExpressionExport } from "./utils/index.js";
12
- import { JsSourceParser } from "./JsSourceParser.js";
13
-
14
- export class AstAnalyser {
15
- /**
16
- * @constructor
17
- * @param {object} [options={}]
18
- * @param {SourceParser} [options.customParser]
19
- * @param {Array<object>} [options.customProbes]
20
- * @param {boolean} [options.skipDefaultProbes=false]
21
- */
22
- constructor(options = {}) {
23
- this.parser = options.customParser ?? new JsSourceParser();
24
- this.probesOptions = {
25
- customProbes: options.customProbes ?? [],
26
- skipDefaultProbes: options.skipDefaultProbes ?? false
27
- };
28
- }
29
-
30
- analyse(str, options = Object.create(null)) {
31
- const {
32
- isMinified = false,
33
- module = true,
34
- removeHTMLComments = false
35
- } = options;
36
-
37
- const body = this.parser.parse(this.prepareSource(str, { removeHTMLComments }), {
38
- isEcmaScriptModule: Boolean(module)
39
- });
40
-
41
- const source = new SourceFile(str, this.probesOptions);
42
-
43
- // we walk each AST Nodes, this is a purely synchronous I/O
44
- walk(body, {
45
- enter(node) {
46
- // Skip the root of the AST.
47
- if (Array.isArray(node)) {
48
- return;
49
- }
50
-
51
- const action = source.walk(node);
52
- if (action === "skip") {
53
- this.skip();
54
- }
55
- }
56
- });
57
-
58
- return {
59
- ...source.getResult(isMinified),
60
- dependencies: source.dependencies,
61
- isOneLineRequire: isOneLineExpressionExport(body)
62
- };
63
- }
64
-
65
- async analyseFile(
66
- pathToFile,
67
- options = {}
68
- ) {
69
- try {
70
- const {
71
- packageName = null,
72
- module = true,
73
- removeHTMLComments = false
74
- } = options;
75
-
76
- const str = await fs.readFile(pathToFile, "utf-8");
77
- const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
78
-
79
- const isMin = filePathString.includes(".min") || isMinified(str);
80
- const data = this.analyse(str, {
81
- isMinified: isMin,
82
- module: path.extname(filePathString) === ".mjs" ? true : module,
83
- removeHTMLComments
84
- });
85
-
86
- if (packageName !== null) {
87
- data.dependencies.delete(packageName);
88
- }
89
-
90
- return {
91
- ok: true,
92
- dependencies: data.dependencies,
93
- warnings: data.warnings,
94
- isMinified: !data.isOneLineRequire && isMin
95
- };
96
- }
97
- catch (error) {
98
- return {
99
- ok: false,
100
- warnings: [
101
- { kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
102
- ]
103
- };
104
- }
105
- }
106
-
107
- /**
108
- * @param {!string} source
109
- * @param {object} options
110
- * @param {boolean} [options.removeHTMLComments=false]
111
- */
112
- prepareSource(source, options = {}) {
113
- if (typeof source !== "string") {
114
- throw new TypeError("source must be a string");
115
- }
116
- const { removeHTMLComments = false } = options;
117
-
118
- /**
119
- * if the file start with a shebang then we remove it because meriyah.parseScript fail to parse it.
120
- * @example
121
- * #!/usr/bin/env node
122
- */
123
- const rawNoShebang = source.startsWith("#") ?
124
- source.slice(source.indexOf("\n") + 1) : source;
125
-
126
- return removeHTMLComments ?
127
- this.#removeHTMLComment(rawNoShebang) : rawNoShebang;
128
- }
129
-
130
- /**
131
- * @param {!string} str
132
- * @returns {string}
133
- */
134
- #removeHTMLComment(str) {
135
- return str.replaceAll(/<!--[\s\S]*?(?:-->)/g, "");
136
- }
137
- }
1
+ // Import Node.js Dependencies
2
+ import fs from "node:fs/promises";
3
+ import fsSync from "node:fs";
4
+ import path from "node:path";
5
+
6
+ // Import Third-party Dependencies
7
+ import { walk } from "estree-walker";
8
+ import isMinified from "is-minified-code";
9
+
10
+ // Import Internal Dependencies
11
+ import { SourceFile } from "./SourceFile.js";
12
+ import { isOneLineExpressionExport } from "./utils/index.js";
13
+ import { JsSourceParser } from "./JsSourceParser.js";
14
+
15
+ export class AstAnalyser {
16
+ /**
17
+ * @constructor
18
+ * @param {object} [options={}]
19
+ * @param {SourceParser} [options.customParser]
20
+ * @param {Array<object>} [options.customProbes]
21
+ * @param {boolean} [options.skipDefaultProbes=false]
22
+ */
23
+ constructor(options = {}) {
24
+ this.parser = options.customParser ?? new JsSourceParser();
25
+ this.probesOptions = {
26
+ customProbes: options.customProbes ?? [],
27
+ skipDefaultProbes: options.skipDefaultProbes ?? false
28
+ };
29
+ }
30
+
31
+ analyse(str, options = Object.create(null)) {
32
+ const {
33
+ isMinified = false,
34
+ module = true,
35
+ removeHTMLComments = false,
36
+ initialize,
37
+ finalize
38
+ } = options;
39
+
40
+ const body = this.parser.parse(this.prepareSource(str, { removeHTMLComments }), {
41
+ isEcmaScriptModule: Boolean(module)
42
+ });
43
+ const source = new SourceFile(str, this.probesOptions);
44
+
45
+ // TODO: this check should be factorized in a way that we reuse it
46
+ // on analyze and anlyseFile
47
+ if (initialize) {
48
+ if (typeof initialize !== "function") {
49
+ throw new TypeError("options.initialize must be a function");
50
+ }
51
+ initialize(source);
52
+ }
53
+
54
+ // we walk each AST Nodes, this is a purely synchronous I/O
55
+ walk(body, {
56
+ enter(node) {
57
+ // Skip the root of the AST.
58
+ if (Array.isArray(node)) {
59
+ return;
60
+ }
61
+
62
+ const action = source.walk(node);
63
+ if (action === "skip") {
64
+ this.skip();
65
+ }
66
+ }
67
+ });
68
+
69
+ // TODO: this check should be factorized in a way that we reuse it
70
+ // on analyze and anlyseFile
71
+ if (finalize) {
72
+ if (typeof finalize !== "function") {
73
+ throw new TypeError("options.finalize must be a function");
74
+ }
75
+ finalize(source);
76
+ }
77
+
78
+ return {
79
+ ...source.getResult(isMinified),
80
+ dependencies: source.dependencies,
81
+ isOneLineRequire: isOneLineExpressionExport(body)
82
+ };
83
+ }
84
+
85
+ async analyseFile(
86
+ pathToFile,
87
+ options = {}
88
+ ) {
89
+ try {
90
+ const {
91
+ packageName = null,
92
+ module = true,
93
+ removeHTMLComments = false,
94
+ initialize,
95
+ finalize
96
+ } = options;
97
+
98
+ const str = await fs.readFile(pathToFile, "utf-8");
99
+ const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
100
+
101
+ const isMin = filePathString.includes(".min") || isMinified(str);
102
+ const data = this.analyse(str, {
103
+ isMinified: isMin,
104
+ module: path.extname(filePathString) === ".mjs" ? true : module,
105
+ removeHTMLComments,
106
+ initialize,
107
+ finalize
108
+ });
109
+
110
+ if (packageName !== null) {
111
+ data.dependencies.delete(packageName);
112
+ }
113
+
114
+ return {
115
+ ok: true,
116
+ dependencies: data.dependencies,
117
+ warnings: data.warnings,
118
+ isMinified: !data.isOneLineRequire && isMin
119
+ };
120
+ }
121
+ catch (error) {
122
+ return {
123
+ ok: false,
124
+ warnings: [
125
+ { kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
126
+ ]
127
+ };
128
+ }
129
+ }
130
+
131
+ analyseFileSync(
132
+ pathToFile,
133
+ options = {}
134
+ ) {
135
+ try {
136
+ const {
137
+ packageName = null,
138
+ module = true,
139
+ removeHTMLComments = false,
140
+ initialize,
141
+ finalize
142
+ } = options;
143
+
144
+ const str = fsSync.readFileSync(pathToFile, "utf-8");
145
+ const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
146
+
147
+ const isMin = filePathString.includes(".min") || isMinified(str);
148
+ const data = this.analyse(str, {
149
+ isMinified: isMin,
150
+ module: path.extname(filePathString) === ".mjs" ? true : module,
151
+ removeHTMLComments,
152
+ initialize,
153
+ finalize
154
+ });
155
+
156
+ if (packageName !== null) {
157
+ data.dependencies.delete(packageName);
158
+ }
159
+
160
+ return {
161
+ ok: true,
162
+ dependencies: data.dependencies,
163
+ warnings: data.warnings,
164
+ isMinified: !data.isOneLineRequire && isMin
165
+ };
166
+ }
167
+ catch (error) {
168
+ return {
169
+ ok: false,
170
+ warnings: [
171
+ { kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
172
+ ]
173
+ };
174
+ }
175
+ }
176
+
177
+ /**
178
+ * @param {!string} source
179
+ * @param {object} options
180
+ * @param {boolean} [options.removeHTMLComments=false]
181
+ */
182
+ prepareSource(source, options = {}) {
183
+ if (typeof source !== "string") {
184
+ throw new TypeError("source must be a string");
185
+ }
186
+ const { removeHTMLComments = false } = options;
187
+
188
+ /**
189
+ * if the file start with a shebang then we remove it because meriyah.parseScript fail to parse it.
190
+ * @example
191
+ * #!/usr/bin/env node
192
+ */
193
+ const rawNoShebang = source.startsWith("#") ?
194
+ source.slice(source.indexOf("\n") + 1) : source;
195
+
196
+ return removeHTMLComments ?
197
+ this.#removeHTMLComment(rawNoShebang) : rawNoShebang;
198
+ }
199
+
200
+ /**
201
+ * @param {!string} str
202
+ * @returns {string}
203
+ */
204
+ #removeHTMLComment(str) {
205
+ return str.replaceAll(/<!--[\s\S]*?(?:-->)/g, "");
206
+ }
207
+ }
@@ -1,99 +1,112 @@
1
- // Import Node.js Dependencies
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
-
5
- // Import Internal Dependencies
6
- import { AstAnalyser } from "./AstAnalyser.js";
7
-
8
- const kDefaultExtensions = ["js", "cjs", "mjs", "node"];
9
-
10
- export class EntryFilesAnalyser {
11
- /**
12
- * @constructor
13
- * @param {object} [options={}]
14
- * @param {AstAnalyser} [options.astAnalyzer=new AstAnalyser()]
15
- * @param {function} [options.loadExtensions]
16
- */
17
- constructor(options = {}) {
18
- this.astAnalyzer = options.astAnalyzer ?? new AstAnalyser();
19
- this.allowedExtensions = options.loadExtensions
20
- ? options.loadExtensions(kDefaultExtensions)
21
- : kDefaultExtensions;
22
- }
23
-
24
- /**
25
- * Asynchronously analyze a set of entry files yielding analysis reports.
26
- *
27
- * @param {(string | URL)[]} entryFiles
28
- * @yields {Object} - Yields an object containing the analysis report for each file.
29
- */
30
- async* analyse(entryFiles) {
31
- this.analyzedDeps = new Set();
32
-
33
- for (const file of entryFiles) {
34
- yield* this.#analyzeFile(file);
35
- }
36
- }
37
-
38
- async* #analyzeFile(file) {
39
- const filePath = file instanceof URL ? file.pathname : file;
40
- const report = await this.astAnalyzer.analyseFile(file);
41
-
42
- yield { url: filePath, ...report };
43
-
44
- if (!report.ok) {
45
- return;
46
- }
47
-
48
- yield* this.#analyzeDeps(report.dependencies, path.dirname(filePath));
49
- }
50
-
51
- async* #analyzeDeps(deps, basePath) {
52
- for (const [name] of deps) {
53
- const depPath = await this.#getInternalDepPath(name, basePath);
54
- if (depPath && !this.analyzedDeps.has(depPath)) {
55
- this.analyzedDeps.add(depPath);
56
-
57
- yield* this.#analyzeFile(depPath);
58
- }
59
- }
60
- }
61
-
62
- async #getInternalDepPath(name, basePath) {
63
- const depPath = path.join(basePath, name);
64
- const existingExt = path.extname(name);
65
- if (existingExt !== "") {
66
- if (!this.allowedExtensions.includes(existingExt.slice(1))) {
67
- return null;
68
- }
69
-
70
- if (await this.#fileExists(depPath)) {
71
- return depPath;
72
- }
73
- }
74
-
75
- for (const ext of this.allowedExtensions) {
76
- const depPathWithExt = `${depPath}.${ext}`;
77
- if (await this.#fileExists(depPathWithExt)) {
78
- return depPathWithExt;
79
- }
80
- }
81
-
82
- return null;
83
- }
84
-
85
- async #fileExists(path) {
86
- try {
87
- await fs.access(path, fs.constants.F_OK);
88
-
89
- return true;
90
- }
91
- catch (error) {
92
- if (error.code !== "ENOENT") {
93
- throw error;
94
- }
95
-
96
- return false;
97
- }
98
- }
99
- }
1
+ // Import Node.js Dependencies
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ // Import Internal Dependencies
7
+ import { AstAnalyser } from "./AstAnalyser.js";
8
+
9
+ // CONSTANTS
10
+ const kDefaultExtensions = ["js", "cjs", "mjs", "node"];
11
+
12
+ export class EntryFilesAnalyser {
13
+ /**
14
+ * @constructor
15
+ * @param {object} [options={}]
16
+ * @param {AstAnalyser} [options.astAnalyzer=new AstAnalyser()]
17
+ * @param {function} [options.loadExtensions]
18
+ */
19
+ constructor(options = {}) {
20
+ this.astAnalyzer = options.astAnalyzer ?? new AstAnalyser();
21
+ const rawAllowedExtensions = options.loadExtensions
22
+ ? options.loadExtensions(kDefaultExtensions)
23
+ : kDefaultExtensions;
24
+
25
+ this.allowedExtensions = new Set(rawAllowedExtensions);
26
+ }
27
+
28
+ /**
29
+ * Asynchronously analyze a set of entry files yielding analysis reports.
30
+ *
31
+ * @param {(string | URL)[]} entryFiles
32
+ * @yields {Object} - Yields an object containing the analysis report for each file.
33
+ */
34
+ async* analyse(entryFiles) {
35
+ this.analyzedDeps = new Set();
36
+
37
+ for (const file of entryFiles) {
38
+ yield* this.#analyzeFile(file);
39
+ }
40
+ }
41
+
42
+ async* #analyzeFile(file) {
43
+ const filePath = file instanceof URL ? fileURLToPath(file) : file;
44
+ const report = await this.astAnalyzer.analyseFile(file);
45
+
46
+ yield { url: filePath, ...report };
47
+
48
+ if (!report.ok) {
49
+ return;
50
+ }
51
+
52
+ yield* this.#analyzeDeps(
53
+ report.dependencies,
54
+ path.dirname(filePath)
55
+ );
56
+ }
57
+
58
+ async* #analyzeDeps(deps, basePath) {
59
+ for (const [name] of deps) {
60
+ const depPath = await this.#getInternalDepPath(name, basePath);
61
+
62
+ if (depPath && !this.analyzedDeps.has(depPath)) {
63
+ this.analyzedDeps.add(depPath);
64
+
65
+ yield* this.#analyzeFile(depPath);
66
+ }
67
+ }
68
+ }
69
+
70
+ async #getInternalDepPath(name, basePath) {
71
+ const depPath = path.join(basePath, name);
72
+ const existingExt = path.extname(name);
73
+
74
+ if (existingExt === "") {
75
+ for (const ext of this.allowedExtensions) {
76
+ const depPathWithExt = `${depPath}.${ext}`;
77
+
78
+ const fileExist = await this.#fileExists(depPathWithExt);
79
+ if (fileExist) {
80
+ return depPathWithExt;
81
+ }
82
+ }
83
+ }
84
+ else {
85
+ if (!this.allowedExtensions.has(existingExt.slice(1))) {
86
+ return null;
87
+ }
88
+
89
+ const fileExist = await this.#fileExists(depPath);
90
+ if (fileExist) {
91
+ return depPath;
92
+ }
93
+ }
94
+
95
+ return null;
96
+ }
97
+
98
+ async #fileExists(path) {
99
+ try {
100
+ await fs.access(path, fs.constants.R_OK);
101
+
102
+ return true;
103
+ }
104
+ catch (error) {
105
+ if (error.code !== "ENOENT") {
106
+ throw error;
107
+ }
108
+
109
+ return false;
110
+ }
111
+ }
112
+ }
@@ -2,17 +2,23 @@
2
2
  import { exportAssignmentHasRequireLeave } from "./exportAssignmentHasRequireLeave.js";
3
3
 
4
4
  export function isOneLineExpressionExport(body) {
5
- if (body.length > 1) {
5
+ if (body.length === 0 || body.length > 1) {
6
6
  return false;
7
7
  }
8
8
 
9
- if (body[0].type !== "ExpressionStatement") {
9
+ const [firstNode] = body;
10
+ if (firstNode.type !== "ExpressionStatement") {
10
11
  return false;
11
12
  }
12
13
 
13
- if (body[0].expression.type !== "AssignmentExpression") {
14
- return false;
14
+ switch (firstNode.expression.type) {
15
+ // module.exports = require('...');
16
+ case "AssignmentExpression":
17
+ return exportAssignmentHasRequireLeave(firstNode.expression.right);
18
+ // require('...');
19
+ case "CallExpression":
20
+ return exportAssignmentHasRequireLeave(firstNode.expression);
21
+ default:
22
+ return false;
15
23
  }
16
-
17
- return exportAssignmentHasRequireLeave(body[0].expression.right);
18
24
  }
package/types/api.d.ts CHANGED
@@ -53,6 +53,8 @@ interface RuntimeOptions {
53
53
  * @default false
54
54
  */
55
55
  isMinified?: boolean;
56
+ initialize?: (sourceFile: SourceFile) => void;
57
+ finalize?: (sourceFile: SourceFile) => void;
56
58
  }
57
59
 
58
60
  interface RuntimeFileOptions extends Omit<RuntimeOptions, "isMinified"> {