@smoothcdn/cli 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ var ts=Object.create;var Ie=Object.defineProperty;var ss=Object.getOwnPropertyDescriptor;var os=Object.getOwnPropertyNames;var rs=Object.getPrototypeOf,ns=Object.prototype.hasOwnProperty;var is=(e,o,t,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of os(o))!ns.call(e,r)&&r!==t&&Ie(e,r,{get:()=>o[r],enumerable:!(s=ss(o,r))||s.enumerable});return e};var f=(e,o,t)=>(t=e!=null?ts(rs(e)):{},is(o||!e||!e.__esModule?Ie(t,"default",{value:e,enumerable:!0}):t,e));var Qt=require("commander");var ze=require("commander");var Te=f(require("axios"),1);var K=f(require("keytar"),1),de="smoothcdn",ue="cli_token";async function Oe(e){await K.default.setPassword(de,ue,e)}async function Le(){return process.env.SCDN_TOKEN||K.default.getPassword(de,ue)}async function _e(){await K.default.deletePassword(de,ue)}async function v(){let e=await Le();return Te.default.create({baseURL:"https://smoothcdn.com/api",headers:e?{Authorization:`Bearer ${e}`}:{}})}var Ve=require("@inquirer/prompts");var Z=f(require("fs"),1),Be=f(require("path"),1);function ge(e=process.cwd()){return Be.default.join(e,".scdn.json")}function Q(){return Z.default.existsSync(ge())}function w(){return Q()?JSON.parse(Z.default.readFileSync(ge(),"utf8")):{}}function P(e){Z.default.writeFileSync(ge(),JSON.stringify(e,null,2))}var Y=f(require("ora"),1),ke=require("date-fns"),Me=require("date-fns/locale"),De=(e,o)=>{let t,s;return o&&(t=o?.status||o?.code,s=o?.response?.data?.error||o?.data?.error||o?.message),`${e}${t?` [${t}]`:""}${s?` [${s}]`:""}`},d=e=>{console.log(e)},O=e=>{(0,Y.default)(e).succeed()},u=(e,o)=>{(0,Y.default)(De(e,o)).fail()},y=e=>(0,Y.default)(e).start(),h=(e,o)=>{o.succeed(e)},g=(e,o,t)=>{o.fail(De(e,t))};function he(e){return typeof e=="string"&&(e=new Date(e)),(0,ke.formatDate)(e,"dd MMMM yyyy HH:mm:ss",{locale:Me.enUS})}function xe(e,o=2){if(!e||e===0)return"0 B";let t=1024,s=o<0?0:o,r=["B","KB","MB","GB","TB"],n=Math.floor(Math.log(e)/Math.log(t));return parseFloat((e/Math.pow(t,n)).toFixed(s))+" "+r[n]}function H(e){if(e==null)return"0";let o=Math.abs(e);return o<1e3?e.toString():o<1e6?(e/1e3).toFixed(e%1e3===0?0:1)+"k":o<1e9?(e/1e6).toFixed(e%1e6===0?0:1)+"M":(e/1e9).toFixed(e%1e9===0?0:1)+"B"}var We=new ze.Command("create-version").description("Create version for next deployments").argument("[version]","Version number, e.g. 1.0.0").action(async(e="")=>{let o=await v(),t=w();if(!t){u("No .scdn.json found.");return}let s=e||t.version,r=await(0,Ve.confirm)({message:"Create version as blank (no assets will be copied from previous version automatically) ?",default:!1});if(s){let n=y(`Creating version "${s}"`);try{let a=await o.post(`/projects/${t.projectId}/versions`,{version:s,blank:r,failIfExist:!1});a.status===201?h(`Created new version "${s}"`,n):a.status===200&&h(`Version exist "${s}"`,n),e&&(t.version=e,P(t),d(".scdn.json config file updated"),O(`Using version ${e}`))}catch(a){g(`Cannot create version ${e}`,n,a)}}else u('Missing version. Please run "scdn create-version <version>" first.')});var Re=require("commander"),ee=f(require("os"),1),He=f(require("open"),1);var Xe=new Re.Command("login").description("Authenticate via browser").action(async()=>{let e=await v(),o=y("Starting login..."),t=[];try{let s=ee.default.userInfo();s.username&&t.push(s.username)}catch{}try{let s=ee.default.platform();s&&t.push(s)}catch{}try{let s=ee.default.machine();s&&t.push(s)}catch{}try{let{data:s}=await e.post("/auth/cli",{label:t.join("-")}),{key_id:r,verification_url:n}=s;await(0,He.default)(n),o.text="Waiting for authentication...";let a=null;for(;!a;){await new Promise(I=>setTimeout(I,1500));let p=await e.post("/auth/cli/poll",{keyId:r}).catch(()=>null);if(!p?.data)continue;let{status:x}=p.data;if(x!=="pending"){if(x==="expired"){g("Login session expired. Try again.",o);return}if(x==="active"){a=p.data.api_key;break}if(x==="already_active"){g("This login session is already used.",o);return}}}await Oe(a);let i=w(),c=await(await v()).get("/status");c.data&&(i.userSlug=c.data?.slug||"",c.data?.plan>=0&&i?.plan!==c.data?.plan&&(i.plan=c.data.plan),P(i)),h("Login successful!",o)}catch{g("There was an error when authorizing",o)}});var Ue=require("commander");var Je=new Ue.Command("logout").description("Remove credential data").action(async()=>{await _e(),O("Successfully logged out")});var ot=require("commander"),S=require("@inquirer/prompts"),rt=f(require("clipboardy"),1);var we=require("globby");async function D(e=[],o=[]){let t=await(0,we.globby)(e,{dot:!0});if(o.length===0)return t;let s=await(0,we.globby)(o,{dot:!0});return t.filter(r=>!s.includes(r))}var qe=["js","mjs","css","json","svg","txt","csv","xml","md"],k=["woff2","woff","ttf","otf"],M=["png","jpg","jpeg","webp","avif"],L=["wav","aiff","aif","flac","mp3","ogg","oga","m4a","aac"],te=["pdf","doc","docx","xls","xlsx","ppt","pptx","odt","ods","odp"],Ge=["json","txt","csv","xml","md","doc","docx","xls","xlsx","ppt","pptx","odt","ods","odp"],ye={0:{projects:1,assetsPerProject:50,bandwidth:1073741824,maxRequests:2e4,maxTextAssetSize:204800,maxImageAssetSize:2097152,maxAudioAssetSize:2097152,usageEventsMaxDays:3,usageEventsMaxAmount:100},1:{projects:10,assetsPerProject:1e4,bandwidth:107374182400,maxRequests:5e6,maxTextAssetSize:512e3,maxImageAssetSize:20971520,maxAudioAssetSize:52428800,usageEventsMaxDays:7,usageEventsMaxAmount:500},2:{projects:50,assetsPerProject:1e5,bandwidth:1099511627776,maxRequests:5e7,maxTextAssetSize:1048576,maxImageAssetSize:52428800,maxAudioAssetSize:104857600,usageEventsMaxDays:14,usageEventsMaxAmount:1e3}};var as=({url:e,alt:o="",critical:t=!1,width:s,height:r})=>`<img
3
+ src="${e}"
4
+ alt="${o}"${t?`
5
+ fetchpriority="high"
6
+ decoding="sync"`:`
7
+ loading="lazy"
8
+ decoding="async"`}${s?`
9
+ width="${s}"`:""}${r?`
10
+ height="${r}"`:""}
11
+ />`,cs=({url:e,scriptType:o="standard"})=>{switch(o){case"critical":return`<link
12
+ rel="preload"
13
+ as="script"
14
+ href="${e}"
15
+ >
16
+ <script
17
+ src="${e}"
18
+ defer
19
+ ></script>`;case"async":return`<script
20
+ src="${e}"
21
+ async
22
+ ></script>`;default:return`<script
23
+ src="${e}"
24
+ defer
25
+ ></script>`}},ls=({url:e,preload:o=!1})=>o?`<link
26
+ rel="preload"
27
+ as="style"
28
+ href="${e}"
29
+ />
30
+ <link
31
+ rel="stylesheet"
32
+ href="${e}"
33
+ />`:`<link
34
+ rel="stylesheet"
35
+ href="${e}"
36
+ />`,fs=({url:e,controls:o=!1,loop:t=!1,preload:s="none"})=>`<audio
37
+ src="${e}"${s?`
38
+ preload="${s}"`:""}${o?`
39
+ controls`:""}${t?`
40
+ loop`:""}
41
+ ></audio>`,ps=({url:e,extension:o,fontFamily:t="",fontDisplay:s="swap"})=>`<style>
42
+ @font-face {
43
+ font-family: "${t}";
44
+ src: url("${e}") format("${o}");
45
+ font-display: ${s};
46
+ }
47
+ </style>`,ms=({url:e,lazy:o=!1,title:t="",width:s="",height:r=""})=>`<iframe
48
+ src="${e}"${o?`
49
+ loading="lazy"`:""}${t?`
50
+ title="${t}`:""}${s?`
51
+ width="${s}"`:""}${r?`
52
+ height="${r}"`:""}
53
+ ></iframe>`,ds=({url:e,assetName:o,newTab:t=!0,download:s=!0,noreferrer:r=!1})=>{let n=[];t&&n.push("noopener"),r&&n.push("noreferrer");let a=n.length?n.join(" "):"";return`<a
54
+ href="${e}"${t?`
55
+ target="_blank"`:""}${a?`
56
+ rel="${a}"`:""}${s?`
57
+ download`:""}
58
+ >Download ${o}</a>`};function Ke(e={}){let{extension:o}=e;return o==="js"||o==="mjs"?cs(e):o==="css"?ls(e):o==="pdf"?ms(e):o==="svg"||M.includes(o)?as(e):L.includes(o)?fs(e):k.includes(o)?ps(e):ds(e)}var se=f(require("fs"),1),Qe=f(require("crypto"),1),U=f(require("path"),1),Ye=require("image-size"),et=require("fast-xml-parser");var z=f(require("fs"),1),V=f(require("path"),1),Se=V.default.resolve(".scdn");function us(){z.default.existsSync(Se)||z.default.mkdirSync(Se,{recursive:!0})}function ve(e){return us(),V.default.join(Se,e)}function F(e){let o=ve(e);return z.default.existsSync(o)||z.default.mkdirSync(o,{recursive:!0}),o}function b(e,o,t,s){let r=F(e),n=V.default.join(r,`${o}.${t}`);return z.default.writeFileSync(n,s),n}function C(e,o,t){let s=V.default.join(ve(e),`${o}.${t}`);return z.default.existsSync(s)}function E(e,o,t){return V.default.join(ve(e),`${o}.${t}`)}var X=f(require("sharp"),1);async function gs(e){return(0,X.default)(e).png({compressionLevel:9,quality:70,adaptiveFiltering:!0,palette:!0,dither:.5,effort:8}).toBuffer()}async function hs(e){return(0,X.default)(e).jpeg({quality:70,chromaSubsampling:"4:2:0",mozjpeg:!0}).toBuffer()}async function Ee(e,o={}){let t=o.quality??80;return(0,X.default)(e).webp({quality:t,effort:6}).toBuffer()}async function Ce(e,o={}){let t=o.quality??50;return(0,X.default)(e).avif({quality:t,speed:4}).toBuffer()}async function Ze(e,o){let t=o.toLowerCase();return t==="png"?gs(e):t==="jpg"||t==="jpeg"?hs(e):t==="webp"?Ee(e):t==="avif"?Ce(e):e}function xs(e){return Qe.default.createHash("sha256").update(e).digest("hex")}async function tt(e){let o=se.default.readFileSync(e),t=U.default.extname(e).replace(".","").toLowerCase(),s=U.default.basename(e,U.default.extname(e)),r=xs(o);F(r);let n=C(r,s,t),a=C(r,s,"webp"),i=C(r,s,"avif"),l=t==="webp",c=t==="avif";if(n&&(l||a)&&(c||i))return{hash:r,originalExt:t,originalPath:E(r,s,t),processedPaths:[!l&&a?E(r,s,"webp"):null,!c&&i?E(r,s,"avif"):null].filter(Boolean)};let p=await Ze(o,t),x=l?null:await Ee(o),I=c?null:await Ce(o),m=b(r,s,t,p),$=!l&&x?b(r,s,"webp",x):null,B=!c&&I?b(r,s,"avif",I):null;return{hash:r,originalExt:t,originalPath:m,processedPaths:[$,B].filter(me=>!me?.endsWith(".scdn"))}}function st(e){if(U.default.extname(e).toLowerCase().replace(".","")==="svg"){let r=se.default.readFileSync(e,"utf-8"),i=new et.XMLParser({ignoreAttributes:!1}).parse(r)?.svg;if(!i)return null;if(i["@_width"]&&i["@_height"])return{width:parseFloat(i["@_width"]),height:parseFloat(i["@_height"])};if(i["@_viewBox"]){let[,,l,c]=i["@_viewBox"].split(/\s+/).map(Number);if(l&&c)return{width:l,height:c}}return null}let t=se.default.readFileSync(e),s=(0,Ye.imageSize)(t);return!s.width||!s.height?null:{width:s.width,height:s.height}}function ws(e,o,t,s,r=""){return t?`https://${t}.smoothcdn.com${r?`/${r}`:""}${s}`:`https://cdn.smoothcdn.com/${e}/${o}${r?`/${r}`:""}${s}`}var nt=new ot.Command("get-snippet").description("Output snippet for asset").action(async()=>{let e=w();if(!e){u("No .scdn.json found.");return}let o=await D(e.sources,e.excludes);if(o.length<=0){u("No assets found");return}d(`Found assets ${o.length}`);let t=await(0,S.select)({message:"What asset to get snippet for:",choices:o.map(c=>({name:c,value:c}))}),[,...s]=t.split("/"),r=`/${s.join("/")}`,n=t.split(".").pop()?.toLowerCase()??"",a=ws(e.userSlug,e.projectSlug,e.customSubdomain||"",r,e.version||""),i={extension:n,url:a,assetName:t};if(n==="svg"||M.includes(n)){let c=await st(t);i.alt=await(0,S.input)({message:"Alt attribute",validate:p=>p?!0:"Alt attribute is required",default:t}),i.critical=await(0,S.confirm)({message:"Is critical asset (must be loaded above the fold e.g. in hero section)"}),i.width=await(0,S.input)({message:"Width",default:c?.width?.toString()||""}),i.height=await(0,S.input)({message:"Height",default:c?.height?.toString()||""})}(n==="js"||n==="mjs")&&(i.type=await(0,S.select)({message:"Script type",choices:[{name:"Standard",value:"standard"},{name:"Critical",value:"critical"},{name:"Asynchronous",value:"async"}]})),n==="css"&&(i.preload=await(0,S.confirm)({message:"Preload"})),L.includes(n)&&(i.controls=await(0,S.confirm)({message:"Controls attribute (shows audio control buttons)"}),i.loop=await(0,S.confirm)({message:"Loop attribute (plays audio in loop)"})),k.includes(n)&&(i.fontFamily=await(0,S.input)({message:"Font family",default:t})),n==="pdf"&&(i.lazy=await(0,S.confirm)({message:"Lazy load"}),i.title=await(0,S.input)({message:"Title",default:t}),i.width=await(0,S.input)({message:"Width",default:"100%"}),i.height=await(0,S.input)({message:"Height",default:"600"})),Ge.includes(n)&&(i.newTab=await(0,S.confirm)({message:"Open in new tab"}),i.noreferrer=await(0,S.confirm)({message:'Hide referrer (adds "noreferrer" to rel attribute)'}),i.download=await(0,S.confirm)({message:"Download attribute"}));let l=Ke(i);await rt.default.write(l),d(""),d(l),d(""),O("Snippet was copied to clipboard")});var ct=require("commander"),J=require("@inquirer/prompts");var it=f(require("micromatch"),1);function oe(e){if(!e)return!1;let o=e.trim();return o.length<6||o.length>254||o.includes("..")?!1:/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(o)}function re(e=0,o,t){let{maxTextAssetSize:s,maxImageAssetSize:r}=ye[e],n=o.split(".").pop()?.toLowerCase()??"",a=qe.includes(n),i=k.includes(n),l=M.includes(n),c=L.includes(n),p=te.includes(n);return!a&&!i&&!l&&!c&&!p?`Unsupported file type: ${n}`:(a||i)&&t>s?`Asset too large. Max allowed ${Math.round(s/1024)} KB, trying to send ${Math.round(t/1024)} KB`:(l||p)&&t>r?`Asset too large. Max allowed ${Math.round(r/(1024*1024))} MB, trying to send ${(t/1024/1024).toFixed(2)} MB`:!0}function at(e=0,o,t){let{maxAudioAssetSize:s}=ye[e],r=o.split(".").pop()?.toLowerCase()??"";return L.includes(r)&&t>s?`Asset too large. Max allowed ${Math.round(s/(1024*1024))} MB, received ${(t/1024/1024).toFixed(2)} MB`:!0}function $e(e){try{it.default.matcher(e)}catch{return"Invalid pattern"}return e.includes("\0")||e.includes("..")||e.includes("~")||e.startsWith("/")||/^[A-Za-z]:\\/.test(e)?"Contains invalid segments":!0}var lt=new ct.Command("grant-access").description("Grant access for smooth resources").option("--force","Override expiration date if access already exists").action(async({force:e=!1})=>{let o=await v(),t=w();if(!t){u("No .scdn.json found.");return}let r=((await o.get(`/projects/${t.projectId}/assets`)).data??[]).filter(n=>n.protected).map(n=>`${n.path}${n.fileName}`);if(r.length===0)u("No protected assets found");else{let n=await(0,J.checkbox)({message:"Select protected assets to grant access to:",choices:r.map(c=>({name:c,value:c})),validate:c=>c.length>0?!0:"Select at least one asset."}),a=await(0,J.input)({message:"Enter user email to grant access (mail@example.com):",validate:c=>oe(c)?!0:"Invalid mail format"}),i=await(0,J.input)({message:"Enter expiration date (leave empty if no expiration should be set):"}),l=y("Granting access...");try{let c={force:e,assets:n,email:a,expiresAt:i},p=await o.post(`/projects/${t.projectId}/accesses/grant`,c);p.status===201?h(`Access granted for "${a}"`,l):p.status===200?h(`Access already exists for "${a}"`,l):g("Unexpected response from server",l,p)}catch(c){g("Failed to grant access",l,c)}}});var kt=require("commander"),N=require("@inquirer/prompts");var _t=require("commander"),Tt=f(require("form-data"),1),q=f(require("fs"),1),Pe=f(require("path"),1);var ft=f(require("fs"),1),pt=f(require("crypto"),1),ne=f(require("path"),1),be=require("child_process");function ys(e){return pt.default.createHash("sha256").update(e).digest("hex")}function Ss(){try{return(0,be.execFileSync)("ffmpeg",["-version"],{stdio:"ignore"}),!0}catch{return!1}}function vs(e,o){(0,be.execFileSync)("ffmpeg",["-y","-i",e,"-map_metadata","-1","-vn","-c:a","aac","-q:a","2","-movflags","+faststart",o],{stdio:"ignore"})}async function mt(e){if(!Ss())return!1;let o=ft.default.readFileSync(e),t=ne.default.extname(e).replace(".","").toLowerCase(),s=ne.default.basename(e,ne.default.extname(e));if(!L.includes(t))throw new Error(`Unsupported audio extension: .${t}`);let r=ys(o);F(r);let n=C(r,s,t),a=C(r,s,"m4a"),i=n?E(r,s,t):b(r,s,t,o);if(a)return{hash:r,originalExt:t,originalPath:i,processedPaths:[E(r,s,"m4a")]};let l=E(r,s,"m4a");return vs(i,l),{hash:r,originalExt:t,originalPath:i,processedPaths:[l]}}var dt=f(require("fs"),1),ut=f(require("crypto"),1),ie=f(require("path"),1),gt=f(require("postcss"),1),ht=f(require("cssnano"),1);function Es(e){return ut.default.createHash("sha256").update(e).digest("hex")}function Cs(e){let o=e.split(`
59
+ `),t=o.reduce((n,a)=>n+a.length,0)/o.length,s=(e.match(/\s/g)?.length??0)/e.length,r=/\/\*/.test(e);return t>200&&s<.12&&!r}async function $s(e){return(await(0,gt.default)([(0,ht.default)({preset:["default",{discardComments:!0,normalizeWhitespace:!0,mergeRules:!1,mergeLonghand:!1,reduceIdents:!1,zindex:!1,calc:!1,colormin:!1,normalizeUrls:!1,normalizeTimingFunctions:!1,normalizeUnicode:!1,minifyFontValues:!1,minifySelectors:!1}]})]).process(e,{from:void 0})).css}async function xt(e){let o=dt.default.readFileSync(e),t=ie.default.extname(e).replace(".","").toLowerCase(),s=ie.default.basename(e,ie.default.extname(e)),r=Es(o);if(F(r),C(r,s,t))return{hash:r,originalExt:t,originalPath:E(r,s,t)};let a=o.toString("utf8"),i=o;if(!Cs(a)){let p=await $s(a);i=Buffer.from(p)}let c=b(r,s,t,i);return{hash:r,originalExt:t,originalPath:c}}var vt=f(require("fs"),1),Et=f(require("crypto"),1),ae=f(require("path"),1);var wt=f(require("ttf2woff2"),1);async function yt(e,o){return e}async function St(e){return Buffer.from((0,wt.default)(e))}function bs(e){return Et.default.createHash("sha256").update(e).digest("hex")}async function Ct(e){let o=vt.default.readFileSync(e),t=ae.default.extname(e).replace(".","").toLowerCase(),s=ae.default.basename(e,ae.default.extname(e)),r=bs(o);F(r);let n=C(r,s,t),a=C(r,s,"woff2"),i=t==="woff2";if(n&&(i||a))return{hash:r,originalExt:t,originalPath:E(r,s,t),processedPaths:[!i&&a?E(r,s,"woff2"):null].filter(Boolean)};let l=await yt(o,t),c=i?null:await St(o),p=b(r,s,t,l),x=!i&&c?b(r,s,"woff2",c):null;return{hash:r,originalExt:t,originalPath:p,processedPaths:[x].filter(I=>I&&!I.endsWith(".scdn"))}}var Ae=f(require("fs"),1),$t=f(require("crypto"),1),ce=f(require("path"),1),bt=require("terser");function As(e){return $t.default.createHash("sha256").update(e).digest("hex")}async function Ps(e){return(await(0,bt.minify)(e,{compress:!1,mangle:{toplevel:!1},format:{comments:!1,semicolons:!0}})).code}function Fs(e){let o=e.split(`
60
+ `),t=o.reduce((r,n)=>r+n.length,0)/o.length,s=(e.match(/\s/g)?.length??0)/e.length;return t>200&&s<.15&&!/\/\*|\n\s*\/\//.test(e)}async function At(e){let o=Ae.default.readFileSync(e),t=ce.default.extname(e).replace(".","").toLowerCase(),s=ce.default.basename(e,ce.default.extname(e)),r=As(o);if(F(r),C(r,s,t))return{hash:r,originalExt:t,originalPath:E(r,s,t)};let a=Ae.default.readFileSync(e),i=a.toString("utf8"),l=a;if(!Fs(i)){let x=await Ps(i);l=Buffer.from(x)}let p=b(r,s,t,l);return{hash:r,originalExt:t,originalPath:p}}var Pt=f(require("fs"),1),Ft=f(require("crypto"),1),le=f(require("path"),1);function Ns(e){return Ft.default.createHash("sha256").update(e).digest("hex")}function js(e){let o=(e.match(/\s/g)?.length??0)/e.length,t=e.split(`
61
+ `).length;return o<.08&&t<=2}function Is(e){let o=JSON.parse(e);return JSON.stringify(o)}async function Nt(e){let o=Pt.default.readFileSync(e),t=le.default.extname(e).replace(".","").toLowerCase(),s=le.default.basename(e,le.default.extname(e)),r=Ns(o);if(F(r),C(r,s,t))return{hash:r,originalExt:t,originalPath:E(r,s,t)};let a=o.toString("utf8"),i=o;if(!js(a)){let p=Is(a);i=Buffer.from(p)}if(!i||i.length===0)throw new Error("JSON minification produced empty output");let c=b(r,s,t,i);return{hash:r,originalExt:t,originalPath:c}}var jt=f(require("fs"),1),It=f(require("crypto"),1),fe=f(require("path"),1),Ot=require("svgo");function Os(e){return It.default.createHash("sha256").update(e).digest("hex")}function Ls(e){let o=(e.match(/\s/g)?.length??0)/e.length,t=/<!--/.test(e),s=e.split(`
62
+ `).length;return o<.1&&s<=2&&!t}function _s(e){let o=console.warn,t=console.info;console.warn=()=>{},console.info=()=>{};let s;try{s=(0,Ot.optimize)(e,{multipass:!0,plugins:[{name:"preset-default",params:{overrides:{convertPathData:!1,convertStyleToAttrs:!1,cleanupIds:!1,removeViewBox:!1,inlineStyles:!1}}},"removeUselessStrokeAndFill"]})}finally{console.warn=o,console.info=t}if(!("data"in s)||!s.data)throw new Error("SVG optimization failed");return s.data}async function Lt(e){let o=jt.default.readFileSync(e),s=fe.default.parse(e).ext.replace(".","").toLowerCase(),r=fe.default.basename(e,fe.default.extname(e)),n=Os(o);if(F(n),C(n,r,s))return{hash:n,originalExt:s,originalPath:E(n,r,s)};let i=o.toString("utf8"),l=o;if(!Ls(i)){let x=_s(i);l=Buffer.from(x)}let p=b(n,r,s,l);return{hash:n,originalExt:s,originalPath:p}}var Fe=async({force:e=!1,sync:o=!1})=>{let t=await v(),s=w();if(!s){u("No .scdn.json found.");return}let r=!1;if(!s.projectId)u('Missing projectId in config file. Please run "scdn init" or "scdn load" command first.');else try{let n=await t.get("/status"),{plan:a,slug:i}=n.data;s.plan!==a&&(s.plan=a,P(s)),s.userSlug!==i&&(s.userSlug=i,P(s)),d(`Deploying assets${s.version?` for version ${s.version}`:""}...`);let l=await D(s.sources,s.excludes),c=await D(s.protected),p=[];if(l.length>0){let x=y("Deploying asset"),I=0;for(let m of l)if(++I,r&&!e)g(`Selected version was already published "${m}"`,x);else try{let $=Pe.default.basename(m),me=m.replace($,"").replace(/^\/+|\/+$/g,""),[,...es]=me.split("/"),Ne=es.join("/"),G=0;try{G=q.default.statSync(m).size}catch{throw new Error(`Cannot read file: ${m}`)}let je=re(s.plan||0,$,G);if(je!==!0)throw new Error(je);let _=$.split(".").pop()?.toLowerCase()??"",A;if(M.includes(_)&&_!=="avif")A=await tt(m);else if(_==="js"||_==="mjs")A=await At(m);else if(_==="css")A=await xt(m);else if(_==="json")A=await Nt(m);else if(_==="svg")A=await Lt(m);else if(k.includes(_))A=await Ct(m);else if(L.includes(_)){if(A=await mt(m),A===!1)throw new Error("To optimize audio - ffmpeg is required. Install it from https://ffmpeg.org/");if(A?.originalPath){try{G=q.default.statSync(A.originalPath).size}catch{throw new Error(`Cannot read file: ${A.originalPath}`)}let R=at(s.plan||0,Pe.default.basename(A.originalPath),G);if(R!==!0)throw new Error(R)}else throw new Error("Audio was not optimized")}else te.includes(_)&&(A={originalPath:m,processedPaths:[]});let T=new Tt.default;T.append("asset",q.default.createReadStream(A?.originalPath?A.originalPath:m)),A?.processedPaths?.forEach(R=>{T.append("sub_assets[]",q.default.createReadStream(R))}),T.append("path",Ne),T.append("version",s.version||""),T.append("optimize",0),T.append("failIfExist",0),T.append("protected",c.includes(m)?1:0),e&&T.append("force",1),p.push({fileName:$,path:Ne});let W=await t.post(`/projects/${s.projectId}/assets`,T,{headers:T.getHeaders()});W.status===201?h(`Deployed "${m}"`,x):W.status===200&&(W.data?.error==="Cannot deploy/override assets on published version"?(r=!0,g(`Failed to deploy ${m}`,x,W)):W.data?.dedupe&&h(`Asset already exists "${m}"`,x))}catch($){g(`Failed to deploy ${m}`,x,$)}if(o){let m=y("Sync...");try{let $=await t.post(`/projects/${s.projectId}/assets/sync`,{force:e,version:s.version,assetsToSync:p});h(`Sync: ${$?.data?.removed??0} files removed`,m)}catch($){g($,m)}}}else u("No assets to deploy")}catch(n){u("Cannot proceed with pushing assets",n)}d("")},Bt=new _t.Command("push").option("--force","Override assets on published versions too").option("--sync","Additionally removes files that do not exists in project anymore").description("Push assets to CDN").action(Fe);var Mt=new kt.Command("init").description("Initialize new project").action(async()=>{let e=await v(),o=!1;if(Q())d("Detected .scdn.json config file");else{let t=await(0,N.input)({message:"Project name:"});o=await(0,N.confirm)({message:"Enable versioning:",default:!1});let s=o?await(0,N.input)({message:"Initial version:",default:"1.0.0"}):"",r=await(0,N.confirm)({message:"Block bots for this variant?",default:!1}),n=await(0,N.confirm)({message:"Block headless browsers?",default:!1}),a=[],i=!0;for(;i;){let m=await(0,N.input)({message:"Add source path/pattern (glob allowed):",validate:B=>B.trim().length>0||"Source required"});a.push(m.trim()),i=await(0,N.confirm)({message:"Add another source?",default:!1})}let l=[];if(await(0,N.confirm)({message:"Add exclude patterns?",default:!1})){let m=!0;for(;m;){let $=await(0,N.input)({message:"Exclude path/pattern:",validate:B=>B.trim().length>0||"Exclude required"});l.push($.trim()),m=await(0,N.confirm)({message:"Add another exclude?",default:!1})}}let p=[];if(await(0,N.confirm)({message:"Add protected patterns?",default:!1})){let m=!0;for(;m;){let $=await(0,N.input)({message:"Protected path/pattern:",validate:B=>B.trim().length>0||"Protected required"});p.push($.trim()),m=await(0,N.confirm)({message:"Add another protected?",default:!1})}}P({project:t,version:s,blockBots:r,blockHeadless:n,sources:a,excludes:l,protected:p}),d("Created .scdn.json config file")}if(Q()){d("Sending to Smooth CDN...");let t=w(),s,r=y(`Creating project "${t.project}"`);try{s=await e.post("/projects",{name:t.project,slug:t.projectSlug||"",withVersioning:o||!!t?.version,version:o||t?.version?t.version:"",block_bots:t.blockBots,block_headless:t.blockHeadless,sources:t?.sources||[],excludes:t?.excludes||[],protected:t?.protected||[],failIfExist:!1}),s.status===201?h(`Created new project "${t.project} ${t.version}"`,r):s.status===200&&h(`Project exist "${t.project} ${t.version}"`,r),s.data.id&&s.data.id!==t.projectId&&(t.projectId=s.data.id,P(t)),await Fe({force:!1}),d("Sent to Smooth CDN")}catch(n){g("Cannot create project",r,n)}}});var Dt=require("commander"),zt=f(require("fs"),1),Vt=f(require("path"),1);var Wt=new Dt.Command("inspect-assets").description("Validates assets paths and lists assets for deployments").action(async()=>{let e=w();if(!e){u("No .scdn.json found.");return}d("Checking paths"),e?.sources?.forEach(t=>{let s=$e(t);s?O(t):u(`${t} [${s}]`)}),e?.excludes?.forEach(t=>{let s=$e(t);s?O(t):u(`${t} [${s}]`)}),d("Checking assets:");let o=await D(e.sources,e.excludes);o.length===0?u("No assets found"):o.forEach(t=>{let s=Vt.default.basename(t),r=0;try{r=zt.default.statSync(t).size}catch{throw new Error(`Cannot read file: ${t}`)}let n=re(e.plan||0,s,r);n===!0?O(s):u(`${s} [${n}]`)}),d("")});var Rt=require("commander"),Ht=require("@inquirer/prompts");var Xt=new Rt.Command("load").description("Initialize project that already exists").action(async()=>{let e=w();if(e?.project&&e?.projectSlug)O("Project already initiated in .scdn config file.");else{let o=y("Fetching available projects...");try{let s=await(await v()).get("/projects?full=1");if(s.data.length>0){h(`${s.data.length} projects available`,o);let r=await(0,Ht.select)({message:"Which project to load:",choices:s.data.map(a=>({name:a.name,value:a.id}))}),n=s.data.find(a=>a.id===r);P({...e,project:n.name,projectId:n.id,projectSlug:n.slug,customSubdomain:n?.customSubdomain||"",version:n.withVersioning?n.latestVersion||n?.versions[0]?.version||"1.0.0":"",blockBots:!!n.blockBots,blockHeadless:!!n.blockHeadless,sources:n?.sources?JSON.parse(n.sources):[],excludes:n?.excludes?JSON.parse(n.excludes):[],protected:n?.protected?JSON.parse(n.protected):[]}),O("Config file saved")}else g("No projects available to be loaded from Smooth CDN",o)}catch(t){g("Failed to fetch projects",o,t)}}});var Ut=require("commander");var Jt=new Ut.Command("publish-version").description("Publish selected version").argument("[version]","Version number, e.g. 1.0.0").action(async(e="")=>{let o=await v(),t=w();if(!t){u("No .scdn.json found.");return}let s=e||t.version;if(s){let r=y(`Publishing version "${s}"`);try{let n=await o.post(`/projects/${t.projectId}/versions/publish`,{version:s});n.status===201?h(`Published new version "${s}"`,r):n.status===200&&h(`Version already published "${s}"`,r)}catch(n){g(`Cannot publish version ${s}`,r,n)}}else u('Missing version. Please run "scdn create-version <version>" first.')});var qt=require("commander"),pe=require("@inquirer/prompts");var Gt=new qt.Command("revoke-access").description("Revoke access for smooth resources").action(async()=>{let e=await v(),o=w();if(!o){u("No .scdn.json found.");return}let t=await(0,pe.input)({message:"Enter user email to revoke access (mail@example.com):",validate:s=>oe(s)?!0:"Invalid mail format"});try{let s=y("Revoking access..."),r=await e.post("/customer/accesses",{email:t});r.data?.length<=0&&g(`No accesses found for "${t}"`,s),h(`Fetched ${r.data.length} accesses`,s);let n=await(0,pe.select)({message:"What access to revoke:",choices:r.data.map(l=>({name:`${l.project} (${l.assets.join(",")})`,value:l.id}))}),a=y("Revoking access..."),i=await e.post(`/projects/${o.projectId}/accesses/${n}/revoke`);i.status===201?h("Selected access revoked",a):i.status===200?h("Selected access already revoked",a):g("Failed to revoke access",a)}catch(s){u("Failed to revoke access",s)}});var Kt=require("commander");var Ts=["FREE","PRO","PREMIUM"],Zt=new Kt.Command("status").description("Get a status summary").action(async()=>{let e=await v(),o=await w();if(!o){u("No .scdn.json found.");return}let t,s=y("Fetching status...");try{t=await e.get("/status"),h("Fetched",s);try{let{name:r,email:n,plan:a,slug:i,planExpiration:l,limits:c,usage:p,projects:x=[]}=t.data;o.plan!==a&&(o.plan=a,P(o)),o.userSlug!==i&&(o.userSlug=i,P(o)),d(`Logged in as ${r} (${i} - ${n})`),d(`Plan: ${Ts[a]} ${l?`(Expires at ${he(l)})`:""}`),p&&(d("Usage limits:"),d(` Requests: ${H(p.requests)}/${H(p.maxRequests)}`),d(` Bandwidth: ${xe(p.bandwidth)}/${xe(p.maxBandwidth)}`),p.periodEnd&&d(` Resets at: ${he(p.periodEnd)}`)),d(""),d(`Projects (used projects: ${x.length}/${c.projects}):`),x.map((I,m)=>{d(` ${m+1}. ${I.name} (used assets: ${H(I.assetsCount)}/${H(c.assetsPerProject)})`)})}catch(r){g("Failed to parse status",s,r)}}catch(r){g("Failed to parse status",s,r)}});var Yt=require("module"),Ms={},Bs=(0,Yt.createRequire)(Ms.url),ks=Bs("../package.json"),j=new Qt.Command;j.name("scdn").description("Smooth CDN CLI").version(ks.version);j.addCommand(We);j.addCommand(nt);j.addCommand(lt);j.addCommand(Mt);j.addCommand(Wt);j.addCommand(Xt);j.addCommand(Xe);j.addCommand(Je);j.addCommand(Jt);j.addCommand(Bt);j.addCommand(Gt);j.addCommand(Zt);j.parse();
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ import{Command as Is}from"commander";import{Command as ut}from"commander";import ft from"axios";import te from"keytar";var se="smoothcdn",oe="cli_token";async function Ae(e){await te.setPassword(se,oe,e)}async function Pe(){return process.env.SCDN_TOKEN||te.getPassword(se,oe)}async function Fe(){await te.deletePassword(se,oe)}async function y(){let e=await Pe();return ft.create({baseURL:"https://smoothcdn.com/api",headers:e?{Authorization:`Bearer ${e}`}:{}})}import{confirm as gt}from"@inquirer/prompts";import re from"fs";import pt from"path";function ne(e=process.cwd()){return pt.join(e,".scdn.json")}function J(){return re.existsSync(ne())}function x(){return J()?JSON.parse(re.readFileSync(ne(),"utf8")):{}}function b(e){re.writeFileSync(ne(),JSON.stringify(e,null,2))}import ie from"ora";import{formatDate as mt}from"date-fns";import{enUS as dt}from"date-fns/locale";var Ne=(e,o)=>{let t,s;return o&&(t=o?.status||o?.code,s=o?.response?.data?.error||o?.data?.error||o?.message),`${e}${t?` [${t}]`:""}${s?` [${s}]`:""}`},m=e=>{console.log(e)},N=e=>{ie(e).succeed()},d=(e,o)=>{ie(Ne(e,o)).fail()},w=e=>ie(e).start(),g=(e,o)=>{o.succeed(e)},u=(e,o,t)=>{o.fail(Ne(e,t))};function ae(e){return typeof e=="string"&&(e=new Date(e)),mt(e,"dd MMMM yyyy HH:mm:ss",{locale:dt})}function ce(e,o=2){if(!e||e===0)return"0 B";let t=1024,s=o<0?0:o,r=["B","KB","MB","GB","TB"],n=Math.floor(Math.log(e)/Math.log(t));return parseFloat((e/Math.pow(t,n)).toFixed(s))+" "+r[n]}function R(e){if(e==null)return"0";let o=Math.abs(e);return o<1e3?e.toString():o<1e6?(e/1e3).toFixed(e%1e3===0?0:1)+"k":o<1e9?(e/1e6).toFixed(e%1e6===0?0:1)+"M":(e/1e9).toFixed(e%1e9===0?0:1)+"B"}var je=new ut("create-version").description("Create version for next deployments").argument("[version]","Version number, e.g. 1.0.0").action(async(e="")=>{let o=await y(),t=x();if(!t){d("No .scdn.json found.");return}let s=e||t.version,r=await gt({message:"Create version as blank (no assets will be copied from previous version automatically) ?",default:!1});if(s){let n=w(`Creating version "${s}"`);try{let a=await o.post(`/projects/${t.projectId}/versions`,{version:s,blank:r,failIfExist:!1});a.status===201?g(`Created new version "${s}"`,n):a.status===200&&g(`Version exist "${s}"`,n),e&&(t.version=e,b(t),m(".scdn.json config file updated"),N(`Using version ${e}`))}catch(a){u(`Cannot create version ${e}`,n,a)}}else d('Missing version. Please run "scdn create-version <version>" first.')});import{Command as ht}from"commander";import le from"os";import xt from"open";var Ie=new ht("login").description("Authenticate via browser").action(async()=>{let e=await y(),o=w("Starting login..."),t=[];try{let s=le.userInfo();s.username&&t.push(s.username)}catch{}try{let s=le.platform();s&&t.push(s)}catch{}try{let s=le.machine();s&&t.push(s)}catch{}try{let{data:s}=await e.post("/auth/cli",{label:t.join("-")}),{key_id:r,verification_url:n}=s;await xt(n),o.text="Waiting for authentication...";let a=null;for(;!a;){await new Promise(F=>setTimeout(F,1500));let f=await e.post("/auth/cli/poll",{keyId:r}).catch(()=>null);if(!f?.data)continue;let{status:h}=f.data;if(h!=="pending"){if(h==="expired"){u("Login session expired. Try again.",o);return}if(h==="active"){a=f.data.api_key;break}if(h==="already_active"){u("This login session is already used.",o);return}}}await Ae(a);let i=x(),c=await(await y()).get("/status");c.data&&(i.userSlug=c.data?.slug||"",c.data?.plan>=0&&i?.plan!==c.data?.plan&&(i.plan=c.data.plan),b(i)),g("Login successful!",o)}catch{u("There was an error when authorizing",o)}});import{Command as wt}from"commander";var Oe=new wt("logout").description("Remove credential data").action(async()=>{await Fe(),N("Successfully logged out")});import{Command as Lt}from"commander";import{confirm as B,input as D,select as ze}from"@inquirer/prompts";import _t from"clipboardy";import{globby as Le}from"globby";async function M(e=[],o=[]){let t=await Le(e,{dot:!0});if(o.length===0)return t;let s=await Le(o,{dot:!0});return t.filter(r=>!s.includes(r))}var _e=["js","mjs","css","json","svg","txt","csv","xml","md"],_=["woff2","woff","ttf","otf"],T=["png","jpg","jpeg","webp","avif"],j=["wav","aiff","aif","flac","mp3","ogg","oga","m4a","aac"],q=["pdf","doc","docx","xls","xlsx","ppt","pptx","odt","ods","odp"],Te=["json","txt","csv","xml","md","doc","docx","xls","xlsx","ppt","pptx","odt","ods","odp"],fe={0:{projects:1,assetsPerProject:50,bandwidth:1073741824,maxRequests:2e4,maxTextAssetSize:204800,maxImageAssetSize:2097152,maxAudioAssetSize:2097152,usageEventsMaxDays:3,usageEventsMaxAmount:100},1:{projects:10,assetsPerProject:1e4,bandwidth:107374182400,maxRequests:5e6,maxTextAssetSize:512e3,maxImageAssetSize:20971520,maxAudioAssetSize:52428800,usageEventsMaxDays:7,usageEventsMaxAmount:500},2:{projects:50,assetsPerProject:1e5,bandwidth:1099511627776,maxRequests:5e7,maxTextAssetSize:1048576,maxImageAssetSize:52428800,maxAudioAssetSize:104857600,usageEventsMaxDays:14,usageEventsMaxAmount:1e3}};var yt=({url:e,alt:o="",critical:t=!1,width:s,height:r})=>`<img
3
+ src="${e}"
4
+ alt="${o}"${t?`
5
+ fetchpriority="high"
6
+ decoding="sync"`:`
7
+ loading="lazy"
8
+ decoding="async"`}${s?`
9
+ width="${s}"`:""}${r?`
10
+ height="${r}"`:""}
11
+ />`,St=({url:e,scriptType:o="standard"})=>{switch(o){case"critical":return`<link
12
+ rel="preload"
13
+ as="script"
14
+ href="${e}"
15
+ >
16
+ <script
17
+ src="${e}"
18
+ defer
19
+ ></script>`;case"async":return`<script
20
+ src="${e}"
21
+ async
22
+ ></script>`;default:return`<script
23
+ src="${e}"
24
+ defer
25
+ ></script>`}},vt=({url:e,preload:o=!1})=>o?`<link
26
+ rel="preload"
27
+ as="style"
28
+ href="${e}"
29
+ />
30
+ <link
31
+ rel="stylesheet"
32
+ href="${e}"
33
+ />`:`<link
34
+ rel="stylesheet"
35
+ href="${e}"
36
+ />`,Et=({url:e,controls:o=!1,loop:t=!1,preload:s="none"})=>`<audio
37
+ src="${e}"${s?`
38
+ preload="${s}"`:""}${o?`
39
+ controls`:""}${t?`
40
+ loop`:""}
41
+ ></audio>`,Ct=({url:e,extension:o,fontFamily:t="",fontDisplay:s="swap"})=>`<style>
42
+ @font-face {
43
+ font-family: "${t}";
44
+ src: url("${e}") format("${o}");
45
+ font-display: ${s};
46
+ }
47
+ </style>`,$t=({url:e,lazy:o=!1,title:t="",width:s="",height:r=""})=>`<iframe
48
+ src="${e}"${o?`
49
+ loading="lazy"`:""}${t?`
50
+ title="${t}`:""}${s?`
51
+ width="${s}"`:""}${r?`
52
+ height="${r}"`:""}
53
+ ></iframe>`,bt=({url:e,assetName:o,newTab:t=!0,download:s=!0,noreferrer:r=!1})=>{let n=[];t&&n.push("noopener"),r&&n.push("noreferrer");let a=n.length?n.join(" "):"";return`<a
54
+ href="${e}"${t?`
55
+ target="_blank"`:""}${a?`
56
+ rel="${a}"`:""}${s?`
57
+ download`:""}
58
+ >Download ${o}</a>`};function Be(e={}){let{extension:o}=e;return o==="js"||o==="mjs"?St(e):o==="css"?vt(e):o==="pdf"?$t(e):o==="svg"||T.includes(o)?yt(e):j.includes(o)?Et(e):_.includes(o)?Ct(e):bt(e)}import ge from"fs";import Nt from"crypto";import K from"path";import{imageSize as jt}from"image-size";import{XMLParser as It}from"fast-xml-parser";import z from"fs";import H from"path";var pe=H.resolve(".scdn");function At(){z.existsSync(pe)||z.mkdirSync(pe,{recursive:!0})}function me(e){return At(),H.join(pe,e)}function A(e){let o=me(e);return z.existsSync(o)||z.mkdirSync(o,{recursive:!0}),o}function C(e,o,t,s){let r=A(e),n=H.join(r,`${o}.${t}`);return z.writeFileSync(n,s),n}function v(e,o,t){let s=H.join(me(e),`${o}.${t}`);return z.existsSync(s)}function S(e,o,t){return H.join(me(e),`${o}.${t}`)}import G from"sharp";async function Pt(e){return G(e).png({compressionLevel:9,quality:70,adaptiveFiltering:!0,palette:!0,dither:.5,effort:8}).toBuffer()}async function Ft(e){return G(e).jpeg({quality:70,chromaSubsampling:"4:2:0",mozjpeg:!0}).toBuffer()}async function de(e,o={}){let t=o.quality??80;return G(e).webp({quality:t,effort:6}).toBuffer()}async function ue(e,o={}){let t=o.quality??50;return G(e).avif({quality:t,speed:4}).toBuffer()}async function ke(e,o){let t=o.toLowerCase();return t==="png"?Pt(e):t==="jpg"||t==="jpeg"?Ft(e):t==="webp"?de(e):t==="avif"?ue(e):e}function Ot(e){return Nt.createHash("sha256").update(e).digest("hex")}async function Me(e){let o=ge.readFileSync(e),t=K.extname(e).replace(".","").toLowerCase(),s=K.basename(e,K.extname(e)),r=Ot(o);A(r);let n=v(r,s,t),a=v(r,s,"webp"),i=v(r,s,"avif"),l=t==="webp",c=t==="avif";if(n&&(l||a)&&(c||i))return{hash:r,originalExt:t,originalPath:S(r,s,t),processedPaths:[!l&&a?S(r,s,"webp"):null,!c&&i?S(r,s,"avif"):null].filter(Boolean)};let f=await ke(o,t),h=l?null:await de(o),F=c?null:await ue(o),p=C(r,s,t,f),E=!l&&h?C(r,s,"webp",h):null,L=!c&&F?C(r,s,"avif",F):null;return{hash:r,originalExt:t,originalPath:p,processedPaths:[E,L].filter(ee=>!ee?.endsWith(".scdn"))}}function De(e){if(K.extname(e).toLowerCase().replace(".","")==="svg"){let r=ge.readFileSync(e,"utf-8"),i=new It({ignoreAttributes:!1}).parse(r)?.svg;if(!i)return null;if(i["@_width"]&&i["@_height"])return{width:parseFloat(i["@_width"]),height:parseFloat(i["@_height"])};if(i["@_viewBox"]){let[,,l,c]=i["@_viewBox"].split(/\s+/).map(Number);if(l&&c)return{width:l,height:c}}return null}let t=ge.readFileSync(e),s=jt(t);return!s.width||!s.height?null:{width:s.width,height:s.height}}function Tt(e,o,t,s,r=""){return t?`https://${t}.smoothcdn.com${r?`/${r}`:""}${s}`:`https://cdn.smoothcdn.com/${e}/${o}${r?`/${r}`:""}${s}`}var Ve=new Lt("get-snippet").description("Output snippet for asset").action(async()=>{let e=x();if(!e){d("No .scdn.json found.");return}let o=await M(e.sources,e.excludes);if(o.length<=0){d("No assets found");return}m(`Found assets ${o.length}`);let t=await ze({message:"What asset to get snippet for:",choices:o.map(c=>({name:c,value:c}))}),[,...s]=t.split("/"),r=`/${s.join("/")}`,n=t.split(".").pop()?.toLowerCase()??"",a=Tt(e.userSlug,e.projectSlug,e.customSubdomain||"",r,e.version||""),i={extension:n,url:a,assetName:t};if(n==="svg"||T.includes(n)){let c=await De(t);i.alt=await D({message:"Alt attribute",validate:f=>f?!0:"Alt attribute is required",default:t}),i.critical=await B({message:"Is critical asset (must be loaded above the fold e.g. in hero section)"}),i.width=await D({message:"Width",default:c?.width?.toString()||""}),i.height=await D({message:"Height",default:c?.height?.toString()||""})}(n==="js"||n==="mjs")&&(i.type=await ze({message:"Script type",choices:[{name:"Standard",value:"standard"},{name:"Critical",value:"critical"},{name:"Asynchronous",value:"async"}]})),n==="css"&&(i.preload=await B({message:"Preload"})),j.includes(n)&&(i.controls=await B({message:"Controls attribute (shows audio control buttons)"}),i.loop=await B({message:"Loop attribute (plays audio in loop)"})),_.includes(n)&&(i.fontFamily=await D({message:"Font family",default:t})),n==="pdf"&&(i.lazy=await B({message:"Lazy load"}),i.title=await D({message:"Title",default:t}),i.width=await D({message:"Width",default:"100%"}),i.height=await D({message:"Height",default:"600"})),Te.includes(n)&&(i.newTab=await B({message:"Open in new tab"}),i.noreferrer=await B({message:'Hide referrer (adds "noreferrer" to rel attribute)'}),i.download=await B({message:"Download attribute"}));let l=Be(i);await _t.write(l),m(""),m(l),m(""),N("Snippet was copied to clipboard")});import{Command as kt}from"commander";import{checkbox as Mt,input as Re}from"@inquirer/prompts";import Bt from"micromatch";function Z(e){if(!e)return!1;let o=e.trim();return o.length<6||o.length>254||o.includes("..")?!1:/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(o)}function Q(e=0,o,t){let{maxTextAssetSize:s,maxImageAssetSize:r}=fe[e],n=o.split(".").pop()?.toLowerCase()??"",a=_e.includes(n),i=_.includes(n),l=T.includes(n),c=j.includes(n),f=q.includes(n);return!a&&!i&&!l&&!c&&!f?`Unsupported file type: ${n}`:(a||i)&&t>s?`Asset too large. Max allowed ${Math.round(s/1024)} KB, trying to send ${Math.round(t/1024)} KB`:(l||f)&&t>r?`Asset too large. Max allowed ${Math.round(r/(1024*1024))} MB, trying to send ${(t/1024/1024).toFixed(2)} MB`:!0}function We(e=0,o,t){let{maxAudioAssetSize:s}=fe[e],r=o.split(".").pop()?.toLowerCase()??"";return j.includes(r)&&t>s?`Asset too large. Max allowed ${Math.round(s/(1024*1024))} MB, received ${(t/1024/1024).toFixed(2)} MB`:!0}function he(e){try{Bt.matcher(e)}catch{return"Invalid pattern"}return e.includes("\0")||e.includes("..")||e.includes("~")||e.startsWith("/")||/^[A-Za-z]:\\/.test(e)?"Contains invalid segments":!0}var He=new kt("grant-access").description("Grant access for smooth resources").option("--force","Override expiration date if access already exists").action(async({force:e=!1})=>{let o=await y(),t=x();if(!t){d("No .scdn.json found.");return}let r=((await o.get(`/projects/${t.projectId}/assets`)).data??[]).filter(n=>n.protected).map(n=>`${n.path}${n.fileName}`);if(r.length===0)d("No protected assets found");else{let n=await Mt({message:"Select protected assets to grant access to:",choices:r.map(c=>({name:c,value:c})),validate:c=>c.length>0?!0:"Select at least one asset."}),a=await Re({message:"Enter user email to grant access (mail@example.com):",validate:c=>Z(c)?!0:"Invalid mail format"}),i=await Re({message:"Enter expiration date (leave empty if no expiration should be set):"}),l=w("Granting access...");try{let c={force:e,assets:n,email:a,expiresAt:i},f=await o.post(`/projects/${t.projectId}/accesses/grant`,c);f.status===201?g(`Access granted for "${a}"`,l):f.status===200?g(`Access already exists for "${a}"`,l):u("Unexpected response from server",l,f)}catch(c){u("Failed to grant access",l,c)}}});import{Command as ys}from"commander";import{input as X,confirm as k}from"@inquirer/prompts";import{Command as xs}from"commander";import ws from"form-data";import Y from"fs";import tt from"path";import Dt from"fs";import zt from"crypto";import xe from"path";import{execFileSync as Xe}from"child_process";function Vt(e){return zt.createHash("sha256").update(e).digest("hex")}function Wt(){try{return Xe("ffmpeg",["-version"],{stdio:"ignore"}),!0}catch{return!1}}function Rt(e,o){Xe("ffmpeg",["-y","-i",e,"-map_metadata","-1","-vn","-c:a","aac","-q:a","2","-movflags","+faststart",o],{stdio:"ignore"})}async function Ue(e){if(!Wt())return!1;let o=Dt.readFileSync(e),t=xe.extname(e).replace(".","").toLowerCase(),s=xe.basename(e,xe.extname(e));if(!j.includes(t))throw new Error(`Unsupported audio extension: .${t}`);let r=Vt(o);A(r);let n=v(r,s,t),a=v(r,s,"m4a"),i=n?S(r,s,t):C(r,s,t,o);if(a)return{hash:r,originalExt:t,originalPath:i,processedPaths:[S(r,s,"m4a")]};let l=S(r,s,"m4a");return Rt(i,l),{hash:r,originalExt:t,originalPath:i,processedPaths:[l]}}import Ht from"fs";import Xt from"crypto";import we from"path";import Ut from"postcss";import Jt from"cssnano";function qt(e){return Xt.createHash("sha256").update(e).digest("hex")}function Gt(e){let o=e.split(`
59
+ `),t=o.reduce((n,a)=>n+a.length,0)/o.length,s=(e.match(/\s/g)?.length??0)/e.length,r=/\/\*/.test(e);return t>200&&s<.12&&!r}async function Kt(e){return(await Ut([Jt({preset:["default",{discardComments:!0,normalizeWhitespace:!0,mergeRules:!1,mergeLonghand:!1,reduceIdents:!1,zindex:!1,calc:!1,colormin:!1,normalizeUrls:!1,normalizeTimingFunctions:!1,normalizeUnicode:!1,minifyFontValues:!1,minifySelectors:!1}]})]).process(e,{from:void 0})).css}async function Je(e){let o=Ht.readFileSync(e),t=we.extname(e).replace(".","").toLowerCase(),s=we.basename(e,we.extname(e)),r=qt(o);if(A(r),v(r,s,t))return{hash:r,originalExt:t,originalPath:S(r,s,t)};let a=o.toString("utf8"),i=o;if(!Gt(a)){let f=await Kt(a);i=Buffer.from(f)}let c=C(r,s,t,i);return{hash:r,originalExt:t,originalPath:c}}import Qt from"fs";import Yt from"crypto";import ye from"path";import Zt from"ttf2woff2";async function qe(e,o){return e}async function Ge(e){return Buffer.from(Zt(e))}function es(e){return Yt.createHash("sha256").update(e).digest("hex")}async function Ke(e){let o=Qt.readFileSync(e),t=ye.extname(e).replace(".","").toLowerCase(),s=ye.basename(e,ye.extname(e)),r=es(o);A(r);let n=v(r,s,t),a=v(r,s,"woff2"),i=t==="woff2";if(n&&(i||a))return{hash:r,originalExt:t,originalPath:S(r,s,t),processedPaths:[!i&&a?S(r,s,"woff2"):null].filter(Boolean)};let l=await qe(o,t),c=i?null:await Ge(o),f=C(r,s,t,l),h=!i&&c?C(r,s,"woff2",c):null;return{hash:r,originalExt:t,originalPath:f,processedPaths:[h].filter(F=>F&&!F.endsWith(".scdn"))}}import Ze from"fs";import ts from"crypto";import Se from"path";import{minify as ss}from"terser";function os(e){return ts.createHash("sha256").update(e).digest("hex")}async function rs(e){return(await ss(e,{compress:!1,mangle:{toplevel:!1},format:{comments:!1,semicolons:!0}})).code}function ns(e){let o=e.split(`
60
+ `),t=o.reduce((r,n)=>r+n.length,0)/o.length,s=(e.match(/\s/g)?.length??0)/e.length;return t>200&&s<.15&&!/\/\*|\n\s*\/\//.test(e)}async function Qe(e){let o=Ze.readFileSync(e),t=Se.extname(e).replace(".","").toLowerCase(),s=Se.basename(e,Se.extname(e)),r=os(o);if(A(r),v(r,s,t))return{hash:r,originalExt:t,originalPath:S(r,s,t)};let a=Ze.readFileSync(e),i=a.toString("utf8"),l=a;if(!ns(i)){let h=await rs(i);l=Buffer.from(h)}let f=C(r,s,t,l);return{hash:r,originalExt:t,originalPath:f}}import is from"fs";import as from"crypto";import ve from"path";function cs(e){return as.createHash("sha256").update(e).digest("hex")}function ls(e){let o=(e.match(/\s/g)?.length??0)/e.length,t=e.split(`
61
+ `).length;return o<.08&&t<=2}function fs(e){let o=JSON.parse(e);return JSON.stringify(o)}async function Ye(e){let o=is.readFileSync(e),t=ve.extname(e).replace(".","").toLowerCase(),s=ve.basename(e,ve.extname(e)),r=cs(o);if(A(r),v(r,s,t))return{hash:r,originalExt:t,originalPath:S(r,s,t)};let a=o.toString("utf8"),i=o;if(!ls(a)){let f=fs(a);i=Buffer.from(f)}if(!i||i.length===0)throw new Error("JSON minification produced empty output");let c=C(r,s,t,i);return{hash:r,originalExt:t,originalPath:c}}import ps from"fs";import ms from"crypto";import Ee from"path";import{optimize as ds}from"svgo";function us(e){return ms.createHash("sha256").update(e).digest("hex")}function gs(e){let o=(e.match(/\s/g)?.length??0)/e.length,t=/<!--/.test(e),s=e.split(`
62
+ `).length;return o<.1&&s<=2&&!t}function hs(e){let o=console.warn,t=console.info;console.warn=()=>{},console.info=()=>{};let s;try{s=ds(e,{multipass:!0,plugins:[{name:"preset-default",params:{overrides:{convertPathData:!1,convertStyleToAttrs:!1,cleanupIds:!1,removeViewBox:!1,inlineStyles:!1}}},"removeUselessStrokeAndFill"]})}finally{console.warn=o,console.info=t}if(!("data"in s)||!s.data)throw new Error("SVG optimization failed");return s.data}async function et(e){let o=ps.readFileSync(e),s=Ee.parse(e).ext.replace(".","").toLowerCase(),r=Ee.basename(e,Ee.extname(e)),n=us(o);if(A(n),v(n,r,s))return{hash:n,originalExt:s,originalPath:S(n,r,s)};let i=o.toString("utf8"),l=o;if(!gs(i)){let h=hs(i);l=Buffer.from(h)}let f=C(n,r,s,l);return{hash:n,originalExt:s,originalPath:f}}var Ce=async({force:e=!1,sync:o=!1})=>{let t=await y(),s=x();if(!s){d("No .scdn.json found.");return}let r=!1;if(!s.projectId)d('Missing projectId in config file. Please run "scdn init" or "scdn load" command first.');else try{let n=await t.get("/status"),{plan:a,slug:i}=n.data;s.plan!==a&&(s.plan=a,b(s)),s.userSlug!==i&&(s.userSlug=i,b(s)),m(`Deploying assets${s.version?` for version ${s.version}`:""}...`);let l=await M(s.sources,s.excludes),c=await M(s.protected),f=[];if(l.length>0){let h=w("Deploying asset"),F=0;for(let p of l)if(++F,r&&!e)u(`Selected version was already published "${p}"`,h);else try{let E=tt.basename(p),ee=p.replace(E,"").replace(/^\/+|\/+$/g,""),[,...lt]=ee.split("/"),$e=lt.join("/"),U=0;try{U=Y.statSync(p).size}catch{throw new Error(`Cannot read file: ${p}`)}let be=Q(s.plan||0,E,U);if(be!==!0)throw new Error(be);let I=E.split(".").pop()?.toLowerCase()??"",$;if(T.includes(I)&&I!=="avif")$=await Me(p);else if(I==="js"||I==="mjs")$=await Qe(p);else if(I==="css")$=await Je(p);else if(I==="json")$=await Ye(p);else if(I==="svg")$=await et(p);else if(_.includes(I))$=await Ke(p);else if(j.includes(I)){if($=await Ue(p),$===!1)throw new Error("To optimize audio - ffmpeg is required. Install it from https://ffmpeg.org/");if($?.originalPath){try{U=Y.statSync($.originalPath).size}catch{throw new Error(`Cannot read file: ${$.originalPath}`)}let W=We(s.plan||0,tt.basename($.originalPath),U);if(W!==!0)throw new Error(W)}else throw new Error("Audio was not optimized")}else q.includes(I)&&($={originalPath:p,processedPaths:[]});let O=new ws;O.append("asset",Y.createReadStream($?.originalPath?$.originalPath:p)),$?.processedPaths?.forEach(W=>{O.append("sub_assets[]",Y.createReadStream(W))}),O.append("path",$e),O.append("version",s.version||""),O.append("optimize",0),O.append("failIfExist",0),O.append("protected",c.includes(p)?1:0),e&&O.append("force",1),f.push({fileName:E,path:$e});let V=await t.post(`/projects/${s.projectId}/assets`,O,{headers:O.getHeaders()});V.status===201?g(`Deployed "${p}"`,h):V.status===200&&(V.data?.error==="Cannot deploy/override assets on published version"?(r=!0,u(`Failed to deploy ${p}`,h,V)):V.data?.dedupe&&g(`Asset already exists "${p}"`,h))}catch(E){u(`Failed to deploy ${p}`,h,E)}if(o){let p=w("Sync...");try{let E=await t.post(`/projects/${s.projectId}/assets/sync`,{force:e,version:s.version,assetsToSync:f});g(`Sync: ${E?.data?.removed??0} files removed`,p)}catch(E){u(E,p)}}}else d("No assets to deploy")}catch(n){d("Cannot proceed with pushing assets",n)}m("")},st=new xs("push").option("--force","Override assets on published versions too").option("--sync","Additionally removes files that do not exists in project anymore").description("Push assets to CDN").action(Ce);var ot=new ys("init").description("Initialize new project").action(async()=>{let e=await y(),o=!1;if(J())m("Detected .scdn.json config file");else{let t=await X({message:"Project name:"});o=await k({message:"Enable versioning:",default:!1});let s=o?await X({message:"Initial version:",default:"1.0.0"}):"",r=await k({message:"Block bots for this variant?",default:!1}),n=await k({message:"Block headless browsers?",default:!1}),a=[],i=!0;for(;i;){let p=await X({message:"Add source path/pattern (glob allowed):",validate:L=>L.trim().length>0||"Source required"});a.push(p.trim()),i=await k({message:"Add another source?",default:!1})}let l=[];if(await k({message:"Add exclude patterns?",default:!1})){let p=!0;for(;p;){let E=await X({message:"Exclude path/pattern:",validate:L=>L.trim().length>0||"Exclude required"});l.push(E.trim()),p=await k({message:"Add another exclude?",default:!1})}}let f=[];if(await k({message:"Add protected patterns?",default:!1})){let p=!0;for(;p;){let E=await X({message:"Protected path/pattern:",validate:L=>L.trim().length>0||"Protected required"});f.push(E.trim()),p=await k({message:"Add another protected?",default:!1})}}b({project:t,version:s,blockBots:r,blockHeadless:n,sources:a,excludes:l,protected:f}),m("Created .scdn.json config file")}if(J()){m("Sending to Smooth CDN...");let t=x(),s,r=w(`Creating project "${t.project}"`);try{s=await e.post("/projects",{name:t.project,slug:t.projectSlug||"",withVersioning:o||!!t?.version,version:o||t?.version?t.version:"",block_bots:t.blockBots,block_headless:t.blockHeadless,sources:t?.sources||[],excludes:t?.excludes||[],protected:t?.protected||[],failIfExist:!1}),s.status===201?g(`Created new project "${t.project} ${t.version}"`,r):s.status===200&&g(`Project exist "${t.project} ${t.version}"`,r),s.data.id&&s.data.id!==t.projectId&&(t.projectId=s.data.id,b(t)),await Ce({force:!1}),m("Sent to Smooth CDN")}catch(n){u("Cannot create project",r,n)}}});import{Command as Ss}from"commander";import vs from"fs";import Es from"path";var rt=new Ss("inspect-assets").description("Validates assets paths and lists assets for deployments").action(async()=>{let e=x();if(!e){d("No .scdn.json found.");return}m("Checking paths"),e?.sources?.forEach(t=>{let s=he(t);s?N(t):d(`${t} [${s}]`)}),e?.excludes?.forEach(t=>{let s=he(t);s?N(t):d(`${t} [${s}]`)}),m("Checking assets:");let o=await M(e.sources,e.excludes);o.length===0?d("No assets found"):o.forEach(t=>{let s=Es.basename(t),r=0;try{r=vs.statSync(t).size}catch{throw new Error(`Cannot read file: ${t}`)}let n=Q(e.plan||0,s,r);n===!0?N(s):d(`${s} [${n}]`)}),m("")});import{Command as Cs}from"commander";import{select as $s}from"@inquirer/prompts";var nt=new Cs("load").description("Initialize project that already exists").action(async()=>{let e=x();if(e?.project&&e?.projectSlug)N("Project already initiated in .scdn config file.");else{let o=w("Fetching available projects...");try{let s=await(await y()).get("/projects?full=1");if(s.data.length>0){g(`${s.data.length} projects available`,o);let r=await $s({message:"Which project to load:",choices:s.data.map(a=>({name:a.name,value:a.id}))}),n=s.data.find(a=>a.id===r);b({...e,project:n.name,projectId:n.id,projectSlug:n.slug,customSubdomain:n?.customSubdomain||"",version:n.withVersioning?n.latestVersion||n?.versions[0]?.version||"1.0.0":"",blockBots:!!n.blockBots,blockHeadless:!!n.blockHeadless,sources:n?.sources?JSON.parse(n.sources):[],excludes:n?.excludes?JSON.parse(n.excludes):[],protected:n?.protected?JSON.parse(n.protected):[]}),N("Config file saved")}else u("No projects available to be loaded from Smooth CDN",o)}catch(t){u("Failed to fetch projects",o,t)}}});import{Command as bs}from"commander";var it=new bs("publish-version").description("Publish selected version").argument("[version]","Version number, e.g. 1.0.0").action(async(e="")=>{let o=await y(),t=x();if(!t){d("No .scdn.json found.");return}let s=e||t.version;if(s){let r=w(`Publishing version "${s}"`);try{let n=await o.post(`/projects/${t.projectId}/versions/publish`,{version:s});n.status===201?g(`Published new version "${s}"`,r):n.status===200&&g(`Version already published "${s}"`,r)}catch(n){u(`Cannot publish version ${s}`,r,n)}}else d('Missing version. Please run "scdn create-version <version>" first.')});import{Command as As}from"commander";import{input as Ps,select as Fs}from"@inquirer/prompts";var at=new As("revoke-access").description("Revoke access for smooth resources").action(async()=>{let e=await y(),o=x();if(!o){d("No .scdn.json found.");return}let t=await Ps({message:"Enter user email to revoke access (mail@example.com):",validate:s=>Z(s)?!0:"Invalid mail format"});try{let s=w("Revoking access..."),r=await e.post("/customer/accesses",{email:t});r.data?.length<=0&&u(`No accesses found for "${t}"`,s),g(`Fetched ${r.data.length} accesses`,s);let n=await Fs({message:"What access to revoke:",choices:r.data.map(l=>({name:`${l.project} (${l.assets.join(",")})`,value:l.id}))}),a=w("Revoking access..."),i=await e.post(`/projects/${o.projectId}/accesses/${n}/revoke`);i.status===201?g("Selected access revoked",a):i.status===200?g("Selected access already revoked",a):u("Failed to revoke access",a)}catch(s){d("Failed to revoke access",s)}});import{Command as Ns}from"commander";var js=["FREE","PRO","PREMIUM"],ct=new Ns("status").description("Get a status summary").action(async()=>{let e=await y(),o=await x();if(!o){d("No .scdn.json found.");return}let t,s=w("Fetching status...");try{t=await e.get("/status"),g("Fetched",s);try{let{name:r,email:n,plan:a,slug:i,planExpiration:l,limits:c,usage:f,projects:h=[]}=t.data;o.plan!==a&&(o.plan=a,b(o)),o.userSlug!==i&&(o.userSlug=i,b(o)),m(`Logged in as ${r} (${i} - ${n})`),m(`Plan: ${js[a]} ${l?`(Expires at ${ae(l)})`:""}`),f&&(m("Usage limits:"),m(` Requests: ${R(f.requests)}/${R(f.maxRequests)}`),m(` Bandwidth: ${ce(f.bandwidth)}/${ce(f.maxBandwidth)}`),f.periodEnd&&m(` Resets at: ${ae(f.periodEnd)}`)),m(""),m(`Projects (used projects: ${h.length}/${c.projects}):`),h.map((F,p)=>{m(` ${p+1}. ${F.name} (used assets: ${R(F.assetsCount)}/${R(c.assetsPerProject)})`)})}catch(r){u("Failed to parse status",s,r)}}catch(r){u("Failed to parse status",s,r)}});import{createRequire as Os}from"module";var Ls=Os(import.meta.url),_s=Ls("../package.json"),P=new Is;P.name("scdn").description("Smooth CDN CLI").version(_s.version);P.addCommand(je);P.addCommand(Ve);P.addCommand(He);P.addCommand(ot);P.addCommand(rt);P.addCommand(nt);P.addCommand(Ie);P.addCommand(Oe);P.addCommand(it);P.addCommand(st);P.addCommand(at);P.addCommand(ct);P.parse();
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@smoothcdn/cli",
3
+ "version": "1.0.3",
4
+ "license": "UNLICENSED",
5
+ "type": "module",
6
+ "private": false,
7
+ "bin": {
8
+ "scdn": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "keywords": [
14
+ "smooth",
15
+ "cdn",
16
+ "content",
17
+ "delivery",
18
+ "network",
19
+ "asset",
20
+ "assets",
21
+ "cli",
22
+ "deploy",
23
+ "token"
24
+ ],
25
+ "scripts": {
26
+ "dev": "ts-node src/index.ts",
27
+ "build": "tsup src/index.ts --format esm,cjs --dts --minify --clean",
28
+ "start": "node dist/index.js",
29
+ "publish": "npm publish --access public"
30
+ },
31
+ "dependencies": {
32
+ "@inquirer/prompts": "^8.0.2",
33
+ "axios": "^1.6.7",
34
+ "chalk": "^5.3.0",
35
+ "clipboardy": "^5.0.2",
36
+ "commander": "^11.0.0",
37
+ "cssnano": "^7.1.2",
38
+ "date-fns": "^4.1.0",
39
+ "fast-xml-parser": "^5.3.3",
40
+ "form-data": "^4.0.0",
41
+ "globby": "^14.0.0",
42
+ "image-size": "^2.0.2",
43
+ "keytar": "^7.9.0",
44
+ "micromatch": "^4.0.8",
45
+ "open": "^10.0.0",
46
+ "ora": "^7.0.1",
47
+ "postcss": "^8.5.6",
48
+ "sharp": "^0.34.5",
49
+ "svgo": "^4.0.0",
50
+ "terser": "^5.44.1",
51
+ "ttf2woff2": "^8.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^20.11.0",
55
+ "ts-node": "^10.9.2",
56
+ "tsup": "^8.5.1",
57
+ "typescript": "^5.3.3"
58
+ }
59
+ }
package/readme.md ADDED
@@ -0,0 +1,94 @@
1
+ # Smooth CDN – CLI Tool
2
+
3
+ Smooth CDN CLI is a command-line tool for managing projects and assets on Smooth CDN.
4
+
5
+ It helps you initialize projects, upload assets, optionally manage versions, and control access to protected resources.
6
+
7
+ The CLI uses a local configuration file (`.scdn.json`) to define project settings, asset sources, and ignore patterns.
8
+
9
+ For CI/CD, you can provide the `SCDN_TOKEN` environment variable to authenticate the CLI in non-interactive environments.
10
+
11
+ [Full documentation](https://smoothcdn.com/docs/cli)
12
+
13
+ ## Requirements
14
+
15
+ - Smooth CDN account – [create an account](https://smoothcdn.com/register)
16
+ - Node.js ≥ 18.17.0 (recommended: Node.js 20 LTS)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install -g @smoothcdn/cli
22
+ ```
23
+
24
+ ## Commands
25
+
26
+ ```shell
27
+ scdn login
28
+ ```
29
+ Logs in to your Smooth CDN account.
30
+
31
+ ```shell
32
+ scdn logout
33
+ ```
34
+ Logs out from your Smooth CDN account.
35
+
36
+ ```shell
37
+ scdn init
38
+ ```
39
+ Initializes a new project and creates a local configuration file.
40
+
41
+ ```shell
42
+ scdn load
43
+ ```
44
+ Loads an existing project into the local config file.
45
+
46
+ ```shell
47
+ scdn create-version [version]
48
+ ```
49
+ Creates a new version and sets it as the active deployment target.
50
+ If no version is provided, the version from the config file will be used.
51
+
52
+ ```shell
53
+ scdn publish-version [version]
54
+ ```
55
+ Publishes the selected version.
56
+ If no version is provided, the version from the config file will be published.
57
+
58
+ ```shell
59
+ scdn push
60
+ ```
61
+ Uploads assets to Smooth CDN.
62
+ Use the `--sync` flag to remove assets from CDN that no longer exist in the project.
63
+
64
+ ```shell
65
+ scdn get-snippet
66
+ ```
67
+ Outputs an embed snippet for the selected asset.
68
+
69
+ ```shell
70
+ scdn inspect-assets
71
+ ```
72
+ Validates asset paths and lists assets before deployment.
73
+
74
+ ```shell
75
+ scdn grant-access
76
+ ```
77
+ Grants an access token for protected assets.
78
+
79
+ ```shell
80
+ scdn revoke-access
81
+ ```
82
+ Revokes a previously granted access token.
83
+
84
+ ```shell
85
+ scdn status
86
+ ```
87
+ Displays account status, including current user, plan, and limits.
88
+
89
+ ## Quick example
90
+ ```shell
91
+ scdn login
92
+ scdn init
93
+ scdn push
94
+ ```