From 4bea3c94f87fc905b0ab0f715fc1432cff5dc156 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 28 May 2026 00:43:44 -0400 Subject: [PATCH] =?UTF-8?q?fix(premiere-plugin-uxp):=20v2.0.2=20=E2=80=94?= =?UTF-8?q?=20resolve=20temp=20folder=20defensively=20(no=20os.tmpdir)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UXP's `os` is a stripped subset of Node's — `os.tmpdir()` isn't exposed in the build PPro 26.0.x ships, so both Import Proxy and Import Hi-Res failed immediately with "os.tmpdir is not a function". Replace with a defensive resolver tried in order: 1. os.tmpdir if present (newer UXP builds) 2. require('uxp').storage.localFileSystem.getTemporaryFolder() → .nativePath (the documented portable approach) 3. process.env.TEMP / TMP / LOCALAPPDATA\\Temp (Windows always sets these) 4. os.homedir() + AppData/Local/Temp tempPath() is now async; both Import.proxy and Import.hires await it. Co-Authored-By: Claude Opus 4.7 --- services/premiere-plugin-uxp/manifest.json | 2 +- .../premiere-plugin-uxp/src/import-flow.js | 43 ++++++++++++++----- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/services/premiere-plugin-uxp/manifest.json b/services/premiere-plugin-uxp/manifest.json index 2f143e4..097d650 100644 --- a/services/premiere-plugin-uxp/manifest.json +++ b/services/premiere-plugin-uxp/manifest.json @@ -2,7 +2,7 @@ "manifestVersion": 5, "id": "net.wilddragon.dragonflight.uxp", "name": "Dragonflight MAM", - "version": "2.0.1", + "version": "2.0.2", "main": "index.html", "host": { "app": "premierepro", diff --git a/services/premiere-plugin-uxp/src/import-flow.js b/services/premiere-plugin-uxp/src/import-flow.js index 95163bc..5b8dd01 100644 --- a/services/premiere-plugin-uxp/src/import-flow.js +++ b/services/premiere-plugin-uxp/src/import-flow.js @@ -6,16 +6,39 @@ const Import = {}; // Use UXP's Node-style fs for streaming writes (avoids buffering multi-GB - // files in memory) and the `os` module for the temp dir. Both are exposed - // when manifest declares "localFileSystem": "fullAccess". - const fs = require('fs'); - const os = require('os'); + // files in memory). `os` is a stripped subset — `os.tmpdir` is not exposed + // in all UXP builds, so resolve the temp folder defensively below. + const fs = require('fs'); const path = require('path'); + let os; try { os = require('os'); } catch (_) { os = {}; } + let uxpFs; + try { uxpFs = require('uxp').storage.localFileSystem; } catch (_) { uxpFs = null; } - // Pick a temp folder dragonflight downloads land in. Cleared on each - // download to keep disk usage bounded. - function tempPath(safeName) { - return path.join(os.tmpdir(), 'dragonflight-' + safeName); + // Resolve a writable temp folder using whichever API this UXP build + // actually exposes. Returns an absolute path string suitable for both + // fs.createWriteStream and premierepro.Project.importFiles. + async function getTempBase() { + // 1. Node-style os.tmpdir (newer UXP, simplest case) + try { if (os.tmpdir) { const t = os.tmpdir(); if (typeof t === 'string' && t.length) return t; } } catch (_) {} + // 2. UXP storage API — documented portable approach + if (uxpFs && uxpFs.getTemporaryFolder) { + try { const tmp = await uxpFs.getTemporaryFolder(); if (tmp && tmp.nativePath) return tmp.nativePath; } catch (_) {} + } + // 3. Windows env vars (always set under PPro) + try { + const e = (typeof process !== 'undefined' && process.env) || {}; + if (e.TEMP) return e.TEMP; + if (e.TMP) return e.TMP; + if (e.LOCALAPPDATA) return path.join(e.LOCALAPPDATA, 'Temp'); + } catch (_) {} + // 4. Homedir + Windows-style fallback + try { if (os.homedir) return path.join(os.homedir(), 'AppData', 'Local', 'Temp'); } catch (_) {} + throw new Error('Could not determine a writable temp folder on this system'); + } + + async function tempPath(safeName) { + const base = await getTempBase(); + return path.join(base, 'dragonflight-' + safeName); } // Stream the body of a fetch Response to a file on disk. @@ -115,7 +138,7 @@ // ── Proxy import: /stream returns a same-host /video URL ────────── Import.proxy = async function (asset) { const safeName = UI.sanitizeFilename((asset.display_name || asset.filename || asset.id) + '.mp4'); - const dest = tempPath(safeName); + const dest = await tempPath(safeName); UI.showProgress('Resolving proxy URL…', 4); const { url } = await API.getProxyUrl(asset.id); @@ -140,7 +163,7 @@ UI.showProgress('Resolving hi-res URL…', 4); const info = await API.getHiresInfo(asset.id); const safeName = UI.sanitizeFilename(info.filename || (asset.display_name || asset.id) + '.' + (info.ext || 'mxf')); - const dest = tempPath(safeName); + const dest = await tempPath(safeName); UI.showProgress('Downloading ' + safeName + ' (' + UI.formatBytes(Number(info.file_size || 0)) + ')…', 8); // info.url is presigned S3 — DO NOT add Bearer (would break signature).