@serveryyswys/node.operit 0.0.0-stage → 0.1.2
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/BUILTINS.json +233 -0
- package/DESIGN.md +513 -0
- package/LICENSE +674 -0
- package/README.md +301 -2
- package/dist/assert/strict.d.ts +2 -0
- package/dist/assert/strict.js +7 -0
- package/dist/assert.d.ts +3 -0
- package/dist/assert.js +483 -0
- package/dist/buffer.d.ts +60 -0
- package/dist/buffer.js +524 -0
- package/dist/child_process.d.ts +56 -0
- package/dist/child_process.js +371 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +19 -0
- package/dist/crypto.d.ts +76 -0
- package/dist/crypto.js +312 -0
- package/dist/dns/promises.d.ts +34 -0
- package/dist/dns/promises.js +190 -0
- package/dist/dns.d.ts +88 -0
- package/dist/dns.js +259 -0
- package/dist/events.d.ts +40 -0
- package/dist/events.js +237 -0
- package/dist/fs/promises.d.ts +159 -0
- package/dist/fs/promises.js +129 -0
- package/dist/fs.d.ts +221 -0
- package/dist/fs.js +944 -0
- package/dist/http.d.ts +92 -0
- package/dist/http.js +319 -0
- package/dist/https.d.ts +296 -0
- package/dist/https.js +50 -0
- package/dist/index.d.ts +2422 -0
- package/dist/index.js +57 -0
- package/dist/lib/bridge.d.ts +60 -0
- package/dist/lib/bridge.js +205 -0
- package/dist/lib/bytes.d.ts +20 -0
- package/dist/lib/bytes.js +239 -0
- package/dist/lib/child-process-types.d.ts +45 -0
- package/dist/lib/child-process-types.js +2 -0
- package/dist/lib/cwd.d.ts +2 -0
- package/dist/lib/cwd.js +32 -0
- package/dist/lib/deep-equal.d.ts +3 -0
- package/dist/lib/deep-equal.js +223 -0
- package/dist/lib/deflate.d.ts +2 -0
- package/dist/lib/deflate.js +234 -0
- package/dist/lib/dns-driver.d.ts +14 -0
- package/dist/lib/dns-driver.js +85 -0
- package/dist/lib/dns-types.d.ts +22 -0
- package/dist/lib/dns-types.js +2 -0
- package/dist/lib/errors.d.ts +48 -0
- package/dist/lib/errors.js +424 -0
- package/dist/lib/fs-driver.d.ts +70 -0
- package/dist/lib/fs-driver.js +364 -0
- package/dist/lib/fs-env.d.ts +30 -0
- package/dist/lib/fs-env.js +157 -0
- package/dist/lib/fs-types.d.ts +19 -0
- package/dist/lib/fs-types.js +2 -0
- package/dist/lib/hashes.d.ts +27 -0
- package/dist/lib/hashes.js +432 -0
- package/dist/lib/http-driver.d.ts +30 -0
- package/dist/lib/http-driver.js +72 -0
- package/dist/lib/http-types.d.ts +18 -0
- package/dist/lib/http-types.js +2 -0
- package/dist/lib/inflate.d.ts +6 -0
- package/dist/lib/inflate.js +284 -0
- package/dist/lib/path-types.d.ts +37 -0
- package/dist/lib/path-types.js +2 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +106 -0
- package/dist/lib/perf-hooks-types.d.ts +18 -0
- package/dist/lib/perf-hooks-types.js +2 -0
- package/dist/lib/process-driver.d.ts +31 -0
- package/dist/lib/process-driver.js +102 -0
- package/dist/lib/punycode.d.ts +8 -0
- package/dist/lib/punycode.js +179 -0
- package/dist/lib/stream-types.d.ts +29 -0
- package/dist/lib/stream-types.js +2 -0
- package/dist/lib/timers-types.d.ts +18 -0
- package/dist/lib/timers-types.js +2 -0
- package/dist/lib/url-search-params.d.ts +26 -0
- package/dist/lib/url-search-params.js +281 -0
- package/dist/lib/url-types.d.ts +44 -0
- package/dist/lib/url-types.js +2 -0
- package/dist/lib/url-whatwg.d.ts +78 -0
- package/dist/lib/url-whatwg.js +857 -0
- package/dist/lib/zlib-types.d.ts +12 -0
- package/dist/lib/zlib-types.js +2 -0
- package/dist/module.d.ts +8 -0
- package/dist/module.js +72 -0
- package/dist/os.d.ts +80 -0
- package/dist/os.js +247 -0
- package/dist/path/posix.d.ts +2 -0
- package/dist/path/posix.js +7 -0
- package/dist/path/win32.d.ts +2 -0
- package/dist/path/win32.js +10 -0
- package/dist/path.d.ts +3 -0
- package/dist/path.js +1222 -0
- package/dist/perf_hooks.d.ts +36 -0
- package/dist/perf_hooks.js +144 -0
- package/dist/process.d.ts +69 -0
- package/dist/process.js +230 -0
- package/dist/querystring.d.ts +15 -0
- package/dist/querystring.js +209 -0
- package/dist/stream/promises.d.ts +5 -0
- package/dist/stream/promises.js +10 -0
- package/dist/stream.d.ts +84 -0
- package/dist/stream.js +875 -0
- package/dist/string_decoder.d.ts +15 -0
- package/dist/string_decoder.js +237 -0
- package/dist/timers/promises.d.ts +19 -0
- package/dist/timers/promises.js +74 -0
- package/dist/timers.d.ts +21 -0
- package/dist/timers.js +41 -0
- package/dist/tty.d.ts +9 -0
- package/dist/tty.js +25 -0
- package/dist/unsupported.d.ts +12 -0
- package/dist/unsupported.js +17 -0
- package/dist/url.d.ts +28 -0
- package/dist/url.js +486 -0
- package/dist/util.d.ts +74 -0
- package/dist/util.js +515 -0
- package/dist/zlib.d.ts +88 -0
- package/dist/zlib.js +371 -0
- package/package.json +29 -4
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.setDriver = setDriver;
|
|
37
|
+
exports.getDriver = getDriver;
|
|
38
|
+
exports.createJavaDriver = createJavaDriver;
|
|
39
|
+
/**
|
|
40
|
+
* fs 的宿主驱动层:把"文件系统原语"与"Node 语义"分开。
|
|
41
|
+
*
|
|
42
|
+
* fs.ts 只做 Node 语义(选项解析、错误码、返回值形状),所有真正的 I/O 都走本接口。
|
|
43
|
+
* 这样:
|
|
44
|
+
* - 桌面测试可以注入一个基于 Node fs 的驱动,从而**把 fs.ts 的语义与 Node 对拍**;
|
|
45
|
+
* - Java 相关的假设全部收敛在 createJavaDriver() 里,真机验证只需针对这一处。
|
|
46
|
+
*
|
|
47
|
+
* 驱动抛出的错误是 bridge 的中间态 Java 错误(带 code),由 fs.ts 补 syscall / path。
|
|
48
|
+
*/
|
|
49
|
+
const bridge = __importStar(require("./bridge"));
|
|
50
|
+
const errors_1 = require("./errors");
|
|
51
|
+
const bytesCodec = __importStar(require("./bytes"));
|
|
52
|
+
let driver = null;
|
|
53
|
+
/** 宿主注入点:桌面测试用,传 null 恢复为 Operit Java 驱动。 */
|
|
54
|
+
function setDriver(next) {
|
|
55
|
+
driver = next;
|
|
56
|
+
}
|
|
57
|
+
function getDriver() {
|
|
58
|
+
if (driver === null) {
|
|
59
|
+
driver = createJavaDriver();
|
|
60
|
+
}
|
|
61
|
+
return driver;
|
|
62
|
+
}
|
|
63
|
+
// ------------------------------------------------------------------ Java 实现
|
|
64
|
+
const DEFAULT_MODE_FILE = 0o666;
|
|
65
|
+
const DEFAULT_MODE_DIR = 0o777;
|
|
66
|
+
/**
|
|
67
|
+
* 基于 java.io.File / java.nio.file.Files / java.io.RandomAccessFile 的驱动。
|
|
68
|
+
*
|
|
69
|
+
* 通道选择(对应 DESIGN.md §6.3):
|
|
70
|
+
* - 读:Files.readAllBytes(Path) 返回 byte[],bridge 会展开成 JSON 数字数组,直接用
|
|
71
|
+
* - 写:RandomAccessFile.writeBytes(String) 写每个字符的低 8 位 —— 正好是 latin1 通道,
|
|
72
|
+
* 避免了"把 JS 数组当 byte[] 参数传进去"这类未验证行为
|
|
73
|
+
*
|
|
74
|
+
* 已知近似(真机验证时要确认):
|
|
75
|
+
* - atime / ctime / birthtime 都取 lastModified
|
|
76
|
+
* - mode 按类型给默认值,不反映真实权限位
|
|
77
|
+
* - readdir 不区分符号链接
|
|
78
|
+
*/
|
|
79
|
+
function createJavaDriver() {
|
|
80
|
+
function fileOf(target) {
|
|
81
|
+
return bridge.newInstance('java.io.File', [target]);
|
|
82
|
+
}
|
|
83
|
+
function pathOf(target) {
|
|
84
|
+
return bridge.callInstance(fileOf(target), 'toPath', []);
|
|
85
|
+
}
|
|
86
|
+
function callFile(target, method, args = []) {
|
|
87
|
+
return bridge.callInstance(fileOf(target), method, args);
|
|
88
|
+
}
|
|
89
|
+
function readAllBytes(target) {
|
|
90
|
+
const raw = bridge.callStatic('java.nio.file.Files', 'readAllBytes', [pathOf(target)]);
|
|
91
|
+
if (!Array.isArray(raw)) {
|
|
92
|
+
throw (0, errors_1.createError)('EIO', 'read');
|
|
93
|
+
}
|
|
94
|
+
const out = new Uint8Array(raw.length);
|
|
95
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
96
|
+
out[i] = raw[i] & 255;
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
function writeAllBytes(target, data, append, exclusive) {
|
|
101
|
+
if (exclusive && callFile(target, 'exists')) {
|
|
102
|
+
throw (0, errors_1.javaError)('java.nio.file.FileAlreadyExistsException: ' + target);
|
|
103
|
+
}
|
|
104
|
+
const handle = bridge.newInstance('java.io.RandomAccessFile', [target, 'rw']);
|
|
105
|
+
try {
|
|
106
|
+
if (append) {
|
|
107
|
+
bridge.callInstance(handle, 'seek', [bridge.callInstance(handle, 'length', [])]);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
bridge.callInstance(handle, 'setLength', [0]);
|
|
111
|
+
}
|
|
112
|
+
bridge.callInstance(handle, 'writeBytes', [bytesCodec.bytesToLatin1(data)]);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
bridge.callInstance(handle, 'close', []);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function describe(target, followLinks) {
|
|
119
|
+
if (!callFile(target, 'exists')) {
|
|
120
|
+
throw (0, errors_1.javaError)('java.io.FileNotFoundException: ' + target);
|
|
121
|
+
}
|
|
122
|
+
const isLink = followLinks ? false : bridge.normalizeBoolean(bridge.callStatic('java.nio.file.Files', 'isSymbolicLink', [pathOf(target)]));
|
|
123
|
+
const isDirectory = bridge.normalizeBoolean(callFile(target, 'isDirectory'));
|
|
124
|
+
const isFile = bridge.normalizeBoolean(callFile(target, 'isFile'));
|
|
125
|
+
const size = Number(callFile(target, 'length')) || 0;
|
|
126
|
+
const modified = Number(callFile(target, 'lastModified')) || 0;
|
|
127
|
+
return {
|
|
128
|
+
isFile: isFile,
|
|
129
|
+
isDirectory: isDirectory,
|
|
130
|
+
isSymbolicLink: isLink,
|
|
131
|
+
size: isFile ? size : 0,
|
|
132
|
+
mode: isDirectory ? DEFAULT_MODE_DIR : DEFAULT_MODE_FILE,
|
|
133
|
+
atimeMs: modified,
|
|
134
|
+
mtimeMs: modified,
|
|
135
|
+
ctimeMs: modified,
|
|
136
|
+
birthtimeMs: modified
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function removeRecursive(target) {
|
|
140
|
+
const handle = fileOf(target);
|
|
141
|
+
if (!bridge.normalizeBoolean(bridge.callInstance(handle, 'exists', []))) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (bridge.normalizeBoolean(bridge.callInstance(handle, 'isDirectory', []))) {
|
|
145
|
+
const children = bridge.callInstance(handle, 'listFiles', []);
|
|
146
|
+
if (Array.isArray(children)) {
|
|
147
|
+
for (let i = 0; i < children.length; i += 1) {
|
|
148
|
+
removeRecursive(String(bridge.callInstance(children[i], 'getAbsolutePath', [])));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
bridge.callInstance(handle, 'delete', []);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* fd 句柄基于 java.io.RandomAccessFile:
|
|
156
|
+
* - 读:FileChannel.read(ByteBuffer, position) —— ByteBuffer.array() 能把数据**返回**出来,
|
|
157
|
+
* 避免"把 byte[] 当参数传进去、靠 Java 原地修改"这种在 bridge 上不可见的行为;
|
|
158
|
+
* - 写:seek + writeBytes(String),写每个字符的低 8 位,正好是 latin1 通道。
|
|
159
|
+
*/
|
|
160
|
+
function openHandle(target, options) {
|
|
161
|
+
if (options.exclusive && bridge.normalizeBoolean(callFile(target, 'exists'))) {
|
|
162
|
+
throw (0, errors_1.javaError)('java.nio.file.FileAlreadyExistsException: ' + target);
|
|
163
|
+
}
|
|
164
|
+
const handle = bridge.newInstance('java.io.RandomAccessFile', [target, options.writable ? 'rw' : 'r']);
|
|
165
|
+
const channel = bridge.callInstance(handle, 'getChannel', []);
|
|
166
|
+
if (options.truncate) {
|
|
167
|
+
bridge.callInstance(handle, 'setLength', [0]);
|
|
168
|
+
}
|
|
169
|
+
if (options.append) {
|
|
170
|
+
bridge.callInstance(handle, 'seek', [bridge.callInstance(handle, 'length', [])]);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
read(position, length) {
|
|
174
|
+
if (length <= 0) {
|
|
175
|
+
return new Uint8Array(0);
|
|
176
|
+
}
|
|
177
|
+
const buffer = bridge.callStatic('java.nio.ByteBuffer', 'allocate', [length]);
|
|
178
|
+
const start = position === null
|
|
179
|
+
? Number(bridge.callInstance(handle, 'getFilePointer', []))
|
|
180
|
+
: position;
|
|
181
|
+
const count = Number(bridge.callInstance(channel, 'read', [buffer, start]));
|
|
182
|
+
if (position === null) {
|
|
183
|
+
bridge.callInstance(handle, 'seek', [start + (isFinite(count) ? count : 0)]);
|
|
184
|
+
}
|
|
185
|
+
if (!isFinite(count) || count <= 0) {
|
|
186
|
+
return new Uint8Array(0);
|
|
187
|
+
}
|
|
188
|
+
const raw = bridge.callInstance(buffer, 'array', []);
|
|
189
|
+
if (!Array.isArray(raw)) {
|
|
190
|
+
return new Uint8Array(0);
|
|
191
|
+
}
|
|
192
|
+
const usable = Math.min(count, raw.length);
|
|
193
|
+
const out = new Uint8Array(usable);
|
|
194
|
+
for (let i = 0; i < usable; i += 1) {
|
|
195
|
+
out[i] = raw[i] & 255;
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
},
|
|
199
|
+
write(position, data) {
|
|
200
|
+
const pointer = Number(bridge.callInstance(handle, 'getFilePointer', []));
|
|
201
|
+
const start = position === null ? pointer : position;
|
|
202
|
+
bridge.callInstance(handle, 'seek', [start]);
|
|
203
|
+
bridge.callInstance(handle, 'writeBytes', [bytesCodec.bytesToLatin1(data)]);
|
|
204
|
+
const advanced = Number(bridge.callInstance(handle, 'getFilePointer', []));
|
|
205
|
+
if (position !== null) {
|
|
206
|
+
// Node 语义:带 position 的写不改变文件位置
|
|
207
|
+
bridge.callInstance(handle, 'seek', [pointer]);
|
|
208
|
+
}
|
|
209
|
+
return advanced - start;
|
|
210
|
+
},
|
|
211
|
+
size() {
|
|
212
|
+
return Number(bridge.callInstance(handle, 'length', [])) || 0;
|
|
213
|
+
},
|
|
214
|
+
truncate(length) {
|
|
215
|
+
bridge.callInstance(handle, 'setLength', [length]);
|
|
216
|
+
},
|
|
217
|
+
sync() {
|
|
218
|
+
bridge.callInstance(bridge.callInstance(handle, 'getFD', []), 'sync', []);
|
|
219
|
+
},
|
|
220
|
+
close() {
|
|
221
|
+
bridge.callInstance(handle, 'close', []);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
openHandle: openHandle,
|
|
227
|
+
stat(target, followLinks) {
|
|
228
|
+
return describe(target, followLinks);
|
|
229
|
+
},
|
|
230
|
+
exists(target) {
|
|
231
|
+
return bridge.normalizeBoolean(callFile(target, 'exists'));
|
|
232
|
+
},
|
|
233
|
+
readdir(target) {
|
|
234
|
+
if (!bridge.normalizeBoolean(callFile(target, 'isDirectory'))) {
|
|
235
|
+
if (callFile(target, 'exists')) {
|
|
236
|
+
throw (0, errors_1.javaError)('java.nio.file.NotDirectoryException: ' + target);
|
|
237
|
+
}
|
|
238
|
+
throw (0, errors_1.javaError)('java.io.FileNotFoundException: ' + target);
|
|
239
|
+
}
|
|
240
|
+
const children = callFile(target, 'listFiles');
|
|
241
|
+
if (!Array.isArray(children)) {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
const out = [];
|
|
245
|
+
for (let i = 0; i < children.length; i += 1) {
|
|
246
|
+
const child = children[i];
|
|
247
|
+
out.push({
|
|
248
|
+
name: String(bridge.callInstance(child, 'getName', [])),
|
|
249
|
+
isFile: bridge.normalizeBoolean(bridge.callInstance(child, 'isFile', [])),
|
|
250
|
+
isDirectory: bridge.normalizeBoolean(bridge.callInstance(child, 'isDirectory', [])),
|
|
251
|
+
isSymbolicLink: false
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
},
|
|
256
|
+
readFileBytes(target) {
|
|
257
|
+
return readAllBytes(target);
|
|
258
|
+
},
|
|
259
|
+
writeFileBytes(target, data, append, exclusive) {
|
|
260
|
+
writeAllBytes(target, data, append, exclusive);
|
|
261
|
+
},
|
|
262
|
+
mkdir(target, recursive) {
|
|
263
|
+
if (recursive) {
|
|
264
|
+
const parent = bridge.callInstance(fileOf(target), 'getParent', []);
|
|
265
|
+
const created = bridge.normalizeBoolean(callFile(target, 'mkdirs'));
|
|
266
|
+
if (!created && !callFile(target, 'isDirectory')) {
|
|
267
|
+
throw (0, errors_1.javaError)('java.io.IOException: Cannot create directory ' + target);
|
|
268
|
+
}
|
|
269
|
+
return created ? (typeof parent === 'string' ? target : target) : undefined;
|
|
270
|
+
}
|
|
271
|
+
const ok = bridge.normalizeBoolean(callFile(target, 'mkdir'));
|
|
272
|
+
if (!ok) {
|
|
273
|
+
if (callFile(target, 'exists')) {
|
|
274
|
+
throw (0, errors_1.javaError)('java.nio.file.FileAlreadyExistsException: ' + target);
|
|
275
|
+
}
|
|
276
|
+
throw (0, errors_1.javaError)('java.io.IOException: Cannot create directory ' + target);
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
},
|
|
280
|
+
remove(target, recursive, force) {
|
|
281
|
+
const handle = fileOf(target);
|
|
282
|
+
if (!bridge.normalizeBoolean(bridge.callInstance(handle, 'exists', []))) {
|
|
283
|
+
if (force) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
throw (0, errors_1.javaError)('java.io.FileNotFoundException: ' + target);
|
|
287
|
+
}
|
|
288
|
+
const isDirectory = bridge.normalizeBoolean(bridge.callInstance(handle, 'isDirectory', []));
|
|
289
|
+
if (isDirectory && !recursive) {
|
|
290
|
+
const children = bridge.callInstance(handle, 'listFiles', []);
|
|
291
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
292
|
+
throw (0, errors_1.javaError)('java.nio.file.DirectoryNotEmptyException: ' + target);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (isDirectory && recursive) {
|
|
296
|
+
removeRecursive(target);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
bridge.callInstance(handle, 'delete', []);
|
|
300
|
+
},
|
|
301
|
+
rename(from, to) {
|
|
302
|
+
const moved = bridge.normalizeBoolean(bridge.callInstance(fileOf(from), 'renameTo', [fileOf(to)]));
|
|
303
|
+
if (!moved) {
|
|
304
|
+
throw (0, errors_1.javaError)('java.io.IOException: Cannot rename ' + from + ' to ' + to);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
copyFile(from, to) {
|
|
308
|
+
writeAllBytes(to, readAllBytes(from), false, false);
|
|
309
|
+
},
|
|
310
|
+
realpath(target) {
|
|
311
|
+
const resolved = callFile(target, 'getCanonicalPath');
|
|
312
|
+
if (typeof resolved !== 'string' || resolved.length === 0) {
|
|
313
|
+
throw (0, errors_1.javaError)('java.io.FileNotFoundException: ' + target);
|
|
314
|
+
}
|
|
315
|
+
return resolved;
|
|
316
|
+
},
|
|
317
|
+
readlink(target) {
|
|
318
|
+
const resolved = bridge.callStatic('java.nio.file.Files', 'readSymbolicLink', [pathOf(target)]);
|
|
319
|
+
return String(bridge.callInstance(resolved, 'toString', []));
|
|
320
|
+
},
|
|
321
|
+
symlink(target, linkPath) {
|
|
322
|
+
bridge.callStatic('java.nio.file.Files', 'createSymbolicLink', [pathOf(linkPath), pathOf(target)]);
|
|
323
|
+
},
|
|
324
|
+
chmod(target, mode) {
|
|
325
|
+
callFile(target, 'setReadable', [(mode & 0o444) !== 0, false]);
|
|
326
|
+
callFile(target, 'setWritable', [(mode & 0o222) !== 0, false]);
|
|
327
|
+
callFile(target, 'setExecutable', [(mode & 0o111) !== 0, false]);
|
|
328
|
+
},
|
|
329
|
+
truncate(target, length) {
|
|
330
|
+
const handle = bridge.newInstance('java.io.RandomAccessFile', [target, 'rw']);
|
|
331
|
+
try {
|
|
332
|
+
bridge.callInstance(handle, 'setLength', [length]);
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
bridge.callInstance(handle, 'close', []);
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
utimes(target, atimeMs, mtimeMs) {
|
|
339
|
+
const ok = bridge.normalizeBoolean(callFile(target, 'setLastModified', [mtimeMs]));
|
|
340
|
+
if (!ok) {
|
|
341
|
+
throw (0, errors_1.javaError)('java.io.IOException: Cannot set times on ' + target);
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
mkdtemp(prefix) {
|
|
345
|
+
for (let attempt = 0; attempt < 64; attempt += 1) {
|
|
346
|
+
const candidate = prefix + randomSuffix();
|
|
347
|
+
if (!bridge.normalizeBoolean(callFile(candidate, 'exists'))) {
|
|
348
|
+
if (bridge.normalizeBoolean(callFile(candidate, 'mkdir'))) {
|
|
349
|
+
return candidate;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
throw (0, errors_1.javaError)('java.io.IOException: Cannot create temp directory under ' + prefix);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const SUFFIX_ALPHABET = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
358
|
+
function randomSuffix() {
|
|
359
|
+
let out = '';
|
|
360
|
+
for (let i = 0; i < 6; i += 1) {
|
|
361
|
+
out += SUFFIX_ALPHABET.charAt(Math.floor(Math.random() * SUFFIX_ALPHABET.length));
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface FsEnvironmentSpec {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
/** 物理根。用函数是为了让 config / app 这类根惰性解析(需要 bridge)。 */
|
|
4
|
+
readonly root: string | (() => string);
|
|
5
|
+
readonly writable: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface FsEnvHooks {
|
|
8
|
+
setCwd(path: string): void;
|
|
9
|
+
getCwd(): string;
|
|
10
|
+
/** 探测根是否可用;抛错即视为不可用。 */
|
|
11
|
+
probe(root: string): void;
|
|
12
|
+
}
|
|
13
|
+
export declare function setHooks(next: FsEnvHooks): void;
|
|
14
|
+
/** 注册自定义环境。内置环境名不可覆盖。 */
|
|
15
|
+
export declare function define(name: string, spec: Omit<FsEnvironmentSpec, 'name'>): void;
|
|
16
|
+
export declare function names(): string[];
|
|
17
|
+
export declare function current(): string | null;
|
|
18
|
+
export declare function root(): string;
|
|
19
|
+
export declare function writable(): boolean;
|
|
20
|
+
/** 显式切换环境。根不可用即拒绝,不做任何降级。 */
|
|
21
|
+
export declare function use(name: string): void;
|
|
22
|
+
/** 作用域内切换,回调必须同步返回。 */
|
|
23
|
+
export declare function withEnvironment<T>(name: string, callback: () => T): T;
|
|
24
|
+
/** 回到 UNBOUND。cwd 不动 —— 调用方要回默认值请再 use('sdcard')。 */
|
|
25
|
+
export declare function reset(): void;
|
|
26
|
+
/** strict 模式下,未显式 use() 就使用 fs 会直接报错。 */
|
|
27
|
+
export declare function strict(flag: boolean): void;
|
|
28
|
+
export declare function isStrict(): boolean;
|
|
29
|
+
/** fs 每次操作前调用:非 strict 时按默认环境惰性绑定。 */
|
|
30
|
+
export declare function ensureBound(): void;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setHooks = setHooks;
|
|
4
|
+
exports.define = define;
|
|
5
|
+
exports.names = names;
|
|
6
|
+
exports.current = current;
|
|
7
|
+
exports.root = root;
|
|
8
|
+
exports.writable = writable;
|
|
9
|
+
exports.use = use;
|
|
10
|
+
exports.withEnvironment = withEnvironment;
|
|
11
|
+
exports.reset = reset;
|
|
12
|
+
exports.strict = strict;
|
|
13
|
+
exports.isStrict = isStrict;
|
|
14
|
+
exports.ensureBound = ensureBound;
|
|
15
|
+
/**
|
|
16
|
+
* fs 的环境状态机。
|
|
17
|
+
*
|
|
18
|
+
* 模型:环境 = 根 + 能力。切环境就是切 cwd —— 相对路径与 path.resolve 的基准随之改变,
|
|
19
|
+
* 所以状态机不是给 fs 打的补丁,而是路径语义的唯一来源。
|
|
20
|
+
*
|
|
21
|
+
* UNBOUND ──首次使用──▶ BOUND(sdcard)
|
|
22
|
+
* BOUND(A) ──use(B)──▶ BOUND(B)
|
|
23
|
+
* BOUND(A) ──with(B, fn)──▶ BOUND(B) ──fn 同步返回──▶ BOUND(A)
|
|
24
|
+
* BOUND(*) ──reset()──▶ UNBOUND
|
|
25
|
+
*
|
|
26
|
+
* 为什么 with() 强制同步:模块实例会跨工具调用复用(见 DESIGN.md §7.4),
|
|
27
|
+
* 一旦回调是异步的,await 期间别的代码就会看到被改掉的环境。
|
|
28
|
+
*/
|
|
29
|
+
const errors_1 = require("./errors");
|
|
30
|
+
const paths_1 = require("./paths");
|
|
31
|
+
const BUILTIN_ENVS = [
|
|
32
|
+
{ name: 'sdcard', root: '/sdcard', writable: true },
|
|
33
|
+
{ name: 'config', root: () => (0, paths_1.pluginConfigDir)(), writable: true },
|
|
34
|
+
{ name: 'app', root: () => (0, paths_1.filesDir)(), writable: true }
|
|
35
|
+
];
|
|
36
|
+
const BUILTIN_NAMES = BUILTIN_ENVS.map((item) => item.name);
|
|
37
|
+
const specs = new Map();
|
|
38
|
+
BUILTIN_ENVS.forEach((item) => specs.set(item.name, item));
|
|
39
|
+
let hooks = null;
|
|
40
|
+
let currentName = null;
|
|
41
|
+
let strictMode = false;
|
|
42
|
+
const stack = [];
|
|
43
|
+
function setHooks(next) {
|
|
44
|
+
hooks = next;
|
|
45
|
+
}
|
|
46
|
+
function requireHooks() {
|
|
47
|
+
if (hooks === null) {
|
|
48
|
+
throw (0, errors_1.onjError)('ONJ_MISSING_HOST', 'fs 环境状态机尚未接入宿主。');
|
|
49
|
+
}
|
|
50
|
+
return hooks;
|
|
51
|
+
}
|
|
52
|
+
function resolveRoot(spec) {
|
|
53
|
+
const value = typeof spec.root === 'function' ? spec.root() : spec.root;
|
|
54
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
55
|
+
throw (0, errors_1.onjError)('ONJ_ENV_UNAVAILABLE', '环境 ' + spec.name + ' 的根解析为空。');
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
/** 注册自定义环境。内置环境名不可覆盖。 */
|
|
60
|
+
function define(name, spec) {
|
|
61
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
62
|
+
throw new TypeError('fs.env.define 的名称必须是非空字符串。');
|
|
63
|
+
}
|
|
64
|
+
if (BUILTIN_NAMES.indexOf(name) >= 0) {
|
|
65
|
+
throw (0, errors_1.onjError)('ONJ_UNKNOWN_ENV', '内置环境 ' + name + ' 不可覆盖。');
|
|
66
|
+
}
|
|
67
|
+
if (spec === null || typeof spec !== 'object') {
|
|
68
|
+
throw new TypeError('fs.env.define 的 spec 必须是对象。');
|
|
69
|
+
}
|
|
70
|
+
specs.set(name, { name: name, root: spec.root, writable: spec.writable !== false });
|
|
71
|
+
}
|
|
72
|
+
function names() {
|
|
73
|
+
return Array.from(specs.keys());
|
|
74
|
+
}
|
|
75
|
+
function current() {
|
|
76
|
+
return currentName;
|
|
77
|
+
}
|
|
78
|
+
function root() {
|
|
79
|
+
const spec = requireCurrent();
|
|
80
|
+
return resolveRoot(spec);
|
|
81
|
+
}
|
|
82
|
+
function writable() {
|
|
83
|
+
return requireCurrent().writable;
|
|
84
|
+
}
|
|
85
|
+
function requireCurrent() {
|
|
86
|
+
if (currentName === null) {
|
|
87
|
+
throw (0, errors_1.onjError)('ONJ_ENV_NOT_SET');
|
|
88
|
+
}
|
|
89
|
+
const spec = specs.get(currentName);
|
|
90
|
+
if (spec === undefined) {
|
|
91
|
+
throw (0, errors_1.onjError)('ONJ_UNKNOWN_ENV', currentName);
|
|
92
|
+
}
|
|
93
|
+
return spec;
|
|
94
|
+
}
|
|
95
|
+
/** 显式切换环境。根不可用即拒绝,不做任何降级。 */
|
|
96
|
+
function use(name) {
|
|
97
|
+
const spec = specs.get(name);
|
|
98
|
+
if (spec === undefined) {
|
|
99
|
+
throw (0, errors_1.onjError)('ONJ_UNKNOWN_ENV', name);
|
|
100
|
+
}
|
|
101
|
+
const target = resolveRoot(spec);
|
|
102
|
+
const host = requireHooks();
|
|
103
|
+
try {
|
|
104
|
+
host.probe(target);
|
|
105
|
+
}
|
|
106
|
+
catch (failure) {
|
|
107
|
+
throw (0, errors_1.onjError)('ONJ_ENV_UNAVAILABLE', '环境 ' + name + ' 的根 ' + target + ' 不可用。');
|
|
108
|
+
}
|
|
109
|
+
host.setCwd(target);
|
|
110
|
+
currentName = name;
|
|
111
|
+
}
|
|
112
|
+
/** 作用域内切换,回调必须同步返回。 */
|
|
113
|
+
function withEnvironment(name, callback) {
|
|
114
|
+
const host = requireHooks();
|
|
115
|
+
const previousName = currentName;
|
|
116
|
+
const previousCwd = host.getCwd();
|
|
117
|
+
use(name);
|
|
118
|
+
try {
|
|
119
|
+
const result = callback();
|
|
120
|
+
if (isThenable(result)) {
|
|
121
|
+
throw (0, errors_1.onjError)('ONJ_ENV_ASYNC_SCOPE');
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
host.setCwd(previousCwd);
|
|
127
|
+
currentName = previousName;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** 回到 UNBOUND。cwd 不动 —— 调用方要回默认值请再 use('sdcard')。 */
|
|
131
|
+
function reset() {
|
|
132
|
+
currentName = null;
|
|
133
|
+
stack.length = 0;
|
|
134
|
+
}
|
|
135
|
+
/** strict 模式下,未显式 use() 就使用 fs 会直接报错。 */
|
|
136
|
+
function strict(flag) {
|
|
137
|
+
strictMode = flag === true;
|
|
138
|
+
}
|
|
139
|
+
function isStrict() {
|
|
140
|
+
return strictMode;
|
|
141
|
+
}
|
|
142
|
+
/** fs 每次操作前调用:非 strict 时按默认环境惰性绑定。 */
|
|
143
|
+
function ensureBound() {
|
|
144
|
+
if (currentName !== null) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (strictMode) {
|
|
148
|
+
throw (0, errors_1.onjError)('ONJ_ENV_NOT_SET');
|
|
149
|
+
}
|
|
150
|
+
use(BUILTIN_NAMES[0]);
|
|
151
|
+
}
|
|
152
|
+
function isThenable(value) {
|
|
153
|
+
if (value === null || typeof value !== 'object') {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
return typeof value.then === 'function';
|
|
157
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fs 的公开类型。
|
|
3
|
+
*
|
|
4
|
+
* 独立成文件的原因与 path-types / zlib-types / stream-types 相同:
|
|
5
|
+
* fs.ts 用 `export =` 导出模块本体,无法再导出类型,而 index.ts 聚合时
|
|
6
|
+
* 要求这些名字是"可命名"的。
|
|
7
|
+
*/
|
|
8
|
+
export interface ReadStreamOptions {
|
|
9
|
+
flags?: string;
|
|
10
|
+
encoding?: string;
|
|
11
|
+
start?: number;
|
|
12
|
+
end?: number;
|
|
13
|
+
highWaterMark?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface WriteStreamOptions {
|
|
16
|
+
flags?: string;
|
|
17
|
+
encoding?: string;
|
|
18
|
+
highWaterMark?: number;
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 纯 JS 的哈希原语(md5 / sha1 / sha224 / sha256 / sha384 / sha512)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么自己实现而不是借运行时:
|
|
5
|
+
* Operit 注入的全局 CryptoJS 只是个 4KB 的 shim,只有 MD5(string) 和 AES.decrypt;
|
|
6
|
+
* NativeInterface.crypto 也只认 md5 与 aes/decrypt。都不足以支撑 crypto 模块。
|
|
7
|
+
*
|
|
8
|
+
* 实现取舍:
|
|
9
|
+
* 32 位算法(md5/sha1/sha256)用标准 32 位算术;
|
|
10
|
+
* sha384/sha512 用 BigInt 写 64 位轮 —— 代码短、不易错,代价是大输入的吞吐。
|
|
11
|
+
* 正确性由 test/crypto.test.js 与 Node 的 crypto 全量对拍保证。
|
|
12
|
+
*/
|
|
13
|
+
export interface HashSpec {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly blockSize: number;
|
|
16
|
+
readonly digestLength: number;
|
|
17
|
+
compute(bytes: Uint8Array): Uint8Array;
|
|
18
|
+
}
|
|
19
|
+
export declare const HASHES: Readonly<Record<string, HashSpec>>;
|
|
20
|
+
/** 把 Node 的算法别名(大小写、连字符、sha-256 之类)归一到注册表的键。 */
|
|
21
|
+
export declare function resolveAlgorithm(name: string): HashSpec;
|
|
22
|
+
export declare function hashNames(): string[];
|
|
23
|
+
/** HMAC(RFC 2104)。 */
|
|
24
|
+
export declare function hmac(spec: HashSpec, key: Uint8Array, message: Uint8Array): Uint8Array;
|
|
25
|
+
/** PBKDF2(RFC 2898),基于 HMAC。 */
|
|
26
|
+
export declare function pbkdf2(spec: HashSpec, password: Uint8Array, salt: Uint8Array, iterations: number, keyLength: number): Uint8Array;
|
|
27
|
+
export declare function toHex(bytes: Uint8Array): string;
|