@arcships/light-ocr-runtime 0.1.6 → 0.1.8
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/package.json +10 -8
- package/src/index.cjs +2 -2
- package/src/index.d.ts +11 -0
- package/src/index.mjs +1 -0
- package/src/load-native.cjs +139 -5
- package/src/metadata.cjs +1 -1
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
},
|
|
5
5
|
"description": "Model-free Node.js runtime for light-ocr",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": "
|
|
7
|
+
"node": ">=22.0.0"
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
@@ -28,12 +28,14 @@
|
|
|
28
28
|
"module": "./src/index.mjs",
|
|
29
29
|
"name": "@arcships/light-ocr-runtime",
|
|
30
30
|
"optionalDependencies": {
|
|
31
|
-
"@arcships/light-ocr-darwin-arm64": "0.5.
|
|
32
|
-
"@arcships/light-ocr-darwin-x64": "0.5.
|
|
33
|
-
"@arcships/light-ocr-linux-arm64-gnu": "0.5.
|
|
34
|
-
"@arcships/light-ocr-linux-
|
|
35
|
-
"@arcships/light-ocr-
|
|
36
|
-
"@arcships/light-ocr-
|
|
31
|
+
"@arcships/light-ocr-darwin-arm64": "0.5.8",
|
|
32
|
+
"@arcships/light-ocr-darwin-x64": "0.5.8",
|
|
33
|
+
"@arcships/light-ocr-linux-arm64-gnu": "0.5.8",
|
|
34
|
+
"@arcships/light-ocr-linux-arm64-musl": "0.5.8",
|
|
35
|
+
"@arcships/light-ocr-linux-x64-gnu": "0.5.8",
|
|
36
|
+
"@arcships/light-ocr-linux-x64-musl": "0.5.8",
|
|
37
|
+
"@arcships/light-ocr-win32-arm64": "0.5.8",
|
|
38
|
+
"@arcships/light-ocr-win32-x64": "0.5.8"
|
|
37
39
|
},
|
|
38
40
|
"publishConfig": {
|
|
39
41
|
"access": "public",
|
|
@@ -45,5 +47,5 @@
|
|
|
45
47
|
},
|
|
46
48
|
"type": "commonjs",
|
|
47
49
|
"types": "./src/index.d.ts",
|
|
48
|
-
"version": "0.1.
|
|
50
|
+
"version": "0.1.8"
|
|
49
51
|
}
|
package/src/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { loadNative } = require('./load-native.cjs');
|
|
3
|
+
const { loadNative, platformIdentity } = require('./load-native.cjs');
|
|
4
4
|
|
|
5
5
|
class OcrError extends Error {
|
|
6
6
|
constructor(code, message, detail) {
|
|
@@ -174,4 +174,4 @@ async function createEngine(options) {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
module.exports = { createEngine, OcrError, loadNative };
|
|
177
|
+
module.exports = { createEngine, OcrError, loadNative, platformIdentity };
|
package/src/index.d.ts
CHANGED
|
@@ -289,4 +289,15 @@ export interface DetectionResult {
|
|
|
289
289
|
readonly timingUs: TimingUs;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
/** Resolved host platform identity used to select the native package. */
|
|
293
|
+
export interface PlatformIdentity {
|
|
294
|
+
readonly id: string;
|
|
295
|
+
readonly os: string;
|
|
296
|
+
readonly architecture: string;
|
|
297
|
+
/** Present for Linux hosts; `"glibc"` or `"musl"`. */
|
|
298
|
+
readonly libc?: 'glibc' | 'musl';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function platformIdentity(): PlatformIdentity;
|
|
302
|
+
|
|
292
303
|
export function createEngine(options: CreateEngineOptions): Promise<OcrEngine>;
|
package/src/index.mjs
CHANGED
package/src/load-native.cjs
CHANGED
|
@@ -1,9 +1,101 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
3
4
|
const crypto = require('node:crypto');
|
|
4
5
|
const fs = require('node:fs');
|
|
5
6
|
const path = require('node:path');
|
|
6
7
|
|
|
8
|
+
// macOS-only integrity relaxation for downstream re-signing.
|
|
9
|
+
//
|
|
10
|
+
// macOS packaging pipelines re-sign the native payload with a Developer ID
|
|
11
|
+
// (or ad-hoc) identity, and osx-sign rewrites the LC_CODE_SIGNATURE blob of
|
|
12
|
+
// every Mach-O under Contents --force. That changes both file size and
|
|
13
|
+
// sha256, so the strict descriptor comparison alone rejects otherwise intact
|
|
14
|
+
// binaries. On macOS only, a Mach-O whose signature verifies AND whose
|
|
15
|
+
// signing identity matches the host process is accepted as an equivalent
|
|
16
|
+
// integrity proof: same TeamIdentifier as process.execPath, or both sides
|
|
17
|
+
// ad-hoc signed. Ad-hoc signatures are reproducible by anyone, so this
|
|
18
|
+
// relaxation stays macOS-only and documented; win32/linux payloads are never
|
|
19
|
+
// re-signed and keep the strict bytes+sha256 gate.
|
|
20
|
+
|
|
21
|
+
const MACHO_MAGIC = new Set([
|
|
22
|
+
0xfeedface, // 32-bit big-endian
|
|
23
|
+
0xcefaedfe, // 32-bit little-endian
|
|
24
|
+
0xfeedfacf, // 64-bit big-endian
|
|
25
|
+
0xcffaedfe, // 64-bit little-endian
|
|
26
|
+
0xcafebabe, // universal (fat) big-endian
|
|
27
|
+
0xbebafeca, // universal (fat) little-endian
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
function isMachO(filename) {
|
|
31
|
+
const fd = fs.openSync(filename, 'r');
|
|
32
|
+
try {
|
|
33
|
+
const magic = Buffer.allocUnsafe(4);
|
|
34
|
+
if (fs.readSync(fd, magic, 0, 4, 0) !== 4) return false;
|
|
35
|
+
return MACHO_MAGIC.has(magic.readUInt32BE(0));
|
|
36
|
+
} finally {
|
|
37
|
+
fs.closeSync(fd);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function codesignOutput(filename) {
|
|
42
|
+
const result = spawnSync('codesign', ['-dv', '--verbose=4', filename], {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
45
|
+
});
|
|
46
|
+
if (result.error || result.status !== 0) return null;
|
|
47
|
+
return `${result.stdout}\n${result.stderr}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Returns { teamIdentifier, adhoc } for a readable signature, or null when
|
|
51
|
+
// the file carries no readable signature (unsigned, corrupt, not Mach-O).
|
|
52
|
+
function codesignIdentity(filename) {
|
|
53
|
+
const output = codesignOutput(filename);
|
|
54
|
+
if (!output) return null;
|
|
55
|
+
if (/^Signature=adhoc\s*$/m.test(output)) {
|
|
56
|
+
return { teamIdentifier: null, adhoc: true };
|
|
57
|
+
}
|
|
58
|
+
const teamLine = output.match(/^TeamIdentifier=(.*)$/m);
|
|
59
|
+
const teamIdentifier = teamLine ? teamLine[1].trim() : '';
|
|
60
|
+
if (teamIdentifier && teamIdentifier !== 'not set') {
|
|
61
|
+
return { teamIdentifier, adhoc: false };
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function codesignVerifies(filename) {
|
|
67
|
+
const result = spawnSync('codesign', ['--verify', '--strict', filename], {
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
70
|
+
});
|
|
71
|
+
return !result.error && result.status === 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let hostCodesignIdentityCache;
|
|
75
|
+
|
|
76
|
+
function hostCodesignIdentity() {
|
|
77
|
+
if (hostCodesignIdentityCache === undefined) {
|
|
78
|
+
hostCodesignIdentityCache = codesignIdentity(process.execPath);
|
|
79
|
+
}
|
|
80
|
+
return hostCodesignIdentityCache;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Accept a re-signature only when it was made with the same identity as the
|
|
84
|
+
// host process: identical TeamIdentifier, or both sides ad-hoc signed.
|
|
85
|
+
function signerMatchesHost(artifactIdentity, host = hostCodesignIdentity()) {
|
|
86
|
+
if (!artifactIdentity || !host) return false;
|
|
87
|
+
if (artifactIdentity.adhoc && host.adhoc) return true;
|
|
88
|
+
return !artifactIdentity.adhoc && !host.adhoc &&
|
|
89
|
+
artifactIdentity.teamIdentifier === host.teamIdentifier;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function acceptsSignedMacOSMutation(filename) {
|
|
93
|
+
if (process.platform !== 'darwin') return false;
|
|
94
|
+
if (!isMachO(filename)) return false;
|
|
95
|
+
if (!codesignVerifies(filename)) return false;
|
|
96
|
+
return signerMatchesHost(codesignIdentity(filename));
|
|
97
|
+
}
|
|
98
|
+
|
|
7
99
|
function adapterError(code, message, detail, cause) {
|
|
8
100
|
const error = new Error(message, cause === undefined ? undefined : { cause });
|
|
9
101
|
error.name = 'OcrError';
|
|
@@ -12,6 +104,22 @@ function adapterError(code, message, detail, cause) {
|
|
|
12
104
|
return error;
|
|
13
105
|
}
|
|
14
106
|
|
|
107
|
+
// Detect musl Linux (Alpine and friends). Only reached when the host does not
|
|
108
|
+
// report glibcVersionRuntime through process.report. The musl dynamic linker
|
|
109
|
+
// path is the primary signal; the ldd output check covers hosts with a
|
|
110
|
+
// non-standard loader location.
|
|
111
|
+
function isMuslLinux() {
|
|
112
|
+
if (process.platform !== 'linux') return false;
|
|
113
|
+
const loader = process.arch === 'arm64'
|
|
114
|
+
? '/lib/ld-musl-aarch64.so.1'
|
|
115
|
+
: '/lib/ld-musl-x86_64.so.1';
|
|
116
|
+
if (fs.existsSync(loader)) return true;
|
|
117
|
+
const ldd = spawnSync('ldd', ['--version'], { encoding: 'utf8', timeout: 5000 });
|
|
118
|
+
if (ldd.error) return false;
|
|
119
|
+
const output = `${ldd.stdout || ''}${ldd.stderr || ''}`;
|
|
120
|
+
return output.toLowerCase().includes('musl');
|
|
121
|
+
}
|
|
122
|
+
|
|
15
123
|
function platformIdentity() {
|
|
16
124
|
const key = `${process.platform}-${process.arch}`;
|
|
17
125
|
const identities = {
|
|
@@ -25,9 +133,12 @@ function platformIdentity() {
|
|
|
25
133
|
if (report?.header?.glibcVersionRuntime) {
|
|
26
134
|
return { id: 'linux-x64', os: 'linux', architecture: 'x86_64', libc: 'glibc' };
|
|
27
135
|
}
|
|
136
|
+
if (isMuslLinux()) {
|
|
137
|
+
return { id: 'linux-x64-musl', os: 'linux', architecture: 'x86_64', libc: 'musl' };
|
|
138
|
+
}
|
|
28
139
|
throw adapterError(
|
|
29
140
|
'unsupported_platform',
|
|
30
|
-
'light-ocr currently supports Linux x64 with glibc only',
|
|
141
|
+
'light-ocr currently supports Linux x64 with glibc or musl only',
|
|
31
142
|
key,
|
|
32
143
|
);
|
|
33
144
|
}
|
|
@@ -36,9 +147,12 @@ function platformIdentity() {
|
|
|
36
147
|
if (report?.header?.glibcVersionRuntime) {
|
|
37
148
|
return { id: 'linux-arm64', os: 'linux', architecture: 'arm64', libc: 'glibc' };
|
|
38
149
|
}
|
|
150
|
+
if (isMuslLinux()) {
|
|
151
|
+
return { id: 'linux-arm64-musl', os: 'linux', architecture: 'arm64', libc: 'musl' };
|
|
152
|
+
}
|
|
39
153
|
throw adapterError(
|
|
40
154
|
'unsupported_platform',
|
|
41
|
-
'light-ocr currently supports Linux arm64 with glibc only',
|
|
155
|
+
'light-ocr currently supports Linux arm64 with glibc or musl only',
|
|
42
156
|
key,
|
|
43
157
|
);
|
|
44
158
|
}
|
|
@@ -57,6 +171,8 @@ function platformPackage() {
|
|
|
57
171
|
'windows-x64': '@arcships/light-ocr-win32-x64',
|
|
58
172
|
'linux-x64': '@arcships/light-ocr-linux-x64-gnu',
|
|
59
173
|
'linux-arm64': '@arcships/light-ocr-linux-arm64-gnu',
|
|
174
|
+
'linux-x64-musl': '@arcships/light-ocr-linux-x64-musl',
|
|
175
|
+
'linux-arm64-musl': '@arcships/light-ocr-linux-arm64-musl',
|
|
60
176
|
};
|
|
61
177
|
return packages[platformIdentity().id];
|
|
62
178
|
}
|
|
@@ -108,12 +224,23 @@ function verifyArtifact(root, artifact, field) {
|
|
|
108
224
|
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
109
225
|
throw adapterError('package_load_failed', 'Descriptor artifact is not a regular file', artifact.path);
|
|
110
226
|
}
|
|
111
|
-
if (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1
|
|
227
|
+
if (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1) {
|
|
112
228
|
throw adapterError('package_load_failed', 'Descriptor artifact byte count mismatch', artifact.path);
|
|
113
229
|
}
|
|
114
|
-
if (!/^[a-f0-9]{64}$/.test(artifact.sha256 || '')
|
|
230
|
+
if (!/^[a-f0-9]{64}$/.test(artifact.sha256 || '')) {
|
|
115
231
|
throw adapterError('package_load_failed', 'Descriptor artifact hash mismatch', artifact.path);
|
|
116
232
|
}
|
|
233
|
+
if (stats.size !== artifact.bytes) {
|
|
234
|
+
if (!acceptsSignedMacOSMutation(filename)) {
|
|
235
|
+
throw adapterError('package_load_failed', 'Descriptor artifact byte count mismatch', artifact.path);
|
|
236
|
+
}
|
|
237
|
+
return filename;
|
|
238
|
+
}
|
|
239
|
+
if (sha256(filename) !== artifact.sha256) {
|
|
240
|
+
if (!acceptsSignedMacOSMutation(filename)) {
|
|
241
|
+
throw adapterError('package_load_failed', 'Descriptor artifact hash mismatch', artifact.path);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
117
244
|
return filename;
|
|
118
245
|
}
|
|
119
246
|
|
|
@@ -496,4 +623,11 @@ function loadNative() {
|
|
|
496
623
|
}
|
|
497
624
|
}
|
|
498
625
|
|
|
499
|
-
|
|
626
|
+
const macOSSignature = Object.freeze({
|
|
627
|
+
isMachO,
|
|
628
|
+
codesignIdentity,
|
|
629
|
+
codesignVerifies,
|
|
630
|
+
signerMatchesHost,
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
module.exports = { loadNative, validateRuntimeDescriptor, macOSSignature, platformIdentity };
|
package/src/metadata.cjs
CHANGED