修改 meta.json 规则, 涉及version 、group 字段.

This commit is contained in:
cheney
2026-06-17 10:03:51 +08:00
parent fc81584878
commit 364efbc1c1
8 changed files with 599 additions and 136 deletions
+43 -48
View File
@@ -12,7 +12,15 @@ function normalizeRemotePath(value) {
if (!value || value === "/") {
return "/";
}
return path.posix.normalize("/" + String(value).replace(/\\/g, "/")).replace(/\/+$|^$/g, "") || "/";
return path.posix.normalize("/" + String(value).replace(/\\/g, "/")).replace(/\/+$/g, "") || "/";
}
function normalizeGroup(group) {
return normalizeRemotePath(group == null ? "/modules" : group);
}
function joinRemote(...items) {
return normalizeRemotePath(items.filter((item) => item !== undefined && item !== null && item !== "").join("/"));
}
function normalizeOs(value) {
@@ -136,7 +144,8 @@ module.exports = class Remote {
}
async list(projectId) {
const state = this.getInstallState(projectId);
const parsed = this.parseProjectVersion(projectId);
const state = this.getInstallState(parsed.projectId);
for (const version of state.installed_versions) {
const current = version === state.current_version ? "*" : " ";
$logger.info(`${current} ${version}${current === "*" ? " (current)" : ""}`);
@@ -150,14 +159,15 @@ module.exports = class Remote {
}
async rollback(projectId) {
const state = this.getInstallState(projectId);
const parsed = this.parseProjectVersion(projectId);
const state = this.getInstallState(parsed.projectId);
const history = [...state.history].reverse();
const current = state.current_version;
const previous = history.find((item) => item.version && item.version !== current && state.installed_versions.includes(item.version));
if (!previous) {
throw new Error(`没有可回滚版本 ${projectId}`);
throw new Error(`没有可回滚版本 ${parsed.projectId}`);
}
return this.activate(projectId, previous.version, "rollback");
return this.activate(parsed.projectId, previous.version, "rollback");
}
async remove(projectVersion) {
@@ -183,10 +193,15 @@ module.exports = class Remote {
parseProjectVersion(value) {
const text = String(value || "kit");
const index = text.lastIndexOf("@");
if (index <= 0) {
return { projectId: text, version: undefined };
const target = index <= 0 ? text : text.substring(0, index);
const version = index <= 0 ? undefined : text.substring(index + 1);
if (target.startsWith("/")) {
const normalized = normalizeRemotePath(target);
const projectId = path.posix.basename(normalized);
const group = normalizeGroup(path.posix.dirname(normalized));
return { projectId, version, group };
}
return { projectId: text.substring(0, index), version: text.substring(index + 1) };
return { projectId: target, version, group: "/modules" };
}
getLocalModuleRoot(projectId) {
@@ -255,6 +270,10 @@ module.exports = class Remote {
];
}
getRemoteProjectRoot(projectId, group = "/modules") {
return joinRemote(normalizeGroup(group), projectId);
}
async ensureDir(remotePath) {
const target = normalizeRemotePath(remotePath);
if (target === "/") {
@@ -299,51 +318,29 @@ module.exports = class Remote {
return this.ensureDir(folder);
}
async wGetModuleInfo(projectId) {
return this.getProjectMetadata(projectId);
async wGetModuleInfo(projectId, group = "/modules") {
return this.getProjectMetadata(projectId, group);
}
async getProjectMetadata(projectId) {
const metadata = await this.getJson(`/${projectId}/metadata.json`, undefined);
async getProjectMetadata(projectId, group = "/modules") {
const metadata = await this.getJson(joinRemote(this.getRemoteProjectRoot(projectId, group), "metadata.json"), undefined);
if (metadata) {
metadata.group = normalizeGroup(metadata.group == null ? group : metadata.group);
return metadata;
}
throw new Error(`远程模块不存在 ${normalizeGroup(group)}/${projectId}`);
}
async getVersionMetadata(projectId, version, group = "/modules") {
const metadata = await this.getJson(joinRemote(this.getRemoteProjectRoot(projectId, group), version, "version.json"), undefined);
if (metadata) {
return metadata;
}
const legacy = await this.getJson(`/modules/${projectId}/meta.json`, undefined);
if (legacy) {
return {
project_id: legacy.id || projectId,
description: legacy.desc || legacy.name || "",
latest_version: legacy.latest && legacy.latest.version,
versions: (legacy.versions || []).map((item) => item.version || item),
extra: legacy
};
}
throw new Error(`远程模块不存在 ${projectId}`);
throw new Error(`远程版本不存在 ${normalizeGroup(group)}/${projectId}@${version}`);
}
async getVersionMetadata(projectId, version) {
const metadata = await this.getJson(`/${projectId}/${version}/version.json`, undefined);
if (metadata) {
return metadata;
}
const project = await this.getProjectMetadata(projectId);
const legacyVersion = project.extra && project.extra.versions && project.extra.versions.find((item) => (item.version || item) === version);
if (legacyVersion && legacyVersion.dist) {
return {
version,
artifacts: legacyVersion.dist.map((dist) => ({
path: `${normalizeOs(dist.os)}/${normalizeArch(dist.platform)}/${dist.filename}`,
type: "executable"
}))
};
}
throw new Error(`远程版本不存在 ${projectId}@${version}`);
}
async downloadArtifact(projectId, version, artifact, versionDir) {
const remotePath = `/${projectId}/${version}/${artifact.path}`;
async downloadArtifact(projectId, version, artifact, versionDir, group = "/modules") {
const remotePath = joinRemote(this.getRemoteProjectRoot(projectId, group), version, artifact.path);
const localPath = path.resolve(versionDir, artifact.path);
await this.wDownload(remotePath, localPath);
return localPath;
@@ -362,5 +359,3 @@ module.exports = class Remote {
return [];
}
}
+5 -4
View File
@@ -50,13 +50,14 @@ function unpackArtifact(artifact, localPath, versionDir) {
module.exports = async function install(remote, id) {
const parsed = remote.parseProjectVersion(id || "kit");
const projectId = parsed.projectId;
const project = await remote.getProjectMetadata(projectId);
const version = parsed.version || project.latest_version;
const group = parsed.group || "/modules";
const project = await remote.getProjectMetadata(projectId, group);
const version = parsed.version || project.version;
if (!version) {
throw new Error(`远程模块没有可安装版本 ${projectId}`);
}
const versionMeta = await remote.getVersionMetadata(projectId, version);
const versionMeta = await remote.getVersionMetadata(projectId, version, project.group || group);
const artifacts = remote.getMatchingArtifacts(versionMeta);
if (artifacts.length < 1) {
throw new Error(`没有匹配当前平台的文件 ${projectId}@${version}`);
@@ -67,7 +68,7 @@ module.exports = async function install(remote, id) {
fs.mkdirSync(versionDir, { recursive: true });
for (const artifact of artifacts) {
const localPath = await remote.downloadArtifact(projectId, version, artifact, versionDir);
const localPath = await remote.downloadArtifact(projectId, version, artifact, versionDir, project.group || group);
verifyChecksum(localPath, artifact.checksum || (versionMeta.checksums && versionMeta.checksums[artifact.path]));
unpackArtifact(artifact, localPath, versionDir);
}
+60 -22
View File
@@ -7,6 +7,21 @@ function now() {
return new Date().toISOString();
}
function normalizeRemotePath(value) {
if (!value || value === "/") {
return "/";
}
return path.posix.normalize("/" + String(value).replace(/\\/g, "/")).replace(/\/+$/g, "") || "/";
}
function normalizeGroup(group) {
return normalizeRemotePath(group == null ? "/modules" : group);
}
function joinRemote(...items) {
return normalizeRemotePath(items.filter((item) => item !== undefined && item !== null && item !== "").join("/"));
}
function normalizeOs(value) {
const name = String(value || "any").toLowerCase();
if (["windows_nt", "win32", "windows"].includes(name)) {
@@ -70,16 +85,6 @@ function archiveFormat(filePath) {
return undefined;
}
function buildVersion(localMeta) {
if (localMeta.release_version) {
return localMeta.release_version;
}
if (localMeta.version) {
return localMeta.version;
}
return new Date().toISOString().replace(/[-:TZ.]/g, "").slice(0, 14);
}
function readLocalMeta(localFile) {
const base = localFile ? path.resolve(localFile) : process.cwd();
const metaFile = fs.statSync(base).isDirectory() ? path.resolve(base, "meta.json") : path.resolve(process.cwd(), "meta.json");
@@ -93,7 +98,8 @@ function readLocalMeta(localFile) {
return {
id: path.basename(localFile, path.extname(localFile)).toLowerCase(),
version: buildVersion({}),
group: "/modules",
version: "",
desc: "",
dist: [{ path: localFile, os: "any", platform: "any", filename: path.basename(localFile) }]
};
@@ -123,6 +129,34 @@ function buildArtifacts(localMeta) {
});
}
function bumpTailVersion(version) {
const text = String(version || "").trim();
if (!text) {
return "1.0";
}
const match = text.match(/^(.*?)(\d+)$/);
if (!match) {
return `${text}.1`;
}
return `${match[1]}${Number(match[2]) + 1}`;
}
async function resolveVersion(remote, projectRoot, localMeta) {
const explicitVersion = String(localMeta.version || "").trim();
if (explicitVersion) {
if (await remote.client.exists(joinRemote(projectRoot, explicitVersion))) {
throw new Error(`远程版本已存在 ${projectRoot}@${explicitVersion}`);
}
return explicitVersion;
}
const metadata = await remote.getJson(joinRemote(projectRoot, "metadata.json"), undefined);
if (metadata && metadata.version) {
return bumpTailVersion(metadata.version);
}
return "1.0";
}
module.exports = async function publish(remote, localFile, remotePath) {
if (remotePath) {
const target = path.posix.resolve("/", remotePath.replace(/\\/g, "/"));
@@ -137,9 +171,11 @@ module.exports = async function publish(remote, localFile, remotePath) {
throw new Error("meta.json 缺少 project_id/id/name");
}
const version = buildVersion(localMeta);
const group = normalizeGroup(localMeta.group);
const projectRoot = remote.getRemoteProjectRoot ? remote.getRemoteProjectRoot(projectId, group) : joinRemote(group, projectId);
const version = await resolveVersion(remote, projectRoot, localMeta);
const artifacts = buildArtifacts(localMeta);
await remote.ensureDir(`/${projectId}/${version}`);
await remote.ensureDir(joinRemote(projectRoot, version));
const versionMeta = {
version,
@@ -150,27 +186,29 @@ module.exports = async function publish(remote, localFile, remotePath) {
};
for (const artifact of artifacts) {
await remote.wUpload(artifact.localPath, `/${projectId}/${version}/${artifact.path}`);
await remote.wUpload(artifact.localPath, joinRemote(projectRoot, version, artifact.path));
}
await remote.putJson(`/${projectId}/${version}/version.json`, versionMeta);
await remote.putJson(joinRemote(projectRoot, version, "version.json"), versionMeta);
const metadata = await remote.getJson(`/${projectId}/metadata.json`, {
const metadata = await remote.getJson(joinRemote(projectRoot, "metadata.json"), {
project_id: projectId,
group,
official_url: localMeta.official_url || localMeta.vcs || "",
description: localMeta.description || localMeta.desc || "",
latest_version: version,
versions: [],
version,
fetch_rules: localMeta.fetch_rules || [],
extra: {}
});
metadata.project_id = projectId;
metadata.group = group;
metadata.official_url = metadata.official_url || localMeta.official_url || localMeta.vcs || "";
metadata.description = localMeta.description || localMeta.desc || metadata.description || "";
metadata.latest_version = version;
metadata.versions = Array.from(new Set([...(metadata.versions || []), version]));
metadata.version = version;
delete metadata.versions;
delete metadata.latest_version;
metadata.extra = Object.assign({}, metadata.extra || {}, localMeta.extra || {});
await remote.putJson(`/${projectId}/metadata.json`, metadata);
$logger.info(`文件上传成功 ${projectId}@${version}`);
await remote.putJson(joinRemote(projectRoot, "metadata.json"), metadata);
$logger.info(`文件上传成功 ${group}/${projectId}@${version}`);
};