@sfmc-bds/devkit 1.0.8 → 2.0.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/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @sfmc-bds/devkit
2
2
 
3
- 模块作者工具核心:Watch、重建部署、启停。供 VS Code/Cursor 扩展与脚本直接 import,**不**经 `sfmc` CLI。
3
+ 模块作者工具核心:Watch、重建部署、启停、索引提交。供 CLI、VS Code/Cursor 扩展与 CI 复用。
4
+
5
+ 重建部署需要另行安装 `@sfmc-bds/cli`(扩展自带),或通过 `SFMC_CLI` / `cliPath` 指定入口;仅提交索引无需安装 CLI 或初始化服务器。
4
6
 
5
7
  建仓请用:
6
8
 
@@ -11,3 +13,31 @@ pnpm dlx @sfmc-bds/create-module@latest
11
13
  ```ts
12
14
  import { startModuleWatch, rebuildAndDeploy, setModuleEnabled } from "@sfmc-bds/devkit";
13
15
  ```
16
+
17
+ ## 索引提交
18
+
19
+ ```bash
20
+ # 在作者仓根目录;预览也会核对公共 npm 精确版本和最新索引分片。
21
+ sfmc-module-submit --dry-run
22
+ sfmc-module-submit
23
+ # 或使用 CLI:sfmc mod submit [模块目录] [--dry-run]
24
+ ```
25
+
26
+ 提交使用 `GH_TOKEN` / `GITHUB_TOKEN`,或 `gh auth login` 的登录凭据。有目标索引仓写权限时在该仓建 PR 分支,否则通过 fork 提供来源分支;`--no-fork` 可显式选择在目标索引仓建分支。
27
+ 提交路径不按账号名、仓库 owner 或模块 `official` 字段分类;只检查写权限与 PR 所需的仓库关联。
28
+ 仓库迁移与 fork 关系按 GitHub 仓库 ID 判断,兼容旧地址重定向。
29
+ 需要目标分支仓 Contents 写权限以及索引仓 Pull requests 写权限。`--wait-for-publish` 在 npm 返回 404 时最多等待 60 秒。
30
+ `--registry owner/repo` 可指定兼容的索引仓;`--entry-file <JSON>` 供受控 CI 入口提交条目,仍执行相同校验。
31
+
32
+ ```ts
33
+ import { submitModuleToRegistry } from "@sfmc-bds/devkit";
34
+ const preview = await submitModuleToRegistry({ moduleRoot: "/path/to/module", dryRun: true });
35
+ const result = await submitModuleToRegistry({ moduleRoot: "/path/to/module" });
36
+ console.log(result.status, result.prUrl);
37
+ ```
38
+
39
+ `sfmc.registry.json` 可补充 `name`、`description`、`category`、`tags`、`authors`,编辑器规范见 `schemas/registry-author.schema.json`。
40
+ 更新已有模块时,未配置覆盖的名称、介绍以及未指定的分类、标签、作者署名沿用索引条目。
41
+ ID、requires 来自 `sapi/manifest.json`;包名、版本、许可证、repository、SDK peerDependency 来自 `package.json`。
42
+ 仅 `modules/<id>.json` 会进入 PR。相同内容跳过;已有待审 PR 按模块复用;拒绝包来源冲突、降版、未知依赖和循环依赖。
43
+ 首次收录为 `official: false`,已有身份沿用索引仓;官方认证由维护者审核。
package/dist/index.d.ts CHANGED
@@ -2,10 +2,12 @@
2
2
  * @sfmc-bds/devkit — 模块作者工具核心(扩展 / CI 共用)
3
3
  * Watch / rebuild / 启停;建仓请用 pnpm dlx @sfmc-bds/create-module。
4
4
  */
5
+ export { findModuleRootFromFile, isValidModuleRoot, readModuleRootInfo, type ModuleRootInfo } from "./module-root.js";
6
+ export { setModuleEnabled, type SetModuleEnabledOptions, type SetModuleEnabledResult } from "./module-toggle.js";
5
7
  export { resolveLocalModuleRoot } from "./paths.js";
6
- export { isValidModuleRoot, readModuleRootInfo, findModuleRootFromFile, type ModuleRootInfo, } from "./module-root.js";
8
+ export { rebuildAndDeploy, type RebuildOptions, type RebuildResult } from "./rebuild.js";
9
+ export { runRegistrySubmitCommand } from "./registry-command.js";
10
+ export { MODULE_REGISTRY_REPO, createRegistryEntry, submitModuleToRegistry, type RegistryMetadata, type RegistryModule, type RegistrySubmitOptions, type RegistrySubmitResult, } from "./registry-submit.js";
11
+ export { resolveSfmcCli, runSfmcCli, type ResolveSfmcCliOptions, type RunSfmcCliOptions, type RunSfmcCliResult, } from "./sfmc-cli.js";
7
12
  export { isValidSfmcRoot } from "./sfmc-root.js";
8
- export { resolveSfmcCli, runSfmcCli, type ResolveSfmcCliOptions, type RunSfmcCliOptions, type RunSfmcCliResult } from "./sfmc-cli.js";
9
13
  export { startModuleWatch, type ModuleWatchOptions } from "./watch.js";
10
- export { rebuildAndDeploy, type RebuildOptions, type RebuildResult } from "./rebuild.js";
11
- export { setModuleEnabled, type SetModuleEnabledOptions, type SetModuleEnabledResult, } from "./module-toggle.js";
package/dist/index.js CHANGED
@@ -2,10 +2,12 @@
2
2
  * @sfmc-bds/devkit — 模块作者工具核心(扩展 / CI 共用)
3
3
  * Watch / rebuild / 启停;建仓请用 pnpm dlx @sfmc-bds/create-module。
4
4
  */
5
+ export { findModuleRootFromFile, isValidModuleRoot, readModuleRootInfo } from "./module-root.js";
6
+ export { setModuleEnabled } from "./module-toggle.js";
5
7
  export { resolveLocalModuleRoot } from "./paths.js";
6
- export { isValidModuleRoot, readModuleRootInfo, findModuleRootFromFile, } from "./module-root.js";
8
+ export { rebuildAndDeploy } from "./rebuild.js";
9
+ export { runRegistrySubmitCommand } from "./registry-command.js";
10
+ export { MODULE_REGISTRY_REPO, createRegistryEntry, submitModuleToRegistry, } from "./registry-submit.js";
11
+ export { resolveSfmcCli, runSfmcCli, } from "./sfmc-cli.js";
7
12
  export { isValidSfmcRoot } from "./sfmc-root.js";
8
- export { resolveSfmcCli, runSfmcCli } from "./sfmc-cli.js";
9
13
  export { startModuleWatch } from "./watch.js";
10
- export { rebuildAndDeploy } from "./rebuild.js";
11
- export { setModuleEnabled, } from "./module-toggle.js";
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/paths.ts", "../src/module-root.ts", "../src/sfmc-root.ts", "../src/sfmc-cli.ts", "../src/watch.ts", "../src/rebuild.ts", "../src/module-toggle.ts"],
4
- "sourcesContent": ["import path from \"node:path\";\n\n/** \u89E3\u6790 `--from local[:path]` / `dir:` \u6216 cwd \u4E3A\u6A21\u5757\u6839\u3002 */\nexport function resolveLocalModuleRoot(args: { from?: string | null; cwd: string }): string {\n const raw = (args.from ?? \"\").trim();\n if (!raw) return path.resolve(args.cwd);\n if (raw === \"local\") return path.resolve(args.cwd);\n if (raw.startsWith(\"local:\")) {\n const p = raw.slice(\"local:\".length).trim();\n if (!p) return path.resolve(args.cwd);\n return path.isAbsolute(p) ? path.resolve(p) : path.resolve(args.cwd, p);\n }\n if (raw.startsWith(\"dir:\")) {\n const p = raw.slice(\"dir:\".length).trim();\n return path.isAbsolute(p) ? path.resolve(p) : path.resolve(args.cwd, p);\n }\n return path.resolve(args.cwd);\n}\n", "/**\n * \u6709\u6548\u6A21\u5757\u6839\u5224\u5B9A\uFF08\u6269\u5C55\u4E0E CLI \u5171\u7528\u6743\u5A01\uFF09\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport type ModuleRootInfo = {\n root: string;\n id: string;\n name?: string;\n schemaVersion: number;\n};\n\n/** \u76EE\u5F55\u662F\u5426\u4E3A\u6709\u6548 SFMC \u6A21\u5757\u6839\uFF1Apackage.json + sapi/manifest.json schemaVersion===2 + \u975E\u7A7A id */\nexport function isValidModuleRoot(dir: string): boolean {\n return readModuleRootInfo(dir) !== null;\n}\n\nexport function readModuleRootInfo(dir: string): ModuleRootInfo | null {\n const pkg = path.join(dir, \"package.json\");\n const manifestPath = path.join(dir, \"sapi\", \"manifest.json\");\n if (!fs.existsSync(pkg) || !fs.existsSync(manifestPath)) return null;\n try {\n const m = JSON.parse(fs.readFileSync(manifestPath, \"utf8\")) as {\n schemaVersion?: number;\n id?: string;\n name?: string;\n };\n if (m.schemaVersion !== 2) return null;\n if (typeof m.id !== \"string\" || !m.id.trim()) return null;\n return {\n root: path.resolve(dir),\n id: m.id.trim(),\n name: typeof m.name === \"string\" ? m.name : undefined,\n schemaVersion: 2,\n };\n } catch {\n return null;\n }\n}\n\n/** \u81EA filePath \u5411\u4E0A\u627E\u6700\u8FD1\u6709\u6548\u6A21\u5757\u6839\uFF1B\u627E\u4E0D\u5230\u8FD4\u56DE null */\nexport function findModuleRootFromFile(filePath: string): string | null {\n let cur = path.dirname(path.resolve(filePath));\n const root = path.parse(cur).root;\n while (true) {\n if (isValidModuleRoot(cur)) return cur;\n if (cur === root) return null;\n const parent = path.dirname(cur);\n if (parent === cur) return null;\n cur = parent;\n }\n}\n", "/**\n * SFMC \u5DE5\u4F5C\u76EE\u5F55\u5224\u5B9A\uFF08\u8FD0\u884C\u65F6\u6839\uFF1Aconfigs/\u3001modules/\uFF1B\u4E0D\u662F\u6E90\u7801\u4ED3\u5E93\u6839\uFF09\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/** \u76EE\u5F55\u662F\u5426\u4E3A\u6709\u6548 SFMC \u5DE5\u4F5C\u76EE\u5F55\uFF08\u542B configs/ \u4E0E modules/\uFF09\u3002 */\nexport function isValidSfmcRoot(dir: string): boolean {\n if (!dir || !dir.trim()) return false;\n const root = path.resolve(dir);\n try {\n return (\n fs.statSync(path.join(root, \"configs\")).isDirectory() &&\n fs.statSync(path.join(root, \"modules\")).isDirectory()\n );\n } catch {\n return false;\n }\n}\n", "/**\n * \u89E3\u6790\u5E76 spawn sfmc CLI\u3002\n * CLI \u4E0E SFMC \u5DE5\u4F5C\u76EE\u5F55\u65E0\u5173\uFF1A\u4ECE\u672C\u5305/\u6269\u5C55\u5B89\u88C5\u6811\u3001PATH \u6216\u663E\u5F0F\u914D\u7F6E\u89E3\u6790\uFF1B\n * \u5DE5\u4F5C\u76EE\u5F55\u53EA\u4F5C\u4E3A spawn \u7684 cwd \u4E0E SFMC_ROOT\u3002\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { isValidSfmcRoot } from \"./sfmc-root.js\";\n\n/** chalk.hex(\"#e06c75\") \u7684\u771F\u5F69\u5E8F\u5217\uFF1BCLI \u5931\u8D25\u6587\u6848\u7ECF c.red() \u5305\u88C5\u3002 */\nconst CLI_ANSI_RED = \"\\u001b[38;2;224;108;117m\";\n\nexport interface ResolveSfmcCliOptions {\n /** \u663E\u5F0F CLI \u5165\u53E3\uFF08.js/.mjs\uFF09\uFF1B\u4E5F\u53EF\u7528\u73AF\u5883\u53D8\u91CF SFMC_CLI\u3002 */\n cliPath?: string;\n}\n\nfunction tryRequireCli(from: string): string | null {\n try {\n const resolved = createRequire(from).resolve(\"@sfmc-bds/cli/cli\");\n return fs.existsSync(resolved) ? resolved : null;\n } catch {\n return null;\n }\n}\n\n/** \u81EA PATH \u4E0A\u7684 sfmc \u53EF\u6267\u884C\u6587\u4EF6\u65C1\u89E3\u6790 @sfmc-bds/cli\uFF08npm global / \u672C\u5730 .bin\uFF09\u3002 */\nfunction resolveCliFromPathEnv(): string | null {\n const pathEnv = process.env.PATH ?? \"\";\n const names =\n process.platform === \"win32\" ? [\"sfmc.cmd\", \"sfmc.exe\", \"sfmc.ps1\", \"sfmc\"] : [\"sfmc\"];\n for (const dir of pathEnv.split(path.delimiter)) {\n if (!dir) continue;\n let hit = false;\n for (const name of names) {\n if (fs.existsSync(path.join(dir, name))) {\n hit = true;\n break;\n }\n }\n if (!hit) continue;\n const nearCli = path.join(dir, \"..\", \"@sfmc-bds\", \"cli\", \"dist\", \"main.js\");\n if (fs.existsSync(nearCli)) return nearCli;\n const nearMeta = path.join(dir, \"..\", \"@sfmc-bds\", \"sfmc\", \"bin\", \"sfmc.mjs\");\n if (fs.existsSync(nearMeta)) return nearMeta;\n const fromBin = tryRequireCli(path.join(dir, \"..\", \"package.json\"));\n if (fromBin) return fromBin;\n }\n return null;\n}\n\n/**\n * \u89E3\u6790 sfmc CLI \u811A\u672C\u8DEF\u5F84\u3002\n * \u987A\u5E8F\uFF1A\u663E\u5F0F cliPath / SFMC_CLI \u2192 \u672C\u5305\u5B89\u88C5\u6811\uFF08@sfmc-bds/cli\uFF09\u2192 cwd \u7684 node_modules \u2192 PATH\u3002\n */\nexport function resolveSfmcCli(opts: ResolveSfmcCliOptions = {}): string {\n const explicit = (opts.cliPath || process.env.SFMC_CLI || \"\").trim();\n if (explicit) {\n const p = path.resolve(explicit);\n if (!fs.existsSync(p)) {\n throw new Error(`\u6307\u5B9A\u7684 sfmc CLI \u4E0D\u5B58\u5728\uFF1A${p}\uFF08\u68C0\u67E5 SFMC_CLI / sfmc.cliPath\uFF09`);\n }\n return p;\n }\n\n const here = fileURLToPath(import.meta.url);\n const fromInstall = tryRequireCli(here);\n if (fromInstall) return fromInstall;\n\n const fromCwd = tryRequireCli(path.join(process.cwd(), \"package.json\"));\n if (fromCwd) return fromCwd;\n\n const fromPath = resolveCliFromPathEnv();\n if (fromPath) return fromPath;\n\n throw new Error(\n \"\u65E0\u6CD5\u89E3\u6790 sfmc CLI\u3002\u8BF7\u5B89\u88C5 @sfmc-bds/cli\uFF08\u6216\u786E\u4FDD PATH \u4E0A\u6709 sfmc\uFF09\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF SFMC_CLI / \u6269\u5C55\u914D\u7F6E sfmc.cliPath\u3002\"\n );\n}\n\nfunction looksLikeCliFailure(output: string): boolean {\n if (output.includes(CLI_ANSI_RED)) return true;\n if (/\\[pack-manager\\].*required/.test(output)) return true;\n if (/^Error:/m.test(output)) return true;\n return false;\n}\n\nexport interface RunSfmcCliResult {\n ok: boolean;\n output: string;\n}\n\nexport interface RunSfmcCliOptions {\n cliPath?: string;\n}\n\n/** \u5728 SFMC \u5DE5\u4F5C\u76EE\u5F55\u4E0B\u6267\u884C `node <sfmc-cli> ...args`\uFF08env.SFMC_ROOT = \u5DE5\u4F5C\u76EE\u5F55\uFF09\u3002 */\nexport async function runSfmcCli(\n sfmcRoot: string,\n args: string[],\n opts: RunSfmcCliOptions = {}\n): Promise<RunSfmcCliResult> {\n const root = path.resolve(sfmcRoot);\n if (!isValidSfmcRoot(root)) {\n return {\n ok: false,\n output: `\u65E0\u6548\u7684 SFMC \u5DE5\u4F5C\u76EE\u5F55\uFF1A${root}\uFF08\u9700\u8981\u542B configs/ \u4E0E modules/\uFF0C\u4E0D\u5FC5\u542B\u6E90\u7801\u4ED3\u5E93\uFF09`,\n };\n }\n\n let script: string;\n try {\n script = resolveSfmcCli({ cliPath: opts.cliPath });\n } catch (e) {\n return { ok: false, output: e instanceof Error ? e.message : String(e) };\n }\n\n const r = await new Promise<{ code: number | null; output: string }>((resolve) => {\n const proc = spawn(process.execPath, [script, ...args], {\n cwd: root,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: { ...process.env, SFMC_ROOT: root },\n });\n let out = \"\";\n proc.stdout?.on(\"data\", (d) => {\n out += d.toString();\n });\n proc.stderr?.on(\"data\", (d) => {\n out += d.toString();\n });\n proc.on(\"exit\", (code) => resolve({ code, output: out }));\n proc.on(\"error\", (e) => resolve({ code: 1, output: e.message }));\n });\n\n const ok = r.code === 0 && !looksLikeCliFailure(r.output);\n return { ok, output: r.output };\n}\n", "/**\n * \u76D1\u542C\u6A21\u5757 sapi/src\uFF0C\u9632\u6296\u540E\u56DE\u8C03 onRebuild\uFF08\u7531\u8C03\u7528\u65B9\u6CE8\u5165 rebuildAndDeploy / \u5176\u5B83\uFF09\u3002\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface ModuleWatchOptions {\n moduleRoot: string;\n debounceMs?: number;\n log?: (line: string) => void;\n onRebuild: () => Promise<{ ok: boolean; message?: string }>;\n}\n\nexport function startModuleWatch(opts: ModuleWatchOptions): { stop: () => void } {\n const log = opts.log ?? ((line: string) => console.log(line));\n const debounceMs = opts.debounceMs ?? 200;\n const sapiDir = path.join(opts.moduleRoot, \"sapi\");\n const srcDir = path.join(sapiDir, \"src\");\n\n if (!fs.existsSync(sapiDir)) {\n log(`[devkit:watch] \u65E0 sapi \u76EE\u5F55: ${sapiDir}`);\n return { stop: () => {} };\n }\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let pending = new Set<string>();\n let running = false;\n let stopped = false;\n\n log(`[devkit:watch] \u76D1\u542C ${opts.moduleRoot}`);\n\n const runOnce = async () => {\n if (running || stopped) return;\n running = true;\n const changed = [...pending];\n pending = new Set();\n const t0 = Date.now();\n log(`[devkit:watch] rebuild (${changed.length})\u2026`);\n try {\n const r = await opts.onRebuild();\n const ms = Date.now() - t0;\n if (r.ok) log(`[devkit:watch] ok ${ms}ms`);\n else log(`[devkit:watch] fail ${ms}ms ${r.message ?? \"\"}`);\n } catch (e) {\n log(`[devkit:watch] error ${(e as Error).message}`);\n } finally {\n running = false;\n }\n };\n\n const onSrc = (rel: string) => {\n if (stopped) return;\n if (rel.includes(\"node_modules\") || rel.includes(\"dist\") || rel.endsWith(\".tgz\")) return;\n pending.add(rel);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => void runOnce(), debounceMs);\n };\n\n const watchers: fs.FSWatcher[] = [];\n if (fs.existsSync(srcDir)) {\n try {\n watchers.push(\n fs.watch(srcDir, { recursive: true, persistent: true }, (_e, filename) => {\n if (filename) onSrc(filename.toString());\n })\n );\n } catch {\n watchers.push(\n fs.watch(srcDir, { persistent: true }, (_e, filename) => {\n if (filename) onSrc(filename.toString());\n })\n );\n }\n }\n\n const watchMeta = (file: string, kind: string) => {\n if (!fs.existsSync(file)) return;\n watchers.push(\n fs.watch(file, { persistent: true }, (event) => {\n if (event === \"change\") log(`[devkit:watch] ${kind} \u5DF2\u6539\uFF0C\u9700\u91CD\u542F BDS\uFF08SAPI \u542F\u52A8\u671F\u7F13\u5B58\uFF09`);\n })\n );\n };\n watchMeta(path.join(sapiDir, \"manifest.json\"), \"manifest\");\n watchMeta(path.join(sapiDir, \"tsconfig.json\"), \"tsconfig\");\n\n return {\n stop() {\n stopped = true;\n if (debounceTimer) clearTimeout(debounceTimer);\n for (const w of watchers) {\n try {\n w.close();\n } catch {\n /* ignore */\n }\n }\n log(\"[devkit:watch] stopped\");\n },\n };\n}\n", "/**\n * \u7EC4\u88C5\u5E76\u90E8\u7F72\u884C\u4E3A\u5305\uFF1A\u5728 SFMC_ROOT \u4E0B spawn `sfmc mod reload`\n * \uFF08\u4E0E CLI \u540C\u4E00\u5165\u53E3\uFF0C\u5B8C\u6574\u4F20\u53C2\uFF1B\u52FF\u76F4\u63A5\u88F8\u8C03 pack-manager verb\uFF09\u3002\n */\n\nimport { runSfmcCli } from \"./sfmc-cli.js\";\n\nexport interface RebuildOptions {\n /** SFMC \u5DE5\u4F5C\u76EE\u5F55\uFF08\u542B configs/\u3001modules/\uFF09\u3002 */\n sfmcRoot: string;\n /** \u4EC5\u6784\u5EFA\u90E8\u7F72\uFF0C\u4E0D\u5411 BDS \u53D1 reload\uFF08\u5BF9\u5E94 `mod reload --build-only`\uFF09\u3002 */\n buildOnly?: boolean;\n /** \u53EF\u9009\uFF1A\u663E\u5F0F sfmc CLI \u5165\u53E3\uFF1B\u9ED8\u8BA4\u7531\u5B89\u88C5\u6811 / PATH / SFMC_CLI \u89E3\u6790\u3002 */\n cliPath?: string;\n}\n\nexport interface RebuildResult {\n ok: boolean;\n output: string;\n}\n\nexport async function rebuildAndDeploy(opts: RebuildOptions): Promise<RebuildResult> {\n const args = [\"mod\", \"reload\"];\n if (opts.buildOnly) args.push(\"--build-only\");\n return runSfmcCli(opts.sfmcRoot, args, { cliPath: opts.cliPath });\n}\n", "/**\n * \u6A21\u5757\u542F\u505C\uFF1Aspawn `sfmc mod enable|disable`\uFF08\u5199 module-lock + best-effort \u70ED\u540C\u6B65\uFF09\u3002\n */\n\nimport { runSfmcCli, type RunSfmcCliResult } from \"./sfmc-cli.js\";\n\nexport interface SetModuleEnabledOptions {\n /** SFMC \u5DE5\u4F5C\u76EE\u5F55\uFF08\u542B modules/module-lock.json\uFF09\u3002 */\n sfmcRoot: string;\n /** \u6A21\u5757 logicalId / folderId / configKey\uFF08\u4E0E CLI `mod enable` \u4E00\u81F4\uFF09\u3002 */\n moduleId: string;\n enabled: boolean;\n /** \u53EF\u9009\uFF1A\u663E\u5F0F sfmc CLI \u5165\u53E3\uFF1B\u9ED8\u8BA4\u7531\u5B89\u88C5\u6811 / PATH / SFMC_CLI \u89E3\u6790\u3002 */\n cliPath?: string;\n}\n\nexport type SetModuleEnabledResult = RunSfmcCliResult;\n\n/** \u542F\u7528\u6216\u5173\u95ED\u6A21\u5757\uFF08\u4E0E CLI `mod enable|disable` \u540C\u6E90\uFF09\u3002 */\nexport async function setModuleEnabled(opts: SetModuleEnabledOptions): Promise<SetModuleEnabledResult> {\n const id = String(opts.moduleId || \"\").trim();\n if (!id) {\n return { ok: false, output: \"moduleId \u4E3A\u7A7A\" };\n }\n const action = opts.enabled ? \"enable\" : \"disable\";\n return runSfmcCli(opts.sfmcRoot, [\"mod\", action, id], { cliPath: opts.cliPath });\n}\n"],
5
- "mappings": "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",
6
- "names": ["path", "fs", "path", "fs", "path", "fs", "path", "fs", "path"]
3
+ "sources": ["../src/module-root.ts", "../src/sfmc-cli.ts", "../src/sfmc-root.ts", "../src/module-toggle.ts", "../src/paths.ts", "../src/rebuild.ts", "../src/registry-command.ts", "../src/registry-submit.ts", "../src/watch.ts"],
4
+ "sourcesContent": ["/**\n * \u6709\u6548\u6A21\u5757\u6839\u5224\u5B9A\uFF08\u6269\u5C55\u4E0E CLI \u5171\u7528\u6743\u5A01\uFF09\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport type ModuleRootInfo = {\n root: string;\n id: string;\n name?: string;\n schemaVersion: number;\n};\n\n/** \u76EE\u5F55\u662F\u5426\u4E3A\u6709\u6548 SFMC \u6A21\u5757\u6839\uFF1Apackage.json + sapi/manifest.json schemaVersion===2 + \u975E\u7A7A id */\nexport function isValidModuleRoot(dir: string): boolean {\n return readModuleRootInfo(dir) !== null;\n}\n\nexport function readModuleRootInfo(dir: string): ModuleRootInfo | null {\n const pkg = path.join(dir, \"package.json\");\n const manifestPath = path.join(dir, \"sapi\", \"manifest.json\");\n if (!fs.existsSync(pkg) || !fs.existsSync(manifestPath)) return null;\n try {\n const m = JSON.parse(fs.readFileSync(manifestPath, \"utf8\")) as {\n schemaVersion?: number;\n id?: string;\n name?: string;\n };\n if (m.schemaVersion !== 2) return null;\n if (typeof m.id !== \"string\" || !m.id.trim()) return null;\n return {\n root: path.resolve(dir),\n id: m.id.trim(),\n name: typeof m.name === \"string\" ? m.name : undefined,\n schemaVersion: 2,\n };\n } catch {\n return null;\n }\n}\n\n/** \u81EA filePath \u5411\u4E0A\u627E\u6700\u8FD1\u6709\u6548\u6A21\u5757\u6839\uFF1B\u627E\u4E0D\u5230\u8FD4\u56DE null */\nexport function findModuleRootFromFile(filePath: string): string | null {\n let cur = path.dirname(path.resolve(filePath));\n const root = path.parse(cur).root;\n while (true) {\n if (isValidModuleRoot(cur)) return cur;\n if (cur === root) return null;\n const parent = path.dirname(cur);\n if (parent === cur) return null;\n cur = parent;\n }\n}\n", "/**\n * \u89E3\u6790\u5E76 spawn sfmc CLI\u3002\n * CLI \u4E0E SFMC \u5DE5\u4F5C\u76EE\u5F55\u65E0\u5173\uFF1A\u4ECE\u672C\u5305/\u6269\u5C55\u5B89\u88C5\u6811\u3001PATH \u6216\u663E\u5F0F\u914D\u7F6E\u89E3\u6790\uFF1B\n * \u5DE5\u4F5C\u76EE\u5F55\u53EA\u4F5C\u4E3A spawn \u7684 cwd \u4E0E SFMC_ROOT\u3002\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { isValidSfmcRoot } from \"./sfmc-root.js\";\n\n/** chalk.hex(\"#e06c75\") \u7684\u771F\u5F69\u5E8F\u5217\uFF1BCLI \u5931\u8D25\u6587\u6848\u7ECF c.red() \u5305\u88C5\u3002 */\nconst CLI_ANSI_RED = \"\\u001b[38;2;224;108;117m\";\n\nexport interface ResolveSfmcCliOptions {\n /** \u663E\u5F0F CLI \u5165\u53E3\uFF08.js/.mjs\uFF09\uFF1B\u4E5F\u53EF\u7528\u73AF\u5883\u53D8\u91CF SFMC_CLI\u3002 */\n cliPath?: string;\n}\n\nfunction tryRequireCli(from: string): string | null {\n try {\n const resolved = createRequire(from).resolve(\"@sfmc-bds/cli/cli\");\n return fs.existsSync(resolved) ? resolved : null;\n } catch {\n return null;\n }\n}\n\n/** \u81EA PATH \u4E0A\u7684 sfmc \u53EF\u6267\u884C\u6587\u4EF6\u65C1\u89E3\u6790 @sfmc-bds/cli\uFF08npm global / \u672C\u5730 .bin\uFF09\u3002 */\nfunction resolveCliFromPathEnv(): string | null {\n const pathEnv = process.env.PATH ?? \"\";\n const names =\n process.platform === \"win32\" ? [\"sfmc.cmd\", \"sfmc.exe\", \"sfmc.ps1\", \"sfmc\"] : [\"sfmc\"];\n for (const dir of pathEnv.split(path.delimiter)) {\n if (!dir) continue;\n let hit = false;\n for (const name of names) {\n if (fs.existsSync(path.join(dir, name))) {\n hit = true;\n break;\n }\n }\n if (!hit) continue;\n const nearCli = path.join(dir, \"..\", \"@sfmc-bds\", \"cli\", \"dist\", \"main.js\");\n if (fs.existsSync(nearCli)) return nearCli;\n const nearMeta = path.join(dir, \"..\", \"@sfmc-bds\", \"sfmc\", \"bin\", \"sfmc.mjs\");\n if (fs.existsSync(nearMeta)) return nearMeta;\n const fromBin = tryRequireCli(path.join(dir, \"..\", \"package.json\"));\n if (fromBin) return fromBin;\n }\n return null;\n}\n\n/**\n * \u89E3\u6790 sfmc CLI \u811A\u672C\u8DEF\u5F84\u3002\n * \u987A\u5E8F\uFF1A\u663E\u5F0F cliPath / SFMC_CLI \u2192 \u672C\u5305\u5B89\u88C5\u6811\uFF08@sfmc-bds/cli\uFF09\u2192 cwd \u7684 node_modules \u2192 PATH\u3002\n */\nexport function resolveSfmcCli(opts: ResolveSfmcCliOptions = {}): string {\n const explicit = (opts.cliPath || process.env.SFMC_CLI || \"\").trim();\n if (explicit) {\n const p = path.resolve(explicit);\n if (!fs.existsSync(p)) {\n throw new Error(`\u6307\u5B9A\u7684 sfmc CLI \u4E0D\u5B58\u5728\uFF1A${p}\uFF08\u68C0\u67E5 SFMC_CLI / sfmc.cliPath\uFF09`);\n }\n return p;\n }\n\n const here = fileURLToPath(import.meta.url);\n const fromInstall = tryRequireCli(here);\n if (fromInstall) return fromInstall;\n\n const fromCwd = tryRequireCli(path.join(process.cwd(), \"package.json\"));\n if (fromCwd) return fromCwd;\n\n const fromPath = resolveCliFromPathEnv();\n if (fromPath) return fromPath;\n\n throw new Error(\n \"\u65E0\u6CD5\u89E3\u6790 sfmc CLI\u3002\u8BF7\u5B89\u88C5 @sfmc-bds/cli\uFF08\u6216\u786E\u4FDD PATH \u4E0A\u6709 sfmc\uFF09\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF SFMC_CLI / \u6269\u5C55\u914D\u7F6E sfmc.cliPath\u3002\"\n );\n}\n\nfunction looksLikeCliFailure(output: string): boolean {\n if (output.includes(CLI_ANSI_RED)) return true;\n if (/\\[pack-manager\\].*required/.test(output)) return true;\n if (/^Error:/m.test(output)) return true;\n return false;\n}\n\nexport interface RunSfmcCliResult {\n ok: boolean;\n output: string;\n}\n\nexport interface RunSfmcCliOptions {\n cliPath?: string;\n}\n\n/** \u5728 SFMC \u5DE5\u4F5C\u76EE\u5F55\u4E0B\u6267\u884C `node <sfmc-cli> ...args`\uFF08env.SFMC_ROOT = \u5DE5\u4F5C\u76EE\u5F55\uFF09\u3002 */\nexport async function runSfmcCli(\n sfmcRoot: string,\n args: string[],\n opts: RunSfmcCliOptions = {}\n): Promise<RunSfmcCliResult> {\n const root = path.resolve(sfmcRoot);\n if (!isValidSfmcRoot(root)) {\n return {\n ok: false,\n output: `\u65E0\u6548\u7684 SFMC \u5DE5\u4F5C\u76EE\u5F55\uFF1A${root}\uFF08\u9700\u8981\u542B configs/ \u4E0E modules/\uFF0C\u4E0D\u5FC5\u542B\u6E90\u7801\u4ED3\u5E93\uFF09`,\n };\n }\n\n let script: string;\n try {\n script = resolveSfmcCli({ cliPath: opts.cliPath });\n } catch (e) {\n return { ok: false, output: e instanceof Error ? e.message : String(e) };\n }\n\n const r = await new Promise<{ code: number | null; output: string }>((resolve) => {\n const proc = spawn(process.execPath, [script, ...args], {\n cwd: root,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: { ...process.env, SFMC_ROOT: root },\n });\n let out = \"\";\n proc.stdout?.on(\"data\", (d) => {\n out += d.toString();\n });\n proc.stderr?.on(\"data\", (d) => {\n out += d.toString();\n });\n proc.on(\"exit\", (code) => resolve({ code, output: out }));\n proc.on(\"error\", (e) => resolve({ code: 1, output: e.message }));\n });\n\n const ok = r.code === 0 && !looksLikeCliFailure(r.output);\n return { ok, output: r.output };\n}\n", "/**\n * SFMC \u5DE5\u4F5C\u76EE\u5F55\u5224\u5B9A\uFF08\u8FD0\u884C\u65F6\u6839\uFF1Aconfigs/\u3001modules/\uFF1B\u4E0D\u662F\u6E90\u7801\u4ED3\u5E93\u6839\uFF09\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/** \u76EE\u5F55\u662F\u5426\u4E3A\u6709\u6548 SFMC \u5DE5\u4F5C\u76EE\u5F55\uFF08\u542B configs/ \u4E0E modules/\uFF09\u3002 */\nexport function isValidSfmcRoot(dir: string): boolean {\n if (!dir || !dir.trim()) return false;\n const root = path.resolve(dir);\n try {\n return (\n fs.statSync(path.join(root, \"configs\")).isDirectory() &&\n fs.statSync(path.join(root, \"modules\")).isDirectory()\n );\n } catch {\n return false;\n }\n}\n", "/**\n * \u6A21\u5757\u542F\u505C\uFF1Aspawn `sfmc mod enable|disable`\uFF08\u5199 module-lock + best-effort \u70ED\u540C\u6B65\uFF09\u3002\n */\n\nimport { runSfmcCli, type RunSfmcCliResult } from \"./sfmc-cli.js\";\n\nexport interface SetModuleEnabledOptions {\n /** SFMC \u5DE5\u4F5C\u76EE\u5F55\uFF08\u542B modules/module-lock.json\uFF09\u3002 */\n sfmcRoot: string;\n /** \u6A21\u5757 logicalId / folderId / configKey\uFF08\u4E0E CLI `mod enable` \u4E00\u81F4\uFF09\u3002 */\n moduleId: string;\n enabled: boolean;\n /** \u53EF\u9009\uFF1A\u663E\u5F0F sfmc CLI \u5165\u53E3\uFF1B\u9ED8\u8BA4\u7531\u5B89\u88C5\u6811 / PATH / SFMC_CLI \u89E3\u6790\u3002 */\n cliPath?: string;\n}\n\nexport type SetModuleEnabledResult = RunSfmcCliResult;\n\n/** \u542F\u7528\u6216\u5173\u95ED\u6A21\u5757\uFF08\u4E0E CLI `mod enable|disable` \u540C\u6E90\uFF09\u3002 */\nexport async function setModuleEnabled(opts: SetModuleEnabledOptions): Promise<SetModuleEnabledResult> {\n const id = String(opts.moduleId || \"\").trim();\n if (!id) {\n return { ok: false, output: \"moduleId \u4E3A\u7A7A\" };\n }\n const action = opts.enabled ? \"enable\" : \"disable\";\n return runSfmcCli(opts.sfmcRoot, [\"mod\", action, id], { cliPath: opts.cliPath });\n}\n", "import path from \"node:path\";\n\n/** \u89E3\u6790 `--from local[:path]` / `dir:` \u6216 cwd \u4E3A\u6A21\u5757\u6839\u3002 */\nexport function resolveLocalModuleRoot(args: { from?: string | null; cwd: string }): string {\n const raw = (args.from ?? \"\").trim();\n if (!raw) return path.resolve(args.cwd);\n if (raw === \"local\") return path.resolve(args.cwd);\n if (raw.startsWith(\"local:\")) {\n const p = raw.slice(\"local:\".length).trim();\n if (!p) return path.resolve(args.cwd);\n return path.isAbsolute(p) ? path.resolve(p) : path.resolve(args.cwd, p);\n }\n if (raw.startsWith(\"dir:\")) {\n const p = raw.slice(\"dir:\".length).trim();\n return path.isAbsolute(p) ? path.resolve(p) : path.resolve(args.cwd, p);\n }\n return path.resolve(args.cwd);\n}\n", "/**\n * \u7EC4\u88C5\u5E76\u90E8\u7F72\u884C\u4E3A\u5305\uFF1A\u5728 SFMC_ROOT \u4E0B spawn `sfmc mod reload`\n * \uFF08\u4E0E CLI \u540C\u4E00\u5165\u53E3\uFF0C\u5B8C\u6574\u4F20\u53C2\uFF1B\u52FF\u76F4\u63A5\u88F8\u8C03 pack-manager verb\uFF09\u3002\n */\n\nimport { runSfmcCli } from \"./sfmc-cli.js\";\n\nexport interface RebuildOptions {\n /** SFMC \u5DE5\u4F5C\u76EE\u5F55\uFF08\u542B configs/\u3001modules/\uFF09\u3002 */\n sfmcRoot: string;\n /** \u4EC5\u6784\u5EFA\u90E8\u7F72\uFF0C\u4E0D\u5411 BDS \u53D1 reload\uFF08\u5BF9\u5E94 `mod reload --build-only`\uFF09\u3002 */\n buildOnly?: boolean;\n /** \u53EF\u9009\uFF1A\u663E\u5F0F sfmc CLI \u5165\u53E3\uFF1B\u9ED8\u8BA4\u7531\u5B89\u88C5\u6811 / PATH / SFMC_CLI \u89E3\u6790\u3002 */\n cliPath?: string;\n}\n\nexport interface RebuildResult {\n ok: boolean;\n output: string;\n}\n\nexport async function rebuildAndDeploy(opts: RebuildOptions): Promise<RebuildResult> {\n const args = [\"mod\", \"reload\"];\n if (opts.buildOnly) args.push(\"--build-only\");\n return runSfmcCli(opts.sfmcRoot, args, { cliPath: opts.cliPath });\n}\n", "import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { submitModuleToRegistry, type RegistryModule, type RegistrySubmitOptions } from \"./registry-submit.js\";\n\n/** CLI \u4E0E CI \u5171\u7528\u53C2\u6570\u89E3\u6790\uFF0C\u672A\u77E5\u53C2\u6570\u8FD4\u56DE\u5931\u8D25\uFF0C\u907F\u514D\u8BEF\u63D0\u4EA4\u3002 */\nexport async function runRegistrySubmitCommand(args: string[]): Promise<string> {\n const options: RegistrySubmitOptions = {};\n let entryFile: string | undefined;\n const help =\n \"sfmc mod submit [\u6A21\u5757\u76EE\u5F55] [--dry-run] [--no-fork] [--wait-for-publish] [--registry owner/repo]\\nCI\uFF1Asfmc-module-submit --entry-file <JSON>\uFF08\u4E0E\u6A21\u5757\u76EE\u5F55\u4E92\u65A5\uFF09\";\n if (args.includes(\"--help\") || args.includes(\"-h\")) return help;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === \"--dry-run\") options.dryRun = true;\n else if (arg === \"--no-fork\") options.noFork = true;\n else if (arg === \"--wait-for-publish\") options.waitForPublish = true;\n else if (arg === \"--registry\" || arg === \"--entry-file\") {\n const value = args[++i];\n if (!value || value.startsWith(\"-\")) throw new Error(`\u7F3A\u5C11 ${arg} \u7684\u503C`);\n if (arg === \"--registry\") options.registryRepo = value;\n else entryFile = value;\n } else if (arg.startsWith(\"-\") || options.moduleRoot) throw new Error(`\u672A\u77E5\u6216\u91CD\u590D\u53C2\u6570\uFF1A${arg}\\n${help}`);\n else options.moduleRoot = path.resolve(arg);\n }\n if (entryFile) {\n if (options.moduleRoot) throw new Error(\"--entry-file \u4E0E\u6A21\u5757\u76EE\u5F55\u4E92\u65A5\");\n options.entry = JSON.parse(await fs.readFile(path.resolve(entryFile), \"utf8\")) as RegistryModule;\n }\n const result = await submitModuleToRegistry(options);\n if (result.status === \"preview\")\n return `\u9884\u89C8\uFF1A${result.registryRepo}/${result.path}\\n\u539F\u6761\u76EE\uFF1A\\n${JSON.stringify(result.previous, null, 2)}\\n\u62DF\u63D0\u4EA4\uFF1A\\n${JSON.stringify(result.entry, null, 2)}\\n\u6821\u9A8C\u901A\u8FC7\uFF0C\u672A\u5199\u5165\u8FDC\u7AEF\u3002`;\n return `${{ unchanged: \"\u5185\u5BB9\u76F8\u540C\uFF0C\u672A\u91CD\u590D\u63D0\u4EA4\", created: \"\u5DF2\u521B\u5EFA\u7D22\u5F15 PR\", updated: \"\u5DF2\u66F4\u65B0\u7D22\u5F15 PR\" }[result.status]}${result.prUrl ? `\uFF1A${result.prUrl}` : \"\"}`;\n}\n", "/** \u6A21\u5757\u7D22\u5F15\u63D0\u4EA4\u6838\u5FC3\u3002\u4EC5\u5199\u5165\u4E13\u7528\u5206\u652F\u4E2D\u7684\u5355\u4E2A\u5206\u7247\uFF0C\u4E0D\u64CD\u4F5C\u4F5C\u8005\u5DE5\u4F5C\u6811\u3002 */\nimport { Ajv, type AnySchema } from \"ajv\";\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport semver from \"semver\";\n\nexport const MODULE_REGISTRY_REPO = \"DogeLakeDev/sfmc-modules\";\ntype Json = Record<string, unknown>;\nexport interface RegistryMetadata {\n name?: string;\n description?: string;\n category?: \"gameplay\" | \"utility\" | \"system\" | \"economy\" | \"social\";\n tags?: string[];\n authors?: string[];\n}\nexport interface RegistryModule extends RegistryMetadata {\n id: string;\n name: string;\n description: string;\n npm: string;\n version: string;\n sdk: string;\n license: string;\n official: boolean;\n repo?: string;\n requires: string[];\n}\nexport interface RegistrySubmitOptions {\n moduleRoot?: string;\n /** \u8868\u5355\u7B49\u5165\u53E3\u63D0\u4F9B\u539F\u59CB\u5B57\u6BB5\uFF0C\u4ECD\u987B\u7ECF\u8FC7\u76F8\u540C npm\u3001Schema \u548C\u7D22\u5F15\u6821\u9A8C\u3002 */\n entry?: RegistryModule;\n registryRepo?: string;\n dryRun?: boolean;\n /** \u7EF4\u62A4\u8005 / GitHub App \u5728\u7D22\u5F15\u4ED3\u5EFA\u5206\u652F\uFF0C\u987B\u6709 Contents \u548C Pull requests \u5199\u6743\u9650\u3002 */\n noFork?: boolean;\n token?: string;\n /** \u7B49\u5F85\u521A\u53D1\u5E03\u7684\u7CBE\u786E\u7248\u672C\u53EF\u89C1\uFF1B\u4EC5\u91CD\u8BD5 404\uFF0C\u6700\u957F 60 \u79D2\u3002 */\n waitForPublish?: boolean;\n log?: (message: string) => void;\n}\nexport interface RegistrySubmitResult {\n status: \"preview\" | \"unchanged\" | \"created\" | \"updated\";\n entry: RegistryModule;\n previous: RegistryModule | null;\n path: string;\n prUrl?: string;\n registryRepo: string;\n}\nfunction object(value: unknown, label: string): Json {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(`${label} \u987B\u4E3A JSON \u5BF9\u8C61`);\n return value as Json;\n}\nfunction text(value: unknown, label: string): string {\n if (typeof value !== \"string\" || !value.trim()) throw new Error(`\u7F3A\u5C11 ${label}`);\n return value.trim();\n}\nfunction sourceRepo(value: unknown): string | undefined {\n if (value === undefined) return undefined;\n const raw = typeof value === \"string\" ? value : object(value, \"repository\").url;\n const repo = text(raw, \"repository.url\")\n .replace(/^git\\+/, \"\")\n .replace(/\\.git$/, \"\");\n const url = new URL(repo);\n if (url.protocol !== \"https:\" || url.username || url.password)\n throw new Error(\"repository \u987B\u4E3A\u4E0D\u542B\u51ED\u636E\u7684 HTTPS \u5730\u5740\");\n return url.href.replace(/\\/$/, \"\");\n}\n\n/** \u4F5C\u8005\u914D\u7F6E\u53EA\u8865\u5145\u5C55\u793A\u5B57\u6BB5\uFF1B\u5305\u3001\u7248\u672C\u3001SDK \u548C\u4F9D\u8D56\u6765\u81EA\u5DF2\u6709\u5951\u7EA6\u3002 */\nexport function createRegistryEntry(\n pkgValue: unknown,\n manifestValue: unknown,\n metadataValue: unknown = {}\n): RegistryModule {\n const pkg = object(pkgValue, \"package.json\");\n const manifest = object(manifestValue, \"sapi/manifest.json\");\n const metadata = object(metadataValue, \"sfmc.registry.json\");\n const allowed = new Set([\"$schema\", \"name\", \"description\", \"category\", \"tags\", \"authors\"]);\n for (const key of Object.keys(metadata))\n if (!allowed.has(key)) throw new Error(`sfmc.registry.json \u4E0D\u652F\u6301\u5B57\u6BB5 ${key}`);\n if (pkg.private === true) throw new Error(\"private \u6A21\u5757\u4E0D\u80FD\u63D0\u4EA4\u516C\u5171\u7D22\u5F15\");\n if (manifest.schemaVersion !== 2) throw new Error(\"\u9700\u8981 schemaVersion=2 \u7684\u6A21\u5757\u6E05\u5355\");\n const peers = object(pkg.peerDependencies ?? {}, \"peerDependencies\");\n const entry: RegistryModule = {\n id: text(manifest.id, \"manifest.id\"),\n name: text(metadata.name ?? manifest.name, \"\u5C55\u793A\u540D\u79F0\uFF08name\uFF09\"),\n description: text(metadata.description ?? pkg.description, \"\u529F\u80FD\u4ECB\u7ECD\uFF08description\uFF09\"),\n npm: text(pkg.name, \"package.name\"),\n version: text(pkg.version, \"package.version\"),\n sdk: text(peers[\"@sfmc-bds/sdk\"], \"peerDependencies.@sfmc-bds/sdk\"),\n license: text(pkg.license, \"package.license\"),\n official: false,\n requires: (manifest.requires ?? []) as string[],\n };\n const repo = sourceRepo(pkg.repository);\n if (repo) entry.repo = repo;\n for (const key of [\"category\", \"tags\", \"authors\"] as const) {\n if (metadata[key] !== undefined) Object.assign(entry, { [key]: metadata[key] });\n }\n return entry;\n}\n\nasync function readLocalEntry(root: string): Promise<{ entry: RegistryModule; overrides: Set<string> }> {\n const realRoot = await fs.realpath(root);\n const read = async (file: string) => {\n const target = await fs.realpath(path.join(realRoot, file));\n const relative = path.relative(realRoot, target);\n if (relative === \"..\" || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))\n throw new Error(`${file} \u987B\u4F4D\u4E8E\u6A21\u5757\u76EE\u5F55\u5185`);\n const raw = await fs.readFile(target, \"utf8\");\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n throw new Error(`${file} \u5305\u542B\u65E0\u6548 JSON`);\n }\n };\n let metadata: unknown = {};\n try {\n metadata = await read(\"sfmc.registry.json\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n return {\n entry: createRegistryEntry(await read(\"package.json\"), await read(\"sapi/manifest.json\"), metadata),\n overrides: new Set(Object.keys(object(metadata, \"sfmc.registry.json\"))),\n };\n}\n\nclass GitHubError extends Error {\n constructor(\n public status: number,\n endpoint: string,\n rateLimited = false\n ) {\n super(\n `GitHub ${status}: ${endpoint}${rateLimited ? \"\uFF08API \u914D\u989D\u5DF2\u7528\u5C3D\uFF0C\u8BF7\u767B\u5F55\u540E\u91CD\u8BD5\u6216\u7B49\u5F85\u914D\u989D\u6062\u590D\uFF09\" : status === 401 || status === 403 ? \"\uFF08\u68C0\u67E5\u767B\u5F55\u53CA Contents / Pull requests \u6743\u9650\uFF09\" : \"\"}`\n );\n }\n}\nasync function resolveToken(required: boolean): Promise<string | undefined> {\n const configured = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;\n if (configured) return configured;\n try {\n const { stdout } = await promisify(execFile)(\"gh\", [\"auth\", \"token\", \"--hostname\", \"github.com\"], {\n timeout: 10000,\n windowsHide: true,\n });\n return stdout.trim() || undefined;\n } catch {\n if (required) throw new Error(\"\u63D0\u4EA4\u9700\u8981 GitHub \u767B\u5F55\uFF1A\u6267\u884C gh auth login\uFF0C\u6216\u8BBE\u7F6E GH_TOKEN\u3002\");\n return undefined;\n }\n}\nfunction github(token?: string) {\n return async function api<T>(endpoint: string, method = \"GET\", body?: unknown): Promise<T> {\n const response = await fetch(`https://api.github.com/${endpoint}`, {\n method,\n headers: {\n Accept: \"application/vnd.github+json\",\n \"User-Agent\": \"sfmc-devkit\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(15000),\n });\n // \u4E0D\u56DE\u663E\u670D\u52A1\u5668\u6B63\u6587\u6216\u8BA4\u8BC1\u4FE1\u606F\u3002\n if (!response.ok)\n throw new GitHubError(\n response.status,\n endpoint,\n response.status === 429 || response.headers.get(\"x-ratelimit-remaining\") === \"0\"\n );\n return response.status === 204 ? (undefined as T) : ((await response.json()) as T);\n };\n}\ntype Api = ReturnType<typeof github>;\ninterface GitHubRepository {\n id: number;\n full_name: string;\n default_branch: string;\n fork: boolean;\n permissions?: { push?: boolean };\n parent?: { id: number; full_name: string };\n source?: { id: number; full_name: string };\n}\ninterface Content {\n type: string;\n encoding: string;\n content: string;\n sha: string;\n}\nfunction decode(file: Content): unknown {\n if (file.type !== \"file\" || file.encoding !== \"base64\") throw new Error(\"\u7D22\u5F15\u5185\u5BB9\u987B\u4E3A\u666E\u901A JSON \u6587\u4EF6\");\n return JSON.parse(Buffer.from(file.content, \"base64\").toString(\"utf8\"));\n}\nasync function content(api: Api, repo: string, file: string, ref: string): Promise<Content | null> {\n try {\n return await api<Content>(`repos/${repo}/contents/${file}?ref=${encodeURIComponent(ref)}`);\n } catch (error) {\n if (error instanceof GitHubError && error.status === 404) return null;\n throw error;\n }\n}\nfunction same(a: unknown, b: unknown): boolean {\n const stable = (value: unknown): unknown =>\n Array.isArray(value)\n ? value.map(stable)\n : value && typeof value === \"object\"\n ? Object.fromEntries(\n Object.entries(value)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, item]) => [key, stable(item)])\n )\n : value;\n return JSON.stringify(stable(a)) === JSON.stringify(stable(b));\n}\nasync function checkPublished(entry: RegistryModule, wait: boolean): Promise<void> {\n const deadline = Date.now() + (wait ? 60000 : 0);\n while (true) {\n const response = await fetch(\n `https://registry.npmjs.org/${encodeURIComponent(entry.npm)}/${encodeURIComponent(entry.version)}`,\n {\n signal: AbortSignal.timeout(15000),\n headers: { Accept: \"application/json\" },\n }\n );\n if (response.status === 404 && Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 3000));\n continue;\n }\n if (!response.ok)\n throw new Error(\n `\u516C\u5171 npm \u5C1A\u65E0\u53EF\u7528\u7684 ${entry.npm}@${entry.version}\uFF08HTTP ${response.status}\uFF09\uFF1B\u5148\u53D1\u5E03\u8BE5\u7CBE\u786E\u7248\u672C\u3002`\n );\n const pkg = object(await response.json(), \"npm \u5143\u6570\u636E\");\n const peers = object(pkg.peerDependencies ?? {}, \"npm peerDependencies\");\n if (\n pkg.name !== entry.npm ||\n pkg.version !== entry.version ||\n pkg.license !== entry.license ||\n peers[\"@sfmc-bds/sdk\"] !== entry.sdk\n ) {\n throw new Error(\"\u5DF2\u53D1\u5E03\u5305\u7684\u540D\u79F0\u3001\u7248\u672C\u3001\u8BB8\u53EF\u8BC1\u6216 SDK \u517C\u5BB9\u8303\u56F4\u4E0E\u63D0\u4EA4\u4FE1\u606F\u4E0D\u4E00\u81F4\");\n }\n if (sourceRepo(pkg.repository) !== entry.repo) throw new Error(\"\u5DF2\u53D1\u5E03\u5305\u7684\u6E90\u7801\u5730\u5740\u4E0E\u63D0\u4EA4\u4FE1\u606F\u4E0D\u4E00\u81F4\");\n return;\n }\n}\n\n/** \u6240\u6709\u5165\u53E3\u5171\u7528\uFF1A\u9884\u89C8\u4E5F\u9A8C\u8BC1\u5DF2\u53D1\u5E03\u7248\u672C\u4E0E\u6700\u65B0\u5206\u7247\uFF1B\u9884\u89C8\u9636\u6BB5\u4E0D\u4EA7\u751F\u8FDC\u7AEF\u5199\u5165\u3002 */\nexport async function submitModuleToRegistry(options: RegistrySubmitOptions = {}): Promise<RegistrySubmitResult> {\n const repo = options.registryRepo ?? MODULE_REGISTRY_REPO;\n if (!/^[\\w.-]+\\/[\\w.-]+$/.test(repo)) throw new Error(\"registryRepo \u987B\u4E3A owner/repo\");\n const local = options.entry ? null : await readLocalEntry(path.resolve(options.moduleRoot ?? process.cwd()));\n const entry = options.entry ? structuredClone(options.entry) : local!.entry;\n entry.requires ??= [];\n const token = options.token ?? (await resolveToken(!options.dryRun));\n if (!options.dryRun && !token) throw new Error(\"\u63D0\u4EA4\u9700\u8981\u6709\u6548\u7684 GitHub \u767B\u5F55\u6216 GH_TOKEN\");\n const api = github(token);\n const upstream = await api<GitHubRepository>(`repos/${repo}`);\n const canonicalRepo = upstream.full_name;\n const base = upstream.default_branch;\n const baseRef = await api<{ object: { sha: string } }>(\n `repos/${canonicalRepo}/git/ref/heads/${encodeURIComponent(base)}`\n );\n const baseSha = baseRef.object.sha;\n const schemaFile = await content(api, canonicalRepo, \"schemas/registry-module.schema.json\", baseSha);\n if (!schemaFile) throw new Error(\"\u7D22\u5F15\u4ED3\u7F3A\u5C11 registry-module.schema.json\");\n const ajv = new Ajv({ allErrors: true, strict: true });\n ajv.addFormat(\"semver-range\", (value: string) => !!value.trim() && semver.validRange(value) !== null);\n const validate = ajv.compile(decode(schemaFile) as AnySchema);\n entry.official = false; // \u9996\u6B21\u6536\u5F55\u6C38\u4E0D\u81EA\u884C\u58F0\u660E\u5B98\u65B9\u8EAB\u4EFD\u3002\n if (!validate(entry)) throw new Error(`\u7D22\u5F15\u5143\u6570\u636E\u4E0D\u5408\u89C4\uFF1A${ajv.errorsText(validate.errors)}`);\n if (!semver.valid(entry.version)) throw new Error(\"\u7248\u672C\u987B\u4E3A\u6709\u6548\u7684\u7CBE\u786E SemVer\");\n const file = `modules/${entry.id}.json`;\n options.log?.(`\u6821\u9A8C ${entry.npm}@${entry.version} \u4E0E\u6700\u65B0\u7D22\u5F15\u5206\u7247`);\n const tree = await api<{ truncated: boolean; tree: Array<{ path: string; type: string; sha: string }> }>(\n `repos/${canonicalRepo}/git/trees/${baseSha}?recursive=1`\n );\n if (tree.truncated) throw new Error(\"\u7D22\u5F15\u6811\u8FC7\u5927\uFF0C\u65E0\u6CD5\u5B8C\u6574\u68C0\u67E5\u4F9D\u8D56\");\n const shards = tree.tree.filter((item) => /^modules\\/[^/]+\\.json$/.test(item.path) && item.type === \"blob\");\n const modules: Record<string, RegistryModule> = Object.create(null) as Record<string, RegistryModule>;\n // \u53EA\u8BFB\u53D6\u8BE5\u6A21\u5757\u548C\u4F9D\u8D56\u95ED\u5305\uFF0C\u907F\u514D\u6BCF\u6B21\u63D0\u4EA4\u626B\u63CF\u6574\u4E2A\u7D22\u5F15\u5E76\u8017\u5C3D API \u914D\u989D\u3002\n const shardByPath = new Map(shards.map((item) => [item.path, item]));\n async function load(id: string): Promise<RegistryModule | null> {\n const shard = shardByPath.get(`modules/${id}.json`);\n if (!shard) return null;\n const blob = await api<{ encoding: string; content: string }>(`repos/${canonicalRepo}/git/blobs/${shard.sha}`);\n if (blob.encoding !== \"base64\") throw new Error(\"\u65E0\u6548\u7D22\u5F15 blob \u7F16\u7801\");\n const module = object(\n JSON.parse(Buffer.from(blob.content, \"base64\").toString(\"utf8\")),\n shard.path\n ) as unknown as RegistryModule;\n if (module.id !== id) throw new Error(`\u7D22\u5F15\u5206\u7247 ID \u4E0D\u4E00\u81F4\uFF1A${shard.path}`);\n return module;\n }\n const previous = await load(entry.id);\n if (previous) {\n if (previous.npm !== entry.npm || (previous.repo && sourceRepo(previous.repo) !== entry.repo))\n throw new Error(\"\u8BE5 ID \u5DF2\u5C5E\u4E8E\u5176\u4ED6 npm \u5305\u6216\u6E90\u7801\u4ED3\uFF1B\u8BF7\u7531\u7EF4\u62A4\u8005\u5904\u7406\u8FC1\u79FB\");\n if (!semver.valid(previous.version) || semver.lt(entry.version, previous.version))\n throw new Error(\"\u4E0D\u80FD\u964D\u4F4E\u5DF2\u6536\u5F55\u7248\u672C\");\n entry.official = previous.official; // \u4EC5\u7EE7\u627F\u7D22\u5F15\u4ED3\u5DF2\u5BA1\u6838\u8EAB\u4EFD\u3002\n // \u5355\u7EAF\u66F4\u65B0\u7248\u672C\u4FDD\u7559\u5DF2\u6574\u7406\u7684\u5C55\u793A\u4FE1\u606F\uFF1B\u4F5C\u8005\u914D\u7F6E\u53EF\u663E\u5F0F\u8986\u76D6\u3002\n if (local) {\n if (!local.overrides.has(\"name\")) entry.name = previous.name;\n if (!local.overrides.has(\"description\")) entry.description = previous.description;\n }\n for (const key of [\"category\", \"tags\", \"authors\"] as const) {\n if (entry[key] === undefined && previous[key] !== undefined)\n Object.assign(entry, { [key]: structuredClone(previous[key]) });\n }\n }\n if (!validate(entry)) throw new Error(`\u7D22\u5F15\u8EAB\u4EFD\u6216\u5B57\u6BB5\u4E0D\u5408\u89C4\uFF1A${ajv.errorsText(validate.errors)}`);\n modules[entry.id] = entry;\n const queue = [...entry.requires];\n const loaded = new Set([entry.id]);\n while (queue.length) {\n const batch = queue.splice(0, 4).filter((id) => {\n if (loaded.has(id)) return false;\n loaded.add(id);\n return true;\n });\n await Promise.all(\n batch.map(async (id) => {\n const dependency = await load(id);\n if (!dependency) throw new Error(`\u4F9D\u8D56 ${id} \u5C1A\u672A\u6536\u5F55\uFF0C\u8BF7\u5148\u63D0\u4EA4\u4F9D\u8D56\u6A21\u5757`);\n if (!validate(dependency)) throw new Error(`\u4F9D\u8D56 ${id} \u7684\u7D22\u5F15\u5143\u6570\u636E\u4E0D\u5408\u89C4`);\n modules[id] = dependency;\n queue.push(...(dependency.requires ?? []));\n })\n );\n }\n const visited = new Set<string>();\n const visiting = new Set<string>();\n function visit(id: string): void {\n if (visiting.has(id)) throw new Error(`\u6A21\u5757\u4F9D\u8D56\u5B58\u5728\u5FAA\u73AF\uFF1A${id}`);\n if (visited.has(id)) return;\n const module = modules[id];\n if (!module) throw new Error(`\u4F9D\u8D56 ${id} \u5C1A\u672A\u6536\u5F55\uFF0C\u8BF7\u5148\u63D0\u4EA4\u4F9D\u8D56\u6A21\u5757`);\n visiting.add(id);\n for (const dependency of module.requires ?? []) visit(dependency);\n visiting.delete(id);\n visited.add(id);\n }\n visit(entry.id);\n await checkPublished(entry, Boolean(options.waitForPublish));\n const result = { entry, previous, path: file, registryRepo: canonicalRepo };\n if (options.dryRun) return { ...result, status: \"preview\" };\n if (same(entry, previous)) return { ...result, status: \"unchanged\" };\n\n let headRepo = canonicalRepo;\n if (!options.noFork && upstream.permissions?.push !== true) {\n const user = await api<{ login: string }>(\"user\");\n const forkName = `${user.login}/${canonicalRepo.split(\"/\")[1]}`;\n let fork: GitHubRepository | undefined;\n try {\n fork = await api(`repos/${forkName}`);\n } catch (error) {\n if (!(error instanceof GitHubError && error.status === 404)) throw error;\n }\n if (!fork)\n fork = await api<GitHubRepository>(`repos/${canonicalRepo}/forks`, \"POST\", { default_branch_only: true });\n if (fork.id === upstream.id) {\n // \u5019\u9009\u5730\u5740\u4E0E\u76EE\u6807\u5730\u5740\u53EF\u80FD\u56E0\u91CD\u5B9A\u5411\u6307\u5411\u540C\u4E00\u4ED3\u5E93\uFF0C\u6309\u5B9E\u9645\u6743\u9650\u5904\u7406\u3002\n if (fork.permissions?.push !== true)\n throw new Error(\n `${forkName} \u5DF2\u91CD\u5B9A\u5411\u5230 ${canonicalRepo}\uFF0C\u5F53\u524D\u51ED\u636E\u6CA1\u6709\u5199\u6743\u9650\uFF1B\u8BF7\u68C0\u67E5 GitHub \u767B\u5F55\u4E0E Contents \u6743\u9650\u3002`\n );\n } else {\n const networkId = upstream.source?.id ?? upstream.id;\n if (!fork.fork || (fork.parent?.id !== upstream.id && fork.source?.id !== networkId))\n throw new Error(\n `${fork.full_name} \u672A\u4E0E\u76EE\u6807\u7D22\u5F15\u4ED3\u5173\u8054\uFF0C\u65E0\u6CD5\u4F5C\u4E3A\u8BE5 PR \u7684\u6765\u6E90\uFF1B\u8BF7\u4F7F\u7528\u76EE\u6807\u4ED3\u7684 fork\uFF0C\u6216\u6709\u5199\u6743\u9650\u65F6\u4F7F\u7528 --no-fork\u3002`\n );\n headRepo = fork.full_name;\n }\n }\n type Pull = { number: number; html_url: string; head: { ref: string; repo: { full_name: string } | null } };\n let pull: Pull | undefined;\n const prefix = `sfmc-submit/${entry.id}/`;\n for (let page = 1; ; page++) {\n const pagePulls = await api<Pull[]>(\n `repos/${canonicalRepo}/pulls?state=open&base=${encodeURIComponent(base)}&per_page=100&page=${page}`\n );\n const matches = pagePulls.filter(\n (item) => item.head.ref.startsWith(prefix) && item.head.repo?.full_name.toLowerCase() === headRepo.toLowerCase()\n );\n if (matches.length > 1 || (pull && matches.length)) throw new Error(\"\u8BE5\u6A21\u5757\u6709\u591A\u4E2A\u63D0\u4EA4 PR\uFF0C\u8BF7\u5148\u5408\u5E76\u6216\u5173\u95ED\u591A\u4F59 PR\");\n pull ??= matches[0];\n if (pagePulls.length < 100) break;\n }\n // \u5DF2\u5408\u5E76\u7684\u5206\u652F\u65E0\u9700\u91CD\u7F6E\uFF1B\u65B0\u4E00\u8F6E\u63D0\u4EA4\u4EE5\u6700\u65B0 main \u7684 SHA \u6807\u8BC6\u3002\n const branch = pull?.head.ref ?? `${prefix}${baseSha.slice(0, 12)}`;\n const head = `${headRepo.split(\"/\")[0]}:${branch}`;\n let branchRef: { object: { sha: string } } | null = null;\n // \u65B0 fork \u7684\u521B\u5EFA\u662F\u5F02\u6B65\u64CD\u4F5C\uFF0C\u6700\u591A\u7B49\u5F85 15 \u79D2\u3002\n for (let attempt = 0; attempt < 6; attempt++) {\n try {\n branchRef = await api(`repos/${headRepo}/git/ref/heads/${encodeURIComponent(branch)}`);\n break;\n } catch (error) {\n if (!(error instanceof GitHubError && error.status === 404)) throw error;\n try {\n branchRef = await api(`repos/${headRepo}/git/refs`, \"POST\", { ref: `refs/heads/${branch}`, sha: baseSha });\n break;\n } catch (createError) {\n if (!(createError instanceof GitHubError && [404, 422].includes(createError.status)) || attempt === 5)\n throw createError;\n await new Promise((resolve) => setTimeout(resolve, 3000));\n }\n }\n }\n if (!branchRef) throw new Error(\"\u63D0\u4EA4\u5206\u652F\u5C1A\u672A\u5C31\u7EEA\uFF0C\u8BF7\u91CD\u8BD5\");\n const comparison = await api<{ files: Array<{ filename: string }> }>(\n `repos/${canonicalRepo}/compare/${baseSha}...${encodeURIComponent(`${headRepo.split(\"/\")[0]}:${branchRef.object.sha}`)}`\n );\n if (comparison.files.some((item) => item.filename !== file))\n throw new Error(\"\u4E13\u7528\u63D0\u4EA4\u5206\u652F\u5305\u542B\u5176\u4ED6\u6587\u4EF6\u6539\u52A8\uFF0C\u8BF7\u5148\u7531\u4F5C\u8005\u5904\u7406\");\n const oldFile = await content(api, headRepo, file, branchRef.object.sha);\n const oldEntry = oldFile ? decode(oldFile) : null;\n if (oldEntry) {\n const pending = object(oldEntry, file);\n if (\n pending.npm !== entry.npm ||\n (pending.repo && sourceRepo(pending.repo) !== entry.repo) ||\n typeof pending.version !== \"string\" ||\n !semver.valid(pending.version) ||\n semver.lt(entry.version, pending.version)\n ) {\n throw new Error(\"\u5DF2\u6709\u63D0\u4EA4\u7684\u6765\u6E90\u6216\u7248\u672C\u51B2\u7A81\uFF1B\u62D2\u7EDD\u8986\u76D6\");\n }\n }\n if (!same(entry, oldEntry)) {\n await api(`repos/${headRepo}/contents/${file}`, \"PUT\", {\n message: `chore(registry): ${entry.id} ${entry.version}`,\n content: Buffer.from(`${JSON.stringify(entry, null, 2)}\\n`).toString(\"base64\"),\n branch,\n ...(oldFile ? { sha: oldFile.sha } : {}),\n });\n }\n const title = `\u6536\u5F55 ${entry.id}@${entry.version}`;\n const body = `\u66F4\u65B0 \\`${file}\\`\u3002\\n\\n- npm\uFF1A\\`${entry.npm}@${entry.version}\\`\uFF08\u516C\u5171\u6E90\u7CBE\u786E\u7248\u672C\u5DF2\u6838\u5BF9\uFF09\\n- \u6E90\u7801\uFF1A${entry.repo ?? \"\u672A\u63D0\u4F9B\"}\\n- \u8BB8\u53EF\u8BC1\uFF1A\\`${entry.license}\\`\\n- SDK\uFF1A\\`${entry.sdk}\\`\\n- \u4F9D\u8D56\uFF1A${entry.requires.join(\", \") || \"\u65E0\"}\\n\\n\u5DF2\u68C0\u67E5\u5F53\u524D\u7D22\u5F15 Schema\u3001ID \u6765\u6E90\u3001\u7248\u672C\u4E0E\u4F9D\u8D56\u5173\u7CFB\u3002\u5B98\u65B9\u8EAB\u4EFD\u6CBF\u7528\u7EF4\u62A4\u8005\u5BA1\u6838\u7ED3\u679C\uFF1B\u8BF7\u68C0\u67E5 PR \u95E8\u7981\u540E\u5408\u5E76\u3002`;\n if (pull) {\n if (same(entry, oldEntry)) return { ...result, status: \"unchanged\", prUrl: pull.html_url };\n await api(`repos/${canonicalRepo}/pulls/${pull.number}`, \"PATCH\", { title, body });\n return { ...result, status: \"updated\", prUrl: pull.html_url };\n }\n const created = await api<{ html_url: string }>(`repos/${canonicalRepo}/pulls`, \"POST\", { title, body, base, head });\n return { ...result, status: \"created\", prUrl: created.html_url };\n}\n", "/**\n * \u76D1\u542C\u6A21\u5757 sapi/src\uFF0C\u9632\u6296\u540E\u56DE\u8C03 onRebuild\uFF08\u7531\u8C03\u7528\u65B9\u6CE8\u5165 rebuildAndDeploy / \u5176\u5B83\uFF09\u3002\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface ModuleWatchOptions {\n moduleRoot: string;\n debounceMs?: number;\n log?: (line: string) => void;\n onRebuild: () => Promise<{ ok: boolean; message?: string }>;\n}\n\nexport function startModuleWatch(opts: ModuleWatchOptions): { stop: () => void } {\n const log = opts.log ?? ((line: string) => console.log(line));\n const debounceMs = opts.debounceMs ?? 200;\n const sapiDir = path.join(opts.moduleRoot, \"sapi\");\n const srcDir = path.join(sapiDir, \"src\");\n\n if (!fs.existsSync(sapiDir)) {\n log(`[devkit:watch] \u65E0 sapi \u76EE\u5F55: ${sapiDir}`);\n return { stop: () => {} };\n }\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let pending = new Set<string>();\n let running = false;\n let stopped = false;\n\n log(`[devkit:watch] \u76D1\u542C ${opts.moduleRoot}`);\n\n const runOnce = async () => {\n if (running || stopped) return;\n running = true;\n const changed = [...pending];\n pending = new Set();\n const t0 = Date.now();\n log(`[devkit:watch] rebuild (${changed.length})\u2026`);\n try {\n const r = await opts.onRebuild();\n const ms = Date.now() - t0;\n if (r.ok) log(`[devkit:watch] ok ${ms}ms`);\n else log(`[devkit:watch] fail ${ms}ms ${r.message ?? \"\"}`);\n } catch (e) {\n log(`[devkit:watch] error ${(e as Error).message}`);\n } finally {\n running = false;\n }\n };\n\n const onSrc = (rel: string) => {\n if (stopped) return;\n if (rel.includes(\"node_modules\") || rel.includes(\"dist\") || rel.endsWith(\".tgz\")) return;\n pending.add(rel);\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => void runOnce(), debounceMs);\n };\n\n const watchers: fs.FSWatcher[] = [];\n if (fs.existsSync(srcDir)) {\n try {\n watchers.push(\n fs.watch(srcDir, { recursive: true, persistent: true }, (_e, filename) => {\n if (filename) onSrc(filename.toString());\n })\n );\n } catch {\n watchers.push(\n fs.watch(srcDir, { persistent: true }, (_e, filename) => {\n if (filename) onSrc(filename.toString());\n })\n );\n }\n }\n\n const watchMeta = (file: string, kind: string) => {\n if (!fs.existsSync(file)) return;\n watchers.push(\n fs.watch(file, { persistent: true }, (event) => {\n if (event === \"change\") log(`[devkit:watch] ${kind} \u5DF2\u6539\uFF0C\u9700\u91CD\u542F BDS\uFF08SAPI \u542F\u52A8\u671F\u7F13\u5B58\uFF09`);\n })\n );\n };\n watchMeta(path.join(sapiDir, \"manifest.json\"), \"manifest\");\n watchMeta(path.join(sapiDir, \"tsconfig.json\"), \"tsconfig\");\n\n return {\n stop() {\n stopped = true;\n if (debounceTimer) clearTimeout(debounceTimer);\n for (const w of watchers) {\n try {\n w.close();\n } catch {\n /* ignore */\n }\n }\n log(\"[devkit:watch] stopped\");\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["fs", "path", "fs", "path", "fs", "path", "path", "fs", "path", "fs", "path", "a", "b", "path", "fs", "fs", "path"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { runRegistrySubmitCommand } from "./registry-command.js";
3
+ runRegistrySubmitCommand(process.argv.slice(2))
4
+ .then(console.log)
5
+ .catch((error) => {
6
+ console.error(error instanceof Error ? error.message : String(error));
7
+ process.exitCode = 1;
8
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/registry-command.ts", "../src/registry-submit.ts", "../src/registry-cli.ts"],
4
+ "sourcesContent": ["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { submitModuleToRegistry, type RegistryModule, type RegistrySubmitOptions } from \"./registry-submit.js\";\n\n/** CLI \u4E0E CI \u5171\u7528\u53C2\u6570\u89E3\u6790\uFF0C\u672A\u77E5\u53C2\u6570\u8FD4\u56DE\u5931\u8D25\uFF0C\u907F\u514D\u8BEF\u63D0\u4EA4\u3002 */\nexport async function runRegistrySubmitCommand(args: string[]): Promise<string> {\n const options: RegistrySubmitOptions = {};\n let entryFile: string | undefined;\n const help =\n \"sfmc mod submit [\u6A21\u5757\u76EE\u5F55] [--dry-run] [--no-fork] [--wait-for-publish] [--registry owner/repo]\\nCI\uFF1Asfmc-module-submit --entry-file <JSON>\uFF08\u4E0E\u6A21\u5757\u76EE\u5F55\u4E92\u65A5\uFF09\";\n if (args.includes(\"--help\") || args.includes(\"-h\")) return help;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === \"--dry-run\") options.dryRun = true;\n else if (arg === \"--no-fork\") options.noFork = true;\n else if (arg === \"--wait-for-publish\") options.waitForPublish = true;\n else if (arg === \"--registry\" || arg === \"--entry-file\") {\n const value = args[++i];\n if (!value || value.startsWith(\"-\")) throw new Error(`\u7F3A\u5C11 ${arg} \u7684\u503C`);\n if (arg === \"--registry\") options.registryRepo = value;\n else entryFile = value;\n } else if (arg.startsWith(\"-\") || options.moduleRoot) throw new Error(`\u672A\u77E5\u6216\u91CD\u590D\u53C2\u6570\uFF1A${arg}\\n${help}`);\n else options.moduleRoot = path.resolve(arg);\n }\n if (entryFile) {\n if (options.moduleRoot) throw new Error(\"--entry-file \u4E0E\u6A21\u5757\u76EE\u5F55\u4E92\u65A5\");\n options.entry = JSON.parse(await fs.readFile(path.resolve(entryFile), \"utf8\")) as RegistryModule;\n }\n const result = await submitModuleToRegistry(options);\n if (result.status === \"preview\")\n return `\u9884\u89C8\uFF1A${result.registryRepo}/${result.path}\\n\u539F\u6761\u76EE\uFF1A\\n${JSON.stringify(result.previous, null, 2)}\\n\u62DF\u63D0\u4EA4\uFF1A\\n${JSON.stringify(result.entry, null, 2)}\\n\u6821\u9A8C\u901A\u8FC7\uFF0C\u672A\u5199\u5165\u8FDC\u7AEF\u3002`;\n return `${{ unchanged: \"\u5185\u5BB9\u76F8\u540C\uFF0C\u672A\u91CD\u590D\u63D0\u4EA4\", created: \"\u5DF2\u521B\u5EFA\u7D22\u5F15 PR\", updated: \"\u5DF2\u66F4\u65B0\u7D22\u5F15 PR\" }[result.status]}${result.prUrl ? `\uFF1A${result.prUrl}` : \"\"}`;\n}\n", "/** \u6A21\u5757\u7D22\u5F15\u63D0\u4EA4\u6838\u5FC3\u3002\u4EC5\u5199\u5165\u4E13\u7528\u5206\u652F\u4E2D\u7684\u5355\u4E2A\u5206\u7247\uFF0C\u4E0D\u64CD\u4F5C\u4F5C\u8005\u5DE5\u4F5C\u6811\u3002 */\nimport { Ajv, type AnySchema } from \"ajv\";\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport semver from \"semver\";\n\nexport const MODULE_REGISTRY_REPO = \"DogeLakeDev/sfmc-modules\";\ntype Json = Record<string, unknown>;\nexport interface RegistryMetadata {\n name?: string;\n description?: string;\n category?: \"gameplay\" | \"utility\" | \"system\" | \"economy\" | \"social\";\n tags?: string[];\n authors?: string[];\n}\nexport interface RegistryModule extends RegistryMetadata {\n id: string;\n name: string;\n description: string;\n npm: string;\n version: string;\n sdk: string;\n license: string;\n official: boolean;\n repo?: string;\n requires: string[];\n}\nexport interface RegistrySubmitOptions {\n moduleRoot?: string;\n /** \u8868\u5355\u7B49\u5165\u53E3\u63D0\u4F9B\u539F\u59CB\u5B57\u6BB5\uFF0C\u4ECD\u987B\u7ECF\u8FC7\u76F8\u540C npm\u3001Schema \u548C\u7D22\u5F15\u6821\u9A8C\u3002 */\n entry?: RegistryModule;\n registryRepo?: string;\n dryRun?: boolean;\n /** \u7EF4\u62A4\u8005 / GitHub App \u5728\u7D22\u5F15\u4ED3\u5EFA\u5206\u652F\uFF0C\u987B\u6709 Contents \u548C Pull requests \u5199\u6743\u9650\u3002 */\n noFork?: boolean;\n token?: string;\n /** \u7B49\u5F85\u521A\u53D1\u5E03\u7684\u7CBE\u786E\u7248\u672C\u53EF\u89C1\uFF1B\u4EC5\u91CD\u8BD5 404\uFF0C\u6700\u957F 60 \u79D2\u3002 */\n waitForPublish?: boolean;\n log?: (message: string) => void;\n}\nexport interface RegistrySubmitResult {\n status: \"preview\" | \"unchanged\" | \"created\" | \"updated\";\n entry: RegistryModule;\n previous: RegistryModule | null;\n path: string;\n prUrl?: string;\n registryRepo: string;\n}\nfunction object(value: unknown, label: string): Json {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(`${label} \u987B\u4E3A JSON \u5BF9\u8C61`);\n return value as Json;\n}\nfunction text(value: unknown, label: string): string {\n if (typeof value !== \"string\" || !value.trim()) throw new Error(`\u7F3A\u5C11 ${label}`);\n return value.trim();\n}\nfunction sourceRepo(value: unknown): string | undefined {\n if (value === undefined) return undefined;\n const raw = typeof value === \"string\" ? value : object(value, \"repository\").url;\n const repo = text(raw, \"repository.url\")\n .replace(/^git\\+/, \"\")\n .replace(/\\.git$/, \"\");\n const url = new URL(repo);\n if (url.protocol !== \"https:\" || url.username || url.password)\n throw new Error(\"repository \u987B\u4E3A\u4E0D\u542B\u51ED\u636E\u7684 HTTPS \u5730\u5740\");\n return url.href.replace(/\\/$/, \"\");\n}\n\n/** \u4F5C\u8005\u914D\u7F6E\u53EA\u8865\u5145\u5C55\u793A\u5B57\u6BB5\uFF1B\u5305\u3001\u7248\u672C\u3001SDK \u548C\u4F9D\u8D56\u6765\u81EA\u5DF2\u6709\u5951\u7EA6\u3002 */\nexport function createRegistryEntry(\n pkgValue: unknown,\n manifestValue: unknown,\n metadataValue: unknown = {}\n): RegistryModule {\n const pkg = object(pkgValue, \"package.json\");\n const manifest = object(manifestValue, \"sapi/manifest.json\");\n const metadata = object(metadataValue, \"sfmc.registry.json\");\n const allowed = new Set([\"$schema\", \"name\", \"description\", \"category\", \"tags\", \"authors\"]);\n for (const key of Object.keys(metadata))\n if (!allowed.has(key)) throw new Error(`sfmc.registry.json \u4E0D\u652F\u6301\u5B57\u6BB5 ${key}`);\n if (pkg.private === true) throw new Error(\"private \u6A21\u5757\u4E0D\u80FD\u63D0\u4EA4\u516C\u5171\u7D22\u5F15\");\n if (manifest.schemaVersion !== 2) throw new Error(\"\u9700\u8981 schemaVersion=2 \u7684\u6A21\u5757\u6E05\u5355\");\n const peers = object(pkg.peerDependencies ?? {}, \"peerDependencies\");\n const entry: RegistryModule = {\n id: text(manifest.id, \"manifest.id\"),\n name: text(metadata.name ?? manifest.name, \"\u5C55\u793A\u540D\u79F0\uFF08name\uFF09\"),\n description: text(metadata.description ?? pkg.description, \"\u529F\u80FD\u4ECB\u7ECD\uFF08description\uFF09\"),\n npm: text(pkg.name, \"package.name\"),\n version: text(pkg.version, \"package.version\"),\n sdk: text(peers[\"@sfmc-bds/sdk\"], \"peerDependencies.@sfmc-bds/sdk\"),\n license: text(pkg.license, \"package.license\"),\n official: false,\n requires: (manifest.requires ?? []) as string[],\n };\n const repo = sourceRepo(pkg.repository);\n if (repo) entry.repo = repo;\n for (const key of [\"category\", \"tags\", \"authors\"] as const) {\n if (metadata[key] !== undefined) Object.assign(entry, { [key]: metadata[key] });\n }\n return entry;\n}\n\nasync function readLocalEntry(root: string): Promise<{ entry: RegistryModule; overrides: Set<string> }> {\n const realRoot = await fs.realpath(root);\n const read = async (file: string) => {\n const target = await fs.realpath(path.join(realRoot, file));\n const relative = path.relative(realRoot, target);\n if (relative === \"..\" || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))\n throw new Error(`${file} \u987B\u4F4D\u4E8E\u6A21\u5757\u76EE\u5F55\u5185`);\n const raw = await fs.readFile(target, \"utf8\");\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n throw new Error(`${file} \u5305\u542B\u65E0\u6548 JSON`);\n }\n };\n let metadata: unknown = {};\n try {\n metadata = await read(\"sfmc.registry.json\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n return {\n entry: createRegistryEntry(await read(\"package.json\"), await read(\"sapi/manifest.json\"), metadata),\n overrides: new Set(Object.keys(object(metadata, \"sfmc.registry.json\"))),\n };\n}\n\nclass GitHubError extends Error {\n constructor(\n public status: number,\n endpoint: string,\n rateLimited = false\n ) {\n super(\n `GitHub ${status}: ${endpoint}${rateLimited ? \"\uFF08API \u914D\u989D\u5DF2\u7528\u5C3D\uFF0C\u8BF7\u767B\u5F55\u540E\u91CD\u8BD5\u6216\u7B49\u5F85\u914D\u989D\u6062\u590D\uFF09\" : status === 401 || status === 403 ? \"\uFF08\u68C0\u67E5\u767B\u5F55\u53CA Contents / Pull requests \u6743\u9650\uFF09\" : \"\"}`\n );\n }\n}\nasync function resolveToken(required: boolean): Promise<string | undefined> {\n const configured = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;\n if (configured) return configured;\n try {\n const { stdout } = await promisify(execFile)(\"gh\", [\"auth\", \"token\", \"--hostname\", \"github.com\"], {\n timeout: 10000,\n windowsHide: true,\n });\n return stdout.trim() || undefined;\n } catch {\n if (required) throw new Error(\"\u63D0\u4EA4\u9700\u8981 GitHub \u767B\u5F55\uFF1A\u6267\u884C gh auth login\uFF0C\u6216\u8BBE\u7F6E GH_TOKEN\u3002\");\n return undefined;\n }\n}\nfunction github(token?: string) {\n return async function api<T>(endpoint: string, method = \"GET\", body?: unknown): Promise<T> {\n const response = await fetch(`https://api.github.com/${endpoint}`, {\n method,\n headers: {\n Accept: \"application/vnd.github+json\",\n \"User-Agent\": \"sfmc-devkit\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(15000),\n });\n // \u4E0D\u56DE\u663E\u670D\u52A1\u5668\u6B63\u6587\u6216\u8BA4\u8BC1\u4FE1\u606F\u3002\n if (!response.ok)\n throw new GitHubError(\n response.status,\n endpoint,\n response.status === 429 || response.headers.get(\"x-ratelimit-remaining\") === \"0\"\n );\n return response.status === 204 ? (undefined as T) : ((await response.json()) as T);\n };\n}\ntype Api = ReturnType<typeof github>;\ninterface GitHubRepository {\n id: number;\n full_name: string;\n default_branch: string;\n fork: boolean;\n permissions?: { push?: boolean };\n parent?: { id: number; full_name: string };\n source?: { id: number; full_name: string };\n}\ninterface Content {\n type: string;\n encoding: string;\n content: string;\n sha: string;\n}\nfunction decode(file: Content): unknown {\n if (file.type !== \"file\" || file.encoding !== \"base64\") throw new Error(\"\u7D22\u5F15\u5185\u5BB9\u987B\u4E3A\u666E\u901A JSON \u6587\u4EF6\");\n return JSON.parse(Buffer.from(file.content, \"base64\").toString(\"utf8\"));\n}\nasync function content(api: Api, repo: string, file: string, ref: string): Promise<Content | null> {\n try {\n return await api<Content>(`repos/${repo}/contents/${file}?ref=${encodeURIComponent(ref)}`);\n } catch (error) {\n if (error instanceof GitHubError && error.status === 404) return null;\n throw error;\n }\n}\nfunction same(a: unknown, b: unknown): boolean {\n const stable = (value: unknown): unknown =>\n Array.isArray(value)\n ? value.map(stable)\n : value && typeof value === \"object\"\n ? Object.fromEntries(\n Object.entries(value)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, item]) => [key, stable(item)])\n )\n : value;\n return JSON.stringify(stable(a)) === JSON.stringify(stable(b));\n}\nasync function checkPublished(entry: RegistryModule, wait: boolean): Promise<void> {\n const deadline = Date.now() + (wait ? 60000 : 0);\n while (true) {\n const response = await fetch(\n `https://registry.npmjs.org/${encodeURIComponent(entry.npm)}/${encodeURIComponent(entry.version)}`,\n {\n signal: AbortSignal.timeout(15000),\n headers: { Accept: \"application/json\" },\n }\n );\n if (response.status === 404 && Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 3000));\n continue;\n }\n if (!response.ok)\n throw new Error(\n `\u516C\u5171 npm \u5C1A\u65E0\u53EF\u7528\u7684 ${entry.npm}@${entry.version}\uFF08HTTP ${response.status}\uFF09\uFF1B\u5148\u53D1\u5E03\u8BE5\u7CBE\u786E\u7248\u672C\u3002`\n );\n const pkg = object(await response.json(), \"npm \u5143\u6570\u636E\");\n const peers = object(pkg.peerDependencies ?? {}, \"npm peerDependencies\");\n if (\n pkg.name !== entry.npm ||\n pkg.version !== entry.version ||\n pkg.license !== entry.license ||\n peers[\"@sfmc-bds/sdk\"] !== entry.sdk\n ) {\n throw new Error(\"\u5DF2\u53D1\u5E03\u5305\u7684\u540D\u79F0\u3001\u7248\u672C\u3001\u8BB8\u53EF\u8BC1\u6216 SDK \u517C\u5BB9\u8303\u56F4\u4E0E\u63D0\u4EA4\u4FE1\u606F\u4E0D\u4E00\u81F4\");\n }\n if (sourceRepo(pkg.repository) !== entry.repo) throw new Error(\"\u5DF2\u53D1\u5E03\u5305\u7684\u6E90\u7801\u5730\u5740\u4E0E\u63D0\u4EA4\u4FE1\u606F\u4E0D\u4E00\u81F4\");\n return;\n }\n}\n\n/** \u6240\u6709\u5165\u53E3\u5171\u7528\uFF1A\u9884\u89C8\u4E5F\u9A8C\u8BC1\u5DF2\u53D1\u5E03\u7248\u672C\u4E0E\u6700\u65B0\u5206\u7247\uFF1B\u9884\u89C8\u9636\u6BB5\u4E0D\u4EA7\u751F\u8FDC\u7AEF\u5199\u5165\u3002 */\nexport async function submitModuleToRegistry(options: RegistrySubmitOptions = {}): Promise<RegistrySubmitResult> {\n const repo = options.registryRepo ?? MODULE_REGISTRY_REPO;\n if (!/^[\\w.-]+\\/[\\w.-]+$/.test(repo)) throw new Error(\"registryRepo \u987B\u4E3A owner/repo\");\n const local = options.entry ? null : await readLocalEntry(path.resolve(options.moduleRoot ?? process.cwd()));\n const entry = options.entry ? structuredClone(options.entry) : local!.entry;\n entry.requires ??= [];\n const token = options.token ?? (await resolveToken(!options.dryRun));\n if (!options.dryRun && !token) throw new Error(\"\u63D0\u4EA4\u9700\u8981\u6709\u6548\u7684 GitHub \u767B\u5F55\u6216 GH_TOKEN\");\n const api = github(token);\n const upstream = await api<GitHubRepository>(`repos/${repo}`);\n const canonicalRepo = upstream.full_name;\n const base = upstream.default_branch;\n const baseRef = await api<{ object: { sha: string } }>(\n `repos/${canonicalRepo}/git/ref/heads/${encodeURIComponent(base)}`\n );\n const baseSha = baseRef.object.sha;\n const schemaFile = await content(api, canonicalRepo, \"schemas/registry-module.schema.json\", baseSha);\n if (!schemaFile) throw new Error(\"\u7D22\u5F15\u4ED3\u7F3A\u5C11 registry-module.schema.json\");\n const ajv = new Ajv({ allErrors: true, strict: true });\n ajv.addFormat(\"semver-range\", (value: string) => !!value.trim() && semver.validRange(value) !== null);\n const validate = ajv.compile(decode(schemaFile) as AnySchema);\n entry.official = false; // \u9996\u6B21\u6536\u5F55\u6C38\u4E0D\u81EA\u884C\u58F0\u660E\u5B98\u65B9\u8EAB\u4EFD\u3002\n if (!validate(entry)) throw new Error(`\u7D22\u5F15\u5143\u6570\u636E\u4E0D\u5408\u89C4\uFF1A${ajv.errorsText(validate.errors)}`);\n if (!semver.valid(entry.version)) throw new Error(\"\u7248\u672C\u987B\u4E3A\u6709\u6548\u7684\u7CBE\u786E SemVer\");\n const file = `modules/${entry.id}.json`;\n options.log?.(`\u6821\u9A8C ${entry.npm}@${entry.version} \u4E0E\u6700\u65B0\u7D22\u5F15\u5206\u7247`);\n const tree = await api<{ truncated: boolean; tree: Array<{ path: string; type: string; sha: string }> }>(\n `repos/${canonicalRepo}/git/trees/${baseSha}?recursive=1`\n );\n if (tree.truncated) throw new Error(\"\u7D22\u5F15\u6811\u8FC7\u5927\uFF0C\u65E0\u6CD5\u5B8C\u6574\u68C0\u67E5\u4F9D\u8D56\");\n const shards = tree.tree.filter((item) => /^modules\\/[^/]+\\.json$/.test(item.path) && item.type === \"blob\");\n const modules: Record<string, RegistryModule> = Object.create(null) as Record<string, RegistryModule>;\n // \u53EA\u8BFB\u53D6\u8BE5\u6A21\u5757\u548C\u4F9D\u8D56\u95ED\u5305\uFF0C\u907F\u514D\u6BCF\u6B21\u63D0\u4EA4\u626B\u63CF\u6574\u4E2A\u7D22\u5F15\u5E76\u8017\u5C3D API \u914D\u989D\u3002\n const shardByPath = new Map(shards.map((item) => [item.path, item]));\n async function load(id: string): Promise<RegistryModule | null> {\n const shard = shardByPath.get(`modules/${id}.json`);\n if (!shard) return null;\n const blob = await api<{ encoding: string; content: string }>(`repos/${canonicalRepo}/git/blobs/${shard.sha}`);\n if (blob.encoding !== \"base64\") throw new Error(\"\u65E0\u6548\u7D22\u5F15 blob \u7F16\u7801\");\n const module = object(\n JSON.parse(Buffer.from(blob.content, \"base64\").toString(\"utf8\")),\n shard.path\n ) as unknown as RegistryModule;\n if (module.id !== id) throw new Error(`\u7D22\u5F15\u5206\u7247 ID \u4E0D\u4E00\u81F4\uFF1A${shard.path}`);\n return module;\n }\n const previous = await load(entry.id);\n if (previous) {\n if (previous.npm !== entry.npm || (previous.repo && sourceRepo(previous.repo) !== entry.repo))\n throw new Error(\"\u8BE5 ID \u5DF2\u5C5E\u4E8E\u5176\u4ED6 npm \u5305\u6216\u6E90\u7801\u4ED3\uFF1B\u8BF7\u7531\u7EF4\u62A4\u8005\u5904\u7406\u8FC1\u79FB\");\n if (!semver.valid(previous.version) || semver.lt(entry.version, previous.version))\n throw new Error(\"\u4E0D\u80FD\u964D\u4F4E\u5DF2\u6536\u5F55\u7248\u672C\");\n entry.official = previous.official; // \u4EC5\u7EE7\u627F\u7D22\u5F15\u4ED3\u5DF2\u5BA1\u6838\u8EAB\u4EFD\u3002\n // \u5355\u7EAF\u66F4\u65B0\u7248\u672C\u4FDD\u7559\u5DF2\u6574\u7406\u7684\u5C55\u793A\u4FE1\u606F\uFF1B\u4F5C\u8005\u914D\u7F6E\u53EF\u663E\u5F0F\u8986\u76D6\u3002\n if (local) {\n if (!local.overrides.has(\"name\")) entry.name = previous.name;\n if (!local.overrides.has(\"description\")) entry.description = previous.description;\n }\n for (const key of [\"category\", \"tags\", \"authors\"] as const) {\n if (entry[key] === undefined && previous[key] !== undefined)\n Object.assign(entry, { [key]: structuredClone(previous[key]) });\n }\n }\n if (!validate(entry)) throw new Error(`\u7D22\u5F15\u8EAB\u4EFD\u6216\u5B57\u6BB5\u4E0D\u5408\u89C4\uFF1A${ajv.errorsText(validate.errors)}`);\n modules[entry.id] = entry;\n const queue = [...entry.requires];\n const loaded = new Set([entry.id]);\n while (queue.length) {\n const batch = queue.splice(0, 4).filter((id) => {\n if (loaded.has(id)) return false;\n loaded.add(id);\n return true;\n });\n await Promise.all(\n batch.map(async (id) => {\n const dependency = await load(id);\n if (!dependency) throw new Error(`\u4F9D\u8D56 ${id} \u5C1A\u672A\u6536\u5F55\uFF0C\u8BF7\u5148\u63D0\u4EA4\u4F9D\u8D56\u6A21\u5757`);\n if (!validate(dependency)) throw new Error(`\u4F9D\u8D56 ${id} \u7684\u7D22\u5F15\u5143\u6570\u636E\u4E0D\u5408\u89C4`);\n modules[id] = dependency;\n queue.push(...(dependency.requires ?? []));\n })\n );\n }\n const visited = new Set<string>();\n const visiting = new Set<string>();\n function visit(id: string): void {\n if (visiting.has(id)) throw new Error(`\u6A21\u5757\u4F9D\u8D56\u5B58\u5728\u5FAA\u73AF\uFF1A${id}`);\n if (visited.has(id)) return;\n const module = modules[id];\n if (!module) throw new Error(`\u4F9D\u8D56 ${id} \u5C1A\u672A\u6536\u5F55\uFF0C\u8BF7\u5148\u63D0\u4EA4\u4F9D\u8D56\u6A21\u5757`);\n visiting.add(id);\n for (const dependency of module.requires ?? []) visit(dependency);\n visiting.delete(id);\n visited.add(id);\n }\n visit(entry.id);\n await checkPublished(entry, Boolean(options.waitForPublish));\n const result = { entry, previous, path: file, registryRepo: canonicalRepo };\n if (options.dryRun) return { ...result, status: \"preview\" };\n if (same(entry, previous)) return { ...result, status: \"unchanged\" };\n\n let headRepo = canonicalRepo;\n if (!options.noFork && upstream.permissions?.push !== true) {\n const user = await api<{ login: string }>(\"user\");\n const forkName = `${user.login}/${canonicalRepo.split(\"/\")[1]}`;\n let fork: GitHubRepository | undefined;\n try {\n fork = await api(`repos/${forkName}`);\n } catch (error) {\n if (!(error instanceof GitHubError && error.status === 404)) throw error;\n }\n if (!fork)\n fork = await api<GitHubRepository>(`repos/${canonicalRepo}/forks`, \"POST\", { default_branch_only: true });\n if (fork.id === upstream.id) {\n // \u5019\u9009\u5730\u5740\u4E0E\u76EE\u6807\u5730\u5740\u53EF\u80FD\u56E0\u91CD\u5B9A\u5411\u6307\u5411\u540C\u4E00\u4ED3\u5E93\uFF0C\u6309\u5B9E\u9645\u6743\u9650\u5904\u7406\u3002\n if (fork.permissions?.push !== true)\n throw new Error(\n `${forkName} \u5DF2\u91CD\u5B9A\u5411\u5230 ${canonicalRepo}\uFF0C\u5F53\u524D\u51ED\u636E\u6CA1\u6709\u5199\u6743\u9650\uFF1B\u8BF7\u68C0\u67E5 GitHub \u767B\u5F55\u4E0E Contents \u6743\u9650\u3002`\n );\n } else {\n const networkId = upstream.source?.id ?? upstream.id;\n if (!fork.fork || (fork.parent?.id !== upstream.id && fork.source?.id !== networkId))\n throw new Error(\n `${fork.full_name} \u672A\u4E0E\u76EE\u6807\u7D22\u5F15\u4ED3\u5173\u8054\uFF0C\u65E0\u6CD5\u4F5C\u4E3A\u8BE5 PR \u7684\u6765\u6E90\uFF1B\u8BF7\u4F7F\u7528\u76EE\u6807\u4ED3\u7684 fork\uFF0C\u6216\u6709\u5199\u6743\u9650\u65F6\u4F7F\u7528 --no-fork\u3002`\n );\n headRepo = fork.full_name;\n }\n }\n type Pull = { number: number; html_url: string; head: { ref: string; repo: { full_name: string } | null } };\n let pull: Pull | undefined;\n const prefix = `sfmc-submit/${entry.id}/`;\n for (let page = 1; ; page++) {\n const pagePulls = await api<Pull[]>(\n `repos/${canonicalRepo}/pulls?state=open&base=${encodeURIComponent(base)}&per_page=100&page=${page}`\n );\n const matches = pagePulls.filter(\n (item) => item.head.ref.startsWith(prefix) && item.head.repo?.full_name.toLowerCase() === headRepo.toLowerCase()\n );\n if (matches.length > 1 || (pull && matches.length)) throw new Error(\"\u8BE5\u6A21\u5757\u6709\u591A\u4E2A\u63D0\u4EA4 PR\uFF0C\u8BF7\u5148\u5408\u5E76\u6216\u5173\u95ED\u591A\u4F59 PR\");\n pull ??= matches[0];\n if (pagePulls.length < 100) break;\n }\n // \u5DF2\u5408\u5E76\u7684\u5206\u652F\u65E0\u9700\u91CD\u7F6E\uFF1B\u65B0\u4E00\u8F6E\u63D0\u4EA4\u4EE5\u6700\u65B0 main \u7684 SHA \u6807\u8BC6\u3002\n const branch = pull?.head.ref ?? `${prefix}${baseSha.slice(0, 12)}`;\n const head = `${headRepo.split(\"/\")[0]}:${branch}`;\n let branchRef: { object: { sha: string } } | null = null;\n // \u65B0 fork \u7684\u521B\u5EFA\u662F\u5F02\u6B65\u64CD\u4F5C\uFF0C\u6700\u591A\u7B49\u5F85 15 \u79D2\u3002\n for (let attempt = 0; attempt < 6; attempt++) {\n try {\n branchRef = await api(`repos/${headRepo}/git/ref/heads/${encodeURIComponent(branch)}`);\n break;\n } catch (error) {\n if (!(error instanceof GitHubError && error.status === 404)) throw error;\n try {\n branchRef = await api(`repos/${headRepo}/git/refs`, \"POST\", { ref: `refs/heads/${branch}`, sha: baseSha });\n break;\n } catch (createError) {\n if (!(createError instanceof GitHubError && [404, 422].includes(createError.status)) || attempt === 5)\n throw createError;\n await new Promise((resolve) => setTimeout(resolve, 3000));\n }\n }\n }\n if (!branchRef) throw new Error(\"\u63D0\u4EA4\u5206\u652F\u5C1A\u672A\u5C31\u7EEA\uFF0C\u8BF7\u91CD\u8BD5\");\n const comparison = await api<{ files: Array<{ filename: string }> }>(\n `repos/${canonicalRepo}/compare/${baseSha}...${encodeURIComponent(`${headRepo.split(\"/\")[0]}:${branchRef.object.sha}`)}`\n );\n if (comparison.files.some((item) => item.filename !== file))\n throw new Error(\"\u4E13\u7528\u63D0\u4EA4\u5206\u652F\u5305\u542B\u5176\u4ED6\u6587\u4EF6\u6539\u52A8\uFF0C\u8BF7\u5148\u7531\u4F5C\u8005\u5904\u7406\");\n const oldFile = await content(api, headRepo, file, branchRef.object.sha);\n const oldEntry = oldFile ? decode(oldFile) : null;\n if (oldEntry) {\n const pending = object(oldEntry, file);\n if (\n pending.npm !== entry.npm ||\n (pending.repo && sourceRepo(pending.repo) !== entry.repo) ||\n typeof pending.version !== \"string\" ||\n !semver.valid(pending.version) ||\n semver.lt(entry.version, pending.version)\n ) {\n throw new Error(\"\u5DF2\u6709\u63D0\u4EA4\u7684\u6765\u6E90\u6216\u7248\u672C\u51B2\u7A81\uFF1B\u62D2\u7EDD\u8986\u76D6\");\n }\n }\n if (!same(entry, oldEntry)) {\n await api(`repos/${headRepo}/contents/${file}`, \"PUT\", {\n message: `chore(registry): ${entry.id} ${entry.version}`,\n content: Buffer.from(`${JSON.stringify(entry, null, 2)}\\n`).toString(\"base64\"),\n branch,\n ...(oldFile ? { sha: oldFile.sha } : {}),\n });\n }\n const title = `\u6536\u5F55 ${entry.id}@${entry.version}`;\n const body = `\u66F4\u65B0 \\`${file}\\`\u3002\\n\\n- npm\uFF1A\\`${entry.npm}@${entry.version}\\`\uFF08\u516C\u5171\u6E90\u7CBE\u786E\u7248\u672C\u5DF2\u6838\u5BF9\uFF09\\n- \u6E90\u7801\uFF1A${entry.repo ?? \"\u672A\u63D0\u4F9B\"}\\n- \u8BB8\u53EF\u8BC1\uFF1A\\`${entry.license}\\`\\n- SDK\uFF1A\\`${entry.sdk}\\`\\n- \u4F9D\u8D56\uFF1A${entry.requires.join(\", \") || \"\u65E0\"}\\n\\n\u5DF2\u68C0\u67E5\u5F53\u524D\u7D22\u5F15 Schema\u3001ID \u6765\u6E90\u3001\u7248\u672C\u4E0E\u4F9D\u8D56\u5173\u7CFB\u3002\u5B98\u65B9\u8EAB\u4EFD\u6CBF\u7528\u7EF4\u62A4\u8005\u5BA1\u6838\u7ED3\u679C\uFF1B\u8BF7\u68C0\u67E5 PR \u95E8\u7981\u540E\u5408\u5E76\u3002`;\n if (pull) {\n if (same(entry, oldEntry)) return { ...result, status: \"unchanged\", prUrl: pull.html_url };\n await api(`repos/${canonicalRepo}/pulls/${pull.number}`, \"PATCH\", { title, body });\n return { ...result, status: \"updated\", prUrl: pull.html_url };\n }\n const created = await api<{ html_url: string }>(`repos/${canonicalRepo}/pulls`, \"POST\", { title, body, base, head });\n return { ...result, status: \"created\", prUrl: created.html_url };\n}\n", "#!/usr/bin/env node\nimport { runRegistrySubmitCommand } from \"./registry-command.js\";\n\nrunRegistrySubmitCommand(process.argv.slice(2))\n .then(console.log)\n .catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n });\n"],
5
+ "mappings": "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",
6
+ "names": ["fs", "path", "a", "b", "path", "fs"]
7
+ }
@@ -0,0 +1,2 @@
1
+ /** CLI 与 CI 共用参数解析,未知参数返回失败,避免误提交。 */
2
+ export declare function runRegistrySubmitCommand(args: string[]): Promise<string>;
@@ -0,0 +1,42 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { submitModuleToRegistry } from "./registry-submit.js";
4
+ /** CLI 与 CI 共用参数解析,未知参数返回失败,避免误提交。 */
5
+ export async function runRegistrySubmitCommand(args) {
6
+ const options = {};
7
+ let entryFile;
8
+ const help = "sfmc mod submit [模块目录] [--dry-run] [--no-fork] [--wait-for-publish] [--registry owner/repo]\nCI:sfmc-module-submit --entry-file <JSON>(与模块目录互斥)";
9
+ if (args.includes("--help") || args.includes("-h"))
10
+ return help;
11
+ for (let i = 0; i < args.length; i++) {
12
+ const arg = args[i];
13
+ if (arg === "--dry-run")
14
+ options.dryRun = true;
15
+ else if (arg === "--no-fork")
16
+ options.noFork = true;
17
+ else if (arg === "--wait-for-publish")
18
+ options.waitForPublish = true;
19
+ else if (arg === "--registry" || arg === "--entry-file") {
20
+ const value = args[++i];
21
+ if (!value || value.startsWith("-"))
22
+ throw new Error(`缺少 ${arg} 的值`);
23
+ if (arg === "--registry")
24
+ options.registryRepo = value;
25
+ else
26
+ entryFile = value;
27
+ }
28
+ else if (arg.startsWith("-") || options.moduleRoot)
29
+ throw new Error(`未知或重复参数:${arg}\n${help}`);
30
+ else
31
+ options.moduleRoot = path.resolve(arg);
32
+ }
33
+ if (entryFile) {
34
+ if (options.moduleRoot)
35
+ throw new Error("--entry-file 与模块目录互斥");
36
+ options.entry = JSON.parse(await fs.readFile(path.resolve(entryFile), "utf8"));
37
+ }
38
+ const result = await submitModuleToRegistry(options);
39
+ if (result.status === "preview")
40
+ return `预览:${result.registryRepo}/${result.path}\n原条目:\n${JSON.stringify(result.previous, null, 2)}\n拟提交:\n${JSON.stringify(result.entry, null, 2)}\n校验通过,未写入远端。`;
41
+ return `${{ unchanged: "内容相同,未重复提交", created: "已创建索引 PR", updated: "已更新索引 PR" }[result.status]}${result.prUrl ? `:${result.prUrl}` : ""}`;
42
+ }
@@ -0,0 +1,45 @@
1
+ export declare const MODULE_REGISTRY_REPO = "DogeLakeDev/sfmc-modules";
2
+ export interface RegistryMetadata {
3
+ name?: string;
4
+ description?: string;
5
+ category?: "gameplay" | "utility" | "system" | "economy" | "social";
6
+ tags?: string[];
7
+ authors?: string[];
8
+ }
9
+ export interface RegistryModule extends RegistryMetadata {
10
+ id: string;
11
+ name: string;
12
+ description: string;
13
+ npm: string;
14
+ version: string;
15
+ sdk: string;
16
+ license: string;
17
+ official: boolean;
18
+ repo?: string;
19
+ requires: string[];
20
+ }
21
+ export interface RegistrySubmitOptions {
22
+ moduleRoot?: string;
23
+ /** 表单等入口提供原始字段,仍须经过相同 npm、Schema 和索引校验。 */
24
+ entry?: RegistryModule;
25
+ registryRepo?: string;
26
+ dryRun?: boolean;
27
+ /** 维护者 / GitHub App 在索引仓建分支,须有 Contents 和 Pull requests 写权限。 */
28
+ noFork?: boolean;
29
+ token?: string;
30
+ /** 等待刚发布的精确版本可见;仅重试 404,最长 60 秒。 */
31
+ waitForPublish?: boolean;
32
+ log?: (message: string) => void;
33
+ }
34
+ export interface RegistrySubmitResult {
35
+ status: "preview" | "unchanged" | "created" | "updated";
36
+ entry: RegistryModule;
37
+ previous: RegistryModule | null;
38
+ path: string;
39
+ prUrl?: string;
40
+ registryRepo: string;
41
+ }
42
+ /** 作者配置只补充展示字段;包、版本、SDK 和依赖来自已有契约。 */
43
+ export declare function createRegistryEntry(pkgValue: unknown, manifestValue: unknown, metadataValue?: unknown): RegistryModule;
44
+ /** 所有入口共用:预览也验证已发布版本与最新分片;预览阶段不产生远端写入。 */
45
+ export declare function submitModuleToRegistry(options?: RegistrySubmitOptions): Promise<RegistrySubmitResult>;
@@ -0,0 +1,397 @@
1
+ /** 模块索引提交核心。仅写入专用分支中的单个分片,不操作作者工作树。 */
2
+ import { Ajv } from "ajv";
3
+ import { execFile } from "node:child_process";
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { promisify } from "node:util";
7
+ import semver from "semver";
8
+ export const MODULE_REGISTRY_REPO = "DogeLakeDev/sfmc-modules";
9
+ function object(value, label) {
10
+ if (!value || typeof value !== "object" || Array.isArray(value))
11
+ throw new Error(`${label} 须为 JSON 对象`);
12
+ return value;
13
+ }
14
+ function text(value, label) {
15
+ if (typeof value !== "string" || !value.trim())
16
+ throw new Error(`缺少 ${label}`);
17
+ return value.trim();
18
+ }
19
+ function sourceRepo(value) {
20
+ if (value === undefined)
21
+ return undefined;
22
+ const raw = typeof value === "string" ? value : object(value, "repository").url;
23
+ const repo = text(raw, "repository.url")
24
+ .replace(/^git\+/, "")
25
+ .replace(/\.git$/, "");
26
+ const url = new URL(repo);
27
+ if (url.protocol !== "https:" || url.username || url.password)
28
+ throw new Error("repository 须为不含凭据的 HTTPS 地址");
29
+ return url.href.replace(/\/$/, "");
30
+ }
31
+ /** 作者配置只补充展示字段;包、版本、SDK 和依赖来自已有契约。 */
32
+ export function createRegistryEntry(pkgValue, manifestValue, metadataValue = {}) {
33
+ const pkg = object(pkgValue, "package.json");
34
+ const manifest = object(manifestValue, "sapi/manifest.json");
35
+ const metadata = object(metadataValue, "sfmc.registry.json");
36
+ const allowed = new Set(["$schema", "name", "description", "category", "tags", "authors"]);
37
+ for (const key of Object.keys(metadata))
38
+ if (!allowed.has(key))
39
+ throw new Error(`sfmc.registry.json 不支持字段 ${key}`);
40
+ if (pkg.private === true)
41
+ throw new Error("private 模块不能提交公共索引");
42
+ if (manifest.schemaVersion !== 2)
43
+ throw new Error("需要 schemaVersion=2 的模块清单");
44
+ const peers = object(pkg.peerDependencies ?? {}, "peerDependencies");
45
+ const entry = {
46
+ id: text(manifest.id, "manifest.id"),
47
+ name: text(metadata.name ?? manifest.name, "展示名称(name)"),
48
+ description: text(metadata.description ?? pkg.description, "功能介绍(description)"),
49
+ npm: text(pkg.name, "package.name"),
50
+ version: text(pkg.version, "package.version"),
51
+ sdk: text(peers["@sfmc-bds/sdk"], "peerDependencies.@sfmc-bds/sdk"),
52
+ license: text(pkg.license, "package.license"),
53
+ official: false,
54
+ requires: (manifest.requires ?? []),
55
+ };
56
+ const repo = sourceRepo(pkg.repository);
57
+ if (repo)
58
+ entry.repo = repo;
59
+ for (const key of ["category", "tags", "authors"]) {
60
+ if (metadata[key] !== undefined)
61
+ Object.assign(entry, { [key]: metadata[key] });
62
+ }
63
+ return entry;
64
+ }
65
+ async function readLocalEntry(root) {
66
+ const realRoot = await fs.realpath(root);
67
+ const read = async (file) => {
68
+ const target = await fs.realpath(path.join(realRoot, file));
69
+ const relative = path.relative(realRoot, target);
70
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
71
+ throw new Error(`${file} 须位于模块目录内`);
72
+ const raw = await fs.readFile(target, "utf8");
73
+ try {
74
+ return JSON.parse(raw);
75
+ }
76
+ catch {
77
+ throw new Error(`${file} 包含无效 JSON`);
78
+ }
79
+ };
80
+ let metadata = {};
81
+ try {
82
+ metadata = await read("sfmc.registry.json");
83
+ }
84
+ catch (error) {
85
+ if (error.code !== "ENOENT")
86
+ throw error;
87
+ }
88
+ return {
89
+ entry: createRegistryEntry(await read("package.json"), await read("sapi/manifest.json"), metadata),
90
+ overrides: new Set(Object.keys(object(metadata, "sfmc.registry.json"))),
91
+ };
92
+ }
93
+ class GitHubError extends Error {
94
+ status;
95
+ constructor(status, endpoint, rateLimited = false) {
96
+ super(`GitHub ${status}: ${endpoint}${rateLimited ? "(API 配额已用尽,请登录后重试或等待配额恢复)" : status === 401 || status === 403 ? "(检查登录及 Contents / Pull requests 权限)" : ""}`);
97
+ this.status = status;
98
+ }
99
+ }
100
+ async function resolveToken(required) {
101
+ const configured = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
102
+ if (configured)
103
+ return configured;
104
+ try {
105
+ const { stdout } = await promisify(execFile)("gh", ["auth", "token", "--hostname", "github.com"], {
106
+ timeout: 10000,
107
+ windowsHide: true,
108
+ });
109
+ return stdout.trim() || undefined;
110
+ }
111
+ catch {
112
+ if (required)
113
+ throw new Error("提交需要 GitHub 登录:执行 gh auth login,或设置 GH_TOKEN。");
114
+ return undefined;
115
+ }
116
+ }
117
+ function github(token) {
118
+ return async function api(endpoint, method = "GET", body) {
119
+ const response = await fetch(`https://api.github.com/${endpoint}`, {
120
+ method,
121
+ headers: {
122
+ Accept: "application/vnd.github+json",
123
+ "User-Agent": "sfmc-devkit",
124
+ "X-GitHub-Api-Version": "2022-11-28",
125
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
126
+ ...(body === undefined ? {} : { "Content-Type": "application/json" }),
127
+ },
128
+ body: body === undefined ? undefined : JSON.stringify(body),
129
+ signal: AbortSignal.timeout(15000),
130
+ });
131
+ // 不回显服务器正文或认证信息。
132
+ if (!response.ok)
133
+ throw new GitHubError(response.status, endpoint, response.status === 429 || response.headers.get("x-ratelimit-remaining") === "0");
134
+ return response.status === 204 ? undefined : (await response.json());
135
+ };
136
+ }
137
+ function decode(file) {
138
+ if (file.type !== "file" || file.encoding !== "base64")
139
+ throw new Error("索引内容须为普通 JSON 文件");
140
+ return JSON.parse(Buffer.from(file.content, "base64").toString("utf8"));
141
+ }
142
+ async function content(api, repo, file, ref) {
143
+ try {
144
+ return await api(`repos/${repo}/contents/${file}?ref=${encodeURIComponent(ref)}`);
145
+ }
146
+ catch (error) {
147
+ if (error instanceof GitHubError && error.status === 404)
148
+ return null;
149
+ throw error;
150
+ }
151
+ }
152
+ function same(a, b) {
153
+ const stable = (value) => Array.isArray(value)
154
+ ? value.map(stable)
155
+ : value && typeof value === "object"
156
+ ? Object.fromEntries(Object.entries(value)
157
+ .sort(([a], [b]) => a.localeCompare(b))
158
+ .map(([key, item]) => [key, stable(item)]))
159
+ : value;
160
+ return JSON.stringify(stable(a)) === JSON.stringify(stable(b));
161
+ }
162
+ async function checkPublished(entry, wait) {
163
+ const deadline = Date.now() + (wait ? 60000 : 0);
164
+ while (true) {
165
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(entry.npm)}/${encodeURIComponent(entry.version)}`, {
166
+ signal: AbortSignal.timeout(15000),
167
+ headers: { Accept: "application/json" },
168
+ });
169
+ if (response.status === 404 && Date.now() < deadline) {
170
+ await new Promise((resolve) => setTimeout(resolve, 3000));
171
+ continue;
172
+ }
173
+ if (!response.ok)
174
+ throw new Error(`公共 npm 尚无可用的 ${entry.npm}@${entry.version}(HTTP ${response.status});先发布该精确版本。`);
175
+ const pkg = object(await response.json(), "npm 元数据");
176
+ const peers = object(pkg.peerDependencies ?? {}, "npm peerDependencies");
177
+ if (pkg.name !== entry.npm ||
178
+ pkg.version !== entry.version ||
179
+ pkg.license !== entry.license ||
180
+ peers["@sfmc-bds/sdk"] !== entry.sdk) {
181
+ throw new Error("已发布包的名称、版本、许可证或 SDK 兼容范围与提交信息不一致");
182
+ }
183
+ if (sourceRepo(pkg.repository) !== entry.repo)
184
+ throw new Error("已发布包的源码地址与提交信息不一致");
185
+ return;
186
+ }
187
+ }
188
+ /** 所有入口共用:预览也验证已发布版本与最新分片;预览阶段不产生远端写入。 */
189
+ export async function submitModuleToRegistry(options = {}) {
190
+ const repo = options.registryRepo ?? MODULE_REGISTRY_REPO;
191
+ if (!/^[\w.-]+\/[\w.-]+$/.test(repo))
192
+ throw new Error("registryRepo 须为 owner/repo");
193
+ const local = options.entry ? null : await readLocalEntry(path.resolve(options.moduleRoot ?? process.cwd()));
194
+ const entry = options.entry ? structuredClone(options.entry) : local.entry;
195
+ entry.requires ??= [];
196
+ const token = options.token ?? (await resolveToken(!options.dryRun));
197
+ if (!options.dryRun && !token)
198
+ throw new Error("提交需要有效的 GitHub 登录或 GH_TOKEN");
199
+ const api = github(token);
200
+ const upstream = await api(`repos/${repo}`);
201
+ const canonicalRepo = upstream.full_name;
202
+ const base = upstream.default_branch;
203
+ const baseRef = await api(`repos/${canonicalRepo}/git/ref/heads/${encodeURIComponent(base)}`);
204
+ const baseSha = baseRef.object.sha;
205
+ const schemaFile = await content(api, canonicalRepo, "schemas/registry-module.schema.json", baseSha);
206
+ if (!schemaFile)
207
+ throw new Error("索引仓缺少 registry-module.schema.json");
208
+ const ajv = new Ajv({ allErrors: true, strict: true });
209
+ ajv.addFormat("semver-range", (value) => !!value.trim() && semver.validRange(value) !== null);
210
+ const validate = ajv.compile(decode(schemaFile));
211
+ entry.official = false; // 首次收录永不自行声明官方身份。
212
+ if (!validate(entry))
213
+ throw new Error(`索引元数据不合规:${ajv.errorsText(validate.errors)}`);
214
+ if (!semver.valid(entry.version))
215
+ throw new Error("版本须为有效的精确 SemVer");
216
+ const file = `modules/${entry.id}.json`;
217
+ options.log?.(`校验 ${entry.npm}@${entry.version} 与最新索引分片`);
218
+ const tree = await api(`repos/${canonicalRepo}/git/trees/${baseSha}?recursive=1`);
219
+ if (tree.truncated)
220
+ throw new Error("索引树过大,无法完整检查依赖");
221
+ const shards = tree.tree.filter((item) => /^modules\/[^/]+\.json$/.test(item.path) && item.type === "blob");
222
+ const modules = Object.create(null);
223
+ // 只读取该模块和依赖闭包,避免每次提交扫描整个索引并耗尽 API 配额。
224
+ const shardByPath = new Map(shards.map((item) => [item.path, item]));
225
+ async function load(id) {
226
+ const shard = shardByPath.get(`modules/${id}.json`);
227
+ if (!shard)
228
+ return null;
229
+ const blob = await api(`repos/${canonicalRepo}/git/blobs/${shard.sha}`);
230
+ if (blob.encoding !== "base64")
231
+ throw new Error("无效索引 blob 编码");
232
+ const module = object(JSON.parse(Buffer.from(blob.content, "base64").toString("utf8")), shard.path);
233
+ if (module.id !== id)
234
+ throw new Error(`索引分片 ID 不一致:${shard.path}`);
235
+ return module;
236
+ }
237
+ const previous = await load(entry.id);
238
+ if (previous) {
239
+ if (previous.npm !== entry.npm || (previous.repo && sourceRepo(previous.repo) !== entry.repo))
240
+ throw new Error("该 ID 已属于其他 npm 包或源码仓;请由维护者处理迁移");
241
+ if (!semver.valid(previous.version) || semver.lt(entry.version, previous.version))
242
+ throw new Error("不能降低已收录版本");
243
+ entry.official = previous.official; // 仅继承索引仓已审核身份。
244
+ // 单纯更新版本保留已整理的展示信息;作者配置可显式覆盖。
245
+ if (local) {
246
+ if (!local.overrides.has("name"))
247
+ entry.name = previous.name;
248
+ if (!local.overrides.has("description"))
249
+ entry.description = previous.description;
250
+ }
251
+ for (const key of ["category", "tags", "authors"]) {
252
+ if (entry[key] === undefined && previous[key] !== undefined)
253
+ Object.assign(entry, { [key]: structuredClone(previous[key]) });
254
+ }
255
+ }
256
+ if (!validate(entry))
257
+ throw new Error(`索引身份或字段不合规:${ajv.errorsText(validate.errors)}`);
258
+ modules[entry.id] = entry;
259
+ const queue = [...entry.requires];
260
+ const loaded = new Set([entry.id]);
261
+ while (queue.length) {
262
+ const batch = queue.splice(0, 4).filter((id) => {
263
+ if (loaded.has(id))
264
+ return false;
265
+ loaded.add(id);
266
+ return true;
267
+ });
268
+ await Promise.all(batch.map(async (id) => {
269
+ const dependency = await load(id);
270
+ if (!dependency)
271
+ throw new Error(`依赖 ${id} 尚未收录,请先提交依赖模块`);
272
+ if (!validate(dependency))
273
+ throw new Error(`依赖 ${id} 的索引元数据不合规`);
274
+ modules[id] = dependency;
275
+ queue.push(...(dependency.requires ?? []));
276
+ }));
277
+ }
278
+ const visited = new Set();
279
+ const visiting = new Set();
280
+ function visit(id) {
281
+ if (visiting.has(id))
282
+ throw new Error(`模块依赖存在循环:${id}`);
283
+ if (visited.has(id))
284
+ return;
285
+ const module = modules[id];
286
+ if (!module)
287
+ throw new Error(`依赖 ${id} 尚未收录,请先提交依赖模块`);
288
+ visiting.add(id);
289
+ for (const dependency of module.requires ?? [])
290
+ visit(dependency);
291
+ visiting.delete(id);
292
+ visited.add(id);
293
+ }
294
+ visit(entry.id);
295
+ await checkPublished(entry, Boolean(options.waitForPublish));
296
+ const result = { entry, previous, path: file, registryRepo: canonicalRepo };
297
+ if (options.dryRun)
298
+ return { ...result, status: "preview" };
299
+ if (same(entry, previous))
300
+ return { ...result, status: "unchanged" };
301
+ let headRepo = canonicalRepo;
302
+ if (!options.noFork && upstream.permissions?.push !== true) {
303
+ const user = await api("user");
304
+ const forkName = `${user.login}/${canonicalRepo.split("/")[1]}`;
305
+ let fork;
306
+ try {
307
+ fork = await api(`repos/${forkName}`);
308
+ }
309
+ catch (error) {
310
+ if (!(error instanceof GitHubError && error.status === 404))
311
+ throw error;
312
+ }
313
+ if (!fork)
314
+ fork = await api(`repos/${canonicalRepo}/forks`, "POST", { default_branch_only: true });
315
+ if (fork.id === upstream.id) {
316
+ // 候选地址与目标地址可能因重定向指向同一仓库,按实际权限处理。
317
+ if (fork.permissions?.push !== true)
318
+ throw new Error(`${forkName} 已重定向到 ${canonicalRepo},当前凭据没有写权限;请检查 GitHub 登录与 Contents 权限。`);
319
+ }
320
+ else {
321
+ const networkId = upstream.source?.id ?? upstream.id;
322
+ if (!fork.fork || (fork.parent?.id !== upstream.id && fork.source?.id !== networkId))
323
+ throw new Error(`${fork.full_name} 未与目标索引仓关联,无法作为该 PR 的来源;请使用目标仓的 fork,或有写权限时使用 --no-fork。`);
324
+ headRepo = fork.full_name;
325
+ }
326
+ }
327
+ let pull;
328
+ const prefix = `sfmc-submit/${entry.id}/`;
329
+ for (let page = 1;; page++) {
330
+ const pagePulls = await api(`repos/${canonicalRepo}/pulls?state=open&base=${encodeURIComponent(base)}&per_page=100&page=${page}`);
331
+ const matches = pagePulls.filter((item) => item.head.ref.startsWith(prefix) && item.head.repo?.full_name.toLowerCase() === headRepo.toLowerCase());
332
+ if (matches.length > 1 || (pull && matches.length))
333
+ throw new Error("该模块有多个提交 PR,请先合并或关闭多余 PR");
334
+ pull ??= matches[0];
335
+ if (pagePulls.length < 100)
336
+ break;
337
+ }
338
+ // 已合并的分支无需重置;新一轮提交以最新 main 的 SHA 标识。
339
+ const branch = pull?.head.ref ?? `${prefix}${baseSha.slice(0, 12)}`;
340
+ const head = `${headRepo.split("/")[0]}:${branch}`;
341
+ let branchRef = null;
342
+ // 新 fork 的创建是异步操作,最多等待 15 秒。
343
+ for (let attempt = 0; attempt < 6; attempt++) {
344
+ try {
345
+ branchRef = await api(`repos/${headRepo}/git/ref/heads/${encodeURIComponent(branch)}`);
346
+ break;
347
+ }
348
+ catch (error) {
349
+ if (!(error instanceof GitHubError && error.status === 404))
350
+ throw error;
351
+ try {
352
+ branchRef = await api(`repos/${headRepo}/git/refs`, "POST", { ref: `refs/heads/${branch}`, sha: baseSha });
353
+ break;
354
+ }
355
+ catch (createError) {
356
+ if (!(createError instanceof GitHubError && [404, 422].includes(createError.status)) || attempt === 5)
357
+ throw createError;
358
+ await new Promise((resolve) => setTimeout(resolve, 3000));
359
+ }
360
+ }
361
+ }
362
+ if (!branchRef)
363
+ throw new Error("提交分支尚未就绪,请重试");
364
+ const comparison = await api(`repos/${canonicalRepo}/compare/${baseSha}...${encodeURIComponent(`${headRepo.split("/")[0]}:${branchRef.object.sha}`)}`);
365
+ if (comparison.files.some((item) => item.filename !== file))
366
+ throw new Error("专用提交分支包含其他文件改动,请先由作者处理");
367
+ const oldFile = await content(api, headRepo, file, branchRef.object.sha);
368
+ const oldEntry = oldFile ? decode(oldFile) : null;
369
+ if (oldEntry) {
370
+ const pending = object(oldEntry, file);
371
+ if (pending.npm !== entry.npm ||
372
+ (pending.repo && sourceRepo(pending.repo) !== entry.repo) ||
373
+ typeof pending.version !== "string" ||
374
+ !semver.valid(pending.version) ||
375
+ semver.lt(entry.version, pending.version)) {
376
+ throw new Error("已有提交的来源或版本冲突;拒绝覆盖");
377
+ }
378
+ }
379
+ if (!same(entry, oldEntry)) {
380
+ await api(`repos/${headRepo}/contents/${file}`, "PUT", {
381
+ message: `chore(registry): ${entry.id} ${entry.version}`,
382
+ content: Buffer.from(`${JSON.stringify(entry, null, 2)}\n`).toString("base64"),
383
+ branch,
384
+ ...(oldFile ? { sha: oldFile.sha } : {}),
385
+ });
386
+ }
387
+ const title = `收录 ${entry.id}@${entry.version}`;
388
+ const body = `更新 \`${file}\`。\n\n- npm:\`${entry.npm}@${entry.version}\`(公共源精确版本已核对)\n- 源码:${entry.repo ?? "未提供"}\n- 许可证:\`${entry.license}\`\n- SDK:\`${entry.sdk}\`\n- 依赖:${entry.requires.join(", ") || "无"}\n\n已检查当前索引 Schema、ID 来源、版本与依赖关系。官方身份沿用维护者审核结果;请检查 PR 门禁后合并。`;
389
+ if (pull) {
390
+ if (same(entry, oldEntry))
391
+ return { ...result, status: "unchanged", prUrl: pull.html_url };
392
+ await api(`repos/${canonicalRepo}/pulls/${pull.number}`, "PATCH", { title, body });
393
+ return { ...result, status: "updated", prUrl: pull.html_url };
394
+ }
395
+ const created = await api(`repos/${canonicalRepo}/pulls`, "POST", { title, body, base, head });
396
+ return { ...result, status: "created", prUrl: created.html_url };
397
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfmc-bds/devkit",
3
- "version": "1.0.8",
3
+ "version": "2.0.0",
4
4
  "description": "SFMC module authoring toolkit — watch, rebuild, enable/disable (for VS Code extension & tooling)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,6 +14,9 @@
14
14
  },
15
15
  "main": "./dist/index.js",
16
16
  "types": "./dist/index.d.ts",
17
+ "bin": {
18
+ "sfmc-module-submit": "./dist/registry-cli.js"
19
+ },
17
20
  "exports": {
18
21
  ".": {
19
22
  "types": "./dist/index.d.ts",
@@ -24,15 +27,18 @@
24
27
  },
25
28
  "files": [
26
29
  "dist",
30
+ "schemas",
27
31
  "README.md"
28
32
  ],
29
33
  "dependencies": {
30
- "@sfmc-bds/bds-tools": "0.2.4",
31
- "@sfmc-bds/cli": "0.3.2",
32
- "@sfmc-bds/sdk": "0.2.4"
34
+ "ajv": "8.17.1",
35
+ "semver": "7.7.2",
36
+ "@sfmc-bds/bds-tools": "0.2.5",
37
+ "@sfmc-bds/sdk": "0.3.0"
33
38
  },
34
39
  "devDependencies": {
35
- "@sfmc-bds/tools": "0.2.4"
40
+ "@types/semver": "7.7.1",
41
+ "@sfmc-bds/tools": "0.2.5"
36
42
  },
37
43
  "publishConfig": {
38
44
  "access": "public",
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "SFMC 模块作者索引展示配置",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "properties": {
7
+ "$schema": { "type": "string" },
8
+ "name": { "type": "string", "minLength": 1, "pattern": "\\S" },
9
+ "description": { "type": "string", "minLength": 1, "pattern": "\\S" },
10
+ "category": { "enum": ["gameplay", "utility", "system", "economy", "social"] },
11
+ "tags": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1, "pattern": "\\S" } },
12
+ "authors": {
13
+ "type": "array",
14
+ "minItems": 1,
15
+ "uniqueItems": true,
16
+ "items": { "type": "string", "minLength": 1, "pattern": "\\S" }
17
+ }
18
+ }
19
+ }