mirror of
https://git.openapi.site/https://github.com/desirecore/registry.git
synced 2026-10-21 02:13:28 +08:00
fix: 保持 Registry 客户端 Schema 信任摘要兼容 (#9)
This commit is contained in:
18
scripts/catalog/static-metadata.mjs
Normal file
18
scripts/catalog/static-metadata.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
/** Publication-only policy. Keep the byte-pinned authored client schema unchanged. */
|
||||
const dynamicFields = new Set([
|
||||
'stars', 'views', 'viewCount', 'visits', 'visitCount', 'downloads', 'downloadCount',
|
||||
'rating', 'ratings', 'ratingCount', 'averageRating', 'popularity', 'usageCount',
|
||||
'installCount', 'installedCount', 'activeUsers', 'installStatus', 'installedVersion',
|
||||
'healthScore', 'resourceUsage', 'lastHealthCheck',
|
||||
])
|
||||
export function findStaticMetrics(value, prefix = '') {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return []
|
||||
const findings = []
|
||||
for (const [key, child] of Object.entries(value)) {
|
||||
if (dynamicFields.has(key)) findings.push(prefix + key)
|
||||
if (['metrics', 'statistics', 'stats', 'metadata'].includes(key)) {
|
||||
findings.push(...findStaticMetrics(child, prefix + key + '.'))
|
||||
}
|
||||
}
|
||||
return findings
|
||||
}
|
||||
17
scripts/catalog/static-metadata.test.mjs
Normal file
17
scripts/catalog/static-metadata.test.mjs
Normal file
@@ -0,0 +1,17 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { findStaticMetrics } from './static-metadata.mjs'
|
||||
|
||||
test('metrics are rejected without interpreting examples or configuration parameters as analytics', () => {
|
||||
assert.deepEqual(findStaticMetrics({ stars: 99, metrics: { downloads: 3 }, installStatus: 'installed' }), ['stars', 'metrics.downloads', 'installStatus'])
|
||||
assert.deepEqual(findStaticMetrics({ description: 'downloads files', toolCount: 13, configSchema: { properties: { rating: { type: 'number' } } } }), [])
|
||||
})
|
||||
|
||||
test('Registry 4.1 keeps the exact published client schema identity', () => {
|
||||
const version = readFileSync(new URL('../../SCHEMA_VERSION', import.meta.url), 'utf8').trim()
|
||||
assert.equal(version, '4.1.0')
|
||||
const bytes = readFileSync(new URL('../../schemas/registry-entry.schema.json', import.meta.url))
|
||||
assert.equal(createHash('sha256').update(bytes).digest('hex'), '767e103a66b4f8f722d1eb4d9073c218536e8f551ef43ac766dcd1be8f88fc62')
|
||||
})
|
||||
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
||||
import { isDeepStrictEqual } from 'node:util'
|
||||
import { basename, join, relative, resolve, sep } from 'node:path'
|
||||
import { validateJsonSchema } from './json-schema.mjs'
|
||||
import { findStaticMetrics } from './static-metadata.mjs'
|
||||
|
||||
export const CATALOG_SIDECAR_FILENAME = 'catalog-metadata.v1.json'
|
||||
|
||||
@@ -222,6 +223,9 @@ export function validateRegistry(repoRoot, options = {}) {
|
||||
}
|
||||
const entry = readJson(manifestPath, diagnostics)
|
||||
if (!entry) continue
|
||||
for (const field of findStaticMetrics(entry)) {
|
||||
diagnostics.push(diagnostic('error', 'static-metric', relativeFile(root, manifestPath), '$.' + field, 'Dynamic metrics must not be published as static data'))
|
||||
}
|
||||
counts.totalEntries += 1
|
||||
if (entry.type === 'docker-app') counts.dockerApps += 1
|
||||
if (entry.type === 'native-app') counts.nativeApps = (counts.nativeApps ?? 0) + 1
|
||||
|
||||
@@ -2,6 +2,7 @@ import { lstat, readFile, readdir } from 'node:fs/promises'
|
||||
import { dirname, join, relative, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import Ajv from 'ajv'
|
||||
import { findStaticMetrics } from './catalog/static-metadata.mjs'
|
||||
|
||||
const scriptDirectory = dirname(fileURLToPath(import.meta.url))
|
||||
const rootArgumentIndex = process.argv.indexOf('--root')
|
||||
@@ -322,6 +323,9 @@ for (const directoryEntry of await readdir(entriesRoot, { withFileTypes: true })
|
||||
}
|
||||
const manifest = await readJson(manifestPath)
|
||||
if (!manifest) continue
|
||||
for (const field of findStaticMetrics(manifest)) {
|
||||
addError(`entries/${directoryEntry.name}/manifest.json: dynamic field ${field} cannot be published as static data`)
|
||||
}
|
||||
manifests.push(manifest)
|
||||
|
||||
if (manifest.id !== directoryEntry.name) {
|
||||
|
||||
Reference in New Issue
Block a user