Skip to content

Commit f46314c

Browse files
mikolalysenkoclaude
andcommitted
feat: add TypeScript schema library to npm wrapper package
Add pared-down TypeScript library to npm/socket-patch/ for use by depscan. Includes schema validation (zod), git-compatible hashing, manifest operations, recovery, and package-json postinstall helpers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6e18576 commit f46314c

File tree

10 files changed

+828
-1
lines changed

10 files changed

+828
-1
lines changed

npm/socket-patch/package.json

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,51 @@
11
{
22
"name": "@socketsecurity/socket-patch",
33
"version": "1.6.3",
4-
"description": "CLI tool for applying security patches to dependencies",
4+
"description": "CLI tool and schema library for applying security patches to dependencies",
5+
"main": "dist/schema/manifest-schema.js",
6+
"types": "dist/schema/manifest-schema.d.ts",
57
"bin": {
68
"socket-patch": "bin/socket-patch"
79
},
10+
"exports": {
11+
"./schema": {
12+
"types": "./dist/schema/manifest-schema.d.ts",
13+
"import": "./dist/schema/manifest-schema.js",
14+
"require": "./dist/schema/manifest-schema.js"
15+
},
16+
"./hash": {
17+
"types": "./dist/hash/git-sha256.d.ts",
18+
"import": "./dist/hash/git-sha256.js",
19+
"require": "./dist/hash/git-sha256.js"
20+
},
21+
"./constants": {
22+
"types": "./dist/constants.d.ts",
23+
"import": "./dist/constants.js",
24+
"require": "./dist/constants.js"
25+
},
26+
"./manifest/operations": {
27+
"types": "./dist/manifest/operations.d.ts",
28+
"import": "./dist/manifest/operations.js",
29+
"require": "./dist/manifest/operations.js"
30+
},
31+
"./manifest/recovery": {
32+
"types": "./dist/manifest/recovery.d.ts",
33+
"import": "./dist/manifest/recovery.js",
34+
"require": "./dist/manifest/recovery.js"
35+
},
36+
"./package-json": {
37+
"types": "./dist/package-json/index.d.ts",
38+
"import": "./dist/package-json/index.js",
39+
"require": "./dist/package-json/index.js"
40+
}
41+
},
842
"publishConfig": {
943
"access": "public"
1044
},
45+
"scripts": {
46+
"build": "tsc",
47+
"test": "pnpm run build && node --test dist/**/*.test.js"
48+
},
1149
"keywords": [
1250
"security",
1351
"patch",
@@ -23,6 +61,13 @@
2361
"engines": {
2462
"node": ">=18.0.0"
2563
},
64+
"dependencies": {
65+
"zod": "^3.24.4"
66+
},
67+
"devDependencies": {
68+
"typescript": "^5.3.0",
69+
"@types/node": "^20.0.0"
70+
},
2671
"optionalDependencies": {
2772
"@socketsecurity/socket-patch-android-arm64": "1.6.3",
2873
"@socketsecurity/socket-patch-darwin-arm64": "1.6.3",

npm/socket-patch/src/constants.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Standard paths and constants used throughout the socket-patch system
3+
*/
4+
5+
/**
6+
* Default path to the patch manifest file
7+
*/
8+
export const DEFAULT_PATCH_MANIFEST_PATH = '.socket/manifest.json'
9+
10+
/**
11+
* Default folder for storing patched file blobs
12+
*/
13+
export const DEFAULT_BLOB_FOLDER = '.socket/blob'
14+
15+
/**
16+
* Default Socket directory
17+
*/
18+
export const DEFAULT_SOCKET_DIR = '.socket'
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import * as crypto from 'crypto'
2+
3+
/**
4+
* Compute Git-compatible SHA256 hash for a buffer
5+
* @param buffer - Buffer or Uint8Array to hash
6+
* @returns Git-compatible SHA256 hash (hex string)
7+
*/
8+
export function computeGitSHA256FromBuffer(
9+
buffer: Buffer | Uint8Array,
10+
): string {
11+
const gitHash = crypto.createHash('sha256')
12+
const header = `blob ${buffer.length}\0`
13+
gitHash.update(header)
14+
gitHash.update(buffer)
15+
return gitHash.digest('hex')
16+
}
17+
18+
/**
19+
* Compute Git-compatible SHA256 hash from an async iterable of chunks
20+
* @param size - Total size of the file in bytes
21+
* @param chunks - Async iterable of Buffer or Uint8Array chunks
22+
* @returns Git-compatible SHA256 hash (hex string)
23+
*/
24+
export async function computeGitSHA256FromChunks(
25+
size: number,
26+
chunks: AsyncIterable<Buffer | Uint8Array>,
27+
): Promise<string> {
28+
const gitHash = crypto.createHash('sha256')
29+
const header = `blob ${size}\0`
30+
gitHash.update(header)
31+
32+
for await (const chunk of chunks) {
33+
gitHash.update(chunk)
34+
}
35+
36+
return gitHash.digest('hex')
37+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import * as fs from 'fs/promises'
2+
import type { PatchManifest, PatchRecord } from '../schema/manifest-schema.js'
3+
import { PatchManifestSchema } from '../schema/manifest-schema.js'
4+
5+
/**
6+
* Get all blob hashes referenced by a manifest (both beforeHash and afterHash)
7+
* Used for garbage collection and validation
8+
*/
9+
export function getReferencedBlobs(manifest: PatchManifest): Set<string> {
10+
const blobs = new Set<string>()
11+
12+
for (const patchRecord of Object.values(manifest.patches)) {
13+
const record = patchRecord as PatchRecord
14+
for (const fileInfo of Object.values(record.files)) {
15+
blobs.add(fileInfo.beforeHash)
16+
blobs.add(fileInfo.afterHash)
17+
}
18+
}
19+
20+
return blobs
21+
}
22+
23+
/**
24+
* Get only afterHash blobs referenced by a manifest
25+
* Used for apply operations - we only need the patched file content, not the original
26+
* This saves disk space since beforeHash blobs are not needed for applying patches
27+
*/
28+
export function getAfterHashBlobs(manifest: PatchManifest): Set<string> {
29+
const blobs = new Set<string>()
30+
31+
for (const patchRecord of Object.values(manifest.patches)) {
32+
const record = patchRecord as PatchRecord
33+
for (const fileInfo of Object.values(record.files)) {
34+
blobs.add(fileInfo.afterHash)
35+
}
36+
}
37+
38+
return blobs
39+
}
40+
41+
/**
42+
* Get only beforeHash blobs referenced by a manifest
43+
* Used for rollback operations - we need the original file content to restore
44+
*/
45+
export function getBeforeHashBlobs(manifest: PatchManifest): Set<string> {
46+
const blobs = new Set<string>()
47+
48+
for (const patchRecord of Object.values(manifest.patches)) {
49+
const record = patchRecord as PatchRecord
50+
for (const fileInfo of Object.values(record.files)) {
51+
blobs.add(fileInfo.beforeHash)
52+
}
53+
}
54+
55+
return blobs
56+
}
57+
58+
/**
59+
* Calculate differences between two manifests
60+
*/
61+
export interface ManifestDiff {
62+
added: Set<string> // PURLs
63+
removed: Set<string>
64+
modified: Set<string>
65+
}
66+
67+
export function diffManifests(
68+
oldManifest: PatchManifest,
69+
newManifest: PatchManifest,
70+
): ManifestDiff {
71+
const oldPurls = new Set(Object.keys(oldManifest.patches))
72+
const newPurls = new Set(Object.keys(newManifest.patches))
73+
74+
const added = new Set<string>()
75+
const removed = new Set<string>()
76+
const modified = new Set<string>()
77+
78+
// Find added and modified
79+
for (const purl of newPurls) {
80+
if (!oldPurls.has(purl)) {
81+
added.add(purl)
82+
} else {
83+
const oldPatch = oldManifest.patches[purl] as PatchRecord
84+
const newPatch = newManifest.patches[purl] as PatchRecord
85+
if (oldPatch.uuid !== newPatch.uuid) {
86+
modified.add(purl)
87+
}
88+
}
89+
}
90+
91+
// Find removed
92+
for (const purl of oldPurls) {
93+
if (!newPurls.has(purl)) {
94+
removed.add(purl)
95+
}
96+
}
97+
98+
return { added, removed, modified }
99+
}
100+
101+
/**
102+
* Validate a parsed manifest object
103+
*/
104+
export function validateManifest(parsed: unknown): {
105+
success: boolean
106+
manifest?: PatchManifest
107+
error?: string
108+
} {
109+
const result = PatchManifestSchema.safeParse(parsed)
110+
if (result.success) {
111+
return { success: true, manifest: result.data }
112+
}
113+
return {
114+
success: false,
115+
error: result.error.message,
116+
}
117+
}
118+
119+
/**
120+
* Read and parse a manifest from the filesystem
121+
*/
122+
export async function readManifest(path: string): Promise<PatchManifest | null> {
123+
try {
124+
const content = await fs.readFile(path, 'utf-8')
125+
const parsed = JSON.parse(content)
126+
const result = validateManifest(parsed)
127+
return result.success ? result.manifest! : null
128+
} catch {
129+
return null
130+
}
131+
}
132+
133+
/**
134+
* Write a manifest to the filesystem
135+
*/
136+
export async function writeManifest(
137+
path: string,
138+
manifest: PatchManifest,
139+
): Promise<void> {
140+
const content = JSON.stringify(manifest, null, 2)
141+
await fs.writeFile(path, content, 'utf-8')
142+
}

0 commit comments

Comments
 (0)