@applift/core 0.8.0 → 0.8.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/dist/plugins/index.d.mts +23 -4
- package/dist/plugins/index.mjs +73 -27
- package/package.json +1 -1
package/dist/plugins/index.d.mts
CHANGED
|
@@ -15,7 +15,11 @@ export declare function dingtalkNotify(options: DingtalkNotifyOptions): AppliftP
|
|
|
15
15
|
* 见 rn-update.ts)——applift 不 spawn 构建命令(架构不变量 1)。
|
|
16
16
|
*/
|
|
17
17
|
interface PushyCli {
|
|
18
|
-
/** 发布热更版本(version/create + 绑定目标原生版本),返回版本 id
|
|
18
|
+
/** 发布热更版本(version/create + 绑定目标原生版本),返回版本 id。
|
|
19
|
+
* deps 为服务端强校验所需(2026-09-17 后创建的应用要求 deps 里有 react-native-update
|
|
20
|
+
* >= 10.53.1——实测缺省被 400 拒绝),由调用方用 readDepVersions 从工程读取。
|
|
21
|
+
* 不上报 commit:实测带 commit(官方 getCommitInfo 同构形态)被 version/create 以
|
|
22
|
+
* 「上传凭据无效」拒绝(2026-10-21 实证,去 commit 即过);溯源走本地 history.jsonl */
|
|
19
23
|
publish(input: {
|
|
20
24
|
appId: number;
|
|
21
25
|
ppk: string;
|
|
@@ -24,11 +28,12 @@ interface PushyCli {
|
|
|
24
28
|
description?: string;
|
|
25
29
|
packageVersion: string;
|
|
26
30
|
rollout?: number;
|
|
31
|
+
deps: Record<string, string>;
|
|
27
32
|
}): Promise<{
|
|
28
33
|
versionId: number;
|
|
29
34
|
}>;
|
|
30
|
-
/** 上传原生基线(versionName
|
|
31
|
-
uploadApp(appId: number, appFile: string, versionName: string): Promise<{
|
|
35
|
+
/** 上传原生基线(versionName 由调用方从产物读取后显式传入;deps 同 publish) */
|
|
36
|
+
uploadApp(appId: number, appFile: string, versionName: string, deps: Record<string, string>): Promise<{
|
|
32
37
|
packageId: number;
|
|
33
38
|
version: string;
|
|
34
39
|
}>;
|
|
@@ -58,7 +63,19 @@ interface PushyApiOptions {
|
|
|
58
63
|
token: string;
|
|
59
64
|
/** 注入点:测试 mock(缺省 globalThis.fetch) */
|
|
60
65
|
fetchImpl?: FetchImpl;
|
|
66
|
+
/** 直传实现注入(缺省 node:http(s) 原生;测试 mock) */
|
|
67
|
+
httpUpload?: HttpUploadImpl;
|
|
61
68
|
}
|
|
69
|
+
/** 直传实现(node:http(s) 原生;测试注入 mock) */
|
|
70
|
+
type HttpUploadImpl = (method: "POST" | "PUT", url: string, headers: Record<string, string>, body: Buffer) => Promise<{
|
|
71
|
+
status: number;
|
|
72
|
+
}>;
|
|
73
|
+
export declare function nodeHttpUpload(method: "POST" | "PUT", url: string, headers: Record<string, string>, body: Buffer): Promise<{
|
|
74
|
+
status: number;
|
|
75
|
+
}>;
|
|
76
|
+
/** 项目直接依赖(dependencies+devDependencies)的实装版本映射(逐级向上找 node_modules,键排序)。
|
|
77
|
+
* 对照官方 dep-versions.js 同构:服务端按 deps["react-native-update"] 做热更兼容门禁与变更提示 */
|
|
78
|
+
export declare function readDepVersions(cwd: string): Record<string, string>;
|
|
62
79
|
/** OpenAPI 实现 PushyCli 的服务端操作(ppk 由调用方产出,见 PushyCli 契约注释) */
|
|
63
80
|
export declare function createPushyApi(opts: PushyApiOptions): PushyCli;
|
|
64
81
|
//#endregion
|
|
@@ -84,10 +101,12 @@ interface RnUpdateOptions {
|
|
|
84
101
|
* 五形态完备性:新原生实体必现于 原生目录/依赖(原生包)/补丁/构建配置/JS——前四判原生,纯 JS 判热更,其余灰区。
|
|
85
102
|
*/
|
|
86
103
|
export declare function judgeDelivery(diff: string[], ctx: FrameworkContext, opts: RnUpdateOptions): DeliveryJudgement;
|
|
104
|
+
/** 生成测试包偏移版本号(major+1000);非数字开头版本号无法偏移,显式报错 */
|
|
105
|
+
export declare function betaPackageVersion(versionName: string): string;
|
|
87
106
|
/** provider 构造(cli 注入点见 resolvePushyClient) */
|
|
88
107
|
export declare function makeRnUpdateProvider(opts: RnUpdateOptions, cli?: PushyCli): UpdateProvider;
|
|
89
108
|
/** rnUpdate 插件工厂:注册完整 provider(判定 + beta 发布 + promote)+ 原生发版自动基线。
|
|
90
109
|
* cli 注入点:测试 mock / 特殊 transport;缺省 OpenAPI 客户端(token 来自 ctx.credentials)。 */
|
|
91
110
|
export declare function rnUpdate(opts?: RnUpdateOptions, cli?: PushyCli): AppliftPlugin;
|
|
92
111
|
//#endregion
|
|
93
|
-
export { type DingtalkNotifyOptions, type FetchImpl, type PushyApiOptions, type PushyCli, type RnUpdateOptions, electronAdapter, electronFramework, flutterAdapter, flutterFramework, knownFrameworkAdapters, nativeAndroidAdapter, nativeAndroidFramework, nativeHarmonyAdapter, nativeHarmonyFramework, nativeIosAdapter, nativeIosFramework, rnAdapter, rnFramework, suggestFramework, tauriAdapter, tauriFramework };
|
|
112
|
+
export { type DingtalkNotifyOptions, type FetchImpl, type HttpUploadImpl, type PushyApiOptions, type PushyCli, type RnUpdateOptions, electronAdapter, electronFramework, flutterAdapter, flutterFramework, knownFrameworkAdapters, nativeAndroidAdapter, nativeAndroidFramework, nativeHarmonyAdapter, nativeHarmonyFramework, nativeIosAdapter, nativeIosFramework, rnAdapter, rnFramework, suggestFramework, tauriAdapter, tauriFramework };
|
package/dist/plugins/index.mjs
CHANGED
|
@@ -3,6 +3,8 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createHmac } from "node:crypto";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
|
+
import nodeHttp from "node:http";
|
|
7
|
+
import nodeHttps from "node:https";
|
|
6
8
|
//#region src/plugins/dingtalk-notify.ts
|
|
7
9
|
/**
|
|
8
10
|
* 官方示例插件 —— 钉钉机器人通知。
|
|
@@ -67,7 +69,7 @@ function dingtalkNotify(options) {
|
|
|
67
69
|
const PUSHY_TOKEN_KEY = "PUSHY_API_TOKEN";
|
|
68
70
|
/** pushy 官方 API 根(官方 CLI 默认端点簇的主端点) */
|
|
69
71
|
const PUSHY_DEFAULT_BASE_URL = "https://update.reactnative.cn/api";
|
|
70
|
-
const UA = "
|
|
72
|
+
const UA = "react-native-update-cli/2.29.3";
|
|
71
73
|
async function query(f, baseUrl, token, api, init) {
|
|
72
74
|
let res;
|
|
73
75
|
try {
|
|
@@ -94,7 +96,7 @@ async function query(f, baseUrl, token, api, init) {
|
|
|
94
96
|
const msg = json?.message || res.statusText || `HTTP ${res.status}`;
|
|
95
97
|
throw new Error(res.status === 401 ? `pushy API 鉴权失败(token 无效或过期):请更新 credentials.json 的 ${PUSHY_TOKEN_KEY}` : `pushy API ${api} 失败(HTTP ${res.status}):${msg}`);
|
|
96
98
|
}
|
|
97
|
-
return json;
|
|
99
|
+
return json ?? {};
|
|
98
100
|
}
|
|
99
101
|
/** 文件 part 的 Content-Type(对照官方 fileContentType 的扩展名表;未列出走 octet-stream) */
|
|
100
102
|
function fileContentType(fileName) {
|
|
@@ -112,7 +114,7 @@ function fileContentType(fileName) {
|
|
|
112
114
|
* (旧 CLI 流式是为进度条与代理改写,非容量需求)。不申请 chunked(响应分支少一半,
|
|
113
115
|
* ppk 增量优化留待 diffStatus 端点,见设计说明)。
|
|
114
116
|
*/
|
|
115
|
-
async function uploadFile(f, baseUrl, token, appId, file) {
|
|
117
|
+
async function uploadFile(f, baseUrl, token, appId, file, upload) {
|
|
116
118
|
if (!fs.existsSync(file)) throw new Error(`pushy 上传文件不存在:${file}`);
|
|
117
119
|
const size = fs.statSync(file).size;
|
|
118
120
|
const instr = await query(f, baseUrl, token, "/upload", {
|
|
@@ -120,19 +122,18 @@ async function uploadFile(f, baseUrl, token, appId, file) {
|
|
|
120
122
|
body: JSON.stringify({
|
|
121
123
|
ext: path.extname(file),
|
|
122
124
|
appId,
|
|
125
|
+
chunked: true,
|
|
123
126
|
size
|
|
124
127
|
})
|
|
125
128
|
});
|
|
129
|
+
const httpUpload = upload ?? nodeHttpUpload;
|
|
126
130
|
if (instr.method === "PUT") {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
headers
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
if (!res.ok) throw new Error(`pushy 基线直传失败(HTTP ${res.status}):${res.statusText || instr.url}`);
|
|
131
|
+
const putBody = fs.readFileSync(file);
|
|
132
|
+
const res = await httpUpload("PUT", instr.url, {
|
|
133
|
+
"content-length": String(size),
|
|
134
|
+
...instr.headers ?? {}
|
|
135
|
+
}, putBody);
|
|
136
|
+
if (res.status > 299) throw new Error(`pushy 基线直传失败(HTTP ${res.status}):${instr.url}`);
|
|
136
137
|
return instr.key;
|
|
137
138
|
}
|
|
138
139
|
const fields = Object.entries(instr.formData ?? {});
|
|
@@ -145,16 +146,32 @@ async function uploadFile(f, baseUrl, token, appId, file) {
|
|
|
145
146
|
fs.readFileSync(file),
|
|
146
147
|
Buffer.from(`\r\n--${boundary}--\r\n`)
|
|
147
148
|
]);
|
|
148
|
-
const res = await
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
149
|
+
const res = await httpUpload("POST", instr.url, {
|
|
150
|
+
accept: "*/*",
|
|
151
|
+
"user-agent": UA,
|
|
152
|
+
"content-type": `multipart/form-data;boundary=${boundary}`,
|
|
153
|
+
"content-length": String(body.length)
|
|
154
|
+
}, body);
|
|
155
|
+
if (res.status > 299) throw new Error(`pushy 文件直传失败(HTTP ${res.status}):${instr.url}`);
|
|
154
156
|
const key = fields.find(([name]) => name === "key")?.[1];
|
|
155
157
|
if (!key) throw new Error(`pushy /upload 指令缺 formData.key(服务端响应异常):${instr.url}`);
|
|
156
158
|
return key;
|
|
157
159
|
}
|
|
160
|
+
async function nodeHttpUpload(method, url, headers, body) {
|
|
161
|
+
const mod = url.startsWith("https:") ? nodeHttps : nodeHttp;
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
const req = mod.request(url, {
|
|
164
|
+
method,
|
|
165
|
+
headers
|
|
166
|
+
});
|
|
167
|
+
req.on("response", (res) => {
|
|
168
|
+
res.resume();
|
|
169
|
+
resolve({ status: res.statusCode ?? 0 });
|
|
170
|
+
});
|
|
171
|
+
req.on("error", (e) => reject(/* @__PURE__ */ new Error(`pushy 直传网络错误:${url}——${e.message}`)));
|
|
172
|
+
req.end(body);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
158
175
|
/** packageVersion → packageId 精确匹配(对照 CLI update:pkg.name === packageVersion) */
|
|
159
176
|
async function bind(q, appId, versionId, packageVersion, rollout) {
|
|
160
177
|
const pkgs = await listAllPackages(q, appId);
|
|
@@ -180,6 +197,31 @@ async function listAllPackages(q, appId) {
|
|
|
180
197
|
}
|
|
181
198
|
return out;
|
|
182
199
|
}
|
|
200
|
+
/** 项目直接依赖(dependencies+devDependencies)的实装版本映射(逐级向上找 node_modules,键排序)。
|
|
201
|
+
* 对照官方 dep-versions.js 同构:服务端按 deps["react-native-update"] 做热更兼容门禁与变更提示 */
|
|
202
|
+
function readDepVersions(cwd) {
|
|
203
|
+
let pkg;
|
|
204
|
+
try {
|
|
205
|
+
pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
206
|
+
} catch {
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
const versions = {};
|
|
210
|
+
for (const dep of /* @__PURE__ */ new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})])) {
|
|
211
|
+
let dir = path.resolve(cwd);
|
|
212
|
+
for (;;) {
|
|
213
|
+
try {
|
|
214
|
+
const installed = JSON.parse(fs.readFileSync(path.join(dir, "node_modules", dep, "package.json"), "utf8"));
|
|
215
|
+
if (typeof installed.version === "string") versions[dep] = installed.version;
|
|
216
|
+
break;
|
|
217
|
+
} catch {}
|
|
218
|
+
const parent = path.dirname(dir);
|
|
219
|
+
if (parent === dir) break;
|
|
220
|
+
dir = parent;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return Object.fromEntries(Object.keys(versions).sort().map((k) => [k, versions[k]]));
|
|
224
|
+
}
|
|
183
225
|
/** OpenAPI 实现 PushyCli 的服务端操作(ppk 由调用方产出,见 PushyCli 契约注释) */
|
|
184
226
|
function createPushyApi(opts) {
|
|
185
227
|
const baseUrl = opts.baseUrl ?? "https://update.reactnative.cn/api";
|
|
@@ -187,27 +229,29 @@ function createPushyApi(opts) {
|
|
|
187
229
|
const q = (api, init) => query(f, baseUrl, opts.token, api, init);
|
|
188
230
|
return {
|
|
189
231
|
async publish(input) {
|
|
190
|
-
const hash = await uploadFile(f, baseUrl, opts.token, input.appId, input.ppk);
|
|
232
|
+
const hash = await uploadFile(f, baseUrl, opts.token, input.appId, input.ppk, opts.httpUpload);
|
|
191
233
|
const { id } = await q(`/app/${input.appId}/version/create`, {
|
|
192
234
|
method: "POST",
|
|
193
235
|
body: JSON.stringify({
|
|
194
236
|
name: input.name,
|
|
195
237
|
hash,
|
|
196
238
|
description: input.description ?? "",
|
|
197
|
-
metaInfo: ""
|
|
239
|
+
metaInfo: "",
|
|
240
|
+
deps: input.deps
|
|
198
241
|
})
|
|
199
242
|
});
|
|
200
243
|
await bind(q, input.appId, id, input.packageVersion, input.rollout);
|
|
201
244
|
return { versionId: id };
|
|
202
245
|
},
|
|
203
|
-
async uploadApp(appId, appFile, versionName) {
|
|
204
|
-
const hash = await uploadFile(f, baseUrl, opts.token, appId, appFile);
|
|
246
|
+
async uploadApp(appId, appFile, versionName, deps) {
|
|
247
|
+
const hash = await uploadFile(f, baseUrl, opts.token, appId, appFile, opts.httpUpload);
|
|
205
248
|
const { id } = await q(`/app/${appId}/package/create`, {
|
|
206
249
|
method: "POST",
|
|
207
250
|
body: JSON.stringify({
|
|
208
251
|
name: versionName,
|
|
209
252
|
hash,
|
|
210
|
-
buildTime: String(Date.now())
|
|
253
|
+
buildTime: String(Date.now()),
|
|
254
|
+
deps
|
|
211
255
|
})
|
|
212
256
|
});
|
|
213
257
|
return {
|
|
@@ -560,7 +604,8 @@ function makeRnUpdateProvider(opts, cli) {
|
|
|
560
604
|
platform: ctx.platform,
|
|
561
605
|
name,
|
|
562
606
|
description: ctx.note,
|
|
563
|
-
packageVersion: betaPackageVersion(versionName)
|
|
607
|
+
packageVersion: betaPackageVersion(versionName),
|
|
608
|
+
deps: readDepVersions(ctx.cwd)
|
|
564
609
|
})).versionId
|
|
565
610
|
};
|
|
566
611
|
},
|
|
@@ -576,7 +621,8 @@ function makeRnUpdateProvider(opts, cli) {
|
|
|
576
621
|
name,
|
|
577
622
|
description: ctx.note,
|
|
578
623
|
packageVersion: versionName,
|
|
579
|
-
rollout
|
|
624
|
+
rollout,
|
|
625
|
+
deps: readDepVersions(ctx.cwd)
|
|
580
626
|
})).versionId,
|
|
581
627
|
...rollout !== void 0 ? { rollout } : {}
|
|
582
628
|
};
|
|
@@ -626,7 +672,7 @@ function rnUpdate(opts = {}, cli) {
|
|
|
626
672
|
try {
|
|
627
673
|
const versionName = readArtifactVersion(result.artifactPath)?.versionName;
|
|
628
674
|
if (!versionName) throw new Error(`未能从产物读出 versionName:${result.artifactPath}(readArtifactVersion 不支持该格式?)`);
|
|
629
|
-
await resolvePushyClient(opts, cli, hookCtx.credentials).uploadApp(appId, result.artifactPath, versionName);
|
|
675
|
+
await resolvePushyClient(opts, cli, hookCtx.credentials).uploadApp(appId, result.artifactPath, versionName, readDepVersions(hookCtx.cwd));
|
|
630
676
|
} catch (e) {
|
|
631
677
|
process.stderr.write(`⚠ pushy 基线上传失败(请人工补传):${e.message}\n`);
|
|
632
678
|
}
|
|
@@ -634,4 +680,4 @@ function rnUpdate(opts = {}, cli) {
|
|
|
634
680
|
};
|
|
635
681
|
}
|
|
636
682
|
//#endregion
|
|
637
|
-
export { PUSHY_DEFAULT_BASE_URL, PUSHY_TOKEN_KEY, createPushyApi, dingtalkNotify, electronAdapter, electronFramework, flutterAdapter, flutterFramework, judgeDelivery, knownFrameworkAdapters, makeRnUpdateProvider, nativeAndroidAdapter, nativeAndroidFramework, nativeHarmonyAdapter, nativeHarmonyFramework, nativeIosAdapter, nativeIosFramework, rnAdapter, rnFramework, rnUpdate, suggestFramework, tauriAdapter, tauriFramework };
|
|
683
|
+
export { PUSHY_DEFAULT_BASE_URL, PUSHY_TOKEN_KEY, betaPackageVersion, createPushyApi, dingtalkNotify, electronAdapter, electronFramework, flutterAdapter, flutterFramework, judgeDelivery, knownFrameworkAdapters, makeRnUpdateProvider, nativeAndroidAdapter, nativeAndroidFramework, nativeHarmonyAdapter, nativeHarmonyFramework, nativeIosAdapter, nativeIosFramework, nodeHttpUpload, readDepVersions, rnAdapter, rnFramework, rnUpdate, suggestFramework, tauriAdapter, tauriFramework };
|