fix(premiere-plugin-uxp): v2.0.2 — resolve temp folder defensively (no os.tmpdir)

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 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-05-28 00:43:44 -04:00
parent f1a3d6a24a
commit 4bea3c94f8
2 changed files with 34 additions and 11 deletions

View file

@ -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",

View file

@ -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).