@oj-module/cli 0.2.23 → 0.2.26

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.
Files changed (56) hide show
  1. package/devkit/manual.md +7 -5
  2. package/devkit/module-dev-and-cdn-playground.md +10 -10
  3. package/package.json +4 -4
  4. package/shell-dist/assets/runtime.js +5 -5
  5. package/shell-dist/index.html +2 -2
  6. package/shell-dist/versions.json +3 -3
  7. package/src/__tests__/build-backend.test.ts +13 -13
  8. package/src/__tests__/cli.test.ts +225 -0
  9. package/src/__tests__/layout.test.ts +8 -5
  10. package/src/__tests__/new-scaffold.test.ts +59 -0
  11. package/src/__tests__/usage.test.ts +45 -47
  12. package/src/build.ts +4 -4
  13. package/src/bundle.ts +9 -9
  14. package/src/cli.ts +265 -0
  15. package/src/contract/check.ts +12 -5
  16. package/src/dev.ts +17 -0
  17. package/src/index.ts +31 -211
  18. package/src/init.ts +2 -4
  19. package/src/keygen.ts +0 -19
  20. package/src/layout.ts +11 -11
  21. package/src/lifecycle/__tests__/authoring.test.ts +3 -14
  22. package/src/lifecycle/__tests__/bundle.test.ts +24 -24
  23. package/src/lifecycle/__tests__/install.test.ts +0 -23
  24. package/src/lifecycle/__tests__/new.test.ts +1 -1
  25. package/src/lifecycle/__tests__/smoke.test.ts +2 -2
  26. package/src/market/__tests__/bootstrap.test.ts +24 -0
  27. package/src/market/__tests__/setup.test.ts +8 -1
  28. package/src/market/bootstrap.ts +10 -8
  29. package/src/market/cdn-layout.ts +3 -3
  30. package/src/market/command.ts +0 -23
  31. package/src/market/setup.ts +14 -2
  32. package/src/market/trial.ts +3 -3
  33. package/src/market/walkthrough.ts +7 -6
  34. package/src/new.ts +21 -3
  35. package/src/preview-config.ts +2 -2
  36. package/src/preview.ts +5 -4
  37. package/src/publish.ts +0 -22
  38. package/src/sign.ts +0 -22
  39. package/src/usage.ts +482 -210
  40. package/src/vendor.ts +5 -1
  41. package/templates/README.md +3 -3
  42. package/templates/cdn/README.md +6 -6
  43. package/templates/config.yaml +5 -4
  44. package/templates/init.include.yaml +38 -2
  45. package/templates/seed.mysql.sql +2 -1
  46. package/templates/seed.pg.sql +2 -1
  47. package/templates/src/_platform/api/market/api.ts +11 -8
  48. package/templates/src/_platform/api/seed.sql +2 -1
  49. package/templates/src/_platform/api/solutions/api.ts +2 -2
  50. package/templates/src/system/web/pages/users/roles.tsx +3 -1
  51. package/templates/tests/lifecycle/market-api.spec.ts +35 -5
  52. package/templates/tests/lifecycle/verify-checks.spec.ts +1 -1
  53. package/templates/tests/lifecycle/verify-report.spec.ts +1 -1
  54. package/vendor/host-versions.json +3 -3
  55. package/src/args.ts +0 -172
  56. package/src/lifecycle/__tests__/args.test.ts +0 -20
@@ -36,7 +36,9 @@ import { parseFixtureSpec } from "./fixture";
36
36
  import { publishPackage } from "./publish";
37
37
 
38
38
  /** 种子管理员(隔离库全新 seed;与 templates seed.sql 一致) */
39
- const SEED_ADMIN_USER = "admin";
39
+ // 种子管理员 = superadmin:模板 seed 账号名=角色名(3d93063f 起 admin 被幂等解绑 superadmin
40
+ // 角色),写 market 安全设置要求 actor 持 superadmin 角色——用 admin 会 403(2026-10-23 演练实踩)。
41
+ const SEED_ADMIN_USER = "superadmin";
40
42
  const SEED_ADMIN_PASS = "123456";
41
43
 
42
44
  /** Ed25519 PKCS#8 DER 前缀(RFC 8410 固定头;与 gen-signed-fixtures.mjs 同一手法) */
@@ -50,7 +52,7 @@ export interface IsolatedPublisher {
50
52
  }
51
53
 
52
54
  export interface IsolatedSite {
53
- /** 临时根(含 config.yaml / src/ / api/dist/ / bin/ / cdn/) */
55
+ /** 临时根(含 config.yaml / src/ / dist/api/ / bin/ / cdn/) */
54
56
  root: string
55
57
  port: number
56
58
  baseUrl: string
@@ -96,7 +98,7 @@ export async function bootstrapIsolatedSite(opts: BootstrapOptions = {}): Promis
96
98
 
97
99
  const root = mkdtempSync(path.join(tmpdir(), "ojm-trial-"));
98
100
  // 新布局(`src/<name>/{api,web}`):config.yaml 在隔离根,源码树根为 <root>/src,
99
- // 构建产物仍是扁平根 <root>/api/dist(与真实工程同形)。
101
+ // 构建产物仍是扁平根 <root>/dist/api(与真实工程同形)。
100
102
  const srcDir = path.join(root, "src");
101
103
  const configPath = path.join(root, "config.yaml");
102
104
  const cdnDir = path.join(root, "cdn");
@@ -143,9 +145,9 @@ export async function bootstrapIsolatedSite(opts: BootstrapOptions = {}): Promis
143
145
  mkdirSync(siteDir, { recursive: true });
144
146
  writeFileSync(path.join(siteDir, "index.html"), "<!doctype html><html><body>ojm trial site</body></html>\n", "utf-8");
145
147
  cfg = cfg.replace("web: ./cdn", `web: ${cdnDir}\n - prefix: /\n web: ${siteDir}\n spa: true`);
146
- // 安装目标恒为 release 部署:装好的模块落在 api/dist,dev 形态(--api-path src)发现不了。
148
+ // 安装目标恒为 release 部署:装好的模块落在 dist/api,dev 形态(--api-path src)发现不了。
147
149
  // 故隔离站点以 dist 起站,platform.root 同步改写(oj 校验 root 必须是生效的 api 挂载目录)。
148
- // root 相对 **config 所在目录** 解析——config 在隔离根,产物在 <root>/api/dist,故不能写死
150
+ // root 相对 **config 所在目录** 解析——config 在隔离根,产物在 <root>/dist/api,故不能写死
149
151
  // `dist`(那是 config 还在 api/ 时的旧口径),按布局取值。
150
152
  const apiDistRel = path.relative(root, resolveLayout(root).apiDist).split(path.sep).join("/");
151
153
  cfg = cfg.replace(" root: src", ` root: ${apiDistRel}`);
@@ -172,14 +174,14 @@ export async function bootstrapIsolatedSite(opts: BootstrapOptions = {}): Promis
172
174
  await publishPackage({ projectRoot: root, fixture: opts.fixture, id: keyId, privateKeyPem: publisher.privateKeyPem });
173
175
  await buildIndex({ projectRoot: root, id: keyId, privateKeyPem: publisher.privateKeyPem });
174
176
 
175
- // 7) 后端构建(api/dist + versions.json;安装与 reloading 都依赖它)
177
+ // 7) 后端构建(dist/api + versions.json;安装与 reloading 都依赖它)
176
178
  await buildBackend(root);
177
179
 
178
180
  // 7b) 起 oj(starter 注入供测试);ready 失败即整体作废,不留孤儿
179
181
  const starter = opts.starter ?? ((cfgPath: string, base: string, apiSrc: string) => startOj(cfgPath, base, apiSrc));
180
182
  let oj: OjProcess;
181
183
  try {
182
- oj = starter(configPath, "/api", path.join(root, "api", "dist"));
184
+ oj = starter(configPath, "/api", resolveLayout(root).apiDist);
183
185
  await oj.ready;
184
186
  }
185
187
  catch (error) {
@@ -258,7 +260,7 @@ function teardownReport(root: string, port: number): string[] {
258
260
  `隔离根:${root}`,
259
261
  " config.yaml + config/ 证书 + db.sqlite 站点配置面",
260
262
  " src/ 后端模块源码树(src/<name>/api,拷自 cli 内置模板)",
261
- " api/dist/ 构建产物(安装目标)",
263
+ " dist/api/ 构建产物(安装目标)",
262
264
  " bin/ oj 二进制与插件(拷贝自源工程)",
263
265
  " cdn/ 隔离 CDN(fixture 包 + 两份索引 + 签名)",
264
266
  `监听端口:127.0.0.1:${port}(进程已停)`,
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * 两条不能破的规矩:
5
5
  * 1. **不许硬编码端口/主机/挂载路径**——全部从 `api/config.yaml` 读。
6
- * dev 形态下 `/cdn` 只在 oj 子进程(`server.port`)上成立,dev server 自己(5174)
7
- * 会把 `/cdn/*` 交给静态层、未命中就回落 index.html(设计 §3.1.1);写成相对路径或
8
- * 写死 9778,市场页拿到的会是 HTML 而不是索引 YAML。
6
+ * (2026-10-23 起 dev 形态 ojm 已把 /cdn 反代给 oj 子进程,`framework.market_cdn_url`
7
+ * 允许写相对路径 `/cdn` = 本站点自供,由消费侧按请求源补全;本模块的绝对基址推导
8
+ * 只服务 `market setup` 写外部地址的场景。)
9
9
  * 2. **基址无尾斜杠**——`market/api.ts` 按 `${base}/bundles.yaml` 拼。
10
10
  *
11
11
  * 文本版(`resolveCdnLayoutFromConfig`)与工程版(`resolveCdnLayout`)分工:前者纯函数便于单测,
@@ -114,26 +114,3 @@ export async function marketCommand(sub: string, argv: string[]): Promise<void>
114
114
  throw new Error(`[ojm] 未知子命令:ojm market ${sub}\n可用:setup / publish / build-index`);
115
115
  }
116
116
  }
117
-
118
- /** `ojm market` 无子命令时的提示(与 usage.ts 同源风格) */
119
- export function marketUsage(): string {
120
- return [
121
- "ojm market setup [--url <站点地址>] [--user <u> --pass <p>] [--new-pass <p>] 把市场地址指向本地 cdn/",
122
- "ojm market publish [--fixture <name>@<version>] [--real <方案工程>] [--id <key>] [--rebuild-index]",
123
- "ojm market build-index [--id <key>] [--check] 扫描 cdn/ 重建索引",
124
- "ojm market verify [--file <tgz>] [--rebuild-index] 离线验包(不起站点、不装)",
125
- "ojm market trial [--fixture <name>@<version>] [--keep] 隔离站点试装(跑完即毁)",
126
- "ojm market walkthrough [--only <op>] [--strict] [--keep] 隔离站点跑全套市场操作并出 AC 表",
127
- ].join("\n");
128
- }
129
-
130
- /** 进程入口:从 argv 取子命令(index.ts 负责 dispatch 到此处) */
131
- export async function runMarket(argv: string[]): Promise<void> {
132
- const sub = argv[0] ?? "";
133
- if (sub === "" || sub === "--help" || sub === "-h") {
134
- console.log(marketUsage());
135
- return;
136
- }
137
- await marketCommand(sub, argv.slice(1));
138
- void process;
139
- }
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * `ojm market setup`(T5):把 `framework.market_cdn_url` 指向本地模拟 CDN。
3
3
  *
4
+ * 2026-10-23 起**非必需**:脚手架 seed 的缺省值即相对路径 `/cdn`(本站点自供,
5
+ * dev 形态 ojm 已把 /cdn 反代给 oj 子进程),新工程免 setup。本命令只剩一个场景:
6
+ * 指向**外部真 CDN**(或本地但要写绝对地址)时显式改写设置。
7
+ *
4
8
  * 两件容易踩的事都收在这一处:
5
- * 1. **地址必须是绝对的**,且端口来自 config 的 `server.port`——dev 形态下 dev server(5174)
6
- * 只会把 `/api` 反代给 oj 子进程,`/cdn/*` 会被静态层回落成 index.html(设计 §3.1.1)。
9
+ * 1. **地址必须是绝对的**,且端口来自 config 的 `server.port`——写给外部访问方的地址,
10
+ * 相对路径对浏览器/服务端消费另有按请求源补全的口径(market/api.ts withSiteOrigin)。
7
11
  * 2. **首跑凭据悬挂**:seed admin `must_change=1`,登录拿的是限权 token,写不了 system 设置;
8
12
  * 须先 `PUT /api/auth/password` 改密换全权 token(该端点是悬挂期唯一可用写端)。
9
13
  */
@@ -56,6 +60,14 @@ export async function setupMarketUrl(opts: SetupOptions = {}): Promise<SetupResu
56
60
  };
57
61
  }
58
62
 
63
+ /**
64
+ * setup 后用于登录的口令:改密闭环走了才用 newPass,否则原口令。
65
+ * 当前 seed must_change=0(账号名=角色名口径)时 setup 不改密,恒用 newPass 会 401。
66
+ */
67
+ export function loginPasswordAfterSetup(result: SetupResult, newPass: string, originalPass: string): string {
68
+ return result.changedPassword ? newPass : originalPass;
69
+ }
70
+
59
71
  /** 登录并在必要时走改密闭环,返回可用 token */
60
72
  async function obtainToken(fetchFn: typeof fetch, base: string, user: string, pass: string, newPass: string | undefined, projectRoot: string): Promise<{ value: string, changedPassword: boolean, generatedPassword?: string }> {
61
73
  let login: { access_token?: string, restricted?: boolean };
@@ -15,7 +15,7 @@ import process from "node:process";
15
15
  import { callApi, login, pollTask } from "../site";
16
16
  import { bootstrapIsolatedSite } from "./bootstrap";
17
17
  import { parseFixtureSpec } from "./fixture";
18
- import { setupMarketUrl } from "./setup";
18
+ import { loginPasswordAfterSetup, setupMarketUrl } from "./setup";
19
19
 
20
20
  export interface TrialOptions {
21
21
  /** 源工程(提供 bin/oj 与 cli 模板);缺省 process.cwd() */
@@ -53,7 +53,7 @@ export async function trialInstall(opts: TrialOptions = {}): Promise<TrialResult
53
53
  try {
54
54
  // 1) 打开市场地址:隔离库是全新 seed,首登必然处于改密悬挂期 → setup 顺带完成改密
55
55
  const newPass = "trial-pass-1";
56
- await setupMarketUrl({
56
+ const setup = await setupMarketUrl({
57
57
  projectRoot: site.root,
58
58
  url: site.baseUrl,
59
59
  user: site.admin.user,
@@ -61,7 +61,7 @@ export async function trialInstall(opts: TrialOptions = {}): Promise<TrialResult
61
61
  newPass,
62
62
  });
63
63
 
64
- const token = await login(fetch, site.baseUrl, site.admin.user, newPass);
64
+ const token = await login(fetch, site.baseUrl, site.admin.user, loginPasswordAfterSetup(setup, newPass, site.admin.pass));
65
65
 
66
66
  // 2) 首次安装:预期停在 needs_trust(发布方是本次临时生成的身份)
67
67
  let task = await installOnce(site.baseUrl, token, spec.name, spec.version);
@@ -19,6 +19,7 @@ import fs from "node:fs";
19
19
 
20
20
  import path from "node:path";
21
21
  import process from "node:process";
22
+ import { resolveLayout } from "../layout";
22
23
  import { signCommand } from "../sign";
23
24
  import { callApi, login, pollTask, unwrapEnvelope } from "../site";
24
25
  import { bootstrapIsolatedSite, derivePublisherKey } from "./bootstrap";
@@ -27,7 +28,7 @@ import { FAILURE_PLAYBOOK } from "./failure-playbook";
27
28
  import { buildEvilZipSlipTgz, parseFixtureSpec } from "./fixture";
28
29
  import { publishPackage } from "./publish";
29
30
  import { writeWalkthroughReport } from "./report";
30
- import { setupMarketUrl } from "./setup";
31
+ import { loginPasswordAfterSetup, setupMarketUrl } from "./setup";
31
32
 
32
33
  interface Ctx {
33
34
  site: IsolatedSite
@@ -154,7 +155,7 @@ const OPS: Op[] = [
154
155
  if (task.status !== "done")
155
156
  return { ok: false, detail: `升级安装未 done:status=${task.status} stage=${task.stage ?? "?"}:${task.message}` };
156
157
  const card = await indexCard(ctx);
157
- const distDir = path.join(ctx.site.root, "api", "dist");
158
+ const distDir = resolveLayout(ctx.site.root).apiDist;
158
159
  const dir010 = path.join(distDir, `${ctx.name}-0.1.0`);
159
160
  const dir020 = path.join(distDir, `${ctx.name}-0.2.0`);
160
161
  const mig010 = path.join(dir010, "migrations", "0001__init.sql");
@@ -185,7 +186,7 @@ const OPS: Op[] = [
185
186
  return { ok: false, detail: `前置安装 0.2.0 未 done:status=${pre.status}:${pre.message}` };
186
187
  card = await indexCard(ctx);
187
188
  }
188
- const distDir = path.join(ctx.site.root, "api", "dist");
189
+ const distDir = resolveLayout(ctx.site.root).apiDist;
189
190
  const before = fs.readdirSync(distDir).sort(); // 零副作用基线:0.1.0 目录是前序合法安装的产物,不代表本次落盘
190
191
  const task = await installOnce(ctx, "0.1.0"); // 包内最大 seq 1 < head 2
191
192
  const blocked = task.status === "failed" && task.stage === "migrating";
@@ -340,7 +341,7 @@ const OPS: Op[] = [
340
341
  run: async (ctx) => {
341
342
  const evil = path.join(ctx.site.cdnDir, `${ctx.name}-evil.tgz`);
342
343
  buildEvilZipSlipTgz(evil); // 成员含 ../evil.sql,无需发布进索引(file 源直传)
343
- const distDir = path.join(ctx.site.root, "api", "dist");
344
+ const distDir = resolveLayout(ctx.site.root).apiDist;
344
345
  const before = fs.readdirSync(distDir).sort(); // 零副作用基线:9.9.9 目录可能是前序 neg-stranger 的合法产物
345
346
  try {
346
347
  let msg = "";
@@ -373,8 +374,8 @@ export async function walkthrough(opts: WalkthroughOptions = {}): Promise<Walkth
373
374
  const site = await bootstrapIsolatedSite({ projectRoot: opts.projectRoot, fixture: opts.fixture, keep: opts.keep, onLog: log });
374
375
  try {
375
376
  const newPass = "trial-pass-1";
376
- await setupMarketUrl({ projectRoot: site.root, url: site.baseUrl, user: site.admin.user, pass: site.admin.pass, newPass });
377
- const token = await login(fetch, site.baseUrl, site.admin.user, newPass);
377
+ const setup = await setupMarketUrl({ projectRoot: site.root, url: site.baseUrl, user: site.admin.user, pass: site.admin.pass, newPass });
378
+ const token = await login(fetch, site.baseUrl, site.admin.user, loginPasswordAfterSetup(setup, newPass, site.admin.pass));
378
379
  const ctx: Ctx = { site, base: site.baseUrl, token, name: spec.name, version: spec.version, log };
379
380
 
380
381
  const selected = opts.only === undefined ? OPS : OPS.filter(op => op.key === opts.only);
package/src/new.ts CHANGED
@@ -124,6 +124,11 @@ title: ${name}
124
124
  summary: ${name} 方案(ojm new 生成的骨架,替换为你的描述)
125
125
  changelog:
126
126
  - 0.1.0 首个版本(ojm new 生成)
127
+ # 市场验包五必填(market verify structureCheck):peerRuntime 对宿主运行时强制比对(C2-09),
128
+ # archive 为 Form A 占位(tgz 内仅允许 file 键,C2-29;发布侧由 publish 填 Form B)。
129
+ peerRuntime: ">=0.0.0"
130
+ archive:
131
+ file: ${name}-0.1.0.tgz
127
132
  ${api}web:
128
133
  route_prefix: /${name}
129
134
  entry: modules/${name}/0.1.0/entry.js
@@ -200,9 +205,22 @@ tables: []
200
205
  }
201
206
 
202
207
  function apiTs(name: string): string {
208
+ // 形状契约:oj 内省只读 default export、链尾须为 HTTP 动词——ping 做成嵌套组
209
+ // (链 ping.get → GET /api/<name>/ping);裸命名导出不进 default 会产出空 routes.js
210
+ // (20261023 市场链路演练实踩)。
203
211
  return `// GET /api/${name}/ping —— 健康探针示例(保存即热更;出参挂平级 .response 即入 openapi.json)。
204
- export async function ping() {
205
- json.ok({ pong: true, module: "${name}" });
206
- }
212
+ const ping = {
213
+ async get() {
214
+ json.ok({ pong: true, module: "${name}" });
215
+ },
216
+ };
217
+
218
+ ping.get.response = {
219
+ type: "object",
220
+ required: ["pong"],
221
+ properties: { pong: { type: "boolean" } },
222
+ };
223
+
224
+ export default { ping };
207
225
  `;
208
226
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 单服务 preview 由 oj 一个进程同时出 API 与静态(web 挂载 + spa: true),
5
5
  * 而 spa 只能来自 config 文件(CLI --app-path 的 upsert 保留但不设置
6
- * spa——实测 oj 0.1.80)。用户在模板里手改 config 不可取(web/dist 缺失时
6
+ * spa——实测 oj 0.1.80)。用户在模板里手改 config 不可取(dist/web 缺失时
7
7
  * oj serve 整体拒启,会连带挂掉 ojm dev),故 preview 现场生成一份
8
8
  * 派生 config:
9
9
  *
@@ -37,7 +37,7 @@ export interface DerivePreviewConfigOptions {
37
37
  */
38
38
  prefix?: string
39
39
  /**
40
- * release 形态的后端模块根(api/dist)。给了且原 config 声明了 platform.modules 时,
40
+ * release 形态的后端模块根(layout.apiDist = dist/api)。给了且原 config 声明了 platform.modules 时,
41
41
  * 把 `platform.root` 一并改写到该目录——特权模块的受控根必须与 `--api-path` 同源,
42
42
  * 否则 release 下 reloadConfig 会按 dev 的 src 判定归属(装好的方案热更不生效)。
43
43
  */
package/src/preview.ts CHANGED
@@ -6,8 +6,9 @@
6
6
  * **单进程同时出 API 与静态**)。ojm 不再自建 http server、不再反代——
7
7
  * 前后端同端口同进程,与「oj serve + web 挂载」的生产公式逐字同形。
8
8
  *
9
- * 派生 config 为临时产物(api/.ojm-preview.yaml,与原件同目录保证相对
10
- * 路径解析一致),三路清理:ready 拒绝路径 / SIGINT / process exit 兜底
9
+ * 派生 config 为临时产物(module-root:工程根 .ojm-preview.yaml,与原件同目录
10
+ * 保证相对路径解析一致;历史布局才是 api/.ojm-preview.yaml),三路清理:ready
11
+ * 拒绝路径 / SIGINT / process exit 兜底
11
12
  * (语义与落盘见 preview-config.ts)。
12
13
  *
13
14
  * web.config.ts 的 htmlTransform 是 serve 期钩子,单服务下 oj 直出 HTML
@@ -59,7 +60,7 @@ export async function runPreview(projectRoot: string, opts: PreviewOptions = {})
59
60
  const siteDir = opts.siteDir ?? layout.distDir;
60
61
  const ojBin = resolveOjBin(projectRoot);
61
62
  const configPath = resolveConfigPath(projectRoot);
62
- // release 根恒为扁平 api/dist(module-root 只改源码布局,不改产物形状——设计 C-3/C-7)
63
+ // release 根恒为扁平 dist/api(module-root 只改源码布局,不改产物形状——设计 C-3/C-7)
63
64
  const apiDist = layout.apiDist;
64
65
 
65
66
  // fail-fast 四查:缺一即提示对应补救(init 幂等补缺 / ojm build 产物)
@@ -89,7 +90,7 @@ export async function runPreview(projectRoot: string, opts: PreviewOptions = {})
89
90
  // 派生 config 在 migrate 成功后落盘:失败即退路径零残留
90
91
  const base = readOjApiPrefix(configPath);
91
92
  const writer = opts.configWriter ?? ((root: string, o: { siteDir: string, prefix?: string, port?: number, apiRoot?: string }) => writePreviewConfig(root, o));
92
- // apiRoot 交给派生器:release 下 platform.root 必须指向 api/dist(与 --api-path 同源)
93
+ // apiRoot 交给派生器:release 下 platform.root 必须指向 dist/api(与 --api-path 同源)
93
94
  const preview = writer(projectRoot, { siteDir, prefix: sitePrefix, port: opts.port, apiRoot: apiDist });
94
95
 
95
96
  // / api 挂载与注入的 web 挂载配对时 oj 不开 SPA 回落(oj 语义,见 devkit 手册 §13)。
package/src/publish.ts CHANGED
@@ -126,25 +126,3 @@ export function publishCommand(dir: string, opts: PublishOptions = {}): PublishR
126
126
  }
127
127
  return { bundlesPath, manifestsPath, bundlesSigPath, manifestsSigPath, keyId, publicKeyHex, fingerprint: fingerprintOfPublicKey(publicKeyHex), checkPassed };
128
128
  }
129
-
130
- /** `ojm publish <dir> [--id <publisher.id>] [--key <pem>] [--check]` 参数解析 */
131
- export function parsePublishArgs(argv: string[]): { positionals: string[], id?: string, key?: string, check?: boolean } {
132
- const values: Record<string, string> = {};
133
- let check = false;
134
- const positionals: string[] = [];
135
- for (let i = 0; i < argv.length; i++) {
136
- const a = argv[i]!;
137
- if (a === "--check") {
138
- check = true;
139
- continue;
140
- }
141
- if (a === "--id" || a === "--key") {
142
- values[a] = argv[i + 1] ?? "";
143
- i++;
144
- continue;
145
- }
146
- if (!a.startsWith("--"))
147
- positionals.push(a);
148
- }
149
- return { positionals, id: values["--id"], key: values["--key"], check };
150
- }
package/src/sign.ts CHANGED
@@ -104,28 +104,6 @@ export function signCommand(tgz: string, opts: SignOptions = {}): SignResult {
104
104
  return { sigPath, keyId, publicKeyHex, fingerprint: fingerprintOfPublicKey(publicKeyHex), sha256, canonical, checkPassed };
105
105
  }
106
106
 
107
- /** `ojm sign <tgz> [--id <publisher.id>] [--key <pem>] [--check]` 参数解析 */
108
- export function parseSignArgs(argv: string[]): { positionals: string[], id?: string, key?: string, check?: boolean } {
109
- const values: Record<string, string> = {};
110
- let check = false;
111
- const positionals: string[] = [];
112
- for (let i = 0; i < argv.length; i++) {
113
- const a = argv[i]!;
114
- if (a === "--check") {
115
- check = true;
116
- continue;
117
- }
118
- if (a === "--id" || a === "--key") {
119
- values[a] = argv[i + 1] ?? "";
120
- i++;
121
- continue;
122
- }
123
- if (!a.startsWith("--"))
124
- positionals.push(a);
125
- }
126
- return { positionals, id: values["--id"], key: values["--key"], check };
127
- }
128
-
129
107
  /** 身份从文件名推导(<name>-<version>.tgz;与 ojm install parseTarget 同规则,Finding C 正则) */
130
108
  function deriveIdentity(stem: string): { name: string, version: string } {
131
109
  const m = /^(.+)-(\d[\w.]+(?:-[\w.]+)?)$/.exec(stem);