bunny-move
Version:
Move files to and from your storage zones with bunny.net in a single cli command 🐰
3 lines (2 loc) • 14.1 kB
JavaScript
import { createRequire } from 'module';const require = createRequire(import.meta.url);
import{createBunnyApiClient as Ze}from"bunny-sdk";import we from"chalk";var Z=new Intl.ListFormat("en",{style:"long",type:"conjunction"});import ce from"tildify";var N=process.cwd();function d(r){return r.startsWith(N)?"."+r.slice(N.length):ce(r)}function p(){}import le from"fs-extra";async function V({absoluteDir:r}){await le.emptyDir(r)}import ye from"p-map";import de from"node:os";var c=de.cpus().length;function h(r){if(!r.ok)throw new Error(`${r.status} ${r.statusText}`,{cause:r});return r}import fe from"camelcase-keys";function L({file:r,folder:t,storageZone:e}){let o=r??t;return o?o.isDirectory?"https://"+e.storageHostname+o.path+o.objectName+"/":"https://"+e.storageHostname+o.path+o.objectName:"https://"+e.storageHostname+"/"+e.name+"/"}async function x({folder:r,storageZone:t}){let e=L({folder:r,storageZone:t});return await fetch(e,{headers:{AccessKey:t.password}}).then(h).then(async n=>n.json()).then(n=>fe(n))}async function U({dispatchTaskEnd:r=p,dispatchTaskStart:t=p,storageZone:e}){let o=await x({storageZone:e});await ye(o,async n=>{let a=`${n.path}${n.objectName}`.replace(`/${n.storageZoneName}`,".");t(a),await fetch(L({file:n,storageZone:e}),{headers:{AccessKey:e.password},method:"DELETE"}).then(h),r(a)},{concurrency:c})}import be from"fs-extra";import he from"p-map";function w(r){return typeof r=="number"?Number.isInteger(r):typeof r=="string"&&r.trim()!==""?Number.isInteger(Number(r)):!1}import gt from"tiny-invariant";async function D({bunnyApiClient:r,id:t}){return r.storagezone.byId(t).get()}import kt from"tiny-invariant";async function M({bunnyApiClient:r,includeDeleted:t=!1,name:e}){let o=await r.storagezone.get({queryParameters:{includeDeleted:t,page:1,perPage:1,search:e}});return o?.items?.[0]?.name===e?o?.items?.[0]:void 0}import ue from"node:path";import ge from"untildify";function F(r){return[...new Set(r.map(t=>{if(t.at(0)==="/"||t.startsWith("./")||t.startsWith("../")||t.startsWith("~/")||t==="."||t==="~"){let e=ue.resolve(ge(t));return e.at(-1)!=="/"?e+"/":e}return t}))]}async function G({bunnyApiClients:r,dispatchTaskEnd:t=p,dispatchTaskStart:e=p,locations:o}){return o=F(o),await he(o,async a=>{e(a.at(0)==="/"?d(a):a);let s=await ke({bunnyApiClients:r,input:a});return t(a.at(0)==="/"?d(a):a),s},{concurrency:c})}async function ke({bunnyApiClients:r,input:t}){if(t.at(0)==="/")return await be.ensureDir(t),{path:t,type:"local"};if(t.includes("/"))throw new Error(`Location "${t}" is invalid. Local paths must be a relative path starting with "./" or an absolute path starting with "/". Storage zone names cannot include "/".`);for(let e of r){if(w(t)){let n=await D({bunnyApiClient:e,id:Number.parseInt(t,10)});if(n)return{bunnyApiClient:e,storageZone:n,type:"storage-zone"};continue}let o=await M({bunnyApiClient:e,name:t});if(o)return{bunnyApiClient:e,storageZone:o,type:"storage-zone"}}throw new Error(`Storage zone "${t}" cannot be found. Does it exist in your account?`)}async function Fe({accessKeys:r,dispatchStoryChange:t=p,dispatchTaskEnd:e=p,dispatchTaskStart:o=p,highlight:n=we.white,locations:a}){t(`Validate locations ${Z.format(a.map(i=>`"${n(d(i))}"`))}`);let s=r.map(i=>Ze({accessKey:i})),m=await G({bunnyApiClients:s,dispatchTaskEnd:e,dispatchTaskStart:o,locations:a});for(let i of m)i.type==="local"&&(t(`Empty "${n(d(i.path))}"`),await V({absoluteDir:i.path})),i.type==="storage-zone"&&(t(`Empty "${n(i.storageZone.name)}" storage zone`),await U({dispatchTaskEnd:e,dispatchTaskStart:o,storageZone:i.storageZone}))}import{createBunnyApiClient as Re,createEdgeStorageApiClient as T}from"bunny-sdk";import Ne from"chalk";import A from"tiny-invariant";function $(){return new Map}import j from"fast-glob";import xe from"fs-extra";import Ae from"p-map";import{createHash as Se}from"node:crypto";import{pipeline as Pe}from"node:stream/promises";async function W(r){let t=Se("sha256");return await Pe(r,t),t.digest("hex").toUpperCase()}async function z({absoluteDir:r,dispatchTaskEnd:t=p,dispatchTaskStart:e=p}){r=r.at(-1)==="/"?r:r+"/";let o=r.slice(0,-1).length,n=j.convertPathToPattern(r)+"**/*",a=await j(n,{absolute:!0,concurrency:c,dot:!0,onlyFiles:!0}),s=await Ae(a,async i=>{e(d(i));let f=await W(xe.createReadStream(i));return t(d(i)),f},{concurrency:c}),m=new Map;for(let i=0;i<a.length;i++)m.set(a[i].slice(o),s[i]);return m}import Ce from"p-map";async function v({dispatchTaskEnd:r=p,dispatchTaskStart:t=p,storageZone:e}){t("./");let o=await x({storageZone:e});r("./");let n=`/${e.name}`;return await q({dispatchTaskEnd:r,dispatchTaskStart:t,fileMap:new Map,filePathPrefix:n,files:o??[],storageZone:e})}async function q({dispatchTaskEnd:r=p,dispatchTaskStart:t=p,fileMap:e,filePathPrefix:o,files:n,storageZone:a}){if(n.length===0)return e;let s=[];for(let f of n)f.isDirectory===!1&&e.set((f.path+f.objectName).slice(o.length),f.checksum),f.isDirectory===!0&&s.push(f);let i=(await Ce(s,async f=>{let k=(f.path+f.objectName).slice(o.length);t("."+k);let g=await x({folder:f,storageZone:a});return r("."+k),g},{concurrency:c})).flat(1);return i.length>0&&await q({fileMap:e,filePathPrefix:o,files:i,storageZone:a}),e}function B(r,t){r=new Map(r),t=new Map(t);for(let[e,o]of r.entries())t.get(e)===o&&(r.delete(e),t.delete(e));return[r,t]}import K from"fs-extra";import Le from"p-map";async function H({bunnyApiClients:r,dispatchTaskEnd:t=p,dispatchTaskStart:e=p,locations:o}){if(o=F(o),o.length<2)throw new Error(`No destination "to" locations found in "${JSON.stringify(o)}" after duplicates were removed.`);return await Le(o,async(a,s)=>{e(a.at(0)==="/"?d(a):a);let m=await De({bunnyApiClients:r,index:s,input:a});return t(a.at(0)==="/"?d(a):a),m},{concurrency:c})}async function De({bunnyApiClients:r,index:t,input:e}){if(e.at(0)==="/"){if(t===0&&!await K.pathExists(e))throw new Error(`Path "${e}" does not exist`);return t>0&&await K.ensureDir(e),{path:e,type:"local"}}if(e.includes("/"))throw new Error(`Location "${e}" is invalid. Local paths must be a relative path starting with "./" or an absolute path starting with "/". Storage zone names cannot include "/".`);for(let o of r){if(w(e)){let a=await D({bunnyApiClient:o,id:Number.parseInt(e,10)});if(a)return{bunnyApiClient:o,storageZone:a,type:"storage-zone"};continue}let n=await M({bunnyApiClient:o,name:e});if(n)return{bunnyApiClient:o,storageZone:n,type:"storage-zone"}}throw new Error(`Storage zone "${e}" cannot be found. Does it exist in your account?`)}import E from"fs-extra";import I from"node:path";import{pipeline as J}from"node:stream/promises";import _ from"p-map";import Me from"node:path";import{Readable as $e}from"node:stream";import{ReadableStream as ve}from"node:stream/web";var O=new TextEncoder;async function S({filePath:r,storageZone:t}){let e=Me.parse(r);e.dir.at(0)==="/"&&(e.dir=e.dir.slice(1));let o=await fetch(`https://${t.storageHostname}/${t.name}/${e.dir}/${e.base}`,{headers:{AccessKey:t.password}}).then(h);return o.body?o.body:ve.from($e.from(O.encode("")))}async function Q({absoluteDir:r,dispatchTaskEnd:t=p,dispatchTaskStart:e=p,storageZone:o,storageZoneFileMap:n}){let a=new Map(n);await _(a.keys(),async s=>{if(!s.endsWith(".html")){e("."+s);let m=I.join(r,s);await E.ensureDir(I.dirname(m)),await J(await S({filePath:s,storageZone:o}),E.createWriteStream(m)),a.delete(s),t("."+s)}},{concurrency:c}),await _(a.keys(),async s=>{e("."+s);let m=I.join(r,s);await E.ensureDir(I.dirname(m)),await J(await S({filePath:s,storageZone:o}),E.createWriteStream(m)),a.delete(s),t("."+s)},{concurrency:c})}import X from"fast-glob";import Be from"fs-extra";import Ee from"p-map";async function Y({dispatchTaskEnd:r=p,dispatchTaskStart:t=p,from:e,to:o}){let n=X.convertPathToPattern(e)+"**/*",a=await X(n,{absolute:!0,concurrency:c,dot:!0,onlyFiles:!0});await Ee(a,async s=>{let m=s.slice(e.length);t("./"+m),await Be.copy(s,`${o}${m}`),r("./"+m)},{concurrency:c})}import ee from"p-map";import{fileTypeFromStream as Ie}from"file-type";import ze from"node:path";import{Readable as Te}from"node:stream";async function P({checksum:r,filePath:t,readableStream:e,storageZone:o}){let[n,a]=e.tee(),s=await Ie(Te.fromWeb(n)),m=ze.parse(t);m.dir.at(0)==="/"&&(m.dir=m.dir.slice(1)),await fetch(`https://${o.storageHostname}/${o.name}/${m.dir}/${m.base}`,{body:a,duplex:"half",headers:{AccessKey:o.password,Checksum:r,"Content-Type":s?.mime??"application/octet-stream"},method:"PUT"}).then(h)}async function te({dispatchTaskEnd:r=p,dispatchTaskStart:t=p,from:e,to:o}){let n=new Map(e.fileMap);await ee(n.keys(),async a=>{a.endsWith(".html")||(t("."+a),await P({checksum:n.get(a),filePath:a,readableStream:await S({filePath:a,storageZone:e.storageZone}),storageZone:o.storageZone}),n.delete(a),r("."+a))},{concurrency:c}),await ee(n.keys(),async a=>{t("."+a),await P({checksum:n.get(a),filePath:a,readableStream:await S({filePath:a,storageZone:e.storageZone}),storageZone:o.storageZone}),n.delete(a),r("."+a)},{concurrency:c})}import oe from"node:fs";import re from"node:path";import{ReadableStream as ae}from"node:stream/web";import ne from"p-map";async function ie({absoluteDir:r,dispatchTaskEnd:t=p,dispatchTaskStart:e=p,localFileMap:o,storageZone:n}){let a=new Map(o);await ne(a.keys(),async s=>{s.endsWith(".html")||(e("."+s),await P({checksum:a.get(s),filePath:s,readableStream:ae.from(oe.createReadStream(re.join(r,s))),storageZone:n}),a.delete(s),t("."+s))},{concurrency:c}),await ne(a.keys(),async s=>{e("."+s),await P({checksum:a.get(s),filePath:s,readableStream:ae.from(oe.createReadStream(re.join(r,s))),storageZone:n}),a.delete(s),t("."+s)},{concurrency:c})}async function Ve({accessKeys:r,dispatchStoryChange:t=p,dispatchTaskEnd:e=p,dispatchTaskStart:o=p,highlight:n=Ne.white,index:a=!1,locations:s}){t(`Validate locations ${Z.format(s.map(l=>`"${n(d(l))}"`))}`);let m=r.map(l=>Re({accessKey:l})),[i,...f]=await H({bunnyApiClients:m,dispatchTaskEnd:e,dispatchTaskStart:o,locations:s});A(i,'"from" is undefined');let k=i.type==="storage-zone"?T({accessKey:i.storageZone.password,baseUrl:`https://${i.storageZone.storageHostname}`}):void 0,g;f.every(l=>l.type==="local"&&i.type==="local")||(i.type==="local"&&(t(`Index "${n(d(i.path))}"`),g=await z({absoluteDir:i.path,dispatchTaskEnd:e,dispatchTaskStart:o})),i.type==="storage-zone"&&(A(k,'"fromEdgeStorageApiClient" is undefined'),t(`Index "${n(i.storageZone.name)}" storage zone`),g=await v({dispatchTaskEnd:e,dispatchTaskStart:o,edgeStorageApiClient:k,storageZone:i.storageZone})));for(let l of f){if(i.type==="local"&&(l.type==="local"&&(t(`Copy from "${n(d(i.path))}" to "${d(l.path)}"`),await Y({dispatchTaskEnd:e,dispatchTaskStart:o,from:i.path,to:l.path})),l.type==="storage-zone")){A(g,'"fromFileMap" is undefined');let u=g,b=i.path,{storageZone:y}=l,C=T({accessKey:y.password,baseUrl:`https://${y.storageHostname}`});t(`Index "${n(y.name)}" storage zone`);let R=a?await v({dispatchTaskEnd:e,dispatchTaskStart:o,edgeStorageApiClient:C,storageZone:y}):$();[u,R]=B(u,R),t(`Sync files from "${n(d(b))}" to "${n(y.name)}" storage zone`),await ie({absoluteDir:b,dispatchTaskEnd:e,dispatchTaskStart:o,localFileMap:u,storageZone:y})}if(i.type==="storage-zone"){if(A(k,'"fromEdgeStorageApiClient" is undefined'),A(g,'"fromFileMap" is undefined'),l.type==="local"){let u=l.path,{storageZone:b}=i,y=g;t(`Index "${n(d(u))}"`);let C=a?await z({absoluteDir:u,dispatchTaskEnd:e,dispatchTaskStart:o}):$();[C,y]=B(C,y),t(`Sync files from "${n(b.name)}" storage zone to "${n(d(u))}"`),await Q({absoluteDir:u,dispatchTaskEnd:e,dispatchTaskStart:o,storageZone:b,storageZoneFileMap:y})}if(l.type==="storage-zone"){let u=T({accessKey:l.storageZone.password,baseUrl:`https://${l.storageZone.storageHostname}`});t(`Index "${n(l.storageZone.name)}" storage zone`);let b=a?await v({dispatchTaskEnd:e,dispatchTaskStart:o,edgeStorageApiClient:u,storageZone:l.storageZone}):$(),y;[b,y]=B(b,g),t(`Sync files from "${n(i.storageZone.name)}" storage zone to "${n(l.storageZone.name)}" storage zone`),await te({dispatchTaskEnd:e,dispatchTaskStart:o,from:{edgeStorageApiClient:k,fileMap:y,storageZone:i.storageZone},to:{edgeStorageApiClient:u,storageZone:l.storageZone}})}}}}import{createBunnyApiClient as We}from"bunny-sdk";import je from"chalk";import qe from"p-map";import Ue from"p-map";import wr from"tiny-invariant";async function se({bunnyApiClient:r,id:t,includeCertificate:e=!0}){return r.pullzone.byId(t).get({queryParameters:{includeCertificate:e}})}import Pr from"tiny-invariant";async function pe({bunnyApiClient:r,includeCertificate:t=!0,name:e}){let o=await r.pullzone.get({queryParameters:{includeCertificate:t,page:1,perPage:1,search:e}});return o?.items?.[0]?.name===e?o?.items?.[0]:void 0}async function me({bunnyApiClients:r,dispatchTaskEnd:t=p,dispatchTaskStart:e=p,locations:o}){if(o=F(o),o.some(a=>a.at(0)==="/"))throw new Error(`All locations found in "${JSON.stringify(o)}" must be pull zones.`);return await Ue(o,async a=>{e(a);let s=await Ge({bunnyApiClients:r,input:a});return t(a),s},{concurrency:c})}async function Ge({bunnyApiClients:r,input:t}){if(t.includes("/"))throw new Error(`Location "${t}" is invalid. Pull zone names cannot include "/".`);for(let e of r){if(w(t)){let n=await se({bunnyApiClient:e,id:Number.parseInt(t,10)});if(n)return{bunnyApiClient:e,pullZone:n,type:"pull-zone"};continue}let o=await pe({bunnyApiClient:e,name:t});if(o)return{bunnyApiClient:e,pullZone:o,type:"pull-zone"}}throw new Error(`Pull zone "${t}" cannot be found. Does it exist in your account?`)}async function Ke({accessKeys:r,dispatchStoryChange:t=p,dispatchTaskEnd:e=p,dispatchTaskStart:o=p,highlight:n=je.white,locations:a}){t(`Validate locations ${Z.format(a.map(i=>`"${n(i)}"`))}`);let s=r.map(i=>We({accessKey:i})),m=await me({bunnyApiClients:s,dispatchTaskEnd:e,dispatchTaskStart:o,locations:a});t(`Purge pull zone caches for ${Z.format(m.map(i=>`"${n(i.pullZone.name)}"`))}`),await qe(m,async i=>{o(i.pullZone.name),await i.bunnyApiClient.pullzone.byId(i.pullZone.id).purgeCache.post({}),e(i.pullZone.name)},{concurrency:c})}export{Fe as empty,Ve as move,Ke as purge};