@co0ontty/wand 4.5.0 → 4.6.1-beta.g27502fa
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/build-info.json +4 -4
- package/dist/distribution-manager.d.ts +5 -0
- package/dist/distribution-manager.js +9 -1
- package/dist/git-quick-commit.js +71 -11
- package/dist/web-ui/content/scripts.js +35 -35
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-07-
|
|
4
|
-
"version": "4.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "27502faec54a3b247afebfe8c6c0be798082bd2a",
|
|
3
|
+
"builtAt": "2026-07-16T03:23:16.959Z",
|
|
4
|
+
"version": "4.6.1-beta.g27502fa",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
|
@@ -29,6 +29,11 @@ export interface DistributionManagerOptions {
|
|
|
29
29
|
fetch?: typeof fetch;
|
|
30
30
|
now?: () => number;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* GitHub Release 正文还包含 Android/macOS/iOS 的安装指引;它们属于发布页,
|
|
34
|
+
* 不该出现在 Android 的更新弹窗。保留分隔线前的变更摘要,并兼容旧版正文。
|
|
35
|
+
*/
|
|
36
|
+
export declare function extractUpdateSummary(releaseBody: string): string;
|
|
32
37
|
export declare class DistributionManager {
|
|
33
38
|
private readonly options;
|
|
34
39
|
private readonly fetchImpl;
|
|
@@ -2,6 +2,14 @@ import { mkdir, readdir, readFile, stat } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { compareApkInstallOrder, compareSemver, extractSemver } from "./version-utils.js";
|
|
4
4
|
const GITHUB_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
5
|
+
/**
|
|
6
|
+
* GitHub Release 正文还包含 Android/macOS/iOS 的安装指引;它们属于发布页,
|
|
7
|
+
* 不该出现在 Android 的更新弹窗。保留分隔线前的变更摘要,并兼容旧版正文。
|
|
8
|
+
*/
|
|
9
|
+
export function extractUpdateSummary(releaseBody) {
|
|
10
|
+
const summary = releaseBody.split(/\r?\n---\s*(?:\r?\n|$)/, 1)[0]?.trim() ?? "";
|
|
11
|
+
return summary.slice(0, 500);
|
|
12
|
+
}
|
|
5
13
|
function asRecord(value) {
|
|
6
14
|
return value && typeof value === "object" ? value : null;
|
|
7
15
|
}
|
|
@@ -215,7 +223,7 @@ export class DistributionManager {
|
|
|
215
223
|
downloadUrl: hit.asset.browser_download_url,
|
|
216
224
|
fileName: hit.asset.name,
|
|
217
225
|
size: hit.asset.size,
|
|
218
|
-
...(extension === ".apk" && hit.body ? { releaseNotes: hit.body
|
|
226
|
+
...(extension === ".apk" && hit.body ? { releaseNotes: extractUpdateSummary(hit.body) } : {}),
|
|
219
227
|
};
|
|
220
228
|
this.githubCache.set(extension, { asset, timestamp: this.now() });
|
|
221
229
|
return asset;
|
package/dist/git-quick-commit.js
CHANGED
|
@@ -81,6 +81,58 @@ async function resolvePushRemoteAsync(cwd) {
|
|
|
81
81
|
catch { /* use origin */ }
|
|
82
82
|
return "origin";
|
|
83
83
|
}
|
|
84
|
+
function githubSshPushUrl(remoteUrl) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = new URL(remoteUrl);
|
|
87
|
+
if (parsed.protocol !== "https:" || parsed.hostname.toLowerCase() !== "github.com")
|
|
88
|
+
return undefined;
|
|
89
|
+
const path = decodeURIComponent(parsed.pathname).replace(/^\/+|\/+$/g, "");
|
|
90
|
+
const parts = path.split("/");
|
|
91
|
+
if (parts.length !== 2 || parts.some((part) => !part))
|
|
92
|
+
return undefined;
|
|
93
|
+
return `git@github.com:${parts[0]}/${parts[1]}`;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function isHttpsCredentialError(error) {
|
|
100
|
+
return /could not read (?:User|Pass)name|authentication failed|terminal prompts disabled|credential[^\n]*(?:failed|missing)|http basic: access denied/i
|
|
101
|
+
.test(getGitErrorMessage(error));
|
|
102
|
+
}
|
|
103
|
+
async function resolveRemotePushUrl(cwd, remote) {
|
|
104
|
+
try {
|
|
105
|
+
return await runGitAsync(["remote", "get-url", "--push", remote], cwd);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return remote;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Push explicit refs through one transport seam. A Wand service deliberately disables interactive
|
|
113
|
+
* credential prompts, so a GitHub HTTPS remote that has no non-interactive credential would
|
|
114
|
+
* otherwise fail even when the same machine is already configured for GitHub SSH.
|
|
115
|
+
*/
|
|
116
|
+
async function pushRemoteRefs(cwd, remote, options = [], refs = []) {
|
|
117
|
+
try {
|
|
118
|
+
await runGitAsync(["push", ...options, remote, ...refs], cwd, GIT_PUSH_TIMEOUT_MS);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (!isHttpsCredentialError(error))
|
|
123
|
+
throw error;
|
|
124
|
+
const configuredUrl = await resolveRemotePushUrl(cwd, remote);
|
|
125
|
+
const sshUrl = githubSshPushUrl(configuredUrl);
|
|
126
|
+
if (!sshUrl)
|
|
127
|
+
throw error;
|
|
128
|
+
try {
|
|
129
|
+
await runGitAsync(["push", ...options, sshUrl, ...refs], cwd, GIT_PUSH_TIMEOUT_MS);
|
|
130
|
+
}
|
|
131
|
+
catch (sshError) {
|
|
132
|
+
throw new Error(`${getGitErrorMessage(error)}\nGitHub SSH 回退也失败:${getGitErrorMessage(sshError)}`, { cause: sshError });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
84
136
|
function unquotePath(raw) {
|
|
85
137
|
if (raw.startsWith("\"") && raw.endsWith("\"")) {
|
|
86
138
|
return raw.slice(1, -1).replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
@@ -751,21 +803,21 @@ async function doPush(opts) {
|
|
|
751
803
|
try {
|
|
752
804
|
if (pushCommits) {
|
|
753
805
|
if (hasUpstream) {
|
|
754
|
-
await
|
|
806
|
+
await pushRemoteRefs(cwd, pushRemote, [recurseFlag]);
|
|
755
807
|
}
|
|
756
808
|
else {
|
|
757
|
-
await
|
|
809
|
+
await pushRemoteRefs(cwd, pushRemote, ["-u", recurseFlag], ["HEAD"]);
|
|
758
810
|
}
|
|
759
811
|
pushedCommits = true;
|
|
760
812
|
}
|
|
761
813
|
if (pushTags) {
|
|
762
814
|
if (Array.isArray(pushTags)) {
|
|
763
815
|
for (const name of pushTags) {
|
|
764
|
-
await
|
|
816
|
+
await pushRemoteRefs(cwd, pushRemote, [], [`refs/tags/${name}`]);
|
|
765
817
|
}
|
|
766
818
|
}
|
|
767
819
|
else {
|
|
768
|
-
await
|
|
820
|
+
await pushRemoteRefs(cwd, pushRemote, ["--tags"]);
|
|
769
821
|
}
|
|
770
822
|
pushedTags = true;
|
|
771
823
|
}
|
|
@@ -1012,7 +1064,7 @@ async function pushSubmodules(parentCwd, subInfos, opts) {
|
|
|
1012
1064
|
continue;
|
|
1013
1065
|
}
|
|
1014
1066
|
try {
|
|
1015
|
-
await
|
|
1067
|
+
await pushRemoteRefs(subCwd, info.remote, [], [`HEAD:refs/heads/${info.branch}`]);
|
|
1016
1068
|
}
|
|
1017
1069
|
catch (error) {
|
|
1018
1070
|
errors.push(`submodule ${info.path} 推送失败:${getGitErrorMessage(error)}`);
|
|
@@ -1020,7 +1072,7 @@ async function pushSubmodules(parentCwd, subInfos, opts) {
|
|
|
1020
1072
|
}
|
|
1021
1073
|
if (opts.pushTags && opts.tagName) {
|
|
1022
1074
|
try {
|
|
1023
|
-
await
|
|
1075
|
+
await pushRemoteRefs(subCwd, info.remote, [], [`refs/tags/${opts.tagName}`]);
|
|
1024
1076
|
}
|
|
1025
1077
|
catch (error) {
|
|
1026
1078
|
errors.push(`submodule ${info.path} 推送 tag 失败:${getGitErrorMessage(error)}`);
|
|
@@ -1312,6 +1364,13 @@ export async function runQuickCommit(opts) {
|
|
|
1312
1364
|
if (!tagName && autoTag) {
|
|
1313
1365
|
tagName = await generateTagAfterCommit(cwd, language, message, ai);
|
|
1314
1366
|
}
|
|
1367
|
+
let submodulePushInfos = submoduleOutcome.commits;
|
|
1368
|
+
if (submodule) {
|
|
1369
|
+
// The selected scope is the declared submodule set, not merely the submodules dirtied by this
|
|
1370
|
+
// request. A clean submodule can still have an unpushed HEAD or a pre-existing pointer change.
|
|
1371
|
+
const collected = await collectSubmodulesForPush(cwd);
|
|
1372
|
+
submodulePushInfos = collected.infos;
|
|
1373
|
+
}
|
|
1315
1374
|
if (tagName) {
|
|
1316
1375
|
try {
|
|
1317
1376
|
await runGitAsync(["tag", tagName], cwd);
|
|
@@ -1319,9 +1378,10 @@ export async function runQuickCommit(opts) {
|
|
|
1319
1378
|
catch (error) {
|
|
1320
1379
|
throw new QuickCommitError(`git tag 失败:${getGitErrorMessage(error)}`, "GIT_TAG_FAILED");
|
|
1321
1380
|
}
|
|
1322
|
-
// 纳入 submodule
|
|
1323
|
-
|
|
1324
|
-
|
|
1381
|
+
// 纳入 submodule 时给全部声明的 submodule 当前 HEAD 打同名 tag。这样纯指针变化或
|
|
1382
|
+
// 已提前提交但尚未推送的 submodule 也不会漏掉发布 tag。
|
|
1383
|
+
if (submodule && submodulePushInfos.length > 0) {
|
|
1384
|
+
await tagSubmodules(cwd, submodulePushInfos, tagName);
|
|
1325
1385
|
}
|
|
1326
1386
|
}
|
|
1327
1387
|
let pushed = false;
|
|
@@ -1330,10 +1390,10 @@ export async function runQuickCommit(opts) {
|
|
|
1330
1390
|
// 纳入 submodule:先把各 submodule 的 HEAD(+ 同名 tag)分别推到各自远端分支,
|
|
1331
1391
|
// 解决 detached HEAD 无法被父仓库 on-demand 递归推送的问题;父仓库随后用
|
|
1332
1392
|
// recurse=no 单独推(submodule 已就绪)。否则父仓库用 recurse=check 做安全校验。
|
|
1333
|
-
const includeSub = !!submodule &&
|
|
1393
|
+
const includeSub = !!submodule && submodulePushInfos.length > 0;
|
|
1334
1394
|
let subPushErrors = [];
|
|
1335
1395
|
if (includeSub) {
|
|
1336
|
-
const subPush = await pushSubmodules(cwd,
|
|
1396
|
+
const subPush = await pushSubmodules(cwd, submodulePushInfos, { pushTags: !!tagName, tagName });
|
|
1337
1397
|
subPushErrors = subPush.errors;
|
|
1338
1398
|
}
|
|
1339
1399
|
if (subPushErrors.length > 0) {
|