feat(hololake): prepare signed 1.2.0 update artifacts
This commit is contained in:
parent
970523ab44
commit
d05a118f64
9 changed files with 118 additions and 10 deletions
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "hololake-clean-desktop",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hololake-clean-desktop",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@tauri-apps/plugin-dialog": "2.7.2",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"name": "hololake-clean-desktop",
|
||||
"private": true,
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 127.0.0.1",
|
||||
"build": "tsc -b && vite build",
|
||||
"test": "npm run build",
|
||||
"test": "npm run build && node --test scripts/*.test.mjs",
|
||||
"release:manifest": "node scripts/build-update-manifest.mjs",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
"kind": "SYSTEM_FOUNDATION",
|
||||
"audience": "SYSTEM",
|
||||
"summary_zh": "只接收通过光湖信任根验证的软件更新。",
|
||||
"state": "CLIENT_TRUST_ROOT_BOUND_SERVER_HTTP_204_NO_PUBLIC_RELEASE",
|
||||
"state": "CLIENT_TRUST_ROOT_BOUND_MANIFEST_BUILDER_VERIFIED_SERVER_HTTP_204_NO_PUBLIC_RELEASE",
|
||||
"source": "src-tauri/tauri.conf.json#plugins.updater"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env node
|
||||
import crypto from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
function required(value, name) {
|
||||
if (!value) throw new Error(`${name}_REQUIRED`)
|
||||
return value
|
||||
}
|
||||
|
||||
export function buildManifest({ version, artifact, signatureFile, baseUrl, notes, publishedAt }) {
|
||||
if (!/^\d+\.\d+\.\d+$/.test(required(version, 'VERSION'))) throw new Error('VERSION_INVALID')
|
||||
const artifactPath = path.resolve(required(artifact, 'ARTIFACT'))
|
||||
const signaturePath = path.resolve(required(signatureFile, 'SIGNATURE'))
|
||||
if (!fs.statSync(artifactPath, { throwIfNoEntry: false })?.isFile()) throw new Error('ARTIFACT_NOT_READABLE')
|
||||
if (!fs.statSync(signaturePath, { throwIfNoEntry: false })?.isFile()) throw new Error('SIGNATURE_NOT_READABLE')
|
||||
const signature = fs.readFileSync(signaturePath, 'utf8').trim()
|
||||
if (signature.length < 32) throw new Error('SIGNATURE_INVALID')
|
||||
const root = new URL(required(baseUrl, 'BASE_URL'))
|
||||
if (root.protocol !== 'https:') throw new Error('HTTPS_BASE_URL_REQUIRED')
|
||||
if (root.username || root.password || root.search || root.hash) throw new Error('BASE_URL_MUST_NOT_CONTAIN_CREDENTIALS_OR_QUERY')
|
||||
const bytes = fs.readFileSync(artifactPath)
|
||||
const fileName = path.basename(artifactPath)
|
||||
const url = new URL(fileName, root.href.endsWith('/') ? root : new URL(`${root.href}/`)).href
|
||||
return {
|
||||
version,
|
||||
notes: notes || 'HoloLake signed update',
|
||||
pub_date: publishedAt || new Date().toISOString(),
|
||||
platforms: {
|
||||
'darwin-aarch64': {
|
||||
signature,
|
||||
url,
|
||||
sha256: crypto.createHash('sha256').update(bytes).digest('hex'),
|
||||
bytes: bytes.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(values) {
|
||||
const result = {}
|
||||
for (let index = 0; index < values.length; index += 2) {
|
||||
if (!values[index]?.startsWith('--') || values[index + 1] === undefined) throw new Error('ARGUMENTS_INVALID')
|
||||
result[values[index].slice(2)] = values[index + 1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const manifest = buildManifest({
|
||||
version: args.version,
|
||||
artifact: args.artifact,
|
||||
signatureFile: args.signature,
|
||||
baseUrl: args['base-url'],
|
||||
notes: args.notes,
|
||||
publishedAt: args['published-at'],
|
||||
})
|
||||
const output = path.resolve(required(args.output, 'OUTPUT'))
|
||||
fs.mkdirSync(path.dirname(output), { recursive: true })
|
||||
const temporary = `${output}.tmp`
|
||||
fs.writeFileSync(temporary, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o644 })
|
||||
fs.renameSync(temporary, output)
|
||||
process.stdout.write(`${JSON.stringify({ outcome: 'MANIFEST_WRITTEN', output, version: manifest.version, platform: 'darwin-aarch64' })}\n`)
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
import { buildManifest } from './build-update-manifest.mjs'
|
||||
|
||||
function fixture() {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-update-manifest-'))
|
||||
const artifact = path.join(root, 'HoloLake.app.tar.gz')
|
||||
const signatureFile = `${artifact}.sig`
|
||||
fs.writeFileSync(artifact, 'signed-update-bytes')
|
||||
fs.writeFileSync(signatureFile, 'trusted-signature-material-that-is-long-enough')
|
||||
return { artifact, signatureFile }
|
||||
}
|
||||
|
||||
test('builds an HTTPS arm64 manifest with immutable evidence', () => {
|
||||
const files = fixture()
|
||||
const manifest = buildManifest({
|
||||
version: '1.2.0', ...files,
|
||||
baseUrl: 'https://guanghulab.com/hololake/releases/1.2.0/',
|
||||
notes: 'Current direct-language baseline',
|
||||
publishedAt: '2026-09-03T00:00:00Z',
|
||||
})
|
||||
assert.equal(manifest.version, '1.2.0')
|
||||
assert.equal(manifest.platforms['darwin-aarch64'].bytes, 19)
|
||||
assert.match(manifest.platforms['darwin-aarch64'].url, /^https:\/\/guanghulab\.com\//)
|
||||
assert.equal(manifest.platforms['darwin-aarch64'].sha256.length, 64)
|
||||
})
|
||||
|
||||
test('rejects HTTP, missing signatures and malformed versions', () => {
|
||||
const files = fixture()
|
||||
assert.throws(() => buildManifest({ version: '1.2', ...files, baseUrl: 'https://guanghulab.com/' }), /VERSION_INVALID/)
|
||||
assert.throws(() => buildManifest({ version: '1.2.0', ...files, baseUrl: 'http://guanghulab.com/' }), /HTTPS_BASE_URL_REQUIRED/)
|
||||
fs.writeFileSync(files.signatureFile, 'short')
|
||||
assert.throws(() => buildManifest({ version: '1.2.0', ...files, baseUrl: 'https://guanghulab.com/' }), /SIGNATURE_INVALID/)
|
||||
})
|
||||
|
|
@ -1328,7 +1328,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||
|
||||
[[package]]
|
||||
name = "hololake-clean-desktop"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "hololake-clean-desktop"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
description = "HoloLake clean personal language operating system shell"
|
||||
authors = ["HoloLake"]
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "HoloLake",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"identifier": "world.guanghu.hololake",
|
||||
"build": { "frontendDist": "../dist", "devUrl": "http://127.0.0.1:5211", "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build" },
|
||||
"app": {
|
||||
|
|
|
|||
|
|
@ -14,13 +14,15 @@
|
|||
"first_preinstalled_module": "HLP-MOD-KB-0001",
|
||||
"knowledge_module_state": "LOCAL_COMPLETE_HUMAN_VISUAL_RENDERING_WITH_CLASSIFICATION_COLORS_READING_TIME_SCROLL_OUTLINE_CREATE_READ_EDIT_IMPORT_EXPORT_TRASH_PRIVATE_GIT",
|
||||
"knowledge_human_rendering_source": "product-source/hololake-clean-desktop/src/modules/knowledge-render/index.tsx",
|
||||
"public_runtime_version": "1.1.0",
|
||||
"public_runtime_version": "1.2.0_SOURCE_CANDIDATE",
|
||||
"public_tcs_gir_agent": "LOCAL_ACTIVE_REGISTERED_KNOWLEDGE_CAPABILITIES_HUMAN_APPROVAL_REQUIRED",
|
||||
"public_persona_runtime": "LOCAL_TRIAL_ACTIVE_EXISTING_PERSONA_VERIFICATION_NOT_CLAIMED",
|
||||
"external_ai_realtime": "GLP_LOCAL_REALTIME_1_LOOPBACK_ACTIVE",
|
||||
"typed_language_sources": ["USER_MESSAGE", "PERSONA_RESPONSE", "EXTERNAL_AI_MESSAGE", "SYSTEM_CONTEXT", "PROTOCOL_EVENT", "AGENT_ACTION", "TOOL_RESULT", "SYSTEM_RECEIPT"],
|
||||
"public_update_endpoint": "https://guanghulab.com/hololake/releases/latest.json",
|
||||
"public_update_release_state": "SERVER_HTTP_204_NO_PUBLIC_RELEASE",
|
||||
"public_update_manifest_builder": "product-source/hololake-clean-desktop/scripts/build-update-manifest.mjs",
|
||||
"public_update_candidate_version": "1.2.0",
|
||||
"enterprise_repository_registry_state": "BLUE_ISOLATED_FIVE_CENTRAL_REPOSITORIES_AND_FIVE_HUMAN_WORK_REPOSITORIES_TEAM_PERMISSIONS_NOT_ASSIGNED_NO_PUBLIC_CUTOVER",
|
||||
"bingshuo_enterprise_language_master_work_repository": "REPO-016:bingshuo/guanghu-language-master-work",
|
||||
"team_enterprise_login": "https://guanghu.chat/code/user/login",
|
||||
|
|
@ -195,6 +197,7 @@
|
|||
"runtime_implemented": true,
|
||||
"runtime_state": "LOCAL_CLEAN_V1_1_0_INSTALLED_BASELINE_REOPENED_FOR_DIRECT_LANGUAGE_ACCEPTANCE",
|
||||
"local_application_version": "1.1.0",
|
||||
"local_candidate_version": "1.2.0",
|
||||
"local_implementation_commit": "92cce2797381f84762a989ce57b991da76412119",
|
||||
"official_development_lane": "CURRENT_TASK_SCOPED_ZC001_EXECUTION",
|
||||
"external_development_anchor_id": "TCS-EVENT-HOLOLAKE-CLEAN-V1-ZC001-REALITY-DEVELOPMENT-TAKEOVER-20260903",
|
||||
|
|
@ -763,7 +766,7 @@
|
|||
"local_source_commit": "e88805cf9123edab8237e8b87a9cec4a2bc4e798",
|
||||
"canonical_public_source_repository": "REPO-014",
|
||||
"canonical_public_source_path": "product-source/hololake-clean-desktop",
|
||||
"source_artifact_alignment": "SOURCE_AT_REPO014_MAIN_INSTALLED_1_1_0_PRESENT_NEW_DIRECT_LANGUAGE_CHANGES_NOT_YET_BUILT_OR_INSTALLED",
|
||||
"source_artifact_alignment": "LOCAL_1_2_0_SOURCE_CANDIDATE_UNPUBLISHED_INSTALLED_1_1_0_STILL_ACTIVE",
|
||||
"previous_repo014_product_source_assessment": "HLP-DESKTOP-GAP-20260809-001_RETAINED_AS_HISTORY_NOT_CURRENT_ARTIFACT",
|
||||
"final_plan_complete": false,
|
||||
"model_receipt_donor_audit": "COMPLETE_DEFERRED_FROM_STAGE1",
|
||||
|
|
|
|||
Loading…
Reference in a new issue