trove-cli 0.0.0-stage → 0.2.0
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 +21 -0
- package/README.md +200 -2
- package/dist/adapters/file-source.d.ts +31 -0
- package/dist/adapters/file-source.js +97 -0
- package/dist/adapters/file-source.js.map +1 -0
- package/dist/adapters/index.d.ts +34 -0
- package/dist/adapters/index.js +146 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +30 -0
- package/dist/adapters/opencode.js +135 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/cli.d.ts +58 -0
- package/dist/cli.js +972 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +81 -0
- package/dist/config.js +99 -0
- package/dist/config.js.map +1 -0
- package/dist/core/archive.d.ts +27 -0
- package/dist/core/archive.js +101 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/chunker.d.ts +28 -0
- package/dist/core/chunker.js +67 -0
- package/dist/core/chunker.js.map +1 -0
- package/dist/core/clean.d.ts +36 -0
- package/dist/core/clean.js +182 -0
- package/dist/core/clean.js.map +1 -0
- package/dist/core/collect.d.ts +39 -0
- package/dist/core/collect.js +194 -0
- package/dist/core/collect.js.map +1 -0
- package/dist/core/concurrency.d.ts +2 -0
- package/dist/core/concurrency.js +16 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/cursor.d.ts +31 -0
- package/dist/core/cursor.js +31 -0
- package/dist/core/cursor.js.map +1 -0
- package/dist/core/events/claude.d.ts +7 -0
- package/dist/core/events/claude.js +287 -0
- package/dist/core/events/claude.js.map +1 -0
- package/dist/core/events/codex.d.ts +6 -0
- package/dist/core/events/codex.js +149 -0
- package/dist/core/events/codex.js.map +1 -0
- package/dist/core/events/doubao.d.ts +9 -0
- package/dist/core/events/doubao.js +151 -0
- package/dist/core/events/doubao.js.map +1 -0
- package/dist/core/events/index.d.ts +6 -0
- package/dist/core/events/index.js +20 -0
- package/dist/core/events/index.js.map +1 -0
- package/dist/core/events/opencode.d.ts +7 -0
- package/dist/core/events/opencode.js +189 -0
- package/dist/core/events/opencode.js.map +1 -0
- package/dist/core/events/schema.d.ts +83 -0
- package/dist/core/events/schema.js +35 -0
- package/dist/core/events/schema.js.map +1 -0
- package/dist/core/layout.d.ts +26 -0
- package/dist/core/layout.js +122 -0
- package/dist/core/layout.js.map +1 -0
- package/dist/core/lessons/analyze.d.ts +40 -0
- package/dist/core/lessons/analyze.js +107 -0
- package/dist/core/lessons/analyze.js.map +1 -0
- package/dist/core/lessons/audit.d.ts +51 -0
- package/dist/core/lessons/audit.js +57 -0
- package/dist/core/lessons/audit.js.map +1 -0
- package/dist/core/lessons/compaction.d.ts +5 -0
- package/dist/core/lessons/compaction.js +58 -0
- package/dist/core/lessons/compaction.js.map +1 -0
- package/dist/core/lessons/config.d.ts +15 -0
- package/dist/core/lessons/config.js +31 -0
- package/dist/core/lessons/config.js.map +1 -0
- package/dist/core/lessons/existing.d.ts +4 -0
- package/dist/core/lessons/existing.js +22 -0
- package/dist/core/lessons/existing.js.map +1 -0
- package/dist/core/lessons/extract.d.ts +21 -0
- package/dist/core/lessons/extract.js +106 -0
- package/dist/core/lessons/extract.js.map +1 -0
- package/dist/core/lessons/frontmatter.d.ts +29 -0
- package/dist/core/lessons/frontmatter.js +87 -0
- package/dist/core/lessons/frontmatter.js.map +1 -0
- package/dist/core/lessons/graph.d.ts +256 -0
- package/dist/core/lessons/graph.js +86 -0
- package/dist/core/lessons/graph.js.map +1 -0
- package/dist/core/lessons/jev.d.ts +36 -0
- package/dist/core/lessons/jev.js +100 -0
- package/dist/core/lessons/jev.js.map +1 -0
- package/dist/core/lessons/layout.d.ts +2 -0
- package/dist/core/lessons/layout.js +56 -0
- package/dist/core/lessons/layout.js.map +1 -0
- package/dist/core/lessons/learn.d.ts +38 -0
- package/dist/core/lessons/learn.js +190 -0
- package/dist/core/lessons/learn.js.map +1 -0
- package/dist/core/lessons/llm.d.ts +42 -0
- package/dist/core/lessons/llm.js +106 -0
- package/dist/core/lessons/llm.js.map +1 -0
- package/dist/core/lessons/memo.d.ts +19 -0
- package/dist/core/lessons/memo.js +170 -0
- package/dist/core/lessons/memo.js.map +1 -0
- package/dist/core/lessons/merge.d.ts +39 -0
- package/dist/core/lessons/merge.js +79 -0
- package/dist/core/lessons/merge.js.map +1 -0
- package/dist/core/lessons/queue.d.ts +6 -0
- package/dist/core/lessons/queue.js +52 -0
- package/dist/core/lessons/queue.js.map +1 -0
- package/dist/core/lessons/recent.d.ts +21 -0
- package/dist/core/lessons/recent.js +111 -0
- package/dist/core/lessons/recent.js.map +1 -0
- package/dist/core/lessons/schema.d.ts +26 -0
- package/dist/core/lessons/schema.js +64 -0
- package/dist/core/lessons/schema.js.map +1 -0
- package/dist/core/lessons/select.d.ts +19 -0
- package/dist/core/lessons/select.js +55 -0
- package/dist/core/lessons/select.js.map +1 -0
- package/dist/core/lessons/sidecar.d.ts +23 -0
- package/dist/core/lessons/sidecar.js +171 -0
- package/dist/core/lessons/sidecar.js.map +1 -0
- package/dist/core/lessons/storage.d.ts +21 -0
- package/dist/core/lessons/storage.js +65 -0
- package/dist/core/lessons/storage.js.map +1 -0
- package/dist/core/lessons/usage.d.ts +31 -0
- package/dist/core/lessons/usage.js +91 -0
- package/dist/core/lessons/usage.js.map +1 -0
- package/dist/core/lessons/write.d.ts +18 -0
- package/dist/core/lessons/write.js +131 -0
- package/dist/core/lessons/write.js.map +1 -0
- package/dist/core/library/build.d.ts +22 -0
- package/dist/core/library/build.js +163 -0
- package/dist/core/library/build.js.map +1 -0
- package/dist/core/library/group.d.ts +14 -0
- package/dist/core/library/group.js +129 -0
- package/dist/core/library/group.js.map +1 -0
- package/dist/core/library/index-file.d.ts +7 -0
- package/dist/core/library/index-file.js +250 -0
- package/dist/core/library/index-file.js.map +1 -0
- package/dist/core/library/schema.d.ts +78 -0
- package/dist/core/library/schema.js +37 -0
- package/dist/core/library/schema.js.map +1 -0
- package/dist/core/library/skills.d.ts +26 -0
- package/dist/core/library/skills.js +168 -0
- package/dist/core/library/skills.js.map +1 -0
- package/dist/core/library/sync.d.ts +48 -0
- package/dist/core/library/sync.js +271 -0
- package/dist/core/library/sync.js.map +1 -0
- package/dist/core/lock.d.ts +6 -0
- package/dist/core/lock.js +56 -0
- package/dist/core/lock.js.map +1 -0
- package/dist/core/pull.d.ts +23 -0
- package/dist/core/pull.js +147 -0
- package/dist/core/pull.js.map +1 -0
- package/dist/core/push.d.ts +29 -0
- package/dist/core/push.js +170 -0
- package/dist/core/push.js.map +1 -0
- package/dist/core/remote-layout.d.ts +67 -0
- package/dist/core/remote-layout.js +94 -0
- package/dist/core/remote-layout.js.map +1 -0
- package/dist/core/sessions.d.ts +29 -0
- package/dist/core/sessions.js +96 -0
- package/dist/core/sessions.js.map +1 -0
- package/dist/core/state.d.ts +94 -0
- package/dist/core/state.js +62 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/sync.d.ts +48 -0
- package/dist/core/sync.js +140 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/core/time-window.d.ts +8 -0
- package/dist/core/time-window.js +36 -0
- package/dist/core/time-window.js.map +1 -0
- package/dist/daemon.d.ts +73 -0
- package/dist/daemon.js +272 -0
- package/dist/daemon.js.map +1 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.js +28 -0
- package/dist/format.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +9 -0
- package/dist/main.js.map +1 -0
- package/dist/paths.d.ts +19 -0
- package/dist/paths.js +19 -0
- package/dist/paths.js.map +1 -0
- package/dist/progress-console.d.ts +12 -0
- package/dist/progress-console.js +42 -0
- package/dist/progress-console.js.map +1 -0
- package/dist/progress.d.ts +7 -0
- package/dist/progress.js +6 -0
- package/dist/progress.js.map +1 -0
- package/dist/remote/create-remote.d.ts +20 -0
- package/dist/remote/create-remote.js +86 -0
- package/dist/remote/create-remote.js.map +1 -0
- package/dist/remote/prompt.d.ts +17 -0
- package/dist/remote/prompt.js +111 -0
- package/dist/remote/prompt.js.map +1 -0
- package/dist/remote/s3-sigv4.d.ts +28 -0
- package/dist/remote/s3-sigv4.js +100 -0
- package/dist/remote/s3-sigv4.js.map +1 -0
- package/dist/remote/s3-store.d.ts +75 -0
- package/dist/remote/s3-store.js +438 -0
- package/dist/remote/s3-store.js.map +1 -0
- package/dist/remote/s3-vendors.d.ts +31 -0
- package/dist/remote/s3-vendors.js +47 -0
- package/dist/remote/s3-vendors.js.map +1 -0
- package/dist/remote/store.d.ts +44 -0
- package/dist/remote/store.js +2 -0
- package/dist/remote/store.js.map +1 -0
- package/dist/remote/webdav-store.d.ts +41 -0
- package/dist/remote/webdav-store.js +285 -0
- package/dist/remote/webdav-store.js.map +1 -0
- package/dist/sqlite.d.ts +3 -0
- package/dist/sqlite.js +12 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/status.d.ts +82 -0
- package/dist/status.js +162 -0
- package/dist/status.js.map +1 -0
- package/package.json +43 -4
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { password as clackPassword, select, text } from '@clack/prompts';
|
|
2
|
+
import { S3_VENDORS, vendorById } from './s3-vendors.js';
|
|
3
|
+
const TYPE_OPTIONS = [
|
|
4
|
+
{ value: 'webdav', label: 'WebDAV(自建/第三方网盘)' },
|
|
5
|
+
{ value: 's3', label: 'S3 兼容对象存储(阿里云 OSS / 腾讯云 COS)' },
|
|
6
|
+
];
|
|
7
|
+
export function isCancel(v) {
|
|
8
|
+
return typeof v === 'symbol';
|
|
9
|
+
}
|
|
10
|
+
/** 问一个文本;返回 undefined 表示用户取消。 */
|
|
11
|
+
async function askText(message, initialValue, placeholder) {
|
|
12
|
+
const v = await text({
|
|
13
|
+
message,
|
|
14
|
+
initialValue: initialValue ?? '',
|
|
15
|
+
...(placeholder ? { placeholder } : {}),
|
|
16
|
+
});
|
|
17
|
+
return isCancel(v) ? undefined : v.trim();
|
|
18
|
+
}
|
|
19
|
+
async function askSecret(message) {
|
|
20
|
+
const v = await clackPassword({ message });
|
|
21
|
+
return isCancel(v) ? undefined : v.trim();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 交互式引导远端配置。
|
|
25
|
+
*
|
|
26
|
+
* @param existing 当前配置,用作默认值
|
|
27
|
+
* @param interactive 是否交互
|
|
28
|
+
* @param forcedType 强制指定类型(init 的 --type flag)
|
|
29
|
+
* @returns 远端配置;非交互模式返回 undefined
|
|
30
|
+
*/
|
|
31
|
+
export async function promptRemoteConfig(existing, interactive, forcedType) {
|
|
32
|
+
if (!interactive)
|
|
33
|
+
return undefined;
|
|
34
|
+
let type;
|
|
35
|
+
if (forcedType) {
|
|
36
|
+
type = forcedType;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const picked = await select({
|
|
40
|
+
message: '远端类型',
|
|
41
|
+
options: TYPE_OPTIONS,
|
|
42
|
+
initialValue: existing?.type ?? 'webdav',
|
|
43
|
+
});
|
|
44
|
+
if (isCancel(picked))
|
|
45
|
+
return undefined;
|
|
46
|
+
type = picked;
|
|
47
|
+
}
|
|
48
|
+
if (type === 's3') {
|
|
49
|
+
return promptS3(existing?.type === 's3' ? existing : undefined);
|
|
50
|
+
}
|
|
51
|
+
return promptWebDav(existing?.type === 'webdav' ? existing : undefined);
|
|
52
|
+
}
|
|
53
|
+
async function promptWebDav(existing) {
|
|
54
|
+
const url = await askText('WebDAV 地址', existing?.url, 'https://dav.example.com/trove/');
|
|
55
|
+
if (url === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
const username = await askText('WebDAV 用户名', existing?.username || 'webdav');
|
|
58
|
+
if (username === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
const password = await askSecret('WebDAV 密码(留空 = 不变)');
|
|
61
|
+
if (password === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
return {
|
|
64
|
+
type: 'webdav',
|
|
65
|
+
url,
|
|
66
|
+
username,
|
|
67
|
+
// 留空保留原密码
|
|
68
|
+
password: password === '' ? (existing?.password ?? '') : password,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function promptS3(existing) {
|
|
72
|
+
const providerId = (existing?.provider ?? 'aliyun');
|
|
73
|
+
const picked = await select({
|
|
74
|
+
message: 'S3 厂商',
|
|
75
|
+
options: Object.values(S3_VENDORS).map((v) => ({
|
|
76
|
+
value: v.id,
|
|
77
|
+
label: `${v.label} — ${v.hint}`,
|
|
78
|
+
})),
|
|
79
|
+
initialValue: providerId,
|
|
80
|
+
});
|
|
81
|
+
if (isCancel(picked))
|
|
82
|
+
return undefined;
|
|
83
|
+
const vendor = vendorById(picked);
|
|
84
|
+
const region = await askText('地域', existing?.region ?? vendor.defaultRegion, vendor.defaultRegion);
|
|
85
|
+
if (region === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
const bucket = await askText('Bucket', existing?.bucket, vendor.id === 'tencent' ? 'my-bucket-1250000000' : 'my-bucket');
|
|
88
|
+
if (bucket === undefined)
|
|
89
|
+
return undefined;
|
|
90
|
+
const accessKeyId = await askText('AccessKey ID', existing?.accessKeyId);
|
|
91
|
+
if (accessKeyId === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
const secretAccessKey = await askSecret('Secret AccessKey(留空 = 不变)');
|
|
94
|
+
if (secretAccessKey === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
// 自定义 endpoint:留空 = 按厂商推导;MinIO 等私有部署填自己的地址
|
|
97
|
+
const endpoint = await askText('自定义 Endpoint(留空 = 用厂商默认)', existing?.endpoint ?? '', vendor.endpoint(region));
|
|
98
|
+
if (endpoint === undefined)
|
|
99
|
+
return undefined;
|
|
100
|
+
return {
|
|
101
|
+
type: 's3',
|
|
102
|
+
provider: vendor.id,
|
|
103
|
+
region,
|
|
104
|
+
bucket,
|
|
105
|
+
accessKeyId,
|
|
106
|
+
secretAccessKey: secretAccessKey === '' ? (existing?.secretAccessKey ?? '') : secretAccessKey,
|
|
107
|
+
...(endpoint ? { endpoint } : {}),
|
|
108
|
+
forcePathStyle: existing?.forcePathStyle ?? vendor.forcePathStyle,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/remote/prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SigV4Credentials {
|
|
2
|
+
accessKeyId: string;
|
|
3
|
+
secretAccessKey: string;
|
|
4
|
+
region: string;
|
|
5
|
+
service: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SignRequestInput extends SigV4Credentials {
|
|
8
|
+
method: string;
|
|
9
|
+
url: string;
|
|
10
|
+
/** 未编码的原始路径;省略时从 url 解析。url.pathname 已被 WHATWG URL 预编码,直接喂给 canonicalUri 会二次编码。 */
|
|
11
|
+
pathname?: string;
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
body: Buffer;
|
|
14
|
+
date: Date;
|
|
15
|
+
}
|
|
16
|
+
export interface SignedRequest {
|
|
17
|
+
headers: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
/** canonical URI:路径部分,/ 分隔符不编码。 */
|
|
20
|
+
export declare function canonicalUri(pathname: string): string;
|
|
21
|
+
/** canonical query:按 key 排序后逐项编码,= 与 & 分隔。 */
|
|
22
|
+
export declare function canonicalQuery(params: Record<string, string>): string;
|
|
23
|
+
/** payload 的 sha256 十六进制。 */
|
|
24
|
+
export declare function payloadHash(body: Buffer): string;
|
|
25
|
+
/**
|
|
26
|
+
* 对一个请求签名,返回带 Authorization / x-amz-date / x-amz-content-sha256 的头部。
|
|
27
|
+
*/
|
|
28
|
+
export declare function signRequest(input: SignRequestInput): SignedRequest;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createHash, createHmac } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* AWS Signature Version 4 签名。
|
|
4
|
+
*
|
|
5
|
+
* 只实现 S3 需要的部分:Get/Put/Delete + ListObjectsV2。
|
|
6
|
+
* 不引第三方 SDK —— 本项目只需要 4 个已签名请求,自己签省掉一整个依赖树。
|
|
7
|
+
*/
|
|
8
|
+
const ALGORITHM = 'AWS4-HMAC-SHA256';
|
|
9
|
+
function sha256Hex(data) {
|
|
10
|
+
return createHash('sha256').update(data).digest('hex');
|
|
11
|
+
}
|
|
12
|
+
function hmac(key, data) {
|
|
13
|
+
return createHmac('sha256', key).update(data).digest();
|
|
14
|
+
}
|
|
15
|
+
/** S3 要求路径中这些字符原样保留,不参与百分号编码。 */
|
|
16
|
+
const UNRESERVED = /[A-Za-z0-9\-._~]/;
|
|
17
|
+
/**
|
|
18
|
+
* URI 编码:非保留字符原样,其余 UTF-8 百分号编码。
|
|
19
|
+
* AWS 的规则与 RFC3986 有两处差异:斜杠只在路径中保留,星号不编码。
|
|
20
|
+
*/
|
|
21
|
+
function uriEncode(value, encodeSlash) {
|
|
22
|
+
let out = '';
|
|
23
|
+
for (const ch of Buffer.from(value, 'utf8')) {
|
|
24
|
+
const s = String.fromCharCode(ch);
|
|
25
|
+
if (UNRESERVED.test(s)) {
|
|
26
|
+
out += s;
|
|
27
|
+
}
|
|
28
|
+
else if (s === '/') {
|
|
29
|
+
out += encodeSlash ? '%2F' : '/';
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
out += `%${ch.toString(16).toUpperCase().padStart(2, '0')}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/** canonical URI:路径部分,/ 分隔符不编码。 */
|
|
38
|
+
export function canonicalUri(pathname) {
|
|
39
|
+
const p = pathname.startsWith('/') ? pathname : `/${pathname}`;
|
|
40
|
+
return uriEncode(p, false);
|
|
41
|
+
}
|
|
42
|
+
/** canonical query:按 key 排序后逐项编码,= 与 & 分隔。 */
|
|
43
|
+
export function canonicalQuery(params) {
|
|
44
|
+
return Object.keys(params)
|
|
45
|
+
.sort()
|
|
46
|
+
.map((k) => `${uriEncode(k, true)}=${uriEncode(params[k], true)}`)
|
|
47
|
+
.join('&');
|
|
48
|
+
}
|
|
49
|
+
/** payload 的 sha256 十六进制。 */
|
|
50
|
+
export function payloadHash(body) {
|
|
51
|
+
return sha256Hex(body);
|
|
52
|
+
}
|
|
53
|
+
/** 头部名小写、按序拼接;每个头部值去除多余空白并折叠。 */
|
|
54
|
+
function canonicalHeaders(headers) {
|
|
55
|
+
const entries = Object.entries(headers)
|
|
56
|
+
.map(([k, v]) => [k.toLowerCase(), v.trim().replace(/\s+/g, ' ')])
|
|
57
|
+
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
58
|
+
const canonical = entries.map(([k, v]) => `${k}:${v}\n`).join('');
|
|
59
|
+
const signed = entries.map(([k]) => k).join(';');
|
|
60
|
+
return { signed, canonical };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 对一个请求签名,返回带 Authorization / x-amz-date / x-amz-content-sha256 的头部。
|
|
64
|
+
*/
|
|
65
|
+
export function signRequest(input) {
|
|
66
|
+
const { method, url, body, date } = input;
|
|
67
|
+
const u = new URL(url);
|
|
68
|
+
const pathname = input.pathname ?? u.pathname;
|
|
69
|
+
const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
70
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
71
|
+
const headers = {
|
|
72
|
+
...input.headers,
|
|
73
|
+
'x-amz-date': amzDate,
|
|
74
|
+
'x-amz-content-sha256': payloadHash(body),
|
|
75
|
+
};
|
|
76
|
+
const { signed, canonical } = canonicalHeaders(headers);
|
|
77
|
+
const canonicalRequest = [
|
|
78
|
+
method.toUpperCase(),
|
|
79
|
+
canonicalUri(pathname),
|
|
80
|
+
canonicalQuery(Object.fromEntries(u.searchParams.entries())),
|
|
81
|
+
canonical,
|
|
82
|
+
signed,
|
|
83
|
+
payloadHash(body),
|
|
84
|
+
].join('\n');
|
|
85
|
+
const scope = `${dateStamp}/${input.region}/${input.service}/aws4_request`;
|
|
86
|
+
const stringToSign = [ALGORITHM, amzDate, scope, sha256Hex(canonicalRequest)].join('\n');
|
|
87
|
+
const kDate = hmac(`AWS4${input.secretAccessKey}`, dateStamp);
|
|
88
|
+
const kRegion = hmac(kDate, input.region);
|
|
89
|
+
const kService = hmac(kRegion, input.service);
|
|
90
|
+
const kSigning = hmac(kService, 'aws4_request');
|
|
91
|
+
const signature = createHmac('sha256', kSigning).update(stringToSign).digest('hex');
|
|
92
|
+
return {
|
|
93
|
+
headers: {
|
|
94
|
+
...headers,
|
|
95
|
+
authorization: `${ALGORITHM} Credential=${input.accessKeyId}/${scope}, ` +
|
|
96
|
+
`SignedHeaders=${signed}, Signature=${signature}`,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=s3-sigv4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-sigv4.js","sourceRoot":"","sources":["../../src/remote/s3-sigv4.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { RemoteEntry, RemoteStore } from './store.js';
|
|
2
|
+
export interface S3Options {
|
|
3
|
+
/** S3 兼容 endpoint,例如 https://s3.oss-cn-hangzhou.aliyuncs.com */
|
|
4
|
+
endpoint: string;
|
|
5
|
+
region: string;
|
|
6
|
+
bucket: string;
|
|
7
|
+
accessKeyId: string;
|
|
8
|
+
secretAccessKey: string;
|
|
9
|
+
/** path-style 寻址(bucket 在路径里);virtual-hosted 时 bucket 在主机名里。 */
|
|
10
|
+
forcePathStyle?: boolean;
|
|
11
|
+
/** 并发连接数上限;对象存储允许并发,默认 8。 */
|
|
12
|
+
maxSockets?: number;
|
|
13
|
+
/** 并发上传块数;与连接池上限一致,默认 8。 */
|
|
14
|
+
concurrency?: number;
|
|
15
|
+
/** 单请求超时(ms)。 */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class S3Error extends Error {
|
|
19
|
+
readonly status?: number | undefined;
|
|
20
|
+
readonly code?: string | undefined;
|
|
21
|
+
constructor(message: string, status?: number | undefined, code?: string | undefined);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* S3 存储实现:对象存储天然是扁平的 key,目录语义靠 ListObjectsV2 的 delimiter 模拟。
|
|
25
|
+
*
|
|
26
|
+
* 与 WebDAV 实现的差异:
|
|
27
|
+
* - 目录不用创建(PutObject 自动建"目录")
|
|
28
|
+
* - 索引写入直接覆盖同 key,天然原子(S3 的 PutObject 对同 key 是原子的)
|
|
29
|
+
* - 允许并发:WebDAV 实测并发 PUT 会 ECONNRESET,对象存储不会
|
|
30
|
+
*/
|
|
31
|
+
export declare class S3Store implements RemoteStore {
|
|
32
|
+
private endpoint;
|
|
33
|
+
private region;
|
|
34
|
+
private bucket;
|
|
35
|
+
private accessKeyId;
|
|
36
|
+
private secretAccessKey;
|
|
37
|
+
private forcePathStyle;
|
|
38
|
+
private timeoutMs;
|
|
39
|
+
private maxSockets;
|
|
40
|
+
private concurrency;
|
|
41
|
+
/** keep-alive 连接池:对象存储允许并发,默认 8 条。 */
|
|
42
|
+
private agents;
|
|
43
|
+
constructor(opts: S3Options);
|
|
44
|
+
/** virtual-hosted:bucket 放主机名;path-style:bucket 放路径。 */
|
|
45
|
+
private hostAndPath;
|
|
46
|
+
private raw;
|
|
47
|
+
/** 带重试的请求;5xx/429 退避重试,4xx 立即抛。 */
|
|
48
|
+
private call;
|
|
49
|
+
/** 列一层:prefix 下的直接子项;delimiter='/' 时把子目录折叠成目录项,空串表示递归。 */
|
|
50
|
+
private listPage;
|
|
51
|
+
list(path: string, signal?: AbortSignal): Promise<RemoteEntry[]>;
|
|
52
|
+
/** 递归列前缀下所有文件(不带 delimiter,一次分页拉完)。 */
|
|
53
|
+
listAll(prefix: string, signal?: AbortSignal): Promise<RemoteEntry[]>;
|
|
54
|
+
listDevices(): Promise<string[]>;
|
|
55
|
+
readJson(path: string, signal?: AbortSignal): Promise<unknown | null>;
|
|
56
|
+
writeJson(path: string, value: unknown): Promise<void>;
|
|
57
|
+
writeFile(path: string, localPath: string, range?: {
|
|
58
|
+
offset: number;
|
|
59
|
+
length: number;
|
|
60
|
+
}): Promise<void>;
|
|
61
|
+
/** 逐段落读本地文件,避免整个大文件进内存。 */
|
|
62
|
+
private readPart;
|
|
63
|
+
/**
|
|
64
|
+
* S3 Multipart Upload:分片只在协议层,最终仍是一个对象。
|
|
65
|
+
* 失败时尽力 Abort,避免桶里留下未完成的分片。
|
|
66
|
+
*/
|
|
67
|
+
private multipart;
|
|
68
|
+
private readRange;
|
|
69
|
+
readFile(path: string, localPath: string): Promise<void>;
|
|
70
|
+
delete(path: string): Promise<void>;
|
|
71
|
+
/** 对象存储允许并发上传;OSS 官方建议大批量小文件用较高并发。 */
|
|
72
|
+
concurrencyHint(): number;
|
|
73
|
+
/** 对象存储:一个文件一个对象,控制台可直接浏览。 */
|
|
74
|
+
layoutHint(): 'chunk' | 'object';
|
|
75
|
+
}
|