@qomicex/cli 0.1.2 → 0.1.4
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/commands/pack.js +10 -5
- package/dist/commands/publish.js +21 -14
- package/dist/index.js +1 -0
- package/dist/lib/signature.js +25 -1
- package/dist/lib/store.js +4 -1
- package/package.json +1 -1
package/dist/commands/pack.js
CHANGED
|
@@ -48,21 +48,26 @@ export async function packCommand(opts = {}) {
|
|
|
48
48
|
if (!id)
|
|
49
49
|
fail('manifest.json 缺少 id');
|
|
50
50
|
let entries = buildPackageEntries(root, manifest);
|
|
51
|
-
//
|
|
51
|
+
// 可选签名(signature.json + signature.cert.json;无商店证书时生成自签证书)
|
|
52
52
|
if (opts.key) {
|
|
53
53
|
const keyContent = await readKeyInput(opts.key);
|
|
54
54
|
const priv = parsePrivateKey(keyContent);
|
|
55
|
-
const { derivePublicKey } = await import("../lib/signature.js");
|
|
55
|
+
const { derivePublicKey, makeSelfSignedCert } = await import("../lib/signature.js");
|
|
56
56
|
const pub = await derivePublicKey(priv);
|
|
57
57
|
const keyId = `ed25519:${pub.slice(0, 8)}`;
|
|
58
58
|
const localCert = join(root, 'signature.cert.json');
|
|
59
59
|
let certJson;
|
|
60
|
-
if (existsSync(localCert))
|
|
60
|
+
if (existsSync(localCert)) {
|
|
61
61
|
certJson = readFileSync(localCert, 'utf8');
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const name = String(manifest.name ?? '');
|
|
65
|
+
certJson = await makeSelfSignedCert(priv, keyId, name);
|
|
66
|
+
writeFileSync(localCert, certJson);
|
|
67
|
+
warn('未找到本地签名证书,已生成自签证书并写入 signature.cert.json;上传商店需先在开发者中心注册对应公钥');
|
|
68
|
+
}
|
|
62
69
|
const sig = await signPackage(entries, priv, keyId, certJson);
|
|
63
70
|
entries = { ...entries, ...sig };
|
|
64
|
-
if (!certJson)
|
|
65
|
-
warn('缺少本地 signature.cert.json,商店上传仍需证书;建议改用 qomicex publish 走完整签名');
|
|
66
71
|
}
|
|
67
72
|
const outDir = resolve(root, opts.outDir ?? 'release');
|
|
68
73
|
mkdirSync(outDir, { recursive: true });
|
package/dist/commands/publish.js
CHANGED
|
@@ -40,18 +40,25 @@ export async function publishCommand(opts = {}) {
|
|
|
40
40
|
// 清掉旧签名文件,避免脏残留
|
|
41
41
|
for (const k of ['signature.json', 'signature.cert.json'])
|
|
42
42
|
delete entries[k];
|
|
43
|
-
// 3)
|
|
43
|
+
// 3) 认证:QOMICEX_API_KEY 存在则跳过设备流(CI 场景),否则设备流登录
|
|
44
44
|
const api = opts.api ?? process.env.QOMICEX_STORE_API ?? DEFAULT_API_BASE;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
let token = '';
|
|
46
|
+
if (process.env.QOMICEX_API_KEY) {
|
|
47
|
+
info('==> 使用 API Key 认证(CI 模式)');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
info('==> 设备流登录');
|
|
51
|
+
const code = await requestDeviceCode(api);
|
|
52
|
+
info(` 请在浏览器打开(或扫码)并登录确认:`);
|
|
53
|
+
info(` ${code.verificationUriComplete}`);
|
|
54
|
+
info(` 授权码: ${code.userCode}`);
|
|
55
|
+
const login = await pollDeviceLogin(api, code);
|
|
56
|
+
token = login.accessToken;
|
|
57
|
+
info(`✔ 登录成功:${login.user.username}`);
|
|
58
|
+
}
|
|
52
59
|
// 4) 注册/刷新签名公钥,获取商店根钥签发的证书
|
|
53
60
|
info('==> 注册签名公钥');
|
|
54
|
-
const keyRes = await registerDevKey(api,
|
|
61
|
+
const keyRes = await registerDevKey(api, token, publicKey);
|
|
55
62
|
const certJson = JSON.stringify(keyRes.cert);
|
|
56
63
|
info(`✔ 签名证书就绪(keyId: ${keyRes.keyId})`);
|
|
57
64
|
// 5) 签名
|
|
@@ -61,7 +68,7 @@ export async function publishCommand(opts = {}) {
|
|
|
61
68
|
// 6) 查找/创建插件记录
|
|
62
69
|
info('==> 确认插件记录');
|
|
63
70
|
let pluginId = '';
|
|
64
|
-
const mine = await fetchMinePlugins(api,
|
|
71
|
+
const mine = await fetchMinePlugins(api, token);
|
|
65
72
|
const existing = mine.find((p) => p.slug === slug);
|
|
66
73
|
if (existing) {
|
|
67
74
|
pluginId = existing.id;
|
|
@@ -69,7 +76,7 @@ export async function publishCommand(opts = {}) {
|
|
|
69
76
|
}
|
|
70
77
|
else {
|
|
71
78
|
try {
|
|
72
|
-
const created = await createPlugin(api,
|
|
79
|
+
const created = await createPlugin(api, token, {
|
|
73
80
|
slug,
|
|
74
81
|
name: String(manifest.name ?? slug),
|
|
75
82
|
description: String(manifest.description ?? ''),
|
|
@@ -80,7 +87,7 @@ export async function publishCommand(opts = {}) {
|
|
|
80
87
|
}
|
|
81
88
|
catch (e) {
|
|
82
89
|
if (e instanceof StoreApiError && e.status === 409) {
|
|
83
|
-
const again = await fetchMinePlugins(api,
|
|
90
|
+
const again = await fetchMinePlugins(api, token);
|
|
84
91
|
const found = again.find((p) => p.slug === slug);
|
|
85
92
|
if (!found)
|
|
86
93
|
throw e;
|
|
@@ -98,13 +105,13 @@ export async function publishCommand(opts = {}) {
|
|
|
98
105
|
info(` 插件: ${slug} v${version}`);
|
|
99
106
|
info(` keyId: ${keyRes.keyId}`);
|
|
100
107
|
info(` 包体: ${signedBytes.length} 字节(已签名)`);
|
|
101
|
-
const ok = await confirm('确认发布到商店?', opts.yes ??
|
|
108
|
+
const ok = await confirm('确认发布到商店?', opts.yes ?? !!process.env.QOMICEX_API_KEY);
|
|
102
109
|
if (!ok) {
|
|
103
110
|
warn('已取消发布');
|
|
104
111
|
return;
|
|
105
112
|
}
|
|
106
113
|
info('==> 上传版本');
|
|
107
|
-
const result = await uploadVersion(api,
|
|
114
|
+
const result = await uploadVersion(api, token, pluginId, signedBytes, opts.changelog);
|
|
108
115
|
info(`✔ 上传完成:${slug} v${version}`);
|
|
109
116
|
info(` 商店地址: ${api.replace('/api/v1', '')}`);
|
|
110
117
|
if (typeof result === 'object' && result && 'status' in result) {
|
package/dist/index.js
CHANGED
package/dist/lib/signature.js
CHANGED
|
@@ -98,10 +98,34 @@ async function signBytes(privDer, msg) {
|
|
|
98
98
|
const sig = await crypto.subtle.sign({ name: ALG }, key, msg);
|
|
99
99
|
return bytesToBase64(new Uint8Array(sig));
|
|
100
100
|
}
|
|
101
|
+
/** 自签开发者证书(开发者私钥签证书体):未注册商店密钥时的离线打包/本地验证用,商店上传需对应公钥已注册。 */
|
|
102
|
+
export async function makeSelfSignedCert(privDer, keyId, developerName = '') {
|
|
103
|
+
const { derivePublicKey } = await import("./signature.js");
|
|
104
|
+
const publicKey = await derivePublicKey(privDer);
|
|
105
|
+
const body = {
|
|
106
|
+
alg: ALG,
|
|
107
|
+
keyId,
|
|
108
|
+
developerId: '',
|
|
109
|
+
developerName,
|
|
110
|
+
publicKey,
|
|
111
|
+
issuedAt: new Date().toISOString(),
|
|
112
|
+
signature: '',
|
|
113
|
+
};
|
|
114
|
+
const certBody = canonicalJson({
|
|
115
|
+
alg: body.alg,
|
|
116
|
+
keyId: body.keyId,
|
|
117
|
+
developerId: body.developerId,
|
|
118
|
+
developerName: body.developerName,
|
|
119
|
+
publicKey: body.publicKey,
|
|
120
|
+
issuedAt: body.issuedAt,
|
|
121
|
+
});
|
|
122
|
+
body.signature = await signBytes(privDer, new TextEncoder().encode(certBody));
|
|
123
|
+
return JSON.stringify(body);
|
|
124
|
+
}
|
|
101
125
|
/**
|
|
102
126
|
* 对包内全部条目生成签名文件。
|
|
103
127
|
* @param certJson 商店根钥签发的开发者证书 JSON(POST /developer/keys 返回值),
|
|
104
|
-
*
|
|
128
|
+
* 省略时仅生成 signature.json(publish 场景由调用方传证书)。
|
|
105
129
|
*/
|
|
106
130
|
export async function signPackage(entries, privDer, keyId, certJson) {
|
|
107
131
|
const payload = await signedPayloadBytes(entries);
|
package/dist/lib/store.js
CHANGED
|
@@ -13,7 +13,10 @@ export class StoreApiError extends Error {
|
|
|
13
13
|
}
|
|
14
14
|
async function request(base, path, init, token) {
|
|
15
15
|
const headers = new Headers(init?.headers);
|
|
16
|
-
|
|
16
|
+
const apiKey = process.env.QOMICEX_API_KEY;
|
|
17
|
+
if (apiKey)
|
|
18
|
+
headers.set('X-API-Key', apiKey);
|
|
19
|
+
else if (token)
|
|
17
20
|
headers.set('Authorization', `Bearer ${token}`);
|
|
18
21
|
let res;
|
|
19
22
|
try {
|