json-function-engine 0.8.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/LICENSE +21 -0
- package/README.md +509 -0
- package/dist/constants.d.ts +13 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +18 -0
- package/dist/constants.js.map +1 -0
- package/dist/engine/Executor.d.ts +92 -0
- package/dist/engine/Executor.d.ts.map +1 -0
- package/dist/engine/Executor.js +260 -0
- package/dist/engine/Executor.js.map +1 -0
- package/dist/engine/FileLoader.d.ts +51 -0
- package/dist/engine/FileLoader.d.ts.map +1 -0
- package/dist/engine/FileLoader.js +195 -0
- package/dist/engine/FileLoader.js.map +1 -0
- package/dist/engine/Pipeline.d.ts +54 -0
- package/dist/engine/Pipeline.d.ts.map +1 -0
- package/dist/engine/Pipeline.js +91 -0
- package/dist/engine/Pipeline.js.map +1 -0
- package/dist/engine/actions/BlockAction.d.ts +3 -0
- package/dist/engine/actions/BlockAction.d.ts.map +1 -0
- package/dist/engine/actions/BlockAction.js +13 -0
- package/dist/engine/actions/BlockAction.js.map +1 -0
- package/dist/engine/actions/FlagAction.d.ts +3 -0
- package/dist/engine/actions/FlagAction.d.ts.map +1 -0
- package/dist/engine/actions/FlagAction.js +29 -0
- package/dist/engine/actions/FlagAction.js.map +1 -0
- package/dist/engine/actions/NotifyAction.d.ts +3 -0
- package/dist/engine/actions/NotifyAction.d.ts.map +1 -0
- package/dist/engine/actions/NotifyAction.js +13 -0
- package/dist/engine/actions/NotifyAction.js.map +1 -0
- package/dist/engine/actions/TransformAction.d.ts +3 -0
- package/dist/engine/actions/TransformAction.d.ts.map +1 -0
- package/dist/engine/actions/TransformAction.js +13 -0
- package/dist/engine/actions/TransformAction.js.map +1 -0
- package/dist/engine/actions/index.d.ts +5 -0
- package/dist/engine/actions/index.d.ts.map +1 -0
- package/dist/engine/actions/index.js +5 -0
- package/dist/engine/actions/index.js.map +1 -0
- package/dist/engine/conditions/ArrayCondition.d.ts +3 -0
- package/dist/engine/conditions/ArrayCondition.d.ts.map +1 -0
- package/dist/engine/conditions/ArrayCondition.js +47 -0
- package/dist/engine/conditions/ArrayCondition.js.map +1 -0
- package/dist/engine/conditions/ComparisonCondition.d.ts +3 -0
- package/dist/engine/conditions/ComparisonCondition.d.ts.map +1 -0
- package/dist/engine/conditions/ComparisonCondition.js +42 -0
- package/dist/engine/conditions/ComparisonCondition.js.map +1 -0
- package/dist/engine/conditions/CompositeCondition.d.ts +3 -0
- package/dist/engine/conditions/CompositeCondition.d.ts.map +1 -0
- package/dist/engine/conditions/CompositeCondition.js +50 -0
- package/dist/engine/conditions/CompositeCondition.js.map +1 -0
- package/dist/engine/conditions/ExistsCondition.d.ts +3 -0
- package/dist/engine/conditions/ExistsCondition.d.ts.map +1 -0
- package/dist/engine/conditions/ExistsCondition.js +14 -0
- package/dist/engine/conditions/ExistsCondition.js.map +1 -0
- package/dist/engine/conditions/MathCondition.d.ts +3 -0
- package/dist/engine/conditions/MathCondition.d.ts.map +1 -0
- package/dist/engine/conditions/MathCondition.js +30 -0
- package/dist/engine/conditions/MathCondition.js.map +1 -0
- package/dist/engine/conditions/RegexCondition.d.ts +3 -0
- package/dist/engine/conditions/RegexCondition.d.ts.map +1 -0
- package/dist/engine/conditions/RegexCondition.js +10 -0
- package/dist/engine/conditions/RegexCondition.js.map +1 -0
- package/dist/engine/conditions/helpers.d.ts +3 -0
- package/dist/engine/conditions/helpers.d.ts.map +1 -0
- package/dist/engine/conditions/helpers.js +14 -0
- package/dist/engine/conditions/helpers.js.map +1 -0
- package/dist/engine/conditions/index.d.ts +7 -0
- package/dist/engine/conditions/index.d.ts.map +1 -0
- package/dist/engine/conditions/index.js +7 -0
- package/dist/engine/conditions/index.js.map +1 -0
- package/dist/engine/engine.d.ts +151 -0
- package/dist/engine/engine.d.ts.map +1 -0
- package/dist/engine/engine.js +246 -0
- package/dist/engine/engine.js.map +1 -0
- package/dist/engine/guards.d.ts +22 -0
- package/dist/engine/guards.d.ts.map +1 -0
- package/dist/engine/guards.js +84 -0
- package/dist/engine/guards.js.map +1 -0
- package/dist/engine/registry.d.ts +23 -0
- package/dist/engine/registry.d.ts.map +1 -0
- package/dist/engine/registry.js +93 -0
- package/dist/engine/registry.js.map +1 -0
- package/dist/engine/reporters/index.d.ts +12 -0
- package/dist/engine/reporters/index.d.ts.map +1 -0
- package/dist/engine/reporters/index.js +146 -0
- package/dist/engine/reporters/index.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +204 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +27 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/cache.d.ts +17 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +52 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/errors.d.ts +45 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +84 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/factories.d.ts +23 -0
- package/dist/utils/factories.d.ts.map +1 -0
- package/dist/utils/factories.js +63 -0
- package/dist/utils/factories.js.map +1 -0
- package/dist/utils/metrics.d.ts +75 -0
- package/dist/utils/metrics.d.ts.map +1 -0
- package/dist/utils/metrics.js +99 -0
- package/dist/utils/metrics.js.map +1 -0
- package/dist/utils/regex.d.ts +33 -0
- package/dist/utils/regex.d.ts.map +1 -0
- package/dist/utils/regex.js +226 -0
- package/dist/utils/regex.js.map +1 -0
- package/dist/utils/schema.d.ts +7 -0
- package/dist/utils/schema.d.ts.map +1 -0
- package/dist/utils/schema.js +226 -0
- package/dist/utils/schema.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peter Williams
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# json-function-engine
|
|
2
|
+
|
|
3
|
+
A standalone JavaScript/TypeScript library for executing logic defined in JSON configuration files. Define functions, conditions, and actions in JSON — execute them against source code, JSON data, or any input. Lightweight, embeddable, and extensible.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Define functions in JSON** - Express logic as conditions and actions in JSON
|
|
8
|
+
- **Execute against source files** - Scan codebases with regex, file filtering, and more
|
|
9
|
+
- **Extensible** - Register custom conditions, actions, and reporters
|
|
10
|
+
- **Multiple output formats** - JSON, Text, HTML, SARIF
|
|
11
|
+
- **Performance** - Regex caching, parallel execution, ReDoS protection
|
|
12
|
+
- **Zero runtime dependencies** - Keep your bundle small
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install json-function-engine
|
|
18
|
+
# or
|
|
19
|
+
pnpm add json-function-engine
|
|
20
|
+
# or
|
|
21
|
+
yarn add json-function-engine
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { Engine } from 'json-function-engine';
|
|
28
|
+
|
|
29
|
+
const engine = new Engine();
|
|
30
|
+
|
|
31
|
+
// Define functions inline
|
|
32
|
+
const functions = {
|
|
33
|
+
version: "1.0",
|
|
34
|
+
functions: [
|
|
35
|
+
{
|
|
36
|
+
id: "NO_TODO",
|
|
37
|
+
name: "No TODO comments",
|
|
38
|
+
enabled: true,
|
|
39
|
+
priority: 1,
|
|
40
|
+
condition: {
|
|
41
|
+
type: "regex",
|
|
42
|
+
pattern: "TODO",
|
|
43
|
+
fileExtensions: [".ts", ".tsx"]
|
|
44
|
+
},
|
|
45
|
+
action: {
|
|
46
|
+
type: "flag",
|
|
47
|
+
severity: "info",
|
|
48
|
+
message: "TODO comment found"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Add functions to engine
|
|
55
|
+
engine.addFunctions(functions.functions);
|
|
56
|
+
|
|
57
|
+
// Execute against source files
|
|
58
|
+
const findings = await engine.execute([
|
|
59
|
+
{ path: "src/auth.ts", content: "const TODO = 'implement auth';" }
|
|
60
|
+
], { cwd: process.cwd() });
|
|
61
|
+
|
|
62
|
+
// Format output
|
|
63
|
+
console.log(engine.format(findings, "json", { pretty: true }));
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Your First Function
|
|
67
|
+
|
|
68
|
+
A 5-minute guide to creating and running your first function:
|
|
69
|
+
|
|
70
|
+
### Step 1: Create a function definition file
|
|
71
|
+
|
|
72
|
+
Create `my-functions.json`:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"version": "1.0",
|
|
77
|
+
"functions": [
|
|
78
|
+
{
|
|
79
|
+
"id": "DETECT_SECRETS",
|
|
80
|
+
"name": "No hardcoded secrets",
|
|
81
|
+
"condition": {
|
|
82
|
+
"type": "regex",
|
|
83
|
+
"pattern": "(api_key|password|secret)\\s*[:=]\\s*['\"][^'\"]+['\"]",
|
|
84
|
+
"fileExtensions": [".ts", ".js", ".env"]
|
|
85
|
+
},
|
|
86
|
+
"action": {
|
|
87
|
+
"type": "flag",
|
|
88
|
+
"severity": "critical",
|
|
89
|
+
"message": "Potential hardcoded secret detected"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Step 2: Load and execute
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { Engine } from 'json-function-engine';
|
|
100
|
+
|
|
101
|
+
const engine = new Engine();
|
|
102
|
+
|
|
103
|
+
// Load functions from file
|
|
104
|
+
const result = await engine.loadFunctions('./my-functions.json');
|
|
105
|
+
console.log(`Loaded ${result.loaded} functions`);
|
|
106
|
+
|
|
107
|
+
// Execute against your codebase
|
|
108
|
+
const findings = await engine.execute([
|
|
109
|
+
{ path: "src/config.ts", content: "const api_key = 'sk-1234567890';" }
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// See results
|
|
113
|
+
console.log(engine.format(findings, 'text'));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Step 3: Output in different formats
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// JSON for programmatic use
|
|
120
|
+
const json = engine.format(findings, 'json');
|
|
121
|
+
|
|
122
|
+
// SARIF for GitHub Security
|
|
123
|
+
const sarif = engine.format(findings, 'sarif', { version: '2.1' });
|
|
124
|
+
|
|
125
|
+
// HTML report
|
|
126
|
+
const html = engine.format(findings, 'html', { theme: 'dark' });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Usage with Attune
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { Engine } from 'json-function-engine';
|
|
133
|
+
|
|
134
|
+
const engine = new Engine();
|
|
135
|
+
const result = await engine.loadFunctions('./functions/*.json');
|
|
136
|
+
|
|
137
|
+
console.log(`Loaded ${result.loaded} functions (${result.errors.length} errors)`);
|
|
138
|
+
|
|
139
|
+
const findings = await engine.execute([
|
|
140
|
+
{ path: 'src/index.ts', content: '...' }
|
|
141
|
+
], { framework: 'nextjs' });
|
|
142
|
+
|
|
143
|
+
// Get SARIF output for GitHub Security
|
|
144
|
+
const sarif = engine.format(findings, 'sarif', { version: '2.1' });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## JSON Schema
|
|
148
|
+
|
|
149
|
+
Validate your function definitions using the JSON Schema:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"$schema": "https://json-function-engine.dev/schema/v1/functions.json",
|
|
154
|
+
"version": "1.0",
|
|
155
|
+
"rules": [...]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Download the schema from [`schema/v1/functions.json`](schema/v1/functions.json) or use with VS Code:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"$schema": "./node_modules/json-function-engine/schema/v1/functions.json"
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Function Schema
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"version": "1.0",
|
|
172
|
+
"functions": [
|
|
173
|
+
{
|
|
174
|
+
"id": "UNIQUE_FUNCTION_ID",
|
|
175
|
+
"name": "Human readable name",
|
|
176
|
+
"description": "What this function detects or does",
|
|
177
|
+
"enabled": true,
|
|
178
|
+
"priority": 1,
|
|
179
|
+
"frameworks": ["react", "vue"],
|
|
180
|
+
"condition": { ... },
|
|
181
|
+
"action": { ... }
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Condition Types
|
|
188
|
+
|
|
189
|
+
| Type | Description |
|
|
190
|
+
|------|-------------|
|
|
191
|
+
| `regex` | Pattern matching |
|
|
192
|
+
| `comparison` | Value comparison (==, !=, >, <, contains, etc.) |
|
|
193
|
+
| `exists` | Field presence check |
|
|
194
|
+
| `composite` | AND/OR/NOT logic |
|
|
195
|
+
|
|
196
|
+
### Regex Condition
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"type": "regex",
|
|
201
|
+
"pattern": "TODO",
|
|
202
|
+
"matchAll": false,
|
|
203
|
+
"fileExtensions": [".ts", ".tsx"]
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Comparison Condition
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"type": "comparison",
|
|
212
|
+
"operator": "==",
|
|
213
|
+
"field": "framework",
|
|
214
|
+
"value": "nextjs"
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Exists Condition
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"type": "exists",
|
|
223
|
+
"field": "framework"
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Composite Condition
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"type": "composite",
|
|
232
|
+
"operator": "AND",
|
|
233
|
+
"conditions": [
|
|
234
|
+
{ "type": "regex", "pattern": "..." },
|
|
235
|
+
{ "type": "exists", "field": "..." }
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Action Types
|
|
241
|
+
|
|
242
|
+
| Type | Description |
|
|
243
|
+
|------|-------------|
|
|
244
|
+
| `flag` | Create a finding |
|
|
245
|
+
| `block` | Stop execution |
|
|
246
|
+
| `transform` | Modify a value |
|
|
247
|
+
| `notify` | Send an alert |
|
|
248
|
+
|
|
249
|
+
### Flag Action
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"type": "flag",
|
|
254
|
+
"severity": "high",
|
|
255
|
+
"message": "Issue description"
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Block Action
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"type": "block",
|
|
264
|
+
"message": "Stopping execution",
|
|
265
|
+
"severity": "critical"
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Reporters
|
|
270
|
+
|
|
271
|
+
| Format | Description |
|
|
272
|
+
|--------|-------------|
|
|
273
|
+
| `json` | JSON output |
|
|
274
|
+
| `text` | Human-readable text |
|
|
275
|
+
| `html` | HTML report |
|
|
276
|
+
| `sarif` | SARIF for CI integration |
|
|
277
|
+
|
|
278
|
+
## Extending the Engine
|
|
279
|
+
|
|
280
|
+
### Custom Conditions
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const engine = new Engine();
|
|
284
|
+
engine.getRegistry().registerCondition('fileExists', {
|
|
285
|
+
name: 'fileExists',
|
|
286
|
+
evaluate: async (config, context, file) => {
|
|
287
|
+
return {
|
|
288
|
+
matched: fs.existsSync(path.join(context.cwd, config.path))
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Custom Actions
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
engine.getRegistry().registerAction('slackNotify', {
|
|
298
|
+
name: 'slackNotify',
|
|
299
|
+
execute: async (config, context, matches, file) => {
|
|
300
|
+
await slack.webhook.send({ channel: config.channel, text: ... });
|
|
301
|
+
return { success: true, notified: true };
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Custom Reporters
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
engine.getRegistry().registerReporter('junit', {
|
|
310
|
+
name: 'JUnit',
|
|
311
|
+
format: (findings) => {
|
|
312
|
+
return `<?xml version="1.0"?>
|
|
313
|
+
<testsuite tests="${findings.length}">
|
|
314
|
+
${findings.map(f => ` <testcase name="${f.message}"/>`).join('\n')}
|
|
315
|
+
</testsuite>`;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## API
|
|
321
|
+
|
|
322
|
+
### Engine
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const engine = new Engine(options?: EngineOptions)
|
|
326
|
+
|
|
327
|
+
// Load functions from file paths
|
|
328
|
+
const result = await engine.loadFunctions(paths: string | string[], options?: EngineOptions)
|
|
329
|
+
// Returns: { loaded: number, errors: Array<{ path: string, error: string }> }
|
|
330
|
+
|
|
331
|
+
// Add functions programmatically
|
|
332
|
+
engine.addFunctions(functions: Rule[])
|
|
333
|
+
|
|
334
|
+
// Get loaded function count
|
|
335
|
+
const count = engine.getFunctionCount()
|
|
336
|
+
|
|
337
|
+
// Inspect loaded functions
|
|
338
|
+
const functions = engine.getFunctions()
|
|
339
|
+
|
|
340
|
+
// Clear all functions
|
|
341
|
+
engine.clear()
|
|
342
|
+
|
|
343
|
+
// Execute functions against files
|
|
344
|
+
const findings = await engine.execute(files: FileInput[], context?: ExecutionContext)
|
|
345
|
+
|
|
346
|
+
// Format findings
|
|
347
|
+
const output = engine.format(findings: Finding[], format: ReporterFormat, options?: FormatOptions)
|
|
348
|
+
|
|
349
|
+
// Convenience method
|
|
350
|
+
const output = await engine.scan(files, format?, context?, formatOptions?)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Options
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
interface EngineOptions {
|
|
357
|
+
include?: string[]; // Only include functions matching patterns
|
|
358
|
+
exclude?: string[]; // Exclude functions matching patterns
|
|
359
|
+
timeout?: number; // Timeout per function in ms (default: 5000)
|
|
360
|
+
parallel?: boolean; // Execute functions in parallel (default: true)
|
|
361
|
+
strict?: boolean; // Throw on errors instead of collecting them
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Execution Context
|
|
366
|
+
|
|
367
|
+
The execution context provides additional information during function execution:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
interface ExecutionContext {
|
|
371
|
+
cwd: string; // Current working directory
|
|
372
|
+
framework?: string; // Target framework (e.g., 'nextjs', 'react')
|
|
373
|
+
signal?: AbortSignal; // Cancellation signal
|
|
374
|
+
[key: string]: unknown; // Custom context values
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Framework Filtering
|
|
379
|
+
|
|
380
|
+
Functions can be targeted to specific frameworks:
|
|
381
|
+
|
|
382
|
+
```json
|
|
383
|
+
{
|
|
384
|
+
"id": "NEXTJS_ONLY",
|
|
385
|
+
"frameworks": ["nextjs"],
|
|
386
|
+
"condition": { "type": "regex", "pattern": "getServerSideProps" },
|
|
387
|
+
"action": { "type": "flag", "severity": "info", "message": "Server-side rendering detected" }
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
When executing, specify the framework to run only matching functions:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
const findings = await engine.execute(files, {
|
|
395
|
+
cwd: '.',
|
|
396
|
+
framework: 'nextjs' // Only runs functions that target nextjs or have no framework restriction
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Cancellation
|
|
401
|
+
|
|
402
|
+
Support cancellation via AbortSignal:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
const controller = new AbortController();
|
|
406
|
+
|
|
407
|
+
const findings = await engine.execute(files, {
|
|
408
|
+
cwd: '.',
|
|
409
|
+
signal: controller.signal
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Cancel after timeout
|
|
413
|
+
setTimeout(() => controller.abort(), 5000);
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Metrics
|
|
417
|
+
|
|
418
|
+
Access execution metrics:
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
await engine.execute(files, { cwd: '.' });
|
|
422
|
+
const metrics = engine.getMetrics().getMetrics();
|
|
423
|
+
|
|
424
|
+
console.log({
|
|
425
|
+
functionsExecuted: metrics.functionsExecuted,
|
|
426
|
+
filesProcessed: metrics.filesProcessed,
|
|
427
|
+
findingsCount: metrics.findingsCount,
|
|
428
|
+
errorsCount: metrics.errorsCount,
|
|
429
|
+
findingsBySeverity: metrics.findingsBySeverity
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Error Aggregation
|
|
434
|
+
|
|
435
|
+
Get execution errors:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
await engine.execute(files, { cwd: '.' });
|
|
439
|
+
const errors = engine.getErrors();
|
|
440
|
+
|
|
441
|
+
for (const error of errors) {
|
|
442
|
+
console.log(`Function ${error.functionId} on ${error.file}: ${error.error}`);
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Error Handling
|
|
447
|
+
|
|
448
|
+
### Invalid JSON Files
|
|
449
|
+
|
|
450
|
+
If a JSON file is malformed, `loadFunctions()` will log a warning and continue:
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
const result = await engine.loadFunctions('./functions/*.json');
|
|
454
|
+
// If some files fail:
|
|
455
|
+
// result.loaded = 5 // successfully loaded
|
|
456
|
+
// result.errors = [{ path: './functions/bad.json', error: 'Unexpected token }' }]
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Invalid Regex Patterns
|
|
460
|
+
|
|
461
|
+
Invalid regex patterns in conditions are logged as warnings. The function is skipped:
|
|
462
|
+
|
|
463
|
+
```json
|
|
464
|
+
{
|
|
465
|
+
"condition": {
|
|
466
|
+
"type": "regex",
|
|
467
|
+
"pattern": "[invalid("
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// Warning: Invalid regex pattern in function INVALID_FUNC
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Duplicate Function IDs
|
|
474
|
+
|
|
475
|
+
When the same function ID is defined multiple times, later definitions override earlier ones. A warning is logged.
|
|
476
|
+
|
|
477
|
+
### Timeout Handling
|
|
478
|
+
|
|
479
|
+
Each function has a configurable timeout (default: 5 seconds). If execution exceeds the timeout, it's terminated and a warning is logged:
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
const engine = new Engine({ timeout: 1000 }); // 1 second timeout
|
|
483
|
+
const findings = await engine.execute(files);
|
|
484
|
+
// If a function takes >1s, it's terminated and execution continues
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Performance
|
|
488
|
+
|
|
489
|
+
- **Bundle size**: < 15KB gzipped
|
|
490
|
+
- **Regex caching**: Compiled patterns are cached
|
|
491
|
+
- **ReDoS protection**: Configurable timeout per function (default: 5s)
|
|
492
|
+
- **Parallel execution**: Functions can run concurrently
|
|
493
|
+
|
|
494
|
+
## Use Cases
|
|
495
|
+
|
|
496
|
+
| Use Case | Description |
|
|
497
|
+
|----------|-------------|
|
|
498
|
+
| Code scanning | Scan source for secrets, TODOs, patterns |
|
|
499
|
+
| Data validation | Validate API responses against functions |
|
|
500
|
+
| Config processing | Evaluate conditions in config files |
|
|
501
|
+
| Simple workflows | Conditional data pipelines |
|
|
502
|
+
|
|
503
|
+
## Interested in Consolidating?
|
|
504
|
+
|
|
505
|
+
If you're a maintainer considering rolling similar functionality into your core package, I'm happy to point users your direction instead. Open an issue to discuss.
|
|
506
|
+
|
|
507
|
+
## License
|
|
508
|
+
|
|
509
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const DEFAULT_TIMEOUT_MS = 5000;
|
|
2
|
+
export declare const MAX_REGEX_CACHE_SIZE = 1000;
|
|
3
|
+
export declare const SEVERITY_WEIGHTS: {
|
|
4
|
+
readonly critical: 5;
|
|
5
|
+
readonly high: 4;
|
|
6
|
+
readonly medium: 3;
|
|
7
|
+
readonly low: 2;
|
|
8
|
+
readonly info: 1;
|
|
9
|
+
};
|
|
10
|
+
export declare const DEFAULT_PARALLEL = true;
|
|
11
|
+
export declare const DEFAULT_INCLUDE_PATTERNS: string[];
|
|
12
|
+
export declare const DEFAULT_EXCLUDE_PATTERNS: string[];
|
|
13
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAGvC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAGzC,eAAO,MAAM,gBAAgB;;;;;;CAMnB,CAAC;AAGX,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAGrC,eAAO,MAAM,wBAAwB,EAAE,MAAM,EAAO,CAAC;AACrD,eAAO,MAAM,wBAAwB,EAAE,MAAM,EAAO,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Default timeout for function execution in milliseconds
|
|
2
|
+
export const DEFAULT_TIMEOUT_MS = 5000;
|
|
3
|
+
// Regex cache limits
|
|
4
|
+
export const MAX_REGEX_CACHE_SIZE = 1000;
|
|
5
|
+
// Severity weights for sorting findings (higher = more severe)
|
|
6
|
+
export const SEVERITY_WEIGHTS = {
|
|
7
|
+
critical: 5,
|
|
8
|
+
high: 4,
|
|
9
|
+
medium: 3,
|
|
10
|
+
low: 2,
|
|
11
|
+
info: 1
|
|
12
|
+
};
|
|
13
|
+
// Default parallel processing setting
|
|
14
|
+
export const DEFAULT_PARALLEL = true;
|
|
15
|
+
// File loading defaults
|
|
16
|
+
export const DEFAULT_INCLUDE_PATTERNS = [];
|
|
17
|
+
export const DEFAULT_EXCLUDE_PATTERNS = [];
|
|
18
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEvC,qBAAqB;AACrB,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAEzC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;CACC,CAAC;AAEX,sCAAsC;AACtC,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,wBAAwB;AACxB,MAAM,CAAC,MAAM,wBAAwB,GAAa,EAAE,CAAC;AACrD,MAAM,CAAC,MAAM,wBAAwB,GAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { FunctionDefinition, FileInput, ExecutionContext, Finding, Logger } from '../types/index.js';
|
|
2
|
+
import type { Registry } from './registry.js';
|
|
3
|
+
import { Pipeline } from './Pipeline.js';
|
|
4
|
+
import { type MetricsCollector } from '../utils/metrics.js';
|
|
5
|
+
export interface ExecutorDependencies {
|
|
6
|
+
registry: Registry;
|
|
7
|
+
logger?: Logger;
|
|
8
|
+
metrics?: MetricsCollector;
|
|
9
|
+
}
|
|
10
|
+
export interface ExecutorOptions {
|
|
11
|
+
timeout?: number;
|
|
12
|
+
parallel?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Classified error information
|
|
16
|
+
*/
|
|
17
|
+
export interface ClassifiedError {
|
|
18
|
+
functionId: string;
|
|
19
|
+
file: string;
|
|
20
|
+
error: string;
|
|
21
|
+
phase: 'condition' | 'action';
|
|
22
|
+
type: 'validation' | 'timeout' | 'runtime' | 'unknown';
|
|
23
|
+
}
|
|
24
|
+
export declare class Executor {
|
|
25
|
+
private registry;
|
|
26
|
+
private logger;
|
|
27
|
+
private options;
|
|
28
|
+
private pipeline;
|
|
29
|
+
private metrics;
|
|
30
|
+
private errors;
|
|
31
|
+
private cancelled;
|
|
32
|
+
constructor(dependencies: ExecutorDependencies, options?: ExecutorOptions);
|
|
33
|
+
/**
|
|
34
|
+
* Cancel the current execution
|
|
35
|
+
*/
|
|
36
|
+
cancel(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Check if execution was cancelled
|
|
39
|
+
*/
|
|
40
|
+
isCancelled(): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Reset cancelled state for next execution
|
|
43
|
+
*/
|
|
44
|
+
private resetCancellation;
|
|
45
|
+
/**
|
|
46
|
+
* Get the execution pipeline
|
|
47
|
+
*/
|
|
48
|
+
getPipeline(): Pipeline;
|
|
49
|
+
/**
|
|
50
|
+
* Execute functions against files
|
|
51
|
+
*/
|
|
52
|
+
execute(functions: FunctionDefinition[], files: FileInput[], context: ExecutionContext): Promise<Finding[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Get execution errors with classification
|
|
55
|
+
*/
|
|
56
|
+
getErrors(): ClassifiedError[];
|
|
57
|
+
/**
|
|
58
|
+
* Get the metrics collector
|
|
59
|
+
*/
|
|
60
|
+
getMetrics(): MetricsCollector;
|
|
61
|
+
/**
|
|
62
|
+
* Classify an error based on its characteristics
|
|
63
|
+
*/
|
|
64
|
+
private classifyError;
|
|
65
|
+
/**
|
|
66
|
+
* Process files sequentially
|
|
67
|
+
*/
|
|
68
|
+
private sequentialProcess;
|
|
69
|
+
/**
|
|
70
|
+
* Process a single file through all enabled functions
|
|
71
|
+
*/
|
|
72
|
+
processFile(file: FileInput & {
|
|
73
|
+
lines: string[];
|
|
74
|
+
}, fns: FunctionDefinition[], context: ExecutionContext): Promise<Finding[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Evaluate a function against a file
|
|
77
|
+
*/
|
|
78
|
+
evaluateFunction(fn: FunctionDefinition, file: FileInput, context: ExecutionContext): Promise<{
|
|
79
|
+
findings?: Finding[];
|
|
80
|
+
blocked?: boolean;
|
|
81
|
+
error?: string;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Set the logger (acceptable to change at runtime)
|
|
85
|
+
*/
|
|
86
|
+
setLogger(logger: Logger): void;
|
|
87
|
+
/**
|
|
88
|
+
* Update options
|
|
89
|
+
*/
|
|
90
|
+
setOptions(options: Partial<ExecutorOptions>): void;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=Executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Executor.d.ts","sourceRoot":"","sources":["../../src/engine/Executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAiC,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACzI,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAgB,MAAM,eAAe,CAAC;AAGvD,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAErF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC9B,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;CACxD;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,SAAS,CAAkB;gBAEvB,YAAY,EAAE,oBAAoB,EAAE,OAAO,GAAE,eAAoB;IAe7E;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;OAEG;IACG,OAAO,CACX,SAAS,EAAE,kBAAkB,EAAE,EAC/B,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,OAAO,EAAE,CAAC;IAyDrB;;OAEG;IACH,SAAS,IAAI,eAAe,EAAE;IAI9B;;OAEG;IACH,UAAU,IAAI,gBAAgB;IAI9B;;OAEG;IACH,OAAO,CAAC,aAAa;IA8CrB;;OAEG;YACW,iBAAiB;IAmB/B;;OAEG;IACG,WAAW,CACf,IAAI,EAAE,SAAS,GAAG;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,EACrC,GAAG,EAAE,kBAAkB,EAAE,EACzB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,OAAO,EAAE,CAAC;IAqDrB;;OAEG;IACG,gBAAgB,CACpB,EAAE,EAAE,kBAAkB,EACtB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;QACT,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;QACrB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAoCF;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI;CAGpD"}
|