Download archify/scripts/update-contract.mjs from smlflg/archify-hai: direct link, hf CLI and curl.
- Browser
- Download file 6.75 kB
-
https://huggingface.co/smlflg/archify-hai/resolve/main/archify/scripts/update-contract.mjs
- Command line
-
hf download hf://smlflg/archify-hai/archify/scripts/update-contract.mjs
-
curl -L -o update-contract.mjs https://huggingface.co/smlflg/archify-hai/resolve/main/archify/scripts/update-contract.mjs
6.75 kB
| export const SKILL_ID = 'archify'; | |
| export const EXPECTED_REPOSITORY = 'https://github.com/tt-a1i/archify'; | |
| export const DEFAULT_MANIFEST_URL = 'https://tt-a1i.github.io/archify/skill-updates/archify/stable.json'; | |
| const CONTROL_OR_BIDI = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u; | |
| const HEX_40 = /^[a-f0-9]{40}$/; | |
| const HEX_64 = /^[a-f0-9]{64}$/; | |
| const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/; | |
| const UTC_SECONDS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/; | |
| export class UpdateContractError extends Error { | |
| constructor(message) { | |
| super(message); | |
| this.name = 'UpdateContractError'; | |
| } | |
| } | |
| function isPlainObject(value) { | |
| return Boolean(value) && typeof value === 'object' && !Array.isArray(value); | |
| } | |
| function hasExactKeys(value, expected) { | |
| return isPlainObject(value) | |
| && Object.keys(value).sort().join('\0') === [...expected].sort().join('\0'); | |
| } | |
| export function parseSemver(value) { | |
| if (typeof value !== 'string' || value.length > 128) { | |
| throw new UpdateContractError(`invalid SemVer: ${JSON.stringify(value)}`); | |
| } | |
| const match = SEMVER.exec(value); | |
| if (!match) throw new UpdateContractError(`invalid SemVer: ${JSON.stringify(value)}`); | |
| const prerelease = match[4]?.split('.') ?? null; | |
| if (prerelease?.some((identifier) => /^\d+$/.test(identifier) | |
| && identifier.length > 1 && identifier.startsWith('0'))) { | |
| throw new UpdateContractError(`invalid SemVer: ${JSON.stringify(value)}`); | |
| } | |
| return { | |
| core: match.slice(1, 4), | |
| prerelease, | |
| build: match[5]?.split('.') ?? null, | |
| }; | |
| } | |
| function compareNumericIdentifiers(left, right) { | |
| if (left.length !== right.length) return left.length < right.length ? -1 : 1; | |
| if (left === right) return 0; | |
| return left < right ? -1 : 1; | |
| } | |
| function comparePrerelease(left, right) { | |
| if (left === null && right === null) return 0; | |
| if (left === null) return 1; | |
| if (right === null) return -1; | |
| const length = Math.max(left.length, right.length); | |
| for (let index = 0; index < length; index += 1) { | |
| if (left[index] === undefined) return -1; | |
| if (right[index] === undefined) return 1; | |
| if (left[index] === right[index]) continue; | |
| const leftNumeric = /^\d+$/.test(left[index]); | |
| const rightNumeric = /^\d+$/.test(right[index]); | |
| if (leftNumeric && rightNumeric) return compareNumericIdentifiers(left[index], right[index]); | |
| if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1; | |
| return left[index] < right[index] ? -1 : 1; | |
| } | |
| return 0; | |
| } | |
| export function compareSemver(leftValue, rightValue) { | |
| const left = parseSemver(leftValue); | |
| const right = parseSemver(rightValue); | |
| for (let index = 0; index < left.core.length; index += 1) { | |
| const comparison = compareNumericIdentifiers(left.core[index], right.core[index]); | |
| if (comparison !== 0) return comparison; | |
| } | |
| return comparePrerelease(left.prerelease, right.prerelease); | |
| } | |
| export function releaseChannelForVersion(value) { | |
| return parseSemver(value).prerelease ? 'development' : 'stable'; | |
| } | |
| export function isStableCoreVersion(value) { | |
| try { | |
| const parsed = parseSemver(value); | |
| return parsed.prerelease === null && parsed.build === null; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| export function validateCanonicalUtcTimestamp(value) { | |
| if (typeof value !== 'string' || !UTC_SECONDS.test(value)) { | |
| throw new UpdateContractError('publication time must use YYYY-MM-DDTHH:mm:ssZ'); | |
| } | |
| const timestamp = Date.parse(value); | |
| if (!Number.isFinite(timestamp) | |
| || new Date(timestamp).toISOString().replace('.000Z', 'Z') !== value) { | |
| throw new UpdateContractError('publication time is not a real UTC calendar instant'); | |
| } | |
| return value; | |
| } | |
| export function validateLocalRelease(value) { | |
| if (!hasExactKeys(value, [ | |
| 'schemaVersion', 'skillId', 'channel', 'version', 'source', 'updateManifestUrl', | |
| ]) | |
| || value.schemaVersion !== 1 | |
| || value.skillId !== SKILL_ID | |
| || !hasExactKeys(value.source, ['repository']) | |
| || value.source.repository !== EXPECTED_REPOSITORY | |
| || value.updateManifestUrl !== DEFAULT_MANIFEST_URL) { | |
| throw new UpdateContractError('invalid local release identity'); | |
| } | |
| const expectedChannel = releaseChannelForVersion(value.version); | |
| if (value.channel !== expectedChannel) { | |
| throw new UpdateContractError('local release channel does not match its version'); | |
| } | |
| return { | |
| schemaVersion: value.schemaVersion, | |
| skillId: value.skillId, | |
| channel: value.channel, | |
| version: value.version, | |
| source: { repository: value.source.repository }, | |
| updateManifestUrl: value.updateManifestUrl, | |
| }; | |
| } | |
| export function validateReleaseNotesUrl(value, version) { | |
| if (!isStableCoreVersion(version)) { | |
| throw new UpdateContractError('release notes require a stable core version'); | |
| } | |
| const expected = `https://github.com/tt-a1i/archify/releases/tag/v${version}`; | |
| if (value !== expected) { | |
| throw new UpdateContractError('release notes URL is outside the exact trusted release path'); | |
| } | |
| return value; | |
| } | |
| export function validateStableUpdateManifest(value) { | |
| if (!hasExactKeys(value, [ | |
| 'schemaVersion', 'skillId', 'channel', 'version', 'publishedAt', 'source', | |
| 'artifact', 'summary', 'releaseNotes', 'severity', | |
| ]) | |
| || value.schemaVersion !== 1 | |
| || value.skillId !== SKILL_ID | |
| || value.channel !== 'stable' | |
| || !isStableCoreVersion(value.version) | |
| || !hasExactKeys(value.source, ['repository', 'ref', 'treeSha']) | |
| || value.source.repository !== EXPECTED_REPOSITORY | |
| || value.source.ref !== `v${value.version}` | |
| || !HEX_40.test(value.source.treeSha) | |
| || !hasExactKeys(value.artifact, ['sha256']) | |
| || !HEX_64.test(value.artifact.sha256)) { | |
| throw new UpdateContractError('invalid immutable stable release identity'); | |
| } | |
| validateCanonicalUtcTimestamp(value.publishedAt); | |
| if (typeof value.summary !== 'string' || value.summary.length < 1 || value.summary.length > 160 | |
| || CONTROL_OR_BIDI.test(value.summary)) { | |
| throw new UpdateContractError('invalid release summary'); | |
| } | |
| validateReleaseNotesUrl(value.releaseNotes, value.version); | |
| if (!['normal', 'security'].includes(value.severity)) { | |
| throw new UpdateContractError('invalid update severity'); | |
| } | |
| return { | |
| schemaVersion: value.schemaVersion, | |
| skillId: value.skillId, | |
| channel: value.channel, | |
| version: value.version, | |
| publishedAt: value.publishedAt, | |
| source: { | |
| repository: value.source.repository, | |
| ref: value.source.ref, | |
| treeSha: value.source.treeSha, | |
| }, | |
| artifact: { sha256: value.artifact.sha256 }, | |
| summary: value.summary, | |
| releaseNotes: value.releaseNotes, | |
| severity: value.severity, | |
| }; | |
| } | |