Skip to content

Commit 89f87c4

Browse files
cursoragentTanya7z
andcommitted
fix(ci): 清理悬空 workspace lock + 对齐 registry/unzip 契约
ootb@72e24e7 在 npm install 因 package-lock 残留已删 modules/packages/* 的 link:true 崩溃。同步修复 registry 解析 modules 嵌套、zip 反斜杠路径归一化, 并去重 catalog folder 解析(DRY/LSP)。 Co-authored-by: Shiroha <Tanya7z@users.noreply.github.com>
1 parent 72e24e7 commit 89f87c4

7 files changed

Lines changed: 57 additions & 58 deletions

File tree

‎modules/catalog.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"_comment": "modules/catalog.json 是本地 mirror;source of truth 为 github:Tanya7z/sfmc-modules/main/index.json。业务模块已全部提取至 Tanya7z/sfmc-modules(最新 modules-v0.4.0)。本仓库 modules/packages/ 不再内置业务包;请用 tools/fetch-module.mjs / sfmc 从 registry 安装到 modules/packages/<id>/。",
2+
"_comment": "modules/catalog.json 是本地 mirror;source of truth 为 github:Tanya7z/sfmc-modules/main/index.json。业务模块默认外置;本仓可保留开箱 fixture(如 afk)。安装/卸载后请用 tools/catalog-sync.mjs 或 fetch-module 投影。",
33
"version": 1,
44
"modules": [
55
{

‎package-lock.json‎

Lines changed: 14 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎sfmc/src/registry.ts‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,35 @@ function writeCache(cache: RegistryCache): void {
5454
}
5555
}
5656

57+
/**
58+
* 解析 first-party index.json。
59+
* 契约与 tools/fetch-module.mjs 一致:权威映射在 `modules` 字段下
60+
* (`{ version, modules: { <folder>: { repo, tag } } }`),忽略 `_` 前缀元数据键。
61+
*/
62+
function parseRegistryIndex(json: unknown): RegistryIndex {
63+
if (typeof json !== "object" || json === null || Array.isArray(json)) {
64+
throw new Error("registry index must be a JSON object with a 'modules' field");
65+
}
66+
const root = json as Record<string, unknown>;
67+
const modulesRaw = root.modules;
68+
if (typeof modulesRaw !== "object" || modulesRaw === null || Array.isArray(modulesRaw)) {
69+
throw new Error("registry index must have a 'modules' object mapping id → { repo, tag }");
70+
}
71+
const filtered: RegistryIndex = {};
72+
for (const [k, v] of Object.entries(modulesRaw as Record<string, unknown>)) {
73+
if (k.startsWith("_")) continue;
74+
if (typeof v !== "object" || v === null || Array.isArray(v)) continue;
75+
const entry = v as Record<string, unknown>;
76+
if (typeof entry.repo !== "string" || typeof entry.tag !== "string") continue;
77+
filtered[k] = { repo: entry.repo, tag: entry.tag };
78+
}
79+
return filtered;
80+
}
81+
5782
async function fetchFresh(): Promise<RegistryIndex> {
5883
const res = await fetch(INDEX_URL, { headers: { "User-Agent": "sfmc-cli" } });
5984
if (!res.ok) throw new Error(`HTTP ${res.status} for ${INDEX_URL}`);
60-
const json = (await res.json()) as unknown;
61-
if (typeof json !== "object" || json === null || Array.isArray(json)) {
62-
throw new Error("registry index must be a JSON object mapping id → { repo, tag }");
63-
}
64-
return json as RegistryIndex;
85+
return parseRegistryIndex(await res.json());
6586
}
6687

6788
export interface RegistryResult {

‎tools/check-modules.mjs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import fs from "node:fs";
1515
import path from "node:path";
1616
import { readCatalog, syncCatalogFromPackages } from "./lib/catalog.mjs";
17-
import { scanInstalledPackages } from "./lib/packages.mjs";
17+
import { folderFromEntryPath, scanInstalledPackages } from "./lib/packages.mjs";
1818
import { ROOT } from "./lib/paths.mjs";
1919
import { exists } from "./lib/io.mjs";
2020

@@ -62,7 +62,8 @@ function main() {
6262
const abs = path.join(ROOT, m.entry.path);
6363
if (!exists(abs)) fail(`${m.id}: entry 不存在: ${m.entry.path}`);
6464

65-
const folder = String(m.entry.path).replace(/\\/g, "/").split("/")[2];
65+
const folder = folderFromEntryPath(m.entry.path);
66+
if (!folder) fail(`${m.id}: 无法从 entry.path 解析 packages 目录: ${m.entry.path}`);
6667
const manifestPath = path.join(ROOT, "modules", "packages", folder, "sapi", "manifest.json");
6768
if (!exists(manifestPath)) fail(`${m.id}: 缺少 packages/${folder}/sapi/manifest.json`);
6869
let manifest;

‎tools/fetch-module.mjs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,13 @@ async function unzip(zipPath, dstDir) {
294294
const data = await fsp.readFile(zipPath);
295295
const zip = await JSZip.loadAsync(data);
296296
for (const e of Object.values(zip.files)) {
297-
const out = path.join(dstDir, e.name);
298-
if (e.dir) {
299-
await fsp.mkdir(out, { recursive: true });
297+
// Windows zip 常带 `\`;必须归一成 `/`,否则 Linux 会写出字面量 `sapi\manifest.json`
298+
const rel = String(e.name).replace(/\\/g, "/").replace(/^\/+/, "");
299+
if (!rel || e.dir || rel.endsWith("/")) {
300+
if (rel) await fsp.mkdir(path.join(dstDir, ...rel.replace(/\/$/, "").split("/").filter(Boolean)), { recursive: true });
300301
continue;
301302
}
303+
const out = path.join(dstDir, ...rel.split("/"));
302304
await fsp.mkdir(path.dirname(out), { recursive: true });
303305
await fsp.writeFile(out, await e.async("nodebuffer"));
304306
}

‎tools/lib/catalog.mjs‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
*/
44
import { CATALOG_PATH } from "./paths.mjs";
55
import { readJson, writeJson } from "./io.mjs";
6-
import { loadPackageCatalogEntry, projectCatalogEntry, scanInstalledPackages } from "./packages.mjs";
6+
import {
7+
folderFromEntryPath,
8+
loadPackageCatalogEntry,
9+
projectCatalogEntry,
10+
scanInstalledPackages,
11+
} from "./packages.mjs";
712

813
const DEFAULT_COMMENT =
914
"modules/catalog.json 是本地 mirror;source of truth 为 github:Tanya7z/sfmc-modules/main/index.json。条目由已安装的 modules/packages/<id>/sapi/manifest.json 投影生成(fetch-module install / catalog-sync)。空数组表示纯 SDK 仓,合法。";
@@ -65,7 +70,7 @@ export function upsertCatalogEntry(folder) {
6570
if (!entry) throw new Error(`packages/${folder}: 无有效 sapi/manifest.json`);
6671
const catalog = readCatalog();
6772
const idx = catalog.modules.findIndex(
68-
(m) => m.id === entry.id || (m.entry && folderFromEntry(m.entry.path) === folder)
73+
(m) => m.id === entry.id || (m.entry && folderFromEntryPath(m.entry.path) === folder)
6974
);
7075
if (idx >= 0) catalog.modules[idx] = entry;
7176
else catalog.modules.push(entry);
@@ -83,18 +88,10 @@ export function removeCatalogEntry(folderOrId) {
8388
const catalog = readCatalog();
8489
const idx = catalog.modules.findIndex((m) => {
8590
if (m.id === folderOrId) return true;
86-
return folderFromEntry(m.entry?.path) === folderOrId;
91+
return folderFromEntryPath(m.entry?.path) === folderOrId;
8792
});
8893
if (idx < 0) return null;
8994
const [removed] = catalog.modules.splice(idx, 1);
9095
writeCatalog(catalog);
9196
return removed;
9297
}
93-
94-
/** @param {string | undefined} entryPath */
95-
function folderFromEntry(entryPath) {
96-
const m = String(entryPath || "")
97-
.replace(/\\/g, "/")
98-
.match(/modules\/packages\/([^/]+)\//);
99-
return m ? m[1] : null;
100-
}

‎tools/lib/packages.mjs‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
/**
2-
* tools/lib/packages.mjs — 扫描 packages/<id>/sapi/manifest.json 并投影为 catalog 条目
3-
*/
41
/**
52
* tools/lib/packages.mjs — 扫描 packages/<id>/sapi/manifest.json 并投影为 catalog 条目
63
*/

0 commit comments

Comments
 (0)