feat(runtimes): 新增推荐运行时清单与离线兜底版本清单 (#44)

- runtimes/recommended.json:Python 3.13.9 (hatch 1.16.5 pin) + Node.js 24.18.0 LTS,
  含 6 平台归档路径与 SHA-256,供主仓库构建时打包离线运行时与首启导入
- runtimes/versions-fallback.json:Node/Python/包管理器版本快照,
  客户端联网查询失败时的兜底列表
- schemas + validate.mjs 映射 + 测试(与主仓库 lib/schemas/agent-service/runtime-manifest.ts 同源导出)
- 不递增 presetDataVersion:runtimes/ 由客户端直接读取,不参与 compute.json 合并
This commit is contained in:
2026-07-07 16:04:14 +08:00
committed by GitHub
parent 847f4bd139
commit 255e5a5b1b
6 changed files with 1972 additions and 0 deletions

View File

@@ -91,6 +91,44 @@ describe('真实数据全量校验', () => {
assert.equal(r1.ok, true, JSON.stringify(r1.errors, null, 2)) assert.equal(r1.ok, true, JSON.stringify(r1.errors, null, 2))
assert.equal(r2.ok, true, JSON.stringify(r2.errors, null, 2)) assert.equal(r2.ok, true, JSON.stringify(r2.errors, null, 2))
}) })
it('runtimes/recommended.json 应通过 runtime-recommended schema', () => {
const result = validateFile(join(ROOT, 'runtimes', 'recommended.json'), validators)
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
})
it('runtimes/versions-fallback.json 应通过 runtime-versions-fallback schema', () => {
const result = validateFile(join(ROOT, 'runtimes', 'versions-fallback.json'), validators)
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
})
})
// ==================== Runtime 清单反例 ====================
describe('runtime-recommended schema 反例', () => {
const validate = compile('runtime-recommended')
function makeValidManifest() {
return JSON.parse(readFileSync(join(ROOT, 'runtimes', 'recommended.json'), 'utf8'))
}
it('拒绝缺少平台归档archives 六平台必填)', () => {
const data = makeValidManifest()
delete data.node.archives['win32-arm64']
assert.equal(validate(data), false)
})
it('拒绝非法 sha256长度/字符集不符)', () => {
const data = makeValidManifest()
data.python.sha256['darwin-arm64'] = 'not-a-sha'
assert.equal(validate(data), false)
})
it('拒绝未知字段additionalProperties: false 保护老客户端)', () => {
const data = makeValidManifest()
data.unknownField = true
assert.equal(validate(data), false)
})
}) })
// ==================== Provider schema 反例 ==================== // ==================== Provider schema 反例 ====================

44
runtimes/recommended.json Normal file
View File

@@ -0,0 +1,44 @@
{
"python": {
"name": "3.13",
"version": "3.13.9",
"distTag": "20251014",
"archives": {
"darwin-arm64": "20251014/cpython-3.13.9+20251014-aarch64-apple-darwin-install_only_stripped.tar.gz",
"darwin-x64": "20251014/cpython-3.13.9+20251014-x86_64-apple-darwin-install_only_stripped.tar.gz",
"win32-x64": "20251014/cpython-3.13.9+20251014-x86_64-pc-windows-msvc-install_only_stripped.tar.gz",
"win32-arm64": "20251014/cpython-3.13.9+20251014-aarch64-pc-windows-msvc-install_only_stripped.tar.gz",
"linux-x64": "20251014/cpython-3.13.9+20251014-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"linux-arm64": "20251014/cpython-3.13.9+20251014-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
},
"sha256": {
"darwin-arm64": "52721745b0fa3196e4d0381fa5c06dda1d54343b90d49d90c3bba52d1171bd98",
"darwin-x64": "7c33b153a69c6255e6f2659cf39738f316b03969d6230d7bc47c73b7fde9a0d4",
"win32-x64": "76e0a9749c4deeb975a4b6b36d54be4e43f0c2a4c654bedab5d2e4d62dbc3006",
"win32-arm64": "419cce7a099d21389fca32a9fd4f7364fc61b91a760369c803de57c6ee1ddb99",
"linux-x64": "c0ccda275948c79996e46993c2c5476ff5cca606dee530f1dea712179131b348",
"linux-arm64": "baa3d107d17e4328448e30c3c9c83cca0eb41ca7a37c10982e14d46a5c3db07d"
}
},
"node": {
"version": "24.18.0",
"ltsCodename": "Krypton",
"archives": {
"darwin-arm64": "v24.18.0/node-v24.18.0-darwin-arm64.tar.gz",
"darwin-x64": "v24.18.0/node-v24.18.0-darwin-x64.tar.gz",
"win32-x64": "v24.18.0/node-v24.18.0-win-x64.zip",
"win32-arm64": "v24.18.0/node-v24.18.0-win-arm64.zip",
"linux-x64": "v24.18.0/node-v24.18.0-linux-x64.tar.gz",
"linux-arm64": "v24.18.0/node-v24.18.0-linux-arm64.tar.gz"
},
"sha256": {
"darwin-arm64": "e1a97e14c99c803e96c7339403282ea05a499c32f8d83defe9ef5ec66f979ed1",
"darwin-x64": "dfd0dbd3e721503434df7b7205e719f61b3a3a31b2bcf9729b8b91fea240f080",
"win32-x64": "0ae68406b42d7725661da979b1403ec9926da205c6770827f33aac9d8f26e821",
"win32-arm64": "f274669adb93b1fd0fbf8f21fd078609e9dcc84333d4f2718d2dde3f9a161a01",
"linux-x64": "783130984963db7ba9cbd01089eaf2c2efb055c7c1693c943174b967b3050cb8",
"linux-arm64": "6b4484c2190274175df9aa8f28e2d758a819cb1c1fe6ab481e2f95b463ab8508"
}
},
"minClientVersion": "10.0.82"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"description": "推荐运行时版本清单:决定构建时打进安装包、首启离线导入的 Python / Node.js 版本。由 config-center 仓库远端维护30 分钟 git fetch 热更新),主仓库 defaults 内置保底种子",
"required": [
"python",
"node",
"minClientVersion"
],
"properties": {
"python": {
"type": "object",
"description": "Python 推荐版本python-build-standalone 发行版,经内置 Hatch 安装)。归档必须与内置 Hatch 版本 pin 的发布完全一致tag + 文件名 + 变体),否则首启离线导入后 hatch 的更新判断会出现偏差",
"required": [
"name",
"version",
"distTag",
"archives"
],
"properties": {
"name": {
"type": "string",
"description": "Hatch distribution 名(如 \"3.13\"`hatch python install <name>` 的实参,也是安装目录 hatch/local/<name>/ 的目录名,幂等判断依据"
},
"version": {
"type": "string",
"description": "精确 Python 版本(如 \"3.13.9\"),仅用于展示与清单更新判断"
},
"distTag": {
"type": "string",
"description": "python-build-standalone 的 release tag如 \"20251014\""
},
"archives": {
"type": "object",
"description": "各平台归档的相对路径(相对下载源根目录)。键为 `<platform>-<arch>`Node.js process.platform/arch 语义),值为源站 URL 尾部路径,同时也是 static/runtimes/<platform>-<arch>/ 下的落盘相对路径",
"required": [
"darwin-arm64",
"darwin-x64",
"win32-x64",
"win32-arm64",
"linux-x64",
"linux-arm64"
],
"properties": {
"darwin-arm64": {
"type": "string",
"description": "macOS Apple Silicon 的归档相对路径"
},
"darwin-x64": {
"type": "string",
"description": "macOS Intel 的归档相对路径"
},
"win32-x64": {
"type": "string",
"description": "Windows x64 的归档相对路径"
},
"win32-arm64": {
"type": "string",
"description": "Windows ARM64 的归档相对路径"
},
"linux-x64": {
"type": "string",
"description": "Linux x64 的归档相对路径"
},
"linux-arm64": {
"type": "string",
"description": "Linux ARM64 的归档相对路径"
}
},
"additionalProperties": false
},
"sha256": {
"type": "object",
"description": "各平台归档的 SHA-256 校验值(十六进制小写)。构建脚本下载后校验,防止归档被镜像/代理篡改或截断",
"properties": {
"darwin-arm64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "macOS Apple Silicon 归档的 SHA-256"
},
"darwin-x64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "macOS Intel 归档的 SHA-256"
},
"win32-x64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "Windows x64 归档的 SHA-256"
},
"win32-arm64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "Windows ARM64 归档的 SHA-256"
},
"linux-x64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "Linux x64 归档的 SHA-256"
},
"linux-arm64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "Linux ARM64 归档的 SHA-256"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"node": {
"type": "object",
"description": "Node.js 推荐版本(官方 dist 归档,经内置 Volta 安装)",
"required": [
"version",
"archives"
],
"properties": {
"version": {
"type": "string",
"description": "精确 Node.js 版本(如 \"24.18.0\",不带 v 前缀):`volta fetch node@<version>` 的实参。必须是精确三段版本——Volta 对 Exact 版本的解析完全离线,不访问 index.json"
},
"ltsCodename": {
"type": "string",
"description": "LTS 代号(如 \"Krypton\"),仅用于展示"
},
"archives": {
"type": "object",
"description": "各平台归档的相对路径(相对下载源根目录)。键为 `<platform>-<arch>`Node.js process.platform/arch 语义),值为源站 URL 尾部路径,同时也是 static/runtimes/<platform>-<arch>/ 下的落盘相对路径",
"required": [
"darwin-arm64",
"darwin-x64",
"win32-x64",
"win32-arm64",
"linux-x64",
"linux-arm64"
],
"properties": {
"darwin-arm64": {
"type": "string",
"description": "macOS Apple Silicon 的归档相对路径"
},
"darwin-x64": {
"type": "string",
"description": "macOS Intel 的归档相对路径"
},
"win32-x64": {
"type": "string",
"description": "Windows x64 的归档相对路径"
},
"win32-arm64": {
"type": "string",
"description": "Windows ARM64 的归档相对路径"
},
"linux-x64": {
"type": "string",
"description": "Linux x64 的归档相对路径"
},
"linux-arm64": {
"type": "string",
"description": "Linux ARM64 的归档相对路径"
}
},
"additionalProperties": false
},
"sha256": {
"type": "object",
"description": "各平台归档的 SHA-256 校验值(十六进制小写)。构建脚本下载后校验,防止归档被镜像/代理篡改或截断",
"properties": {
"darwin-arm64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "macOS Apple Silicon 归档的 SHA-256"
},
"darwin-x64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "macOS Intel 归档的 SHA-256"
},
"win32-x64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "Windows x64 归档的 SHA-256"
},
"win32-arm64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "Windows ARM64 归档的 SHA-256"
},
"linux-x64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "Linux x64 归档的 SHA-256"
},
"linux-arm64": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "Linux ARM64 归档的 SHA-256"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"minClientVersion": {
"type": "string",
"description": "使用此清单所需的最低客户端版本package.json#version 语义)。远端热更新的清单若要求高于当前客户端版本,则忽略远端、继续使用内置种子——防止老客户端的内置 Hatch/Volta 不认识新版本格式"
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,132 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"description": "离线兜底版本清单:联网查询可安装版本列表失败时的替代数据,消除「空列表 + 无限重试」。各字段条目形状与对应实时接口的最终返回值完全同构,前端无需区分解析",
"required": [
"generatedAt",
"node",
"python",
"pkgManagers"
],
"properties": {
"generatedAt": {
"type": "string",
"description": "快照生成时间ISO 8601用于展示「离线列表可能不是最新」的提示"
},
"node": {
"type": "array",
"description": "Node.js 版本条目快照nodejs.org/dist/index.json 元素子集,含 version/date/lts/npm 等字段)",
"items": {
"type": "object",
"required": [
"version"
],
"properties": {
"version": {
"type": "string",
"description": "版本号(带 v 前缀,如 \"v24.18.0\""
}
},
"additionalProperties": true
}
},
"python": {
"type": "array",
"description": "Python 发行版条目快照(与 `hatch python show` 解析结果同构)",
"items": {
"type": "object",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"description": "Hatch distribution 名(如 \"3.13\"、\"pypy3.11\""
},
"version": {
"type": "string",
"description": "精确版本号(如 \"3.13.9\""
}
},
"additionalProperties": false
}
},
"pkgManagers": {
"type": "object",
"description": "包管理器 release 条目快照(与 listAvailablePkgManagers 最终返回形状一致已过滤预发布、yarn 已归一化 tag_name",
"required": [
"npm",
"pnpm",
"yarn"
],
"properties": {
"npm": {
"type": "array",
"description": "npm 的正式版本条目",
"items": {
"type": "object",
"required": [
"tag_name"
],
"properties": {
"tag_name": {
"type": "string",
"description": "版本 tag如 \"v11.18.0\""
},
"created_at": {
"type": "string",
"description": "发布时间ISO 8601"
}
},
"additionalProperties": true
}
},
"pnpm": {
"type": "array",
"description": "pnpm 的正式版本条目",
"items": {
"type": "object",
"required": [
"tag_name"
],
"properties": {
"tag_name": {
"type": "string",
"description": "版本 tag如 \"v10.12.1\""
},
"created_at": {
"type": "string",
"description": "发布时间ISO 8601"
}
},
"additionalProperties": true
}
},
"yarn": {
"type": "array",
"description": "yarn (berry) 的正式版本条目",
"items": {
"type": "object",
"required": [
"tag_name"
],
"properties": {
"tag_name": {
"type": "string",
"description": "版本 tag如 \"v4.17.0\",已从 name 字段归一化)"
},
"created_at": {
"type": "string",
"description": "发布时间ISO 8601"
}
},
"additionalProperties": true
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}

View File

@@ -47,6 +47,8 @@ function pickSchemaKey(absPath) {
if (rel === 'compute/pricing.json') return 'pricing' if (rel === 'compute/pricing.json') return 'pricing'
if (rel === 'compute/service-map.json') return 'service-map' if (rel === 'compute/service-map.json') return 'service-map'
if (rel === 'compute/providers/_index.json') return 'providers-index' if (rel === 'compute/providers/_index.json') return 'providers-index'
if (rel === 'runtimes/recommended.json') return 'runtime-recommended'
if (rel === 'runtimes/versions-fallback.json') return 'runtime-versions-fallback'
if (rel === 'compute/coding-plans/_index.json') return 'providers-index' if (rel === 'compute/coding-plans/_index.json') return 'providers-index'
if (rel === 'compute/model-specs/_index.json') return 'providers-index' if (rel === 'compute/model-specs/_index.json') return 'providers-index'
if (rel.startsWith('compute/providers/') && rel.endsWith('.json')) return 'provider' if (rel.startsWith('compute/providers/') && rel.endsWith('.json')) return 'provider'