Files
jhonny-editor/code/scripts/verify-uxp-hybrid-addon-receipt.mjs
T
João Henrique b541f502ba feat: initial commit - Jhonny Editor
- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
2026-09-08 09:59:31 -04:00

222 lines
11 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { resolve } from "node:path";
import {
UXP_HYBRID_ADDON_AUTHORITY_URL,
UXP_HYBRID_ADDON_ENTRYPOINT_PATH,
UXP_HYBRID_ADDON_RECEIPT_LEGACY_SCHEMA_VERSION,
UXP_HYBRID_ADDON_RECEIPT_LEGACY_SEMANTICS,
UXP_HYBRID_ADDON_RECEIPT_SCHEMA_VERSION,
UXP_HYBRID_ADDON_RECEIPT_SEMANTICS,
UXP_HYBRID_ADDON_TARGETS,
compareUxpHybridPaths,
} from "./uxp-hybrid-addon-receipt-contract.mjs";
import {
canonicalNativeSdkHeaderInventorySha256,
verifyNativeSdkHeaderInventory,
} from "./verify-native-sdk-header-inventory.mjs";
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
const MAX_PATH_LENGTH = 512;
const MAX_ARTIFACT_BYTES = 2 ** 31;
const MAX_ENTRYPOINT_BYTES = 16 * 1024 * 1024;
function receiptError(message) {
const error = new Error(message);
error.code = "UXP_HYBRID_ADDON_RECEIPT_INVALID";
return error;
}
function record(value, label, expectedKeys) {
if (!value || typeof value !== "object" || Array.isArray(value)) throw receiptError(`${label} must be an object`);
const keys = Object.keys(value).sort(compareUxpHybridPaths);
const expected = [...expectedKeys].sort(compareUxpHybridPaths);
if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) {
throw receiptError(`${label} must contain only the documented receipt fields`);
}
return value;
}
function nonEmptyString(value, label, maximum = MAX_PATH_LENGTH) {
if (typeof value !== "string" || !value.trim() || value.length > maximum || value.includes("\0")) {
throw receiptError(`${label} must be a non-empty string of at most ${maximum} characters`);
}
return value;
}
function sha256(value, label) {
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
throw receiptError(`${label} must be a lowercase SHA-256 hex digest`);
}
return value;
}
function safePositiveInteger(value, label) {
if (!Number.isSafeInteger(value) || value <= 0 || value > MAX_ARTIFACT_BYTES) {
throw receiptError(`${label} must be a positive safe integer no larger than ${MAX_ARTIFACT_BYTES}`);
}
return value;
}
function addonName(value) {
const name = nonEmptyString(value, "manifest.addonName", 128);
if (!/^[A-Za-z0-9._-]+\.uxpaddon$/.test(name)) {
throw receiptError("manifest.addonName must be a simple .uxpaddon filename");
}
return name;
}
function canonicalize(value) {
if (Array.isArray(value)) return value.map(canonicalize);
if (value && typeof value === "object") {
return Object.fromEntries(Object.keys(value).sort(compareUxpHybridPaths).map((key) => [key, canonicalize(value[key])]));
}
return value;
}
function expectedArtifactPath(target, name) {
return `${target.pathPrefix}/${name}`;
}
export function verifyUxpHybridAddonReceipt(document, options = {}) {
if (!document || typeof document !== "object" || Array.isArray(document)) throw receiptError("receipt must be an object");
const legacy = document.schemaVersion === UXP_HYBRID_ADDON_RECEIPT_LEGACY_SCHEMA_VERSION;
const receipt = record(document, "receipt", legacy
? ["schemaVersion", "source", "manifest", "semantics", "stats", "artifacts"]
: ["schemaVersion", "source", "manifest", "entrypoint", "semantics", "stats", "artifacts"]);
if (!legacy && receipt.schemaVersion !== UXP_HYBRID_ADDON_RECEIPT_SCHEMA_VERSION) {
throw receiptError(`schemaVersion must be ${UXP_HYBRID_ADDON_RECEIPT_LEGACY_SCHEMA_VERSION} or ${UXP_HYBRID_ADDON_RECEIPT_SCHEMA_VERSION}`);
}
const source = record(receipt.source, "source", ["sdk", "sdkVersion", "sdkHeaderReceiptSha256", "authorityUrl"]);
if (source.sdk !== "uxp-hybrid") throw receiptError("source.sdk must be uxp-hybrid");
nonEmptyString(source.sdkVersion, "source.sdkVersion", 128);
sha256(source.sdkHeaderReceiptSha256, "source.sdkHeaderReceiptSha256");
if (source.authorityUrl !== UXP_HYBRID_ADDON_AUTHORITY_URL) {
throw receiptError("source.authorityUrl must match the documented Hybrid addon build guide");
}
const manifest = record(receipt.manifest, "manifest", ["manifestVersion", "hostApp", "hostMinVersion", "addonName", "enableAddon"]);
if (!Number.isInteger(manifest.manifestVersion) || manifest.manifestVersion < 6) {
throw receiptError("manifest.manifestVersion must be 6 or newer");
}
if (manifest.hostApp !== "premierepro") throw receiptError("manifest.hostApp must be premierepro");
if (!/^\d+\.\d+\.\d+$/.test(String(manifest.hostMinVersion || ""))) {
throw receiptError("manifest.hostMinVersion must be a semantic version");
}
const name = addonName(manifest.addonName);
if (manifest.enableAddon !== true) throw receiptError("manifest.enableAddon must be true");
const semantics = record(receipt.semantics, "semantics", ["listed", "doesNotEstablish"]);
const requiredSemantics = legacy ? UXP_HYBRID_ADDON_RECEIPT_LEGACY_SEMANTICS : UXP_HYBRID_ADDON_RECEIPT_SEMANTICS;
if (semantics.listed !== requiredSemantics.listed || semantics.doesNotEstablish !== requiredSemantics.doesNotEstablish) {
throw receiptError("semantics must retain the documented evidence boundary");
}
let entrypoint;
if (!legacy) {
const value = record(receipt.entrypoint, "entrypoint", ["path", "bytes", "sha256"]);
if (value.path !== UXP_HYBRID_ADDON_ENTRYPOINT_PATH) {
throw receiptError(`entrypoint.path must be ${UXP_HYBRID_ADDON_ENTRYPOINT_PATH}`);
}
entrypoint = {
path: value.path,
bytes: safePositiveInteger(value.bytes, "entrypoint.bytes"),
sha256: sha256(value.sha256, "entrypoint.sha256"),
};
if (entrypoint.bytes > MAX_ENTRYPOINT_BYTES) {
throw receiptError(`entrypoint.bytes must be no larger than ${MAX_ENTRYPOINT_BYTES}`);
}
}
if (!Array.isArray(receipt.artifacts) || receipt.artifacts.length !== UXP_HYBRID_ADDON_TARGETS.length) {
throw receiptError(`artifacts must contain exactly ${UXP_HYBRID_ADDON_TARGETS.length} required target entries`);
}
const artifacts = receipt.artifacts.map((entry, index) => {
const artifact = record(entry, `artifacts[${index}]`, ["target", "path", "bytes", "sha256"]);
const expectedTarget = UXP_HYBRID_ADDON_TARGETS[index];
if (artifact.target !== expectedTarget.target) throw receiptError(`artifacts[${index}].target must be ${expectedTarget.target}`);
const path = nonEmptyString(artifact.path, `artifacts[${index}].path`, MAX_PATH_LENGTH);
if (path !== expectedArtifactPath(expectedTarget, name)) throw receiptError(`artifacts[${index}].path must be the documented ${expectedTarget.target} addon path`);
return { target: artifact.target, path, bytes: safePositiveInteger(artifact.bytes, `artifacts[${index}].bytes`), sha256: sha256(artifact.sha256, `artifacts[${index}].sha256`) };
});
const stats = record(receipt.stats, "stats", legacy
? ["artifacts", "bytes"]
: ["artifacts", "addonBytes", "entrypoints", "entrypointBytes"]);
if (stats.artifacts !== artifacts.length) throw receiptError("stats.artifacts does not match artifacts");
const addonBytes = artifacts.reduce((total, artifact) => total + artifact.bytes, 0);
if (!Number.isSafeInteger(addonBytes)) throw receiptError("addon artifact bytes exceed the supported total");
if (legacy && stats.bytes !== addonBytes) throw receiptError("stats.bytes does not match artifacts");
if (!legacy) {
if (stats.addonBytes !== addonBytes) throw receiptError("stats.addonBytes does not match artifacts");
if (stats.entrypoints !== 1) throw receiptError("stats.entrypoints must be 1");
if (stats.entrypointBytes !== entrypoint.bytes) throw receiptError("stats.entrypointBytes does not match entrypoint");
}
if (options.sdkHeaderReceipt !== undefined) {
const summary = verifyNativeSdkHeaderInventory(options.sdkHeaderReceipt);
if (summary.sdk !== "uxp-hybrid") throw receiptError("sdkHeaderReceipt must identify uxp-hybrid");
if (options.sdkHeaderReceipt.source.sdkVersion !== source.sdkVersion) {
throw receiptError("source.sdkVersion must match sdkHeaderReceipt.source.sdkVersion");
}
if (canonicalNativeSdkHeaderInventorySha256(options.sdkHeaderReceipt) !== source.sdkHeaderReceiptSha256) {
throw receiptError("source.sdkHeaderReceiptSha256 does not match sdkHeaderReceipt");
}
}
return Object.freeze(legacy
? { addonName: name, artifacts: artifacts.length, bytes: addonBytes }
: { addonName: name, artifacts: artifacts.length, addonBytes, entrypoints: 1, entrypointBytes: entrypoint.bytes });
}
export function canonicalUxpHybridAddonReceiptSha256(document) {
verifyUxpHybridAddonReceipt(document);
return createHash("sha256").update(JSON.stringify(canonicalize(document))).digest("hex");
}
function parseArguments(argv) {
const options = { input: null, sdkHeaderReceipt: null, printCanonicalSha256: false };
for (let index = 0; index < argv.length; index += 1) {
const argument = argv[index];
if (argument === "--print-canonical-sha256") options.printCanonicalSha256 = true;
else if (["--input", "--sdk-header-receipt"].includes(argument)) {
const value = argv[++index];
if (!value || value.startsWith("--")) throw receiptError(`${argument} requires a receipt path`);
if (argument === "--input") options.input = value;
else options.sdkHeaderReceipt = value;
} else throw receiptError(`Unknown argument: ${argument}`);
}
if (!options.input || !options.sdkHeaderReceipt) {
throw receiptError("Usage: node scripts/verify-uxp-hybrid-addon-receipt.mjs --input <receipt.json> --sdk-header-receipt <sdk-header-receipt.json> [--print-canonical-sha256]");
}
return { inputPath: resolve(options.input), sdkHeaderReceiptPath: resolve(options.sdkHeaderReceipt), printCanonicalSha256: options.printCanonicalSha256 };
}
async function readJson(path, label) {
try { return JSON.parse(await readFile(path, "utf8")); } catch { throw receiptError(`${label} must be a readable JSON receipt`); }
}
async function main() {
const options = parseArguments(process.argv.slice(2));
const [receipt, sdkHeaderReceipt] = await Promise.all([
readJson(options.inputPath, "input"),
readJson(options.sdkHeaderReceiptPath, "sdkHeaderReceipt"),
]);
const summary = verifyUxpHybridAddonReceipt(receipt, { sdkHeaderReceipt });
const entrypointText = "entrypoints" in summary ? ` and ${summary.entrypoints} entrypoint` : "";
const bytes = "addonBytes" in summary ? summary.addonBytes + summary.entrypointBytes : summary.bytes;
process.stdout.write(`UXP Hybrid addon receipt is valid: ${summary.artifacts} addon artifacts${entrypointText}, ${bytes} bytes.\n`);
if (options.printCanonicalSha256) process.stdout.write(`Canonical receipt SHA-256: ${canonicalUxpHybridAddonReceiptSha256(receipt)}\n`);
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
main().catch((error) => {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
process.exitCode = 1;
});
}