UNPKG

bunny-transfer

Version:

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

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