UNPKG

bunny-move

Version:

Move files to and from your storage zones with bunny.net in a single cli command 🐰

3 lines (2 loc) 16.9 kB
import { createRequire } from 'module';const require = createRequire(import.meta.url); import{argument as W,option as Ut}from"pastel";import Wt from"react";import{z as M}from"zod";import Qe from"camelcase-keys";import et from"fast-deep-equal/es6/index.js";import z from"fs-extra";import tt from"is-obj";import J from"p-map";import{parse as rt}from"smol-toml";import _e from"node:os";var l=_e.cpus().length;function V(e,t){return e.filter((r,o)=>o===e.findIndex(a=>a[t]===r[t]))}import{createBunnyApiClient as He}from"bunny-sdk";import Ye from"is-obj";function _({firstName:e,lastName:t}){if(e&&t)return`${e} ${t}`;if(t)return t;if(e)return e}async function K({accessKey:e,profile:t=e}){let o=await He({accessKey:e}).user.get().catch(n=>{throw Ye(n)&&"responseStatusCode"in n&&n.responseStatusCode===401?new Error(`Cannot find user with accessKey "${e}"`):n});if(!o)throw new Error(`Cannot find user with accessKey "${e}"`);let a=_({firstName:o.firstName,lastName:o.lastName});return{accessKey:e,email:o.email,id:o.id,...a&&{name:a},profile:t}}import qe from"node:path";import Je from"untildify";import{z as C}from"zod";var N="~/.bunny/credentials",D=qe.resolve(Je(N)),H=C.object({accessKey:C.string(),email:C.string(),id:C.string(),name:C.string().optional(),profile:C.string()});import{stringify as Xe}from"smol-toml";function Y(e,t){return e.reduce((r,o)=>{let{[t]:a,...n}=o;return(typeof a=="string"||typeof a=="number"||typeof a=="symbol")&&(r[a]=n),r},{})}function q(e){return Xe(Y(e.map(t=>({...t,profile:t.profile??t.accessKey})),"profile"))}async function X({accessKey:e=[],credentialsPath:t=D,profile:r=[]}={}){let o=typeof e=="string"?[e]:e,a=typeof r=="string"?[r]:r,n=await z.pathExists(t),i=[];if(n){let c=await z.readFile(t,"utf8"),S=rt(c),h=!1;if(i=await J(Object.entries(S),async([d,g])=>{let x=H.parse({...tt(g)?Qe({...g,profile:d}):{}}),u=await K({accessKey:x.accessKey,profile:d});return et(x,u)||(h=!0),u},{concurrency:l}),h){let d=q(i);await z.outputFile(t,d)}}let p=await J(o,async c=>K({accessKey:c,profile:c}),{concurrency:l}),s=V([...i,...p],"profile");return a.length>0?s.filter(c=>a.includes(c.profile??"")):s}async function Q(e){let t=await X(e);return[...new Set(t.map(o=>o.accessKey))]}import{option as ot}from"pastel";import{z as te}from"zod";var ee=te.string().trim().uuid(),nt=te.preprocess(e=>process.env.BUNNY_ACCESS_KEY&&!e?process.env.BUNNY_ACCESS_KEY.split(","):e,ee.or(ee.array()).optional().transform(e=>typeof e=="string"?[e]:e)),re=nt.describe(ot({alias:"k",description:"Bunny API Access Key",valueDescription:"uuid"}));import{option as at}from"pastel";import{z as ne}from"zod";var oe=ne.string().trim().min(1),it=ne.preprocess(e=>process.env.BUNNY_PROFILE&&!e?process.env.BUNNY_PROFILE.split(","):e,oe.or(oe.array()).optional().transform(e=>typeof e=="string"?[e]:e)),ae=it.describe(at({alias:"p",description:"Bunny user profile name",valueDescription:"string"}));import st from"node:path";import{option as pt}from"pastel";import ct from"untildify";import{z as ie}from"zod";var mt=N,dt=ie.preprocess(e=>{if(process.env.BUNNY_SHARED_CREDENTIALS_FILE&&!e){let t=process.env.BUNNY_SHARED_CREDENTIALS_FILE.trim();return st.resolve(ct(t))}return e},ie.string().trim().optional().default(D)),se=dt.describe(pt({alias:"c",defaultValueDescription:mt,description:"Bunny shared credentials file path",valueDescription:"file-path"}));import{createBunnyApiClient as Dt,createEdgeStorageApiClient as O}from"bunny-sdk";import Rt from"chalk";import T from"tiny-invariant";var pe=new Intl.ListFormat("en",{style:"long",type:"conjunction"});import lt from"tildify";var ce=process.cwd();function b(e){return e.startsWith(ce)?"."+e.slice(ce.length):lt(e)}function m(){}function R(){return new Map}import de from"fast-glob";import ut from"fs-extra";import gt from"p-map";import{createHash as ft}from"node:crypto";import{pipeline as yt}from"node:stream/promises";async function me(e){let t=ft("sha256");return await yt(e,t),t.digest("hex").toUpperCase()}async function I({absoluteDir:e,dispatchTaskEnd:t=m,dispatchTaskStart:r=m}){e=e.at(-1)==="/"?e:e+"/";let o=e.slice(0,-1).length,a=de.convertPathToPattern(e)+"**/*",n=await de(a,{absolute:!0,concurrency:l,dot:!0,onlyFiles:!0}),i=await gt(n,async s=>{r(b(s));let c=await me(ut.createReadStream(s));return t(b(s)),c},{concurrency:l}),p=new Map;for(let s=0;s<n.length;s++)p.set(n[s].slice(o),i[s]);return p}import ht from"p-map";import bt from"camelcase-keys";function F(e){if(!e.ok)throw new Error(`${e.status} ${e.statusText}`,{cause:e});return e}function le({file:e,folder:t,storageZone:r}){let o=e??t;return o?o.isDirectory?"https://"+r.storageHostname+o.path+o.objectName+"/":"https://"+r.storageHostname+o.path+o.objectName:"https://"+r.storageHostname+"/"+r.name+"/"}async function j({folder:e,storageZone:t}){let r=le({folder:e,storageZone:t});return await fetch(r,{headers:{AccessKey:t.password}}).then(F).then(async a=>a.json()).then(a=>bt(a))}async function v({dispatchTaskEnd:e=m,dispatchTaskStart:t=m,storageZone:r}){t("./");let o=await j({storageZone:r});e("./");let a=`/${r.name}`;return await fe({dispatchTaskEnd:e,dispatchTaskStart:t,fileMap:new Map,filePathPrefix:a,files:o??[],storageZone:r})}async function fe({dispatchTaskEnd:e=m,dispatchTaskStart:t=m,fileMap:r,filePathPrefix:o,files:a,storageZone:n}){if(a.length===0)return r;let i=[];for(let c of a)c.isDirectory===!1&&r.set((c.path+c.objectName).slice(o.length),c.checksum),c.isDirectory===!0&&i.push(c);let s=(await ht(i,async c=>{let S=(c.path+c.objectName).slice(o.length);t("."+S);let h=await j({folder:c,storageZone:n});return e("."+S),h},{concurrency:l})).flat(1);return s.length>0&&await fe({fileMap:r,filePathPrefix:o,files:s,storageZone:n}),r}function $(e,t){e=new Map(e),t=new Map(t);for(let[r,o]of e.entries())t.get(r)===o&&(e.delete(r),t.delete(r));return[e,t]}import he from"fs-extra";import St from"p-map";function ye(e){return typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1}import fo from"tiny-invariant";async function ue({bunnyApiClient:e,id:t}){return e.storagezone.byId(t).get()}import go from"tiny-invariant";async function ge({bunnyApiClient:e,includeDeleted:t=!1,name:r}){let o=await e.storagezone.get({queryParameters:{includeDeleted:t,page:1,perPage:1,search:r}});return o?.items?.[0]?.name===r?o?.items?.[0]:void 0}import kt from"node:path";import xt from"untildify";function be(e){return[...new Set(e.map(t=>{if(t.at(0)==="/"||t.startsWith("./")||t.startsWith("../")||t.startsWith("~/")||t==="."||t==="~"){let r=kt.resolve(xt(t));return r.at(-1)!=="/"?r+"/":r}return t}))]}async function ke({bunnyApiClients:e,dispatchTaskEnd:t=m,dispatchTaskStart:r=m,locations:o}){if(o=be(o),o.length<2)throw new Error(`No destination "to" locations found in "${JSON.stringify(o)}" after duplicates were removed.`);return await St(o,async(n,i)=>{r(n.at(0)==="/"?b(n):n);let p=await wt({bunnyApiClients:e,index:i,input:n});return t(n.at(0)==="/"?b(n):n),p},{concurrency:l})}async function wt({bunnyApiClients:e,index:t,input:r}){if(r.at(0)==="/"){if(t===0&&!await he.pathExists(r))throw new Error(`Path "${r}" does not exist`);return t>0&&await he.ensureDir(r),{path:r,type:"local"}}if(r.includes("/"))throw new Error(`Location "${r}" 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 e){if(ye(r)){let n=await ue({bunnyApiClient:o,id:Number.parseInt(r,10)});if(n)return{bunnyApiClient:o,storageZone:n,type:"storage-zone"};continue}let a=await ge({bunnyApiClient:o,name:r});if(a)return{bunnyApiClient:o,storageZone:a,type:"storage-zone"}}throw new Error(`Storage zone "${r}" cannot be found. Does it exist in your account?`)}import B from"fs-extra";import L from"node:path";import{pipeline as Se}from"node:stream/promises";import we from"p-map";import Ct from"node:path";import{Readable as Ft}from"node:stream";import{ReadableStream as At}from"node:stream/web";var xe=new TextEncoder;async function A({filePath:e,storageZone:t}){let r=Ct.parse(e);r.dir.at(0)==="/"&&(r.dir=r.dir.slice(1));let o=await fetch(`https://${t.storageHostname}/${t.name}/${r.dir}/${r.base}`,{headers:{AccessKey:t.password}}).then(F);return o.body?o.body:At.from(Ft.from(xe.encode("")))}async function Ce({absoluteDir:e,dispatchTaskEnd:t=m,dispatchTaskStart:r=m,storageZone:o,storageZoneFileMap:a}){let n=new Map(a);await we(n.keys(),async i=>{if(!i.endsWith(".html")){r("."+i);let p=L.join(e,i);await B.ensureDir(L.dirname(p)),await Se(await A({filePath:i,storageZone:o}),B.createWriteStream(p)),n.delete(i),t("."+i)}},{concurrency:l}),await we(n.keys(),async i=>{r("."+i);let p=L.join(e,i);await B.ensureDir(L.dirname(p)),await Se(await A({filePath:i,storageZone:o}),B.createWriteStream(p)),n.delete(i),t("."+i)},{concurrency:l})}import Fe from"fast-glob";import Zt from"fs-extra";import Mt from"p-map";async function Ae({dispatchTaskEnd:e=m,dispatchTaskStart:t=m,from:r,to:o}){let a=Fe.convertPathToPattern(r)+"**/*",n=await Fe(a,{absolute:!0,concurrency:l,dot:!0,onlyFiles:!0});await Mt(n,async i=>{let p=i.slice(r.length);t("./"+p),await Zt.copy(i,`${o}${p}`),e("./"+p)},{concurrency:l})}import Ze from"p-map";import{fileTypeFromStream as Tt}from"file-type";import Pt from"node:path";import{Readable as Et}from"node:stream";async function Z({checksum:e,filePath:t,readableStream:r,storageZone:o}){let[a,n]=r.tee(),i=await Tt(Et.fromWeb(a)),p=Pt.parse(t);p.dir.at(0)==="/"&&(p.dir=p.dir.slice(1)),await fetch(`https://${o.storageHostname}/${o.name}/${p.dir}/${p.base}`,{body:n,duplex:"half",headers:{AccessKey:o.password,Checksum:e,"Content-Type":i?.mime??"application/octet-stream"},method:"PUT"}).then(F)}async function Me({dispatchTaskEnd:e=m,dispatchTaskStart:t=m,from:r,to:o}){let a=new Map(r.fileMap);await Ze(a.keys(),async n=>{n.endsWith(".html")||(t("."+n),await Z({checksum:a.get(n),filePath:n,readableStream:await A({filePath:n,storageZone:r.storageZone}),storageZone:o.storageZone}),a.delete(n),e("."+n))},{concurrency:l}),await Ze(a.keys(),async n=>{t("."+n),await Z({checksum:a.get(n),filePath:n,readableStream:await A({filePath:n,storageZone:r.storageZone}),storageZone:o.storageZone}),a.delete(n),e("."+n)},{concurrency:l})}import Te from"node:fs";import Pe from"node:path";import{ReadableStream as Ee}from"node:stream/web";import De from"p-map";async function Re({absoluteDir:e,dispatchTaskEnd:t=m,dispatchTaskStart:r=m,localFileMap:o,storageZone:a}){let n=new Map(o);await De(n.keys(),async i=>{i.endsWith(".html")||(r("."+i),await Z({checksum:n.get(i),filePath:i,readableStream:Ee.from(Te.createReadStream(Pe.join(e,i))),storageZone:a}),n.delete(i),t("."+i))},{concurrency:l}),await De(n.keys(),async i=>{r("."+i),await Z({checksum:n.get(i),filePath:i,readableStream:Ee.from(Te.createReadStream(Pe.join(e,i))),storageZone:a}),n.delete(i),t("."+i)},{concurrency:l})}async function ve({accessKeys:e,dispatchStoryChange:t=m,dispatchTaskEnd:r=m,dispatchTaskStart:o=m,highlight:a=Rt.white,index:n=!1,locations:i}){t(`Validate locations ${pe.format(i.map(d=>`"${a(b(d))}"`))}`);let p=e.map(d=>Dt({accessKey:d})),[s,...c]=await ke({bunnyApiClients:p,dispatchTaskEnd:r,dispatchTaskStart:o,locations:i});T(s,'"from" is undefined');let S=s.type==="storage-zone"?O({accessKey:s.storageZone.password,baseUrl:`https://${s.storageZone.storageHostname}`}):void 0,h;c.every(d=>d.type==="local"&&s.type==="local")||(s.type==="local"&&(t(`Index "${a(b(s.path))}"`),h=await I({absoluteDir:s.path,dispatchTaskEnd:r,dispatchTaskStart:o})),s.type==="storage-zone"&&(T(S,'"fromEdgeStorageApiClient" is undefined'),t(`Index "${a(s.storageZone.name)}" storage zone`),h=await v({dispatchTaskEnd:r,dispatchTaskStart:o,edgeStorageApiClient:S,storageZone:s.storageZone})));for(let d of c){if(s.type==="local"&&(d.type==="local"&&(t(`Copy from "${a(b(s.path))}" to "${b(d.path)}"`),await Ae({dispatchTaskEnd:r,dispatchTaskStart:o,from:s.path,to:d.path})),d.type==="storage-zone")){T(h,'"fromFileMap" is undefined');let g=h,x=s.path,{storageZone:u}=d,E=O({accessKey:u.password,baseUrl:`https://${u.storageHostname}`});t(`Index "${a(u.name)}" storage zone`);let G=n?await v({dispatchTaskEnd:r,dispatchTaskStart:o,edgeStorageApiClient:E,storageZone:u}):R();[g,G]=$(g,G),t(`Sync files from "${a(b(x))}" to "${a(u.name)}" storage zone`),await Re({absoluteDir:x,dispatchTaskEnd:r,dispatchTaskStart:o,localFileMap:g,storageZone:u})}if(s.type==="storage-zone"){if(T(S,'"fromEdgeStorageApiClient" is undefined'),T(h,'"fromFileMap" is undefined'),d.type==="local"){let g=d.path,{storageZone:x}=s,u=h;t(`Index "${a(b(g))}"`);let E=n?await I({absoluteDir:g,dispatchTaskEnd:r,dispatchTaskStart:o}):R();[E,u]=$(E,u),t(`Sync files from "${a(x.name)}" storage zone to "${a(b(g))}"`),await Ce({absoluteDir:g,dispatchTaskEnd:r,dispatchTaskStart:o,storageZone:x,storageZoneFileMap:u})}if(d.type==="storage-zone"){let g=O({accessKey:d.storageZone.password,baseUrl:`https://${d.storageZone.storageHostname}`});t(`Index "${a(d.storageZone.name)}" storage zone`);let x=n?await v({dispatchTaskEnd:r,dispatchTaskStart:o,edgeStorageApiClient:g,storageZone:d.storageZone}):R(),u;[x,u]=$(x,h),t(`Sync files from "${a(s.storageZone.name)}" storage zone to "${a(d.storageZone.name)}" storage zone`),await Me({dispatchTaskEnd:r,dispatchTaskStart:o,from:{edgeStorageApiClient:S,fileMap:u,storageZone:s.storageZone},to:{edgeStorageApiClient:g,storageZone:d.storageZone}})}}}}function vt(e){return typeof e=="object"&&e!==null&&"message"in e&&typeof e.message=="string"}function $t(e){if(vt(e))return e;try{return new Error(JSON.stringify(e))}catch{return new Error(String(e))}}function $e(e){return $t(e).message}import U from"figures";import{Newline as w,Text as f}from"ink";import Bt from"ink-spinner";import{Fragment as Lt,jsx as y,jsxs as k}from"react/jsx-runtime";function Be({feedback:e}){let t=k(Lt,{children:[y(w,{}),e.pastStories.map(r=>k(f,{children:[y(f,{color:"green",children:U.tick}),k(f,{color:"white",children:[" ",r]}),y(w,{})]},r))]});return e.error?k(f,{children:[t,k(f,{children:[y(f,{color:"red",children:U.cross}),k(f,{color:"white",children:[" ",e.story]}),y(w,{}),y(w,{}),y(f,{color:"red",children:e.error}),y(w,{})]})]}):e.done?k(f,{children:[t,k(f,{children:[y(f,{color:"magenta",children:U.heart}),y(f,{color:"white",children:" Done"}),y(w,{})]})]}):k(f,{children:[t,k(f,{children:[y(f,{color:"cyan",children:y(Bt,{})}),k(f,{color:"white",children:[" ",e.story]}),y(f,{children:e.tasks.map(r=>k(f,{children:[y(w,{}),y(f,{color:"gray",children:r})]},r))}),y(w,{})]})]})}import{createAction as P,createReducer as Nt}from"@reduxjs/toolkit";import Ke from"react";function Le(e){return e.at(0).toUpperCase()+e.slice(1)}function Kt(e,t){return Object.fromEntries(Object.entries(t).map(([r,o])=>[`dispatch${Le(r)}`,(...a)=>{e(o(...a))}]))}function Ne(e,t,r){let[o,a]=Ke.useReducer(e,t),n=Ke.useMemo(()=>Kt(a,r),[a,r]);return[o,n]}var ze=P("feedback/epic/end"),Ie=P("feedback/epic/error"),je=P("feedback/story/change"),Oe=P("feedback/tasks/end"),Ue=P("feedback/tasks/start"),zt={epicEnd:ze,epicError:Ie,storyChange:je,taskEnd:Oe,taskStart:Ue},We={done:!1,error:null,pastStories:[],story:"",tasks:[]},It=Nt(We,e=>e.addCase(je,(t,r)=>{t.story!==""&&t.pastStories.push(t.story),t.story=r.payload}).addCase(Ue,(t,r)=>{t.tasks.push(r.payload)}).addCase(Oe,(t,r)=>{let o=t.tasks.indexOf(r.payload);o!==-1&&t.tasks.splice(o,1)}).addCase(ze,t=>{t.story!==""&&t.pastStories.push(t.story),t.story="",t.done=!0}).addCase(Ie,(t,r)=>{t.error=r.payload}));function Ge(){return Ne(It,We,zt)}import{useApp as jt}from"ink";import Ot from"react";function Ve(e){let{exit:t}=jt();Ot.useEffect(()=>{(e.done||e.error)&&(e.error&&(process.exitCode=1,process.env.CI&&(console.error(e.error),console.error())),t())},[e.done,e.error,t])}import{jsx as _t}from"react/jsx-runtime";var pa=!0,ca=M.tuple([M.string().describe(W({description:"Source location to move from",name:"from"})),M.string().describe(W({description:"Destination location to move to",name:"to"}))]).rest(M.string().describe(W({name:"to"}))),Gt=M.coerce.boolean().optional().default(!1).describe(Ut({description:"Index destinations to skip duplicate files",valueDescription:"boolean"})),ma=M.object({accessKey:re,index:Gt,profile:ae,sharedCredentialsFile:se});function Vt({args:e,options:t}){let[r,{dispatchEpicEnd:o,dispatchEpicError:a,dispatchStoryChange:n,dispatchTaskEnd:i,dispatchTaskStart:p}]=Ge();return Ve(r),Wt.useEffect(()=>{async function s(){try{n("Validate credentials");let c=await Q({accessKey:t.accessKey,credentialsPath:t.sharedCredentialsFile,profile:t.profile});if(c.length===0)throw new Error("No accessKeys found");await ve({accessKeys:c,dispatchStoryChange:n,dispatchTaskEnd:i,dispatchTaskStart:p,index:t.index,locations:e}),o()}catch(c){a($e(c))}}s()},[e,t.accessKey,t.index,t.profile,t.sharedCredentialsFile,o,a,n,i,p]),_t(Be,{feedback:r})}export{ca as args,Vt as default,pa as isDefault,ma as options};