shipup 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/CHANGELOG.md +25 -0
- package/LICENSE +14 -0
- package/README.md +112 -0
- package/README.zh-CN.md +105 -0
- package/SECURITY.md +32 -0
- package/creds.example.yaml +62 -0
- package/dist/adapters/android/honor/api.js +360 -0
- package/dist/adapters/android/honor/api.js.map +1 -0
- package/dist/adapters/android/honor/index.js +122 -0
- package/dist/adapters/android/honor/index.js.map +1 -0
- package/dist/adapters/android/huawei/api.js +389 -0
- package/dist/adapters/android/huawei/api.js.map +1 -0
- package/dist/adapters/android/huawei/index.js +128 -0
- package/dist/adapters/android/huawei/index.js.map +1 -0
- package/dist/adapters/android/meizu/api.js +367 -0
- package/dist/adapters/android/meizu/api.js.map +1 -0
- package/dist/adapters/android/meizu/index.js +129 -0
- package/dist/adapters/android/meizu/index.js.map +1 -0
- package/dist/adapters/android/oppo/api.js +397 -0
- package/dist/adapters/android/oppo/api.js.map +1 -0
- package/dist/adapters/android/oppo/index.js +135 -0
- package/dist/adapters/android/oppo/index.js.map +1 -0
- package/dist/adapters/android/qq/api.js +184 -0
- package/dist/adapters/android/qq/api.js.map +1 -0
- package/dist/adapters/android/qq/index.js +80 -0
- package/dist/adapters/android/qq/index.js.map +1 -0
- package/dist/adapters/android/samsung/api.js +355 -0
- package/dist/adapters/android/samsung/api.js.map +1 -0
- package/dist/adapters/android/samsung/index.js +111 -0
- package/dist/adapters/android/samsung/index.js.map +1 -0
- package/dist/adapters/android/specs.js +106 -0
- package/dist/adapters/android/specs.js.map +1 -0
- package/dist/adapters/android/stub.js +27 -0
- package/dist/adapters/android/stub.js.map +1 -0
- package/dist/adapters/android/vivo/api.js +236 -0
- package/dist/adapters/android/vivo/api.js.map +1 -0
- package/dist/adapters/android/vivo/index.js +121 -0
- package/dist/adapters/android/vivo/index.js.map +1 -0
- package/dist/adapters/android/xiaomi/api.js +163 -0
- package/dist/adapters/android/xiaomi/api.js.map +1 -0
- package/dist/adapters/android/xiaomi/index.js +108 -0
- package/dist/adapters/android/xiaomi/index.js.map +1 -0
- package/dist/adapters/ios/appstore/asc.js +280 -0
- package/dist/adapters/ios/appstore/asc.js.map +1 -0
- package/dist/adapters/ios/appstore/index.js +323 -0
- package/dist/adapters/ios/appstore/index.js.map +1 -0
- package/dist/adapters/registry.js +34 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/util.js +26 -0
- package/dist/adapters/util.js.map +1 -0
- package/dist/cli/index.js +479 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output.js +65 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/core/exit.js +20 -0
- package/dist/core/exit.js.map +1 -0
- package/dist/core/orchestrator.js +24 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/types.js +20 -0
- package/dist/core/types.js.map +1 -0
- package/dist/creds/load.js +68 -0
- package/dist/creds/load.js.map +1 -0
- package/dist/creds/schema.js +30 -0
- package/dist/creds/schema.js.map +1 -0
- package/dist/infra/crypto.js +12 -0
- package/dist/infra/crypto.js.map +1 -0
- package/dist/infra/http.js +87 -0
- package/dist/infra/http.js.map +1 -0
- package/dist/pkginfo/apk-icon.js +89 -0
- package/dist/pkginfo/apk-icon.js.map +1 -0
- package/dist/pkginfo/icon.js +83 -0
- package/dist/pkginfo/icon.js.map +1 -0
- package/dist/pkginfo/index.js +33 -0
- package/dist/pkginfo/index.js.map +1 -0
- package/docs/android-markets.md +190 -0
- package/docs/credentials.md +72 -0
- package/docs/providers.md +60 -0
- package/lib/common.mjs +265 -0
- package/lib/harmony.mjs +190 -0
- package/lib/huawei.mjs +153 -0
- package/lib/ios.mjs +477 -0
- package/package.json +70 -0
- package/shipup.mjs +160 -0
package/shipup.mjs
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// shipup — mobile application release CLI
|
|
3
|
+
//
|
|
4
|
+
// shipup harmony upload|submit|status AppGallery(HarmonyOS .app,Publishing API v3)
|
|
5
|
+
// shipup huawei upload|status 华为应用市场兼容命令(安卓 APK,Publish API v2)
|
|
6
|
+
// shipup android upload|status Android 八渠道发布
|
|
7
|
+
// shipup ios upload|submit|status|release App Store(altool + ASC API)
|
|
8
|
+
//
|
|
9
|
+
// 设计不变量:无业务逻辑,appId/密钥全由凭证文件注入;JSON→stdout、
|
|
10
|
+
// 诊断→stderr;GET 才自动重试;统一退出码 0/2/3/4/5/124;--dry-run 不发请求。
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { CliError, ExitCode, log, loadPlatformCreds } from "./lib/common.mjs";
|
|
15
|
+
import * as harmony from "./lib/harmony.mjs";
|
|
16
|
+
import * as huawei from "./lib/huawei.mjs";
|
|
17
|
+
import * as ios from "./lib/ios.mjs";
|
|
18
|
+
|
|
19
|
+
const PLATFORMS = {
|
|
20
|
+
harmony: { module: harmony, commands: ["upload", "submit", "status"], channel: "appgallery" },
|
|
21
|
+
huawei: { module: huawei, commands: ["upload", "status"], channel: "huawei" },
|
|
22
|
+
ios: { module: ios, commands: ["upload", "submit", "status", "release"], channel: "appstore" },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const VERSION = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")).version;
|
|
26
|
+
const DEFAULT_CREDS = join(homedir(), ".config", "shipup", "credentials.yaml");
|
|
27
|
+
|
|
28
|
+
const USAGE = `shipup — 跨平台应用商店发布 CLI
|
|
29
|
+
|
|
30
|
+
用法:
|
|
31
|
+
shipup harmony upload --package <path.app> [--creds <file>] [--release-note <t|@f>] [--lang] [--no-wait]
|
|
32
|
+
shipup harmony submit [--creds <file>] [--release-note] [--remark <10-300字>] [--release-time <t>] [--phased --phased-desc <t>]
|
|
33
|
+
shipup harmony status [--creds <file>]
|
|
34
|
+
|
|
35
|
+
shipup huawei upload --package <path.apk> [--creds <file>] [--release-note <t|@f>] [--submit-review]
|
|
36
|
+
shipup huawei status [--creds <file>]
|
|
37
|
+
|
|
38
|
+
shipup android upload --upload <渠道=path.apk...> [--creds <file>] [--submit-review] [--dry-run]
|
|
39
|
+
shipup android status --channel <渠道> [--creds <file>]
|
|
40
|
+
渠道: huawei | honor | oppo | vivo | xiaomi | samsung | qq | meizu
|
|
41
|
+
|
|
42
|
+
shipup ios upload --package <path.ipa> [--creds <file>] [--submit-review] [--whats-new <t|@f>] [--metadata <yaml>] [--release-type MANUAL|AFTER_APPROVAL]
|
|
43
|
+
shipup ios submit --app-version <1.0.0> --build-version <1> [--creds <file>] [--bundle-id <id>] [--whats-new] [--metadata] [--release-type]
|
|
44
|
+
shipup ios status --app-version <1.0.0> [--creds <file>] [--bundle-id <id>]
|
|
45
|
+
shipup ios release --app-version <1.0.0> [--creds <file>] [--bundle-id <id>] [--phased | --complete | --pause | --resume]
|
|
46
|
+
(兼容旧参数 --version / --build)
|
|
47
|
+
|
|
48
|
+
通用选项:
|
|
49
|
+
--creds <file> 凭证 YAML;默认读取 SHIPUP_CREDS 或 ~/.config/shipup/credentials.yaml
|
|
50
|
+
--output json|text 输出格式(默认 text;json 时 stdout 只有结果 JSON)
|
|
51
|
+
--timeout <s> 总超时秒数(默认 900)
|
|
52
|
+
--dry-run 只校验参数、凭证与软件包,不发任何请求
|
|
53
|
+
|
|
54
|
+
退出码: 0 成功 / 2 失败 / 3 用法错误 / 4 凭证错误 / 5 输入缺失 / 124 超时`;
|
|
55
|
+
|
|
56
|
+
const FLAG_KEYS = new Set(["--dry-run", "--no-wait", "--phased", "--submit-review", "--complete", "--pause", "--resume"]);
|
|
57
|
+
|
|
58
|
+
function parseArgs(argv) {
|
|
59
|
+
const [platform, command, ...rest] = argv;
|
|
60
|
+
if (["-v", "--version", "version"].includes(platform)) {
|
|
61
|
+
console.log(VERSION);
|
|
62
|
+
process.exit(ExitCode.OK);
|
|
63
|
+
}
|
|
64
|
+
if (!platform || ["-h", "--help", "help"].includes(platform)) {
|
|
65
|
+
console.log(USAGE);
|
|
66
|
+
process.exit(ExitCode.OK);
|
|
67
|
+
}
|
|
68
|
+
const p = PLATFORMS[platform];
|
|
69
|
+
if (!p) throw new CliError(ExitCode.USAGE, `未知平台: ${platform}(支持 harmony | huawei | ios)\n\n${USAGE}`);
|
|
70
|
+
if (!command || !p.commands.includes(command)) {
|
|
71
|
+
throw new CliError(ExitCode.USAGE, `${platform} 支持的命令: ${p.commands.join(" | ")}(收到 ${command ?? "无"})`);
|
|
72
|
+
}
|
|
73
|
+
const opts = { platform, command, output: "text", timeout: 900 };
|
|
74
|
+
for (let i = 0; i < rest.length; i++) {
|
|
75
|
+
const key = rest[i];
|
|
76
|
+
if (!key.startsWith("--")) throw new CliError(ExitCode.USAGE, `意外的参数: ${key}`);
|
|
77
|
+
const name = key.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
78
|
+
if (FLAG_KEYS.has(key)) {
|
|
79
|
+
opts[name] = true;
|
|
80
|
+
} else {
|
|
81
|
+
const val = rest[++i];
|
|
82
|
+
if (val === undefined) throw new CliError(ExitCode.USAGE, `${key} 缺少值`);
|
|
83
|
+
opts[name] = val;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
opts.timeout = Number(opts.timeout);
|
|
87
|
+
if (!Number.isFinite(opts.timeout) || opts.timeout <= 0) throw new CliError(ExitCode.USAGE, "--timeout 必须是正数(秒)");
|
|
88
|
+
if (!["json", "text"].includes(opts.output)) throw new CliError(ExitCode.USAGE, "--output 只能是 json 或 text");
|
|
89
|
+
opts.creds = opts.creds || process.env.SHIPUP_CREDS || (existsSync(DEFAULT_CREDS) ? DEFAULT_CREDS : "");
|
|
90
|
+
if (!opts.creds) {
|
|
91
|
+
throw new CliError(
|
|
92
|
+
ExitCode.CREDS,
|
|
93
|
+
"未找到凭证:请传 --creds、设置 SHIPUP_CREDS,或创建 ~/.config/shipup/credentials.yaml",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (command === "upload" && !opts.package) throw new CliError(ExitCode.USAGE, "upload 需要 --package");
|
|
97
|
+
if (platform === "harmony") {
|
|
98
|
+
if (opts.phased && command === "submit" && !opts.phasedDesc) {
|
|
99
|
+
throw new CliError(ExitCode.USAGE, "--phased 时必须提供 --phased-desc(官方要求 phasedReleaseDescription 必填)");
|
|
100
|
+
}
|
|
101
|
+
if (opts.remark && (opts.remark.length < 10 || opts.remark.length > 300)) {
|
|
102
|
+
throw new CliError(ExitCode.USAGE, "--remark 长度必须在 10-300 之间");
|
|
103
|
+
}
|
|
104
|
+
if (opts.releaseTime && !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4}$/.test(opts.releaseTime)) {
|
|
105
|
+
throw new CliError(ExitCode.USAGE, "--release-time 格式须为 yyyy-MM-ddTHH:mm:ss+0800");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return opts;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function main() {
|
|
112
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
113
|
+
const { module, channel } = PLATFORMS[opts.platform];
|
|
114
|
+
const creds = loadPlatformCreds(opts.creds, opts.platform);
|
|
115
|
+
const deadline = Date.now() + opts.timeout * 1000;
|
|
116
|
+
const started = Date.now();
|
|
117
|
+
|
|
118
|
+
let result, ok = true, errorCode = "", message = "";
|
|
119
|
+
try {
|
|
120
|
+
result = await module[opts.command](creds, opts, deadline);
|
|
121
|
+
message = result.message ?? "";
|
|
122
|
+
} catch (e) {
|
|
123
|
+
if (!(e instanceof CliError)) throw e;
|
|
124
|
+
ok = false;
|
|
125
|
+
errorCode = e.errorCode || String(e.exitCode);
|
|
126
|
+
message = e.message;
|
|
127
|
+
result = { status: "failed" };
|
|
128
|
+
if (opts.output !== "json") log(`✗ ${message}`);
|
|
129
|
+
process.exitCode = e.exitCode;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const payload = {
|
|
133
|
+
tool: "shipup",
|
|
134
|
+
platform: opts.platform,
|
|
135
|
+
command: opts.command,
|
|
136
|
+
ok,
|
|
137
|
+
appId: creds.app_id ?? "",
|
|
138
|
+
packageName: creds.package_name ?? creds.bundle_id ?? "",
|
|
139
|
+
summary: { total: 1, succeeded: ok ? 1 : 0, failed: ok ? 0 : 1, skipped: result.status === "skipped" ? 1 : 0 },
|
|
140
|
+
results: [{ channel, ...result, errorCode, message, durationMs: Date.now() - started }],
|
|
141
|
+
};
|
|
142
|
+
if (opts.output === "json") {
|
|
143
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
144
|
+
} else if (ok) {
|
|
145
|
+
console.log(`${opts.platform} ${opts.command} ${result.status}${message ? `: ${message}` : ""}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (["android", "ios"].includes(process.argv[2])) {
|
|
150
|
+
await import("./dist/cli/index.js");
|
|
151
|
+
} else {
|
|
152
|
+
main().catch((e) => {
|
|
153
|
+
if (e instanceof CliError) {
|
|
154
|
+
console.error(e.message);
|
|
155
|
+
process.exit(e.exitCode);
|
|
156
|
+
}
|
|
157
|
+
console.error(e.stack || String(e));
|
|
158
|
+
process.exit(ExitCode.FAIL);
|
|
159
|
+
});
|
|
160
|
+
}
|