@oj-module/cli 0.2.69 → 0.2.71
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/devkit/README.md +10 -8
- package/devkit/framework-dev.md +1 -1
- package/devkit/manual.md +45 -19
- package/devkit/module-dev-and-cdn-playground.md +20 -14
- package/devkit/publish-walkthrough.md +132 -43
- package/devkit/wecom-customer-reauth-guide.md +110 -0
- package/package.json +2 -2
- package/shell-dist/assets/{runtime.zrv2zhj9j0.js → runtime.7x99hmm020.js} +2 -2
- package/shell-dist/bundles/oj-market-0.2.0.tgz +0 -0
- package/shell-dist/bundles/oj-market-0.2.0.tgz.sig +8 -0
- package/shell-dist/index.html +4 -4
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/list.test.ts +7 -7
- package/src/__tests__/oj-health-host.test.ts +19 -0
- package/src/__tests__/site-tenant-header.test.ts +2 -2
- package/src/__tests__/templates-builtin-list.test.ts +47 -0
- package/src/__tests__/templates-schema-columns.test.ts +11 -2
- package/src/__tests__/templates-tenant-scope.test.ts +2 -3
- package/src/__tests__/users-roles-display.test.ts +84 -0
- package/src/bundle.ts +1 -1
- package/src/contract/__tests__/market-client.integration.test.ts +23 -22
- package/src/enable.ts +2 -2
- package/src/init.ts +45 -0
- package/src/install.ts +5 -5
- package/src/lifecycle/__tests__/install.test.ts +29 -29
- package/src/lifecycle/__tests__/market-bundle.test.ts +87 -0
- package/src/lifecycle/__tests__/publish.test.ts +8 -10
- package/src/lifecycle/__tests__/trust.test.ts +14 -14
- package/src/lifecycle/__tests__/uninstall.test.ts +4 -4
- package/src/list.ts +3 -3
- package/src/market/__tests__/bootstrap.test.ts +24 -7
- package/src/market/__tests__/failure-playbook.test.ts +0 -1
- package/src/market/__tests__/remote-push.test.ts +228 -0
- package/src/market/__tests__/verify.test.ts +45 -207
- package/src/market/bootstrap.ts +12 -7
- package/src/market/command.ts +33 -35
- package/src/market/db-seed.ts +129 -0
- package/src/market/failure-playbook.ts +3 -9
- package/src/market/fixture.ts +2 -6
- package/src/market/publish.ts +145 -89
- package/src/market/remote-push.ts +236 -0
- package/src/market/trial.ts +2 -2
- package/src/market/verify.ts +17 -270
- package/src/market/walkthrough.ts +38 -63
- package/src/oj.ts +13 -2
- package/src/preview.ts +5 -3
- package/src/publish.ts +3 -2
- package/src/scaffold-guard.ts +4 -0
- package/src/site.ts +1 -1
- package/src/trust.ts +8 -8
- package/src/uninstall.ts +2 -2
- package/src/usage.ts +23 -21
- package/templates/config.yaml +22 -3
- package/templates/init.include.yaml +16 -11
- package/templates/openapi.json +3948 -3655
- package/templates/src/.ojm-builtin.json +13 -0
- package/templates/src/README.md +5 -2
- package/templates/src/app/api/_platform/README.md +3 -2
- package/templates/src/app/api/_platform/_lifecycle/atomic.ts +7 -2
- package/templates/src/app/api/_platform/_lifecycle/bundle-converge.ts +158 -0
- package/templates/src/app/api/_platform/_lifecycle/history.ts +3 -2
- package/templates/src/app/api/_platform/_lifecycle/installer.ts +25 -5
- package/templates/src/app/api/_platform/_lifecycle/local-archive.ts +61 -0
- package/templates/src/app/api/_platform/_lifecycle/manifest-of-upload.ts +62 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +12 -2
- package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +4 -1
- package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +8 -2
- package/templates/src/app/api/_platform/_lifecycle/types.ts +1 -1
- package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +14 -5
- package/templates/src/app/api/_platform/_shared/site-facade.ts +31 -0
- package/templates/src/app/api/_platform/manifest.yaml +1 -2
- package/templates/src/app/api/_platform/migrations/0003__market_catalog.mysql.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0003__market_catalog.pg.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0003__market_catalog.sql +18 -0
- package/templates/src/app/api/_platform/migrations/0004__market_drop_source_seq.mysql.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0004__market_drop_source_seq.pg.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0004__market_drop_source_seq.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0005__market_sig_registry.mysql.sql +8 -0
- package/templates/src/app/api/_platform/migrations/0005__market_sig_registry.pg.sql +8 -0
- package/templates/src/app/api/_platform/migrations/0005__market_sig_registry.sql +8 -0
- package/templates/src/app/api/_platform/schema.yaml +8 -11
- package/templates/src/app/api/_platform/seed.mysql.sql +1 -1
- package/templates/src/app/api/_platform/seed.pg.sql +1 -1
- package/templates/src/app/api/_platform/seed.sql +1 -1
- package/templates/src/app/api/auth/_shared/wecom/sync.ts +6 -0
- package/templates/src/app/web/_platform/__client/api.schemas.ts +0 -114
- package/templates/src/app/web/_platform/__client/index.ts +1 -333
- package/templates/src/app/web/system/locales/en-US.json +15 -1
- package/templates/src/app/web/system/locales/zh-CN.json +15 -1
- package/templates/src/app/web/system/pages/departments/sync-bar.tsx +9 -3
- package/templates/src/app/web/system/pages/roles/index.tsx +4 -8
- package/templates/src/app/web/system/pages/roles/role-label.ts +26 -0
- package/templates/src/app/web/system/pages/solutions/index.tsx +110 -0
- package/templates/src/app/web/system/pages/solutions/market-entry.ts +46 -0
- package/templates/src/app/web/system/pages/users/index.tsx +12 -2
- package/templates/src/app/web/system/pages/users/match-keyword.ts +19 -0
- package/templates/src/app/web/system/pages/users/roles.tsx +43 -14
- package/templates/src/app/web/system/routes.ts +18 -1
- package/templates/src/{app/api/_platform/market → oj-market/api}/api.ts +475 -592
- package/templates/src/oj-market/api/backfill.ts +221 -0
- package/templates/src/{app/api/_platform/market → oj-market/api}/bootstrap.ts +2 -2
- package/templates/src/oj-market/api/cdn/api.ts +312 -0
- package/templates/src/oj-market/api/cdn/shared.ts +104 -0
- package/templates/src/oj-market/api/cdn/signed-auth.ts +75 -0
- package/templates/src/oj-market/api/manifest.yaml +8 -0
- package/templates/src/oj-market/solution.yaml +21 -0
- package/templates/src/oj-market/web/__client/api.schemas.ts +167 -0
- package/templates/src/oj-market/web/__client/index.ts +509 -0
- package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +6 -6
- package/templates/src/oj-market/web/api/client.ts +6 -6
- package/templates/src/oj-market/web/api/defaultClient.ts +31 -25
- package/templates/src/oj-market/web/entry.ts +5 -3
- package/templates/src/oj-market/web/hooks/useMarket.ts +6 -6
- package/templates/src/oj-market/web/pages/MarketApp.tsx +20 -3
- package/templates/src/oj-market/web/publish/locales/en-US.json +1 -1
- package/templates/src/oj-market/web/publish/locales/zh-CN.json +1 -1
- package/templates/src/oj-market/web/publish/pages/history.tsx +2 -11
- package/templates/src/oj-market/web/routes.ts +12 -0
- package/templates/src/oj-market/web/types.ts +14 -8
- package/templates/tests/lifecycle/anonymous-wiring.spec.ts +65 -0
- package/templates/tests/lifecycle/backfill.spec.ts +392 -0
- package/templates/tests/lifecycle/bootstrap.spec.ts +1 -1
- package/templates/tests/lifecycle/bundle-converge.spec.ts +264 -0
- package/templates/tests/lifecycle/cdn-upload.spec.ts +661 -0
- package/templates/tests/lifecycle/deploy-smoke.spec.ts +25 -0
- package/templates/tests/lifecycle/exec-grant.spec.ts +38 -0
- package/templates/tests/lifecycle/installer.spec.ts +36 -0
- package/templates/tests/lifecycle/market-api.spec.ts +448 -803
- package/templates/tests/lifecycle/preflight.spec.ts +18 -0
- package/templates/tests/lifecycle/trust.spec.ts +2 -2
- package/templates/tests/lifecycle/uninstall.spec.ts +36 -2
- package/templates/tests/lifecycle/verify-checks.spec.ts +1 -1
- package/templates/tests/solutions-entry.spec.ts +55 -0
- package/templates/tsconfig.paths.json +1 -0
- package/templates/web.config.ts +6 -6
- package/vendor/host-versions.json +3 -3
- package/bin/oj +0 -4
- package/src/market/__tests__/build-index.test.ts +0 -641
- package/src/market/__tests__/sharding.test.ts +0 -90
- package/src/market/__tests__/yank-ledger.test.ts +0 -60
- package/src/market/build-index.ts +0 -526
- package/src/market/index-builder.ts +0 -149
- package/src/market/sharding.ts +0 -94
- package/src/market/yank-ledger.ts +0 -91
- package/templates/src/app/api/_platform/_lifecycle/source-seq.ts +0 -88
- package/templates/src/app/api/_platform/market/index-source.ts +0 -112
- package/templates/tests/lifecycle/source-seq.spec.ts +0 -55
package/src/oj.ts
CHANGED
|
@@ -21,7 +21,7 @@ import fs from "node:fs";
|
|
|
21
21
|
import http from "node:http";
|
|
22
22
|
import path from "node:path";
|
|
23
23
|
import process from "node:process";
|
|
24
|
-
import { readOjPluginsDir, readOjPort } from "./oj-config";
|
|
24
|
+
import { readOjPluginsDir, readOjPort, readOjServerField } from "./oj-config";
|
|
25
25
|
import { resolveOjBin } from "./vendor";
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -61,6 +61,17 @@ function readLatestLogError(logsDir: string): string {
|
|
|
61
61
|
return picked.join("\n").trim();
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* 健康轮询目标 host:跟随 server.host(用户改绑局域网 IP 时回环探测会永不就绪),
|
|
66
|
+
* 通配地址(0.0.0.0 / :: / 空)回退 127.0.0.1——通配绑定时回环必然可达。
|
|
67
|
+
*/
|
|
68
|
+
export function healthHostOf(host: string | undefined): string {
|
|
69
|
+
if (!host)
|
|
70
|
+
return "127.0.0.1";
|
|
71
|
+
const trimmed = host.trim();
|
|
72
|
+
return trimmed === "" || trimmed === "0.0.0.0" || trimmed === "::" ? "127.0.0.1" : trimmed;
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
export interface OjProcess {
|
|
65
76
|
port: number
|
|
66
77
|
/** 健康就绪(或失败拒绝:秒退带 stderr 尾部 / 超时) */
|
|
@@ -118,7 +129,7 @@ export function startOj(
|
|
|
118
129
|
// clap 拒掉):历史布局传目录 → `--api-path <dir>`;module-root 布局传**已拼好的
|
|
119
130
|
// 旗标数组** → `["--module-root", "<project>/src"]`。
|
|
120
131
|
const apiArgs = Array.isArray(apiSource) ? apiSource : ["--api-path", apiSource];
|
|
121
|
-
const healthUrl = `http
|
|
132
|
+
const healthUrl = `http://${healthHostOf(readOjServerField(configPath, "host"))}:${port}${base}/health`;
|
|
122
133
|
|
|
123
134
|
// 插件目录发现显式化:oj 的 resolve_plugins_dir 依赖 current_exe() 启发式,而
|
|
124
135
|
// Linux glibc 发行包的 bin/oj 是 ld-linux 启动器(exec 加载器而非二进制),
|
package/src/preview.ts
CHANGED
|
@@ -28,7 +28,7 @@ import process from "node:process";
|
|
|
28
28
|
import { loadModulesConfig } from "./config";
|
|
29
29
|
import { resolveConfigPath, resolveLayout } from "./layout";
|
|
30
30
|
import { startOj } from "./oj";
|
|
31
|
-
import { readOjApiPrefix } from "./oj-config";
|
|
31
|
+
import { readOjApiPrefix, readOjServerField } from "./oj-config";
|
|
32
32
|
import { hasRootApiMount, parseMounts, writePreviewConfig } from "./preview-config";
|
|
33
33
|
import { normalizeSiteBase } from "./site-base";
|
|
34
34
|
import { resolveOjBin } from "./vendor";
|
|
@@ -119,8 +119,10 @@ export async function runPreview(projectRoot: string, opts: PreviewOptions = {})
|
|
|
119
119
|
preview.cleanup();
|
|
120
120
|
throw error;
|
|
121
121
|
}
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
// 提示跟随 config 的 server.host(绑定是 oj 自己做的,提示不能再硬编码 localhost)
|
|
123
|
+
const host = readOjServerField(preview.configPath, "host") || "127.0.0.1";
|
|
124
|
+
console.log(`[ojm] oj 已就绪(release/js,单服务:${base} API + 静态 SPA 同进程):http://${host}:${oj.port}`);
|
|
125
|
+
console.log(`\n[ojm] preview 已启动:http://${host}:${oj.port}${sitePrefix === "/" ? "" : sitePrefix}`);
|
|
124
126
|
console.log(`[ojm] 静态由 oj web 挂载直出(spa 深链回落);派生 config ${path.basename(preview.configPath)} 为本进程临时产物,退出自动清理`);
|
|
125
127
|
|
|
126
128
|
// 退出回收:派生 config 必须随 oj 一起消亡(test/integration 断言其不存在)
|
package/src/publish.ts
CHANGED
|
@@ -185,7 +185,8 @@ export function recomputeIndex(text: string): string {
|
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
187
|
* 索引文件唯一写入口(REG-CS5 原子落盘,红队 F2):先落 `<路径>.tmp.<pid>.<rand>` 再 `rename`
|
|
188
|
-
*
|
|
188
|
+
* 覆盖。全仓只此一条写索引的路径——`grep writeFileSync` 可证没有直写(A6:市场侧索引
|
|
189
|
+
* 生成器 build-index 已随 DB 目录化退役,本写入口现服务 registry-* 留档面)。
|
|
189
190
|
* 临时文件后缀非 `.yaml`,读取器永不把它当索引;进程被杀最多残留一个 tmp,由下次重建扫掉。
|
|
190
191
|
*/
|
|
191
192
|
export function writeIndexFile(file: string, text: string): void {
|
|
@@ -266,7 +267,7 @@ export function publishCommand(dir: string, opts: PublishOptions = {}): PublishR
|
|
|
266
267
|
for (const no of shardNos)
|
|
267
268
|
shardSigs.set(no, signIndex("shard", pins.get(no)!, shardTexts.get(no)!, privateKeyPem, keyId));
|
|
268
269
|
|
|
269
|
-
// 原子落盘,顺序固定「先分片 → 再 catalog」(catalog
|
|
270
|
+
// 原子落盘,顺序固定「先分片 → 再 catalog」(catalog 是提交点)
|
|
270
271
|
// `--dry-run`:同一份计算、同一个顺序,只是不执行写——写盘清单照常返回,CI 可先比对
|
|
271
272
|
const writes: string[] = [];
|
|
272
273
|
for (const no of shardNos) {
|
package/src/scaffold-guard.ts
CHANGED
|
@@ -132,6 +132,10 @@ export function collectDefaultModuleDrift(projectRoot: string): ScaffoldDrift |
|
|
|
132
132
|
if (!guardEnabled(projectRoot))
|
|
133
133
|
return null;
|
|
134
134
|
const templatesDir = templatesRoot();
|
|
135
|
+
// 工程即 cli 包内 templates 自身(上游源仓):基线 == 自身,比较无意义
|
|
136
|
+
// (白名单外的 demo-only 文件在 extra 轴上必误报),直接放行
|
|
137
|
+
if (path.resolve(projectRoot) === path.resolve(templatesDir))
|
|
138
|
+
return null;
|
|
135
139
|
const expected = protectedTemplateFiles(templatesDir, readInitInclude(templatesDir));
|
|
136
140
|
const expectedSet = new Set(expected);
|
|
137
141
|
const drift: ScaffoldDrift = { modified: [], missing: [], extra: [] };
|
package/src/site.ts
CHANGED
|
@@ -152,7 +152,7 @@ export async function pollTask(fetchFn: typeof fetch, base: string, token: strin
|
|
|
152
152
|
let interval = interval0;
|
|
153
153
|
let waited = 0;
|
|
154
154
|
for (;;) {
|
|
155
|
-
const record = await callApi<TaskRecord>(fetchFn, base, token, "GET", `/api/
|
|
155
|
+
const record = await callApi<TaskRecord>(fetchFn, base, token, "GET", `/api/oj-market/task?id=${encodeURIComponent(taskId)}`);
|
|
156
156
|
if (isTerminal(record.status))
|
|
157
157
|
return record;
|
|
158
158
|
if (waited + interval > timeoutMs)
|
package/src/trust.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// ojm trust(P2-2,C2-38/39,G3 双轨第一轨 / G5):宿主信任库运维瘦客户端。
|
|
2
2
|
// 子命令:
|
|
3
|
-
// ojm trust ls —— 列表(GET /api/
|
|
4
|
-
// ojm trust add <key_id> <pubkey_hex> —— 新增信任(POST /api/
|
|
5
|
-
// ojm trust revoke <key_id> —— 吊销(DELETE /api/
|
|
3
|
+
// ojm trust ls —— 列表(GET /api/oj-market/trust)
|
|
4
|
+
// ojm trust add <key_id> <pubkey_hex> —— 新增信任(POST /api/oj-market/trust/key)
|
|
5
|
+
// ojm trust revoke <key_id> —— 吊销(DELETE /api/oj-market/trust/key?key_id=)
|
|
6
6
|
// ojm trust confirm --task <task_id> —— TOFU 确认(P2-3):先展示指纹再调 POST /market/trust
|
|
7
7
|
// 本地零状态,纯转发(uninstall.ts 同款);operator 由站点侧按登录用户反查,CLI 不传。
|
|
8
8
|
import { Buffer } from "node:buffer";
|
|
@@ -79,7 +79,7 @@ export async function trustCommand(sub: string, args: string[], opts: TrustOptio
|
|
|
79
79
|
const token = await login(fetchFn, base, user, pass);
|
|
80
80
|
switch (sub) {
|
|
81
81
|
case "ls": {
|
|
82
|
-
const result = await callApi<{ keys: TrustKeyRow[], note: string | null }>(fetchFn, base, token, "GET", "/api/
|
|
82
|
+
const result = await callApi<{ keys: TrustKeyRow[], note: string | null }>(fetchFn, base, token, "GET", "/api/oj-market/trust");
|
|
83
83
|
if (result.note)
|
|
84
84
|
console.log(`[ojm] 注意:${result.note}`);
|
|
85
85
|
printRows(result.keys ?? []);
|
|
@@ -87,13 +87,13 @@ export async function trustCommand(sub: string, args: string[], opts: TrustOptio
|
|
|
87
87
|
}
|
|
88
88
|
case "add": {
|
|
89
89
|
const [keyId, publicKey] = args;
|
|
90
|
-
const result = await callApi<{ result: "added" | "exists_same" }>(fetchFn, base, token, "POST", "/api/
|
|
90
|
+
const result = await callApi<{ result: "added" | "exists_same" }>(fetchFn, base, token, "POST", "/api/oj-market/trust/key", { key_id: keyId, public_key: publicKey });
|
|
91
91
|
console.log(result.result === "added" ? `[ojm] 已信任发布方:${keyId}` : `[ojm] 该 key_id 已在信任库且公钥一致(无需重复添加):${keyId}`);
|
|
92
92
|
break;
|
|
93
93
|
}
|
|
94
94
|
case "revoke": {
|
|
95
95
|
const [keyId] = args;
|
|
96
|
-
const result = await callApi<{ revoked: boolean }>(fetchFn, base, token, "DELETE", `/api/
|
|
96
|
+
const result = await callApi<{ revoked: boolean }>(fetchFn, base, token, "DELETE", `/api/oj-market/trust/key?key_id=${encodeURIComponent(keyId)}`);
|
|
97
97
|
console.log(result.revoked ? `[ojm] 已吊销:${keyId}(已装方案继续运行,新装/升级将被拒)` : `[ojm] 信任库中无此 key_id:${keyId}`);
|
|
98
98
|
if (!result.revoked)
|
|
99
99
|
process.exitCode = 1;
|
|
@@ -102,13 +102,13 @@ export async function trustCommand(sub: string, args: string[], opts: TrustOptio
|
|
|
102
102
|
case "confirm": {
|
|
103
103
|
const taskId = opts.task!.trim();
|
|
104
104
|
// 先取任务记录展示指纹(防「确认 A、落 B」:人看到的与服务端入信任库的同源)
|
|
105
|
-
const rec = await callApi<{ id: string, status: string, name: string, version: string, trust?: { key_id: string, public_key: string, fingerprint: string } }>(fetchFn, base, token, "GET", `/api/
|
|
105
|
+
const rec = await callApi<{ id: string, status: string, name: string, version: string, trust?: { key_id: string, public_key: string, fingerprint: string } }>(fetchFn, base, token, "GET", `/api/oj-market/task?id=${encodeURIComponent(taskId)}`);
|
|
106
106
|
if (rec.trust === undefined || rec.status !== "needs_trust")
|
|
107
107
|
throw new Error(`[ojm] 任务 ${taskId} 不在待信任确认状态(status=${rec.status})——无需确认`);
|
|
108
108
|
console.log(`[ojm] 待信任发布方:${rec.trust.key_id}`);
|
|
109
109
|
console.log(`[ojm] 指纹(sha256(publicKey),请与发布方公布值逐字核对):${formatFingerprint(rec.trust.fingerprint)}`);
|
|
110
110
|
console.log("[ojm] 确认即把该公钥加入宿主信任库(首次绑定不可改;吊销只阻断新装/升级,不动已装方案)");
|
|
111
|
-
const result = await callApi<{ result: "added" | "exists_same", key_id: string }>(fetchFn, base, token, "POST", "/api/
|
|
111
|
+
const result = await callApi<{ result: "added" | "exists_same", key_id: string }>(fetchFn, base, token, "POST", "/api/oj-market/trust", { task_id: taskId });
|
|
112
112
|
console.log(result.result === "added" ? `[ojm] 已信任发布方:${result.key_id}——重试安装命令即可继续(CDN 源重试自动重下,file 源重传包体与 .sig)` : `[ojm] 该发布方已在信任库:${result.key_id}——直接重试安装即可`);
|
|
113
113
|
break;
|
|
114
114
|
}
|
package/src/uninstall.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ojm uninstall(P1-15a,缺口 B):卸载瘦客户端——POST /api/
|
|
1
|
+
// ojm uninstall(P1-15a,缺口 B):卸载瘦客户端——POST /api/oj-market/uninstall
|
|
2
2
|
// 触发站点侧 runUninstall 四处同删(锁/sidecar/sys_solution_route/modules.json + 权限级联)。
|
|
3
3
|
// 本地零状态,纯转发(enable.ts 同款);operator 由站点侧按登录用户反查,CLI 不传。
|
|
4
4
|
//
|
|
@@ -34,7 +34,7 @@ export async function uninstallCommand(name: string, opts: UninstallOptions = {}
|
|
|
34
34
|
const { base, user, pass } = resolveSite(opts);
|
|
35
35
|
const token = await login(fetchFn, base, user, pass);
|
|
36
36
|
console.log(`[ojm] 该包来自站点 ${base} 的已装清单(卸载不涉及仓库源)`);
|
|
37
|
-
const result = await callApi<{ ok: boolean, message: string }>(fetchFn, base, token, "POST", "/api/
|
|
37
|
+
const result = await callApi<{ ok: boolean, message: string }>(fetchFn, base, token, "POST", "/api/oj-market/uninstall", { name: name.trim() });
|
|
38
38
|
console.log(`[ojm] ${result.message}`);
|
|
39
39
|
if (!result.ok)
|
|
40
40
|
process.exitCode = 1;
|
package/src/usage.ts
CHANGED
|
@@ -265,8 +265,8 @@ export const HELP_GROUPS: HelpGroup[] = [
|
|
|
265
265
|
{ raw: "--json", zh: "stdout 只放一份 JSON 信封(人话改走 stderr)", en: "Emit a single JSON envelope on stdout (human lines move to stderr)" },
|
|
266
266
|
],
|
|
267
267
|
detail: {
|
|
268
|
-
zh: "ojm list [--site-url <u>] [--user <u>] [--pass <p>] [--all] [--json]\n 读 GET /api/
|
|
269
|
-
en: "ojm list [--site-url <u>] [--user <u>] [--pass <p>] [--all] [--json]\n Reads GET /api/
|
|
268
|
+
zh: "ojm list [--site-url <u>] [--user <u>] [--pass <p>] [--all] [--json]\n 读 GET /api/oj-market/index(superadmin):只挑已装的行,打「名字 / 已装版本 / 启用态 / 最新版 / 可升级 / 签名可信」\n --all 连未安装的索引行一起列(installed 显示 -);升级前看谁落后、禁用后确认状态、卸载前确认名字都靠它\n 退出码:0 成功(清单为空也是 0)/ 1 站点报错 / 2 用法错误(缺凭据)",
|
|
269
|
+
en: "ojm list [--site-url <u>] [--user <u>] [--pass <p>] [--all] [--json]\n Reads GET /api/oj-market/index (superadmin) and keeps installed rows only: name / installed / enabled / latest / upgradable / trust\n --all also lists index rows that are not installed (installed shows -); use it before upgrading, after disabling, or before uninstalling\n Exit codes: 0 success (an empty list is still 0) / 1 site error / 2 usage error (missing credentials)",
|
|
270
270
|
},
|
|
271
271
|
},
|
|
272
272
|
{
|
|
@@ -462,8 +462,8 @@ export const HELP_GROUPS: HelpGroup[] = [
|
|
|
462
462
|
{
|
|
463
463
|
key: "market",
|
|
464
464
|
name: "market [sub] [...rest]",
|
|
465
|
-
zh: "
|
|
466
|
-
en: "
|
|
465
|
+
zh: "市场链路(setup / publish 远端签名推送 / yank / verify / trial / walkthrough)",
|
|
466
|
+
en: "Marketplace pipeline (setup / remote signed publish / yank / verify / trial / walkthrough)",
|
|
467
467
|
allowUnknownOptions: true,
|
|
468
468
|
options: [
|
|
469
469
|
{ raw: "--site-url <url>", zh: "站点地址", en: "Site URL" },
|
|
@@ -472,38 +472,40 @@ export const HELP_GROUPS: HelpGroup[] = [
|
|
|
472
472
|
{ raw: "--new-pass <pass>", zh: "首次改密期的新密码(setup)", en: "New password during the forced-reset period (setup)" },
|
|
473
473
|
{ raw: "--fixture <name@version>", zh: "合成 fixture 方案包(publish / trial)", en: "Synthesize a fixture bundle (publish / trial)" },
|
|
474
474
|
{ raw: "--real <project>", zh: "走真实方案工程发包(publish)", en: "Publish from a real solution project (publish)" },
|
|
475
|
-
{ raw: "--id <key>", zh: "签名 key id", en: "Signing key id" },
|
|
475
|
+
{ raw: "--id <key>", zh: "签名 key id(publish / yank 的发布身份)", en: "Signing key id (publisher identity for publish / yank)" },
|
|
476
476
|
{ raw: "--key <pem>", zh: "签名私钥 pem", en: "Signing private key pem" },
|
|
477
|
-
{ raw: "--
|
|
477
|
+
{ raw: "--registry-id <registry>", zh: "签发方 registry 身份(包级签名 canonical 首段)", en: "Signer registry identity (first segment of the package signature canonical)" },
|
|
478
|
+
{ raw: "--reason <原因>", zh: "下线原因(yank 审计留痕)", en: "Yank reason (audit trail)" },
|
|
479
|
+
{ raw: "--restore", zh: "恢复在架(yank 的反向操作)", en: "Restore to listing (reverse of yank)" },
|
|
478
480
|
{ raw: "--check", zh: "离线自检签名", en: "Offline signature self-check" },
|
|
479
481
|
],
|
|
480
482
|
subs: {
|
|
481
|
-
|
|
483
|
+
setup: {
|
|
482
484
|
short: { zh: "framework.market_cdn_url 指向外部真 CDN(脚手架缺省 /cdn 免 setup)", en: "Point framework.market_cdn_url at an external CDN (scaffold defaults to /cdn, no setup needed)" },
|
|
483
485
|
zh: "ojm market setup [--site-url <站点地址>] [--user <u> --pass <p>] [--new-pass <p>]\n 把系统设置 framework.market_cdn_url 改写为绝对地址(外部真 CDN 场景;脚手架 seed 缺省 /cdn = 本站点自供,免 setup)。首跑若账号处于首次改密期(seed admin must_change=1),会自动改密换全权 token 后再写入",
|
|
484
486
|
en: "ojm market setup [--site-url <site>] [--user <u> --pass <p>] [--new-pass <p>]\n Rewrite the framework.market_cdn_url system setting to an absolute URL (external CDN scenario; the scaffold seed defaults to /cdn = site-served, no setup needed). On first run, if the account is in the forced password-reset period (seed admin must_change=1), it resets the password to obtain a full-privilege token before writing",
|
|
485
487
|
},
|
|
486
|
-
|
|
487
|
-
short: { zh: "
|
|
488
|
-
zh: "ojm market publish [--fixture <name@version>
|
|
489
|
-
en: "ojm market publish [--fixture <name@version>
|
|
488
|
+
publish: {
|
|
489
|
+
short: { zh: "远端签名推送发布到目标站点市场(不经账号口令)", en: "Publish to a target site's market via signed push (no account credentials)" },
|
|
490
|
+
zh: "ojm market publish --site-url <url> [--fixture <name@version> | --real <方案工程>] --id <key> [--key <pem>] --registry-id <registry> [--check]\n 构建 + 包级签名后经签名推送发进目标站点市场:catalog 探测站点身份(registry_id)→ 先推 .sig 旁车再推 .tgz(pair 约束)→ 服务端重算 sha256 验签落库(market_release 目录行 + cdn/ 字节)。半成功(sig 已推 tgz 被拒)重跑本命令幂等补齐。签名身份 = ojm keygen --credential 兑换的发布 key(须经站点信任库)",
|
|
491
|
+
en: "ojm market publish --site-url <url> [--fixture <name@version> | --real <project>] --id <key> [--key <pem>] --registry-id <registry> [--check]\n Build + package-sign, then signed-push into the target site's market: discover the site identity via catalog (registry_id) → push the .sig sidecar before the .tgz (pair constraint) → the server recomputes sha256, verifies the signature and lands the row (market_release catalog row + cdn/ bytes). Half-success (sig pushed, tgz rejected): re-run this command to idempotently complete. Signing identity = publish key exchanged via ojm keygen --credential (must be in the site trust store)",
|
|
490
492
|
},
|
|
491
|
-
|
|
492
|
-
short: { zh: "
|
|
493
|
-
zh: "ojm market
|
|
494
|
-
en: "ojm market
|
|
493
|
+
yank: {
|
|
494
|
+
short: { zh: "签名下线/恢复某版本(仅发布者本人)", en: "Signed yank/restore of a version (publisher only)" },
|
|
495
|
+
zh: "ojm market yank <name>@<version> --site-url <url> --id <key> [--key <pem>] [--reason <原因>] [--by <操作人>] [--restore]\n 签名下线(或 --restore 恢复)目标站点市场目录里的某版本:清单即摘除、详情保留 yanked 标记、阻断新装/升级;不动已装方案。仅该行发布者本人可操作(服务端按行 key_id 仲裁);重复下线幂等",
|
|
496
|
+
en: "ojm market yank <name>@<version> --site-url <url> --id <key> [--key <pem>] [--reason <reason>] [--by <operator>] [--restore]\n Signed-yank (or --restore) a version in the target site's market catalog: removed from listing, kept in detail with yanked flag, new installs/upgrades blocked; already-installed solutions untouched. Only the row's publisher may operate (server arbitrates by row key_id); repeated yank is idempotent",
|
|
495
497
|
},
|
|
496
|
-
|
|
497
|
-
short: { zh: "
|
|
498
|
-
zh: "ojm market verify [--file <tgz>]
|
|
499
|
-
en: "ojm market verify [--file <tgz>]
|
|
498
|
+
verify: {
|
|
499
|
+
short: { zh: "离线验包(包级验签 + manifest 结构)", en: "Offline bundle verification (package signature + manifest structure)" },
|
|
500
|
+
zh: "ojm market verify [--file <tgz>]\n 离线验包(不起站点、不装、不要凭据):包级 Ed25519 detached 验签(信任锚)+ manifest 结构(必填字段 / 原生清单 desc / 0001 迁移跨版本一致)。注意:校验通过只说明来源与完整性,不构成内容安全性背书",
|
|
501
|
+
en: "ojm market verify [--file <tgz>]\n Offline bundle verification (no site, no install, no credentials): package-level Ed25519 detached signature (trust anchor) + manifest structure (required fields / native manifest desc / 0001 migration consistency across versions). Note: passing only proves origin and integrity — it is not an endorsement of content safety",
|
|
500
502
|
},
|
|
501
|
-
|
|
503
|
+
trial: {
|
|
502
504
|
short: { zh: "一次性隔离站点试装,跑完即毁", en: "Full install trial in a throwaway isolated site" },
|
|
503
505
|
zh: "ojm market trial [--fixture <name@version>] [--keep]\n 在一次性隔离站点(随机端口 + 一次性 DB/密钥/凭据/信任库/CDN)里跑完整安装,跑完即毁;--keep 保留现场供排查",
|
|
504
506
|
en: "ojm market trial [--fixture <name@version>] [--keep]\n Run a full install in a throwaway isolated site (random port + throwaway DB/keys/credentials/trust store/CDN), destroyed right after; --keep preserves the scene for debugging",
|
|
505
507
|
},
|
|
506
|
-
|
|
508
|
+
walkthrough: {
|
|
507
509
|
short: { zh: "隔离站点跑全套市场操作并出 AC 表", en: "Run the full marketplace operation set in an isolated site, print an AC table" },
|
|
508
510
|
zh: "ojm market walkthrough [--only <op>] [--strict] [--keep]\n 隔离站点跑全套市场操作并出 AC 表",
|
|
509
511
|
en: "ojm market walkthrough [--only <op>] [--strict] [--keep]\n Run the full marketplace operation set in an isolated site and print an AC table",
|
package/templates/config.yaml
CHANGED
|
@@ -110,7 +110,10 @@ vars:
|
|
|
110
110
|
wecom_base_url: ''
|
|
111
111
|
|
|
112
112
|
platform:
|
|
113
|
-
|
|
113
|
+
# 安装器热更 config 需要 platform 特权(reloadConfig/信任库写面);编排面自 Phase B 起在
|
|
114
|
+
# oj-market 模块(/api/oj-market/*),与 _platform 同权——缺 oj-market 时装到 reloading
|
|
115
|
+
# 段 NotCapable(2026-11-07 市场线演练实锤)。
|
|
116
|
+
modules: [_platform, oj-market]
|
|
114
117
|
root: src # 后端模块源码根(src/<name>/api 的父目录);preview 派生 config 时改写成 dist
|
|
115
118
|
# ⚠️ 手写部署(不用 ojm,靠 mounts 起站)时:root 必须等于 api 挂载目录,
|
|
116
119
|
# 否则 oj serve 直接拒启 `platform.root (…) must be mounted as an api mount`。
|
|
@@ -123,10 +126,14 @@ platform:
|
|
|
123
126
|
# 下面三条命令与安装器逐字对齐(含每个空格),一个 token 都不能动:
|
|
124
127
|
exec:
|
|
125
128
|
_platform:
|
|
126
|
-
allow:
|
|
129
|
+
allow: &INSTALLER_EXEC_ALLOW
|
|
127
130
|
- 'tar -tvzf ${path:archive}'
|
|
128
131
|
- 'tar -xzf ${path:archive} -C ${path:dest}'
|
|
129
132
|
- 'oj migrate --format json -d ${path:dir} ${raw:module}'
|
|
133
|
+
# 市场编排面(src/oj-market/api,端点在 /api/oj-market/*)与 _platform 用同一安装器,
|
|
134
|
+
# 请求模块上下文 = oj-market(oj exec 按模块逐条授权)——缺这条真机安装/收敛全 NotCapable。
|
|
135
|
+
oj-market:
|
|
136
|
+
allow: *INSTALLER_EXEC_ALLOW
|
|
130
137
|
|
|
131
138
|
# 安装器落盘面:解包到 modules/.staging、写 dist/modules、备份 db.sqlite,全在 config 目录内。
|
|
132
139
|
# 缺这段会 NotCapable,且症状很阴:安装提交返回 200,但 task 查询恒 404。
|
|
@@ -159,6 +166,15 @@ auth:
|
|
|
159
166
|
- /auth/wecom/ticket # 一次性票据换 TokenBundle:票据本身就是「刚验过的登录证明」
|
|
160
167
|
- /auth/wecom/event # 企微指令回调:靠 msg_signature 验签,不走 Bearer
|
|
161
168
|
- /publish/credential/exchange # 凭证换发布身份:凭证自身即持有型证明,CLI 可能没有账号密码
|
|
169
|
+
# —— 方案市场匿名面(v4 §五:签名制不经账号口令,企微用户无障碍)——
|
|
170
|
+
# catalog 匿名读:站点身份发现面(registry_id)+ CLI 2×2 兼容矩阵探测(旧站点无此 API → 404 → 人话)
|
|
171
|
+
# cdn/upload 签名推送:X-OJM-Key-Id / X-OJM-Signature 即凭证(信任库 + Ed25519),不经 Bearer
|
|
172
|
+
# yank 签名下线:同凭证制——漏列则 oj-auth 守卫在 handler 前硬 401,签名永无到达之日(终审 C1 修补)
|
|
173
|
+
# `**` 跨段(≥0 段,oj-auth v0.1.20 语义):一条同盖 catalog 与 catalog/detail
|
|
174
|
+
# 生效口径:config.yaml 启动冻结段,改后须重启(restart_pending)
|
|
175
|
+
- /oj-market/catalog/**
|
|
176
|
+
- /oj-market/cdn/upload
|
|
177
|
+
- /oj-market/yank
|
|
162
178
|
|
|
163
179
|
# 多租户(企微代开发 v3 Phase 0):开启后所有 {base} 请求必须带 X-TENANT-ID,否则 400。
|
|
164
180
|
# 前端由 tenantStore + headerProvider 统一注入(单租户部署恒为 default,UX 不变)。
|
|
@@ -180,7 +196,6 @@ tenant:
|
|
|
180
196
|
- sys_login_throttle # 登录节流(登录前就要用)
|
|
181
197
|
- market_history # 解决方案安装/升级/回滚是站点级留痕
|
|
182
198
|
- market_release # 发布历史:CDN 是站点级的,历史按站点归口
|
|
183
|
-
- reg_source_seq # 索引新鲜度水位属于站点(防回放)
|
|
184
199
|
- wecom_app_profile # 企微 corp→租户映射权威:查它时必然还没有租户上下文(D9)
|
|
185
200
|
- wecom_suite_ticket # 套件级凭证,按 suite_id 全局唯一
|
|
186
201
|
- wecom_login_ticket # 一次性登录票据:跨「匿名 302 回跳」与「已认证换取」两段
|
|
@@ -198,6 +213,10 @@ tenant:
|
|
|
198
213
|
- /auth/wecom/ticket # 一次性票据换会话:租户由票据行携带
|
|
199
214
|
- /auth/wecom/event # 企微指令回调:验签+解密即认证,无租户语义
|
|
200
215
|
- /publish/credential/exchange # 凭证兑换:CLI 的裸 fetch 不带租户头;表本身是共享表(B 组)
|
|
216
|
+
# 市场匿名面同列(CLI 推送/下线/发现的裸 fetch 不带租户头;与 auth.anonymous_paths 同条目)
|
|
217
|
+
- /oj-market/catalog/**
|
|
218
|
+
- /oj-market/cdn/upload
|
|
219
|
+
- /oj-market/yank
|
|
201
220
|
# tenant.require_signed_claim:租户头的服务端绑定(平台级 fail-closed)。
|
|
202
221
|
# 暂保持 false——上游 oj 的 jwt.sign 目前会静默丢弃自定义 claim(需求 R6),
|
|
203
222
|
# 开启前无法把 tenant 放进 token。R6 落地后改 true,裸头即失去自证能力。
|
|
@@ -83,6 +83,8 @@ include:
|
|
|
83
83
|
- tests/users-page.spec.ts
|
|
84
84
|
# 企微连接器与组织同步:源码本就分发(src/app/api/auth),对应回归也必须跟着走,
|
|
85
85
|
# 否则用户工程升级后改配置/换套件时没有任何本地自检手段。
|
|
86
|
+
# 方案安装入口(Phase B T3)的纯函数钉测:零基建依赖,随源码下发
|
|
87
|
+
- tests/solutions-entry.spec.ts
|
|
86
88
|
- tests/wecom-admin.spec.ts
|
|
87
89
|
- tests/wecom-client-timeout.spec.ts
|
|
88
90
|
- tests/wecom-connector.spec.ts
|
|
@@ -92,6 +94,8 @@ include:
|
|
|
92
94
|
- tests/wecom-org-sync.spec.ts
|
|
93
95
|
- tests/wecom-sync.spec.ts
|
|
94
96
|
- tests/lifecycle/atomic-state.spec.ts
|
|
97
|
+
# bundle 启动收敛上拉不下拉规则(Phase B T3b):纯函数零基建依赖
|
|
98
|
+
- tests/lifecycle/bundle-converge.spec.ts
|
|
95
99
|
- tests/lifecycle/deploy-smoke.spec.ts
|
|
96
100
|
- tests/lifecycle/migrate.spec.ts
|
|
97
101
|
- tests/lifecycle/register.spec.ts
|
|
@@ -129,15 +133,16 @@ include:
|
|
|
129
133
|
- src/app/web/notification
|
|
130
134
|
- src/app/web/personal-center
|
|
131
135
|
- src/app/web/system
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
- src/oj-market/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
- src/oj-market/web/
|
|
136
|
+
# 解决方案市场(Phase B T2 起「出厂未装」分层):api 编排面(install/catalog/
|
|
137
|
+
# yank/trust 推送/cdn upload/backfill)随脚手架内置——市场 UI 未装/已卸载时
|
|
138
|
+
# 端点仍在(方案 §2「安装入口(市场外)」),system 域安装入口经它接线。
|
|
139
|
+
# v4 api 迁移(42369d5b6)漏收白名单致脚手架零 /api/oj-market/*——本条补上。
|
|
140
|
+
- src/oj-market/api
|
|
141
|
+
# 市场 UI 是纯 web 方案包(src/oj-market/solution.yaml,ojm build --bundle oj-market),
|
|
142
|
+
# superadmin 首装后由安装器 deploy-web 供给——页面源码不进脚手架。
|
|
143
|
+
# 唯独两份 __client 生成物照拷:openapi.json 含 /api/oj-market/*(api 内置),
|
|
144
|
+
# ojm api --check 的生成物对账需要落点(同 app/web/_platform 先例;publish
|
|
145
|
+
# 契约的生成物归属 oj-market 随发布页走包,用户工程只需文件在场)
|
|
146
|
+
- src/oj-market/web/__client
|
|
147
|
+
- src/oj-market/web/publish/__client
|
|
143
148
|
- src/app/web/web
|