Skip to content

Commit c601bd5

Browse files
committed
feat(js): supports NPM modules (goja_nodejs)
Signed-off-by: Dwi Siswanto <[email protected]>
1 parent 160eab9 commit c601bd5

File tree

23 files changed

+4711
-1
lines changed

23 files changed

+4711
-1
lines changed

pkg/js/CONTRIBUTE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ Libraries/node_modules represent adding new protocol or something similar and sh
3131
5. Import newly created library with '_' import in [compiler](./compiler/compiler.go)
3232

3333

34+
## Adding New Libraries (NPM)
35+
36+
Uses [add-node-lib.js](./devtools/add-node-lib.js) script to adds new NPM module.
37+
38+
```
39+
Usage: node add-node-lib.js --[m]odule=<module> [--[n]ame=<name>] --import-[a]s=<import-as> --build-[platform|target|format]=<value>
40+
```
41+
3442
## Adding Helper Objects/Types/Functions
3543

3644
Helper objects/types/functions can simply be understood as javascript utils to simplify writing javscript and reduce code duplication in javascript templates. Helper functions/objects are divided into two categories

pkg/js/DESIGN.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,8 @@ gojs contain minimalistic types and interfaces used to register packages written
4444

4545
## [libs](./libs/)
4646

47-
libs contains all go native packages that contain **actual** implementation of all the functions and types that are exposed to javascript runtime.
47+
libs contains all go native packages that contain **actual** implementation of all the functions and types that are exposed to javascript runtime.
48+
49+
## [node_libraries](./node_libraries/)
50+
51+
node_libraries contains NPM modules that are exposed to JavaScript runtime.

pkg/js/compiler/pool.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/dop251/goja_nodejs/require"
1313
"github.com/kitabisa/go-ci"
1414
"github.com/projectdiscovery/gologger"
15+
"github.com/projectdiscovery/nuclei/v3/pkg/js"
1516
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libbytes"
1617
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libfs"
1718
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libikev2"
@@ -214,6 +215,9 @@ func createNewRuntime() *goja.Runtime {
214215
if err := global.RegisterNativeScripts(runtime); err != nil {
215216
gologger.Error().Msgf("Could not register scripts: %s\n", err)
216217
}
218+
219+
js.RegisterNodeModules(runtime)
220+
217221
return runtime
218222
}
219223

pkg/js/devtools/add-node-lib.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env node
2+
3+
const { spawnSync } = require('node:child_process');
4+
const esbuild = require('esbuild');
5+
const fs = require('node:fs');
6+
const os = require('node:os');
7+
const path = require('node:path');
8+
const plugin = require('node-stdlib-browser/helpers/esbuild/plugin');
9+
const stdLibBrowser = require('node-stdlib-browser');
10+
const util = require('node:util');
11+
// const { nodeModulesPolyfillPlugin } = require('esbuild-plugins-node-modules-polyfill');
12+
13+
const NODE_LIBS_PATH = path.join(path.dirname(require.resolve(__filename)), '..', 'node_libraries');
14+
const NPM_PREFIX = path.join(NODE_LIBS_PATH, '.modules');
15+
16+
const { values: args } = util.parseArgs({
17+
args: process.argv.slice(2),
18+
options: {
19+
"module": { type: 'string', short: 'm' },
20+
"import-name": { type: 'string', short: 'n' },
21+
"import-as": { type: 'string', short: 'a' },
22+
"build-platform": { type: 'string' },
23+
"build-target": { type: 'string' },
24+
"build-format": { type: 'string' },
25+
},
26+
allowPositionals: true,
27+
});
28+
29+
const NPM_MODULE = args["module"] || '';
30+
let MODULE_NAME = args["import-name"] || NPM_MODULE;
31+
let IMPORT_AS = args["import-as"] || '';
32+
const BUILD_PLATFORM = args["build-platform"] || 'browser';
33+
const BUILD_TARGET = args["build-target"] || 'node10';
34+
const BUILD_FORMAT = args["build-format"] || 'cjs';
35+
36+
if (!MODULE_NAME) {
37+
MODULE_NAME = NPM_MODULE.split('/').pop().replace(/@.*$/, '');
38+
}
39+
40+
if (!NPM_MODULE || !IMPORT_AS) {
41+
const prog = path.basename(process.argv[1]);
42+
console.error(`Usage: node ${prog} --[m]odule=<module> [--[n]ame=<name>] --import-[a]s=<import-as> --build-[platform|target|format]=<value>`);
43+
process.exit(1);
44+
}
45+
46+
IMPORT_AS = path.join(NODE_LIBS_PATH, IMPORT_AS);
47+
48+
if (!fs.existsSync(NODE_LIBS_PATH)) {
49+
console.error(`Error: Node libraries path does not exist: ${NODE_LIBS_PATH}`);
50+
process.exit(1);
51+
}
52+
53+
if (!fs.existsSync(NPM_PREFIX)) {
54+
console.error(`Error: NPM prefix path does not exist: ${NPM_PREFIX}`);
55+
process.exit(1);
56+
}
57+
58+
async function main() {
59+
try {
60+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), MODULE_NAME.replace(/[^a-zA-Z0-9]/g, '-') + '-'));
61+
62+
spawnSync('npm', [
63+
'install',
64+
'--prefix', NPM_PREFIX,
65+
'--save-dev'
66+
], {
67+
stdio: 'inherit',
68+
env: { ...process.env }
69+
});
70+
71+
spawnSync('npm', [
72+
'install',
73+
NPM_MODULE,
74+
'--prefix', NPM_PREFIX,
75+
'--save-dev'
76+
], {
77+
stdio: 'inherit',
78+
env: { ...process.env }
79+
});
80+
81+
const libPath = path.join(tempDir, 'index.js');
82+
const libContent = util.format(`module.exports = require(%j);`, MODULE_NAME);
83+
// const libContent = util.format(`try { module.exports = require(%j) } catch (e) { };`, MODULE_NAME);
84+
85+
fs.writeFileSync(libPath, libContent);
86+
// spawnSync('bun', [
87+
// 'build',
88+
// '--minify',
89+
// '--format=cjs',
90+
// '--target=browser',
91+
// '--outfile', path.join(IMPORT_AS, 'index.js'),
92+
// libPath
93+
// ], {
94+
// stdio: 'inherit',
95+
// env: {
96+
// ...process.env,
97+
// NODE_PATH: path.join(NPM_PREFIX, 'node_modules')
98+
// }
99+
// });
100+
// spawnSync('browserify', [
101+
// '-e', libPath,
102+
// '--insert-globals',
103+
// '-o', path.join(IMPORT_AS, 'index.js'),
104+
// ], {
105+
// stdio: 'inherit',
106+
// env: {
107+
// ...process.env,
108+
// NODE_PATH: path.join(NPM_PREFIX, 'node_modules')
109+
// }
110+
// });
111+
// spawnSync('esbuild', [
112+
// '--minify',
113+
// '--format=cjs',
114+
// '--platform=browser',
115+
// '--target=node10',
116+
// '--outdir=' + IMPORT_AS,
117+
// '--bundle', libPath
118+
// ], {
119+
// stdio: 'inherit',
120+
// env: {
121+
// ...process.env,
122+
// NODE_PATH: path.join(NPM_PREFIX, 'node_modules')
123+
// }
124+
// });
125+
126+
try {
127+
await esbuild.build({
128+
entryPoints: [libPath],
129+
minify: true,
130+
bundle: true,
131+
platform: BUILD_PLATFORM,
132+
target: BUILD_TARGET,
133+
format: BUILD_FORMAT,
134+
loader: { '.js': 'jsx' },
135+
outfile: path.join(IMPORT_AS, 'index.js'),
136+
inject: [require.resolve('node-stdlib-browser/helpers/esbuild/shim')],
137+
define: {
138+
global: 'global',
139+
process: 'process',
140+
Buffer: 'Buffer'
141+
},
142+
plugins: [plugin(stdLibBrowser)],
143+
// plugins: [
144+
// nodeModulesPolyfillPlugin({
145+
// globals: {
146+
// process: true,
147+
// Buffer: true,
148+
// },
149+
// }),
150+
// ],
151+
});
152+
} catch (esbuildError) {
153+
console.log(`Removing failed module ${NPM_MODULE}...`);
154+
spawnSync('npm', [
155+
'remove',
156+
NPM_MODULE,
157+
'--prefix', NPM_PREFIX
158+
], {
159+
stdio: 'inherit',
160+
env: { ...process.env }
161+
});
162+
163+
throw esbuildError; // Re-throw to trigger the outer catch block
164+
}
165+
166+
fs.rmSync(tempDir, { recursive: true });
167+
} catch (error) {
168+
console.error('Error:', error.message);
169+
process.exit(1);
170+
}
171+
}
172+
173+
main().catch(err => {
174+
console.error('Unhandled error:', err);
175+
process.exit(1);
176+
});

0 commit comments

Comments
 (0)