skrypt-ai 0.3.4 → 0.4.1
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 +1 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/index.js +3 -5
- package/dist/autofix/index.js +15 -3
- package/dist/cli.js +19 -4
- package/dist/commands/check-links.js +164 -174
- package/dist/commands/deploy.js +5 -2
- package/dist/commands/generate.js +206 -199
- package/dist/commands/i18n.js +3 -20
- package/dist/commands/init.js +47 -40
- package/dist/commands/lint.js +3 -20
- package/dist/commands/mcp.js +125 -122
- package/dist/commands/monitor.js +125 -108
- package/dist/commands/review-pr.js +1 -1
- package/dist/commands/sdk.js +1 -1
- package/dist/config/loader.js +21 -2
- package/dist/generator/organizer.d.ts +3 -0
- package/dist/generator/organizer.js +4 -9
- package/dist/generator/writer.js +2 -10
- package/dist/github/pr-comments.js +21 -8
- package/dist/plugins/index.js +1 -0
- package/dist/scanner/index.js +8 -2
- package/dist/template/docs.json +2 -1
- package/dist/template/next.config.mjs +2 -1
- package/dist/template/package.json +17 -15
- package/dist/template/public/favicon.svg +4 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +120 -25
- package/dist/template/src/app/api/chat/route.ts +11 -3
- package/dist/template/src/app/docs/README.md +28 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +139 -16
- package/dist/template/src/app/docs/auth/page.mdx +589 -0
- package/dist/template/src/app/docs/autofix/page.mdx +624 -0
- package/dist/template/src/app/docs/cli/page.mdx +217 -0
- package/dist/template/src/app/docs/config/page.mdx +428 -0
- package/dist/template/src/app/docs/configuration/page.mdx +86 -0
- package/dist/template/src/app/docs/deployment/page.mdx +112 -0
- package/dist/template/src/app/docs/error.tsx +20 -0
- package/dist/template/src/app/docs/generator/generator.md +504 -0
- package/dist/template/src/app/docs/generator/organizer.md +779 -0
- package/dist/template/src/app/docs/generator/page.mdx +613 -0
- package/dist/template/src/app/docs/github/page.mdx +502 -0
- package/dist/template/src/app/docs/llm/anthropic-client.md +549 -0
- package/dist/template/src/app/docs/llm/index.md +471 -0
- package/dist/template/src/app/docs/llm/page.mdx +428 -0
- package/dist/template/src/app/docs/llms-full.md +256 -0
- package/dist/template/src/app/docs/llms.txt +2971 -0
- package/dist/template/src/app/docs/not-found.tsx +23 -0
- package/dist/template/src/app/docs/page.mdx +0 -3
- package/dist/template/src/app/docs/plugins/page.mdx +1793 -0
- package/dist/template/src/app/docs/pro/page.mdx +121 -0
- package/dist/template/src/app/docs/quickstart/page.mdx +93 -0
- package/dist/template/src/app/docs/scanner/content-type.md +599 -0
- package/dist/template/src/app/docs/scanner/index.md +212 -0
- package/dist/template/src/app/docs/scanner/page.mdx +307 -0
- package/dist/template/src/app/docs/scanner/python.md +469 -0
- package/dist/template/src/app/docs/scanner/python_parser.md +1056 -0
- package/dist/template/src/app/docs/scanner/rust.md +325 -0
- package/dist/template/src/app/docs/scanner/typescript.md +201 -0
- package/dist/template/src/app/error.tsx +3 -3
- package/dist/template/src/app/icon.tsx +29 -0
- package/dist/template/src/app/layout.tsx +42 -0
- package/dist/template/src/app/not-found.tsx +35 -0
- package/dist/template/src/app/page.tsx +62 -28
- package/dist/template/src/components/ai-chat.tsx +26 -21
- package/dist/template/src/components/breadcrumbs.tsx +46 -2
- package/dist/template/src/components/copy-button.tsx +17 -3
- package/dist/template/src/components/docs-layout.tsx +142 -8
- package/dist/template/src/components/feedback.tsx +4 -2
- package/dist/template/src/components/footer.tsx +42 -0
- package/dist/template/src/components/header.tsx +29 -5
- package/dist/template/src/components/mdx/accordion.tsx +7 -6
- package/dist/template/src/components/mdx/card.tsx +19 -7
- package/dist/template/src/components/mdx/code-block.tsx +17 -3
- package/dist/template/src/components/mdx/code-group.tsx +65 -18
- package/dist/template/src/components/mdx/code-playground.tsx +3 -0
- package/dist/template/src/components/mdx/go-playground.tsx +3 -0
- package/dist/template/src/components/mdx/highlighted-code.tsx +171 -76
- package/dist/template/src/components/mdx/python-playground.tsx +2 -0
- package/dist/template/src/components/mdx/tabs.tsx +74 -6
- package/dist/template/src/components/page-header.tsx +19 -0
- package/dist/template/src/components/scroll-to-top.tsx +33 -0
- package/dist/template/src/components/search-dialog.tsx +206 -52
- package/dist/template/src/components/sidebar.tsx +136 -77
- package/dist/template/src/components/table-of-contents.tsx +23 -7
- package/dist/template/src/lib/highlight.ts +90 -31
- package/dist/template/src/lib/search.ts +14 -4
- package/dist/template/src/lib/theme-utils.ts +140 -0
- package/dist/template/src/styles/globals.css +307 -166
- package/dist/template/src/types/remark-gfm.d.ts +2 -0
- package/dist/utils/files.d.ts +9 -0
- package/dist/utils/files.js +33 -0
- package/dist/utils/validation.d.ts +4 -0
- package/dist/utils/validation.js +38 -0
- package/package.json +1 -4
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# Python.ts
|
|
2
|
+
|
|
3
|
+
## Classes
|
|
4
|
+
|
|
5
|
+
### `PythonScanner`
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
class PythonScanner implements Scanner
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use this to scan Python source files and extract structured metadata (functions, classes, imports, etc.) by delegating parsing to a Python3 subprocess.
|
|
12
|
+
|
|
13
|
+
`PythonScanner` implements the `Scanner` interface and is the go-to handler for any `.py` file in an autodocs pipeline. It spawns a `python3` process running an internal parser script and returns a `ScanResult` with the extracted symbols and structure.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
### Properties
|
|
18
|
+
|
|
19
|
+
| Property | Type | Description |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `languages` | `string[]` | Always `['python']` — declares which languages this scanner handles |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### Methods
|
|
26
|
+
|
|
27
|
+
#### `canHandle(filePath)`
|
|
28
|
+
|
|
29
|
+
| Name | Type | Required | Description |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| `filePath` | `string` | ✅ | Path to the file to check |
|
|
32
|
+
|
|
33
|
+
**Returns:** `boolean` — `true` if the file ends with `.py`, `false` otherwise. Use this as a fast pre-check before calling `scanFile`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
#### `scanFile(filePath)`
|
|
38
|
+
|
|
39
|
+
| Name | Type | Required | Description |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| `filePath` | `string` | ✅ | Absolute or relative path to the `.py` file to parse |
|
|
42
|
+
|
|
43
|
+
**Returns:** `Promise<ScanResult>` — resolves with the parsed structure of the file (symbols, classes, functions, imports, etc.). Resolves (never rejects) even on parse failure — check the result for error fields.
|
|
44
|
+
|
|
45
|
+
> ⚠️ **Requires `python3`** to be available on the system `PATH`. The scanner spawns a subprocess for every file scanned — avoid calling it in tight loops without concurrency control.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
**Example:**
|
|
50
|
+
|
|
51
|
+
```typescript example.ts
|
|
52
|
+
import { spawn } from 'child_process'
|
|
53
|
+
import { writeFileSync, unlinkSync } from 'fs'
|
|
54
|
+
import { join } from 'path'
|
|
55
|
+
import { tmpdir } from 'os'
|
|
56
|
+
|
|
57
|
+
// --- Inline types (mirrors autodocs Scanner interface) ---
|
|
58
|
+
type ScanResult = {
|
|
59
|
+
filePath: string
|
|
60
|
+
language: string
|
|
61
|
+
symbols?: Array<{ name: string; kind: string; line: number }>
|
|
62
|
+
error?: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface Scanner {
|
|
66
|
+
languages: string[]
|
|
67
|
+
canHandle(filePath: string): boolean
|
|
68
|
+
scanFile(filePath: string): Promise<ScanResult>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --- Self-contained PythonScanner implementation ---
|
|
72
|
+
class PythonScanner implements Scanner {
|
|
73
|
+
languages = ['python']
|
|
74
|
+
|
|
75
|
+
canHandle(filePath: string): boolean {
|
|
76
|
+
return filePath.endsWith('.py')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async scanFile(filePath: string): Promise<ScanResult> {
|
|
80
|
+
// Inline parser script as a string — no external .py file needed
|
|
81
|
+
const parserScript = `
|
|
82
|
+
import ast, json, sys
|
|
83
|
+
|
|
84
|
+
def scan(path):
|
|
85
|
+
try:
|
|
86
|
+
with open(path, 'r') as f:
|
|
87
|
+
source = f.read()
|
|
88
|
+
tree = ast.parse(source)
|
|
89
|
+
symbols = []
|
|
90
|
+
for node in ast.walk(tree):
|
|
91
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
92
|
+
symbols.append({'name': node.name, 'kind': 'function', 'line': node.lineno})
|
|
93
|
+
elif isinstance(node, ast.ClassDef):
|
|
94
|
+
symbols.append({'name': node.name, 'kind': 'class', 'line': node.lineno})
|
|
95
|
+
print(json.dumps({'filePath': path, 'language': 'python', 'symbols': symbols}))
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print(json.dumps({'filePath': path, 'language': 'python', 'error': str(e)}))
|
|
98
|
+
|
|
99
|
+
scan(sys.argv[1])
|
|
100
|
+
`
|
|
101
|
+
// Write the inline parser to a temp file
|
|
102
|
+
const parserPath = join(tmpdir(), `_autodocs_parser_${process.pid}.py`)
|
|
103
|
+
writeFileSync(parserPath, parserScript)
|
|
104
|
+
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
let stdout = ''
|
|
107
|
+
let stderr = ''
|
|
108
|
+
|
|
109
|
+
const proc = spawn('python3', [parserPath, filePath], {
|
|
110
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
proc.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString() })
|
|
114
|
+
proc.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString() })
|
|
115
|
+
|
|
116
|
+
proc.on('close', (code: number) => {
|
|
117
|
+
try { unlinkSync(parserPath) } catch { /* cleanup best-effort */ }
|
|
118
|
+
|
|
119
|
+
if (code !== 0 || !stdout.trim()) {
|
|
120
|
+
resolve({
|
|
121
|
+
filePath,
|
|
122
|
+
language: 'python',
|
|
123
|
+
error: stderr.trim() || `python3 exited with code ${code}`,
|
|
124
|
+
})
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
resolve(JSON.parse(stdout.trim()) as ScanResult)
|
|
130
|
+
} catch {
|
|
131
|
+
resolve({ filePath, language: 'python', error: 'Failed to parse scanner output' })
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
proc.on('error', (err: Error) => {
|
|
136
|
+
resolve({ filePath, language: 'python', error: err.message })
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- Demo ---
|
|
143
|
+
async function main() {
|
|
144
|
+
const scanner = new PythonScanner()
|
|
145
|
+
|
|
146
|
+
// Write a sample Python file to scan
|
|
147
|
+
const samplePy = join(tmpdir(), 'sample_autodocs.py')
|
|
148
|
+
writeFileSync(samplePy, `
|
|
149
|
+
class DataProcessor:
|
|
150
|
+
def __init__(self, config):
|
|
151
|
+
self.config = config
|
|
152
|
+
|
|
153
|
+
def process(self, data):
|
|
154
|
+
return [x * 2 for x in data]
|
|
155
|
+
|
|
156
|
+
async def fetch_records(url: str) -> list:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
def helper():
|
|
160
|
+
return True
|
|
161
|
+
`)
|
|
162
|
+
|
|
163
|
+
console.log('canHandle(".py"):', scanner.canHandle(samplePy)) // true
|
|
164
|
+
console.log('canHandle(".js"):', scanner.canHandle('index.js')) // false
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const result = await scanner.scanFile(samplePy)
|
|
168
|
+
console.log('\nScan result:')
|
|
169
|
+
console.log(JSON.stringify(result, null, 2))
|
|
170
|
+
// Expected output:
|
|
171
|
+
// {
|
|
172
|
+
// "filePath": "/tmp/sample_autodocs.py",
|
|
173
|
+
// "language": "python",
|
|
174
|
+
// "symbols": [
|
|
175
|
+
// { "name": "DataProcessor", "kind": "class", "line": 2 },
|
|
176
|
+
// { "name": "__init__", "kind": "function", "line": 3 },
|
|
177
|
+
// { "name": "process", "kind": "function", "line": 6 },
|
|
178
|
+
// { "name": "fetch_records", "kind": "function", "line": 9 },
|
|
179
|
+
// { "name": "helper", "kind": "function", "line": 12 }
|
|
180
|
+
// ]
|
|
181
|
+
// }
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('Scan failed unexpectedly:', error)
|
|
184
|
+
} finally {
|
|
185
|
+
try { unlinkSync(samplePy) } catch { /* cleanup */ }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
main()
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Methods
|
|
193
|
+
|
|
194
|
+
#### `canHandle`
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
canHandle(filePath: string): boolean
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Use this to quickly check whether a given file path should be processed by the Python scanner before committing to a full file scan.
|
|
201
|
+
|
|
202
|
+
Call `canHandle` as a pre-flight check to filter out non-Python files in a pipeline, avoiding unnecessary scan overhead.
|
|
203
|
+
|
|
204
|
+
### Parameters
|
|
205
|
+
|
|
206
|
+
| Name | Type | Required | Description |
|
|
207
|
+
|------|------|----------|-------------|
|
|
208
|
+
| `filePath` | `string` | ✅ | The file path to evaluate. Can be a relative path, absolute path, or just a filename. |
|
|
209
|
+
|
|
210
|
+
### Returns
|
|
211
|
+
|
|
212
|
+
| Value | Condition |
|
|
213
|
+
|-------|-----------|
|
|
214
|
+
| `true` | The `filePath` ends with `.py` |
|
|
215
|
+
| `false` | The `filePath` ends with any other extension (or no extension) |
|
|
216
|
+
|
|
217
|
+
**Example:**
|
|
218
|
+
|
|
219
|
+
```typescript example.ts
|
|
220
|
+
// Inline implementation of PythonScanner.canHandle
|
|
221
|
+
class PythonScanner {
|
|
222
|
+
languages = ['python']
|
|
223
|
+
|
|
224
|
+
canHandle(filePath: string): boolean {
|
|
225
|
+
return filePath.endsWith('.py')
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Simulate a list of files discovered in a project directory
|
|
230
|
+
const discoveredFiles = [
|
|
231
|
+
'/project/src/main.py',
|
|
232
|
+
'/project/src/utils.py',
|
|
233
|
+
'/project/src/config.json',
|
|
234
|
+
'/project/src/README.md',
|
|
235
|
+
'/project/src/server.ts',
|
|
236
|
+
'/project/scripts/deploy.py',
|
|
237
|
+
'/project/scripts/build.sh',
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
const scanner = new PythonScanner()
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
// Filter only the files this scanner can handle
|
|
244
|
+
const pythonFiles = discoveredFiles.filter((file) => scanner.canHandle(file))
|
|
245
|
+
|
|
246
|
+
console.log('All discovered files:', discoveredFiles.length)
|
|
247
|
+
console.log('Python files to scan:', pythonFiles)
|
|
248
|
+
// Output:
|
|
249
|
+
// All discovered files: 7
|
|
250
|
+
// Python files to scan: [
|
|
251
|
+
// '/project/src/main.py',
|
|
252
|
+
// '/project/src/utils.py',
|
|
253
|
+
// '/project/scripts/deploy.py'
|
|
254
|
+
// ]
|
|
255
|
+
|
|
256
|
+
// Spot-check individual cases
|
|
257
|
+
console.log('\nSpot checks:')
|
|
258
|
+
console.log(scanner.canHandle('analysis.py')) // true
|
|
259
|
+
console.log(scanner.canHandle('analysis.py.bak')) // false — .bak wins
|
|
260
|
+
console.log(scanner.canHandle('script.ts')) // false
|
|
261
|
+
console.log(scanner.canHandle('')) // false
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error('Unexpected error during file filtering:', error)
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### `scanFile`
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
async scanFile(filePath: string): Promise<ScanResult>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Use this to scan a Python source file and extract structured metadata — functions, classes, imports, and other code elements — by running a Python parser subprocess and returning the results asynchronously.
|
|
274
|
+
|
|
275
|
+
This is the core method of `PythonScanner`. It delegates parsing to a `python3` child process, making it suitable for deep, AST-level analysis of `.py` files without reimplementing Python parsing in TypeScript.
|
|
276
|
+
|
|
277
|
+
## Parameters
|
|
278
|
+
|
|
279
|
+
| Name | Type | Required | Description |
|
|
280
|
+
|------|------|----------|-------------|
|
|
281
|
+
| `filePath` | `string` | ✅ | Absolute or relative path to the `.py` file to scan |
|
|
282
|
+
|
|
283
|
+
## Returns
|
|
284
|
+
|
|
285
|
+
Returns a `Promise<ScanResult>` that resolves when the Python subprocess completes.
|
|
286
|
+
|
|
287
|
+
| Scenario | Result |
|
|
288
|
+
|----------|--------|
|
|
289
|
+
| File parsed successfully | Resolves with a `ScanResult` containing extracted code metadata |
|
|
290
|
+
| File not found / parse error | Resolves (or rejects) with an error-state `ScanResult` or throws |
|
|
291
|
+
| Non-`.py` file passed | Undefined behavior — use `canHandle(filePath)` to guard first |
|
|
292
|
+
|
|
293
|
+
### `ScanResult` Shape (typical)
|
|
294
|
+
```ts
|
|
295
|
+
{
|
|
296
|
+
filePath: string // path that was scanned
|
|
297
|
+
language: string // e.g. "python"
|
|
298
|
+
functions: string[] // extracted function names
|
|
299
|
+
classes: string[] // extracted class names
|
|
300
|
+
imports: string[] // top-level imports
|
|
301
|
+
errors: string[] // any parse errors encountered
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Notes
|
|
306
|
+
- Internally spawns `python3` as a subprocess — ensure Python 3 is available in the runtime environment.
|
|
307
|
+
- Always call `canHandle(filePath)` before `scanFile()` to confirm the file is a `.py` file.
|
|
308
|
+
- The method is async; always `await` it or chain `.then()`.
|
|
309
|
+
|
|
310
|
+
**Example:**
|
|
311
|
+
|
|
312
|
+
```typescript example.ts
|
|
313
|
+
import { spawn } from 'child_process'
|
|
314
|
+
import { writeFileSync, unlinkSync } from 'fs'
|
|
315
|
+
import { tmpdir } from 'os'
|
|
316
|
+
import { join } from 'path'
|
|
317
|
+
|
|
318
|
+
// --- Inline types (mirrors the real ScanResult shape) ---
|
|
319
|
+
interface ScanResult {
|
|
320
|
+
filePath: string
|
|
321
|
+
language: string
|
|
322
|
+
functions: string[]
|
|
323
|
+
classes: string[]
|
|
324
|
+
imports: string[]
|
|
325
|
+
errors: string[]
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// --- Inline PythonScanner (self-contained, no external imports) ---
|
|
329
|
+
class PythonScanner {
|
|
330
|
+
canHandle(filePath: string): boolean {
|
|
331
|
+
return filePath.endsWith('.py')
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async scanFile(filePath: string): Promise<ScanResult> {
|
|
335
|
+
// Inline parser script: prints JSON to stdout
|
|
336
|
+
const inlineParserScript = `
|
|
337
|
+
import ast, json, sys
|
|
338
|
+
|
|
339
|
+
def scan(path):
|
|
340
|
+
try:
|
|
341
|
+
with open(path, 'r') as f:
|
|
342
|
+
source = f.read()
|
|
343
|
+
tree = ast.parse(source)
|
|
344
|
+
functions = [n.name for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
345
|
+
classes = [n.name for n in ast.walk(tree) if isinstance(n, ast.ClassDef)]
|
|
346
|
+
imports = []
|
|
347
|
+
for n in ast.walk(tree):
|
|
348
|
+
if isinstance(n, ast.Import):
|
|
349
|
+
imports += [a.name for a in n.names]
|
|
350
|
+
elif isinstance(n, ast.ImportFrom):
|
|
351
|
+
imports.append(n.module or '')
|
|
352
|
+
print(json.dumps({
|
|
353
|
+
"filePath": path,
|
|
354
|
+
"language": "python",
|
|
355
|
+
"functions": functions,
|
|
356
|
+
"classes": classes,
|
|
357
|
+
"imports": imports,
|
|
358
|
+
"errors": []
|
|
359
|
+
}))
|
|
360
|
+
except Exception as e:
|
|
361
|
+
print(json.dumps({
|
|
362
|
+
"filePath": path,
|
|
363
|
+
"language": "python",
|
|
364
|
+
"functions": [],
|
|
365
|
+
"classes": [],
|
|
366
|
+
"imports": [],
|
|
367
|
+
"errors": [str(e)]
|
|
368
|
+
}))
|
|
369
|
+
|
|
370
|
+
scan(sys.argv[1])
|
|
371
|
+
`
|
|
372
|
+
// Write the inline parser to a temp file
|
|
373
|
+
const parserPath = join(tmpdir(), `_scanner_parser_${Date.now()}.py`)
|
|
374
|
+
writeFileSync(parserPath, inlineParserScript)
|
|
375
|
+
|
|
376
|
+
return new Promise((resolve, reject) => {
|
|
377
|
+
const proc = spawn('python3', [parserPath, filePath], {
|
|
378
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
let stdout = ''
|
|
382
|
+
let stderr = ''
|
|
383
|
+
|
|
384
|
+
proc.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString() })
|
|
385
|
+
proc.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString() })
|
|
386
|
+
|
|
387
|
+
proc.on('close', (code: number) => {
|
|
388
|
+
try { unlinkSync(parserPath) } catch { /* cleanup best-effort */ }
|
|
389
|
+
|
|
390
|
+
if (code !== 0) {
|
|
391
|
+
return reject(new Error(`python3 exited with code ${code}: ${stderr}`))
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
const result: ScanResult = JSON.parse(stdout.trim())
|
|
395
|
+
resolve(result)
|
|
396
|
+
} catch {
|
|
397
|
+
reject(new Error(`Failed to parse scanner output: ${stdout}`))
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
proc.on('error', (err: Error) => {
|
|
402
|
+
try { unlinkSync(parserPath) } catch { /* cleanup best-effort */ }
|
|
403
|
+
reject(new Error(`Failed to spawn python3: ${err.message}`))
|
|
404
|
+
})
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// --- Sample .py file to scan ---
|
|
410
|
+
const samplePyPath = join(tmpdir(), `sample_${Date.now()}.py`)
|
|
411
|
+
writeFileSync(samplePyPath, `
|
|
412
|
+
import os
|
|
413
|
+
import json
|
|
414
|
+
from pathlib import Path
|
|
415
|
+
|
|
416
|
+
class DataProcessor:
|
|
417
|
+
def __init__(self, config: dict):
|
|
418
|
+
self.config = config
|
|
419
|
+
|
|
420
|
+
def process(self, data: list) -> list:
|
|
421
|
+
return [item.strip() for item in data]
|
|
422
|
+
|
|
423
|
+
def load_config(path: str) -> dict:
|
|
424
|
+
with open(path) as f:
|
|
425
|
+
return json.load(f)
|
|
426
|
+
|
|
427
|
+
def main():
|
|
428
|
+
config = load_config("config.json")
|
|
429
|
+
processor = DataProcessor(config)
|
|
430
|
+
print(processor.process(["hello ", " world"]))
|
|
431
|
+
`)
|
|
432
|
+
|
|
433
|
+
// --- Run the scanner ---
|
|
434
|
+
async function main() {
|
|
435
|
+
const scanner = new PythonScanner()
|
|
436
|
+
|
|
437
|
+
// Guard: only scan .py files
|
|
438
|
+
if (!scanner.canHandle(samplePyPath)) {
|
|
439
|
+
console.error('File is not a Python file — skipping.')
|
|
440
|
+
process.exit(1)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
console.log(`Scanning: ${samplePyPath}\n`)
|
|
445
|
+
const result = await scanner.scanFile(samplePyPath)
|
|
446
|
+
|
|
447
|
+
console.log('Scan complete!')
|
|
448
|
+
console.log('Language :', result.language)
|
|
449
|
+
console.log('Classes :', result.classes)
|
|
450
|
+
console.log('Functions:', result.functions)
|
|
451
|
+
console.log('Imports :', result.imports)
|
|
452
|
+
console.log('Errors :', result.errors)
|
|
453
|
+
|
|
454
|
+
// Expected output:
|
|
455
|
+
// Language : python
|
|
456
|
+
// Classes : [ 'DataProcessor' ]
|
|
457
|
+
// Functions: [ '__init__', 'process', 'load_config', 'main' ]
|
|
458
|
+
// Imports : [ 'os', 'json', 'pathlib' ]
|
|
459
|
+
// Errors : []
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.error('Scan failed:', error instanceof Error ? error.message : error)
|
|
462
|
+
} finally {
|
|
463
|
+
try { unlinkSync(samplePyPath) } catch { /* cleanup */ }
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
main()
|
|
468
|
+
```
|
|
469
|
+
|