#!/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`) }