Skip to content

Commit 52a5083

Browse files
sunnylqmclaude
andcommitted
feat: self-hosted node (rnu-node) support — appId routing & presigned PUT uploads
- /upload request now carries appId so the server can route uploads for node-bound apps (to the node's PostObject-compatible endpoint — that path needs no other CLI change, formData is forwarded opaquely) - handle the {method:'PUT', url, key} instruction shape: stream the file via presigned PUT (bytes go straight to the user's S3-compatible bucket, never touching the node), explicit content-length as required by presigned PUTs Publish as a GitHub Release tagged >=2.19.0 (npm latest is 2.18.0; the version comes from the release tag, package.json in git stays stale). The server gates node-bound uploads on >=2.19.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 01e5b57 commit 52a5083

3 files changed

Lines changed: 45 additions & 4 deletions

File tree

src/api.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,18 @@ export const post = queryWithBody('POST');
202202
export const put = queryWithBody('PUT');
203203
export const doDelete = queryWithBody('DELETE');
204204

205-
export async function uploadFile(fn: string, key?: string) {
206-
const { url, backupUrl, formData, maxSize } = await post('/upload', {
205+
export async function uploadFile(
206+
fn: string,
207+
key?: string,
208+
appId?: string | number,
209+
) {
210+
// appId 用于服务端路由:绑定了自托管节点(rnu-node)的应用,
211+
// 上传指令会指向节点或其对象存储
212+
const resp = await post('/upload', {
207213
ext: path.extname(fn),
214+
...(appId ? { appId: Number(appId) } : {}),
208215
});
216+
const { url, backupUrl, formData, maxSize } = resp;
209217
let realUrl = url;
210218
if (backupUrl) {
211219
if (global.USE_ACC_OSS) {
@@ -240,6 +248,39 @@ export async function uploadFile(fn: string, key?: string) {
240248
total: fileSize,
241249
});
242250

251+
// 自托管节点的 s3 直传:服务端下发预签名 PUT,字节直达用户的对象存储
252+
if (resp.method === 'PUT') {
253+
const fileStream = fs.createReadStream(fn);
254+
fileStream.on('data', (data) => {
255+
bar.tick(data.length);
256+
});
257+
let putRes: fetch.Response;
258+
try {
259+
putRes = await fetch(realUrl, {
260+
method: 'PUT',
261+
body: fileStream,
262+
// 预签名 PUT 不接受 chunked 传输,必须显式声明长度
263+
headers: { 'content-length': String(fileSize), ...(resp.headers || {}) },
264+
});
265+
} catch (error) {
266+
if (isProxyRelatedError(error)) {
267+
const rawMessage =
268+
error instanceof Error ? error.message : String(error);
269+
throw new Error(
270+
`${rawMessage}\n\n${t('proxyNetworkError')}\n${t('proxyNetworkErrorTips')}`,
271+
);
272+
}
273+
throw createRequestError(error, realUrl);
274+
}
275+
if (putRes.status > 299) {
276+
throw createRequestError(
277+
`${putRes.status}: ${putRes.statusText || 'Upload failed'}`,
278+
realUrl,
279+
);
280+
}
281+
return { hash: resp.key };
282+
}
283+
243284
const form = new FormData();
244285

245286
for (const [k, v] of Object.entries(formData)) {

src/package.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ async function uploadNativePackage(
141141
console.log(t('usingCustomVersion', { version: versionName }));
142142
}
143143

144-
const { hash } = await uploadFile(filePath);
144+
const { hash } = await uploadFile(filePath, undefined, appId);
145145
const normalizedBuildTime = config.normalizeBuildTime
146146
? config.normalizeBuildTime(buildTime)
147147
: buildTime;

src/versions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ export const versionCommands = {
475475
const nonInteractive =
476476
getBooleanOption(options, 'no-interactive', false) || isNonInteractive();
477477

478-
const { hash } = await uploadFile(fn);
478+
const { hash } = await uploadFile(fn, undefined, appId);
479479

480480
const versionName =
481481
name ||

0 commit comments

Comments
 (0)