@gpzhang2001/sharpkit-analysis 0.2.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/LICENSE +201 -0
- package/README.md +24 -0
- package/THIRD_PARTY_NOTICES.md +48 -0
- package/lib/index.d.ts +125 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1231 -0
- package/lib/index.js.map +1 -0
- package/package.json +48 -0
- package/src/index.ts +815 -0
- package/src/stores.ts +97 -0
package/src/stores.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes
|
|
3
|
+
* (temp-in-dir + rename), hydrate-on-start, per-store in-memory maps.
|
|
4
|
+
* One instance per scan, owned by the analysis service.
|
|
5
|
+
* @module @gpzhang2001/sharpkit-analysis/stores
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
9
|
+
import { dirname, join } from 'node:path'
|
|
10
|
+
|
|
11
|
+
/** A JSON file mirror with atomic write and hydrate. */
|
|
12
|
+
export class JsonMirrorStore {
|
|
13
|
+
private readonly path: string | null
|
|
14
|
+
private readonly map = new Map<string, Record<string, unknown>>()
|
|
15
|
+
|
|
16
|
+
constructor(path: string | null) {
|
|
17
|
+
this.path = path
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */
|
|
21
|
+
async hydrate(): Promise<void> {
|
|
22
|
+
if (this.path === null) return
|
|
23
|
+
try {
|
|
24
|
+
const raw = await readFile(this.path, 'utf8')
|
|
25
|
+
const parsed = JSON.parse(raw) as unknown
|
|
26
|
+
if (typeof parsed !== 'object' || parsed === null) return
|
|
27
|
+
for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {
|
|
28
|
+
if (typeof value === 'object' && value !== null) this.map.set(key, value as Record<string, unknown>)
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Missing or unreadable mirror: start empty.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Persist the whole map atomically (strix `_persist_locked`). */
|
|
36
|
+
async persist(): Promise<void> {
|
|
37
|
+
if (this.path === null) return
|
|
38
|
+
const payload = JSON.stringify(Object.fromEntries(this.map), null, 2)
|
|
39
|
+
await mkdir(dirname(this.path), { recursive: true })
|
|
40
|
+
const temp = `${dirname(this.path)}/.${join('', this.path.split('/').pop() ?? 'store')}.${process.pid}.tmp`
|
|
41
|
+
await writeFile(temp, payload, 'utf8')
|
|
42
|
+
await rename(temp, this.path)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get(key: string): Record<string, unknown> | undefined {
|
|
46
|
+
return this.map.get(key)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set(key: string, value: Record<string, unknown>): void {
|
|
50
|
+
this.map.set(key, value)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
delete(key: string): boolean {
|
|
54
|
+
return this.map.delete(key)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
values(): Record<string, unknown>[] {
|
|
58
|
+
return [...this.map.values()]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get size(): number {
|
|
62
|
+
return this.map.size
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Generate a 6-hex id with bounded collision retries (strix parity). */
|
|
67
|
+
export function generateId(existing: ReadonlySet<string>): string | null {
|
|
68
|
+
for (let attempt = 0; attempt < 1024; attempt++) {
|
|
69
|
+
const id = Math.random().toString(16).slice(2, 8).padEnd(6, '0')
|
|
70
|
+
if (!existing.has(id)) return id
|
|
71
|
+
}
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Normalize a remote target to its identity (threat_model/tools.py
|
|
77
|
+
* `_normalize_remote_target` + `_normalize_git_remote`): scp-style and
|
|
78
|
+
* scheme'd git remotes map to https URLs, `.git` stripped, trailing slash
|
|
79
|
+
* stripped, host lowercased with default ports collapsed.
|
|
80
|
+
* @param target - the raw target string.
|
|
81
|
+
*/
|
|
82
|
+
export function normalizeTargetIdentity(target: string): string {
|
|
83
|
+
const trimmed = target.trim()
|
|
84
|
+
const scp = /^git@([^:]+):(.+?)(?:\.git)?$/.exec(trimmed)
|
|
85
|
+
if (scp !== null) return `https://${scp[1]?.toLowerCase()}/${scp[2]}`
|
|
86
|
+
const withScheme = /^([a-z][a-z0-9+.-]*):\/\/([^/?#]+)([^#]*)/i.exec(trimmed)
|
|
87
|
+
if (withScheme !== null) {
|
|
88
|
+
const scheme = withScheme[1] ?? ''
|
|
89
|
+
const authority = withScheme[2] ?? ''
|
|
90
|
+
const path = withScheme[3] ?? ''
|
|
91
|
+
const defaultPort = scheme.toLowerCase() === 'https' ? '443' : scheme.toLowerCase() === 'http' ? '80' : null
|
|
92
|
+
let host = authority.toLowerCase()
|
|
93
|
+
if (defaultPort !== null && host.endsWith(`:${defaultPort}`)) host = host.slice(0, -defaultPort.length - 1)
|
|
94
|
+
return `https://${host}${path.replace(/\/$/, '')}`.replace(/\.git$/, '')
|
|
95
|
+
}
|
|
96
|
+
return trimmed.replace(/\/$/, '').replace(/\.git$/, '')
|
|
97
|
+
}
|