@overlayed/cli 0.6.7 → 0.6.9

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.
Files changed (2) hide show
  1. package/dist/cli.mjs +16 -20
  2. package/package.json +6 -5
package/dist/cli.mjs CHANGED
@@ -1,21 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import{Builtins as e,Cli as t,Command as n,UsageError as r}from"clipanion";import{scope as i,type as a}from"arktype";import o,{createWriteStream as s,existsSync as c,mkdirSync as ee,readdirSync as te,unlinkSync as ne,writeFileSync as re}from"fs";import{ensureDirSync as ie,ensureFileSync as ae}from"fs-extra";import l,{join as oe}from"path";import u from"node:path";import d from"picocolors";import f,{createReadStream as p,existsSync as m,lstatSync as se,readFileSync as ce}from"node:fs";import{glob as h}from"glob";import le,{fileURLToPath as ue}from"node:url";import{createJiti as de}from"jiti";import{readdir as fe}from"node:fs/promises";import g from"jszip";import _,{isXiorError as v}from"xior";import{checkbox as y,password as b}from"@inquirer/prompts";import{Listr as x}from"listr2";import{ListrInquirerPromptAdapter as S}from"@listr2/prompt-adapter-inquirer";import{exec as C}from"child_process";import{promisify as w}from"util";import T from"update-notifier";const E={deployment:`deployment`};function D(e){throw e}function O(e){return Object.prototype.toString.call(e)===`[object Object]`}function k(e){return e?Array.isArray(e)?e:[e]:[]}function A(e){return{all:e||=new Map,on:function(t,n){var r=e.get(t);r?r.push(n):e.set(t,[n])},off:function(t,n){var r=e.get(t);r&&(n?r.splice(r.indexOf(n)>>>0,1):e.set(t,[]))},emit:function(t,n){var r=e.get(t);r&&r.slice().map(function(e){e(n)}),(r=e.get(`*`))&&r.slice().map(function(e){e(t,n)})}}}var j=class{emitter;constructor(){this.emitter=A()}on(e,t){k(e).forEach(e=>{this.emitter.on(e,t)})}off(e,t){k(e).forEach(e=>{this.emitter.off(e,t)})}emit(e,t){k(e).forEach(e=>{this.emitter.emit(e,t)})}removeAllListeners(){this.emitter.all.clear()}};function M(e,...t){return class extends e{static _instance;static getInstance(){return this._instance||=new e(...t),this._instance}static clearInstance(){this._instance=void 0}}}const N=M(class extends j{destroy(){this.removeAllListeners()}fatal(e,t,n){this.emit(`fatal`,{code:t,message:e,data:n,timestamp:Date.now()})}error(e,t,n){this.emit(`error`,{code:t,message:e,data:n,timestamp:Date.now()})}warn(e,t,n){this.emit(`warning`,{code:t,message:e,data:n,timestamp:Date.now()})}});var P=class{data;_schema;_defaultValue;_path;constructor(e){let{schema:t,default:n,path:r}=e;this._schema=t,this._defaultValue=n,this._path=r}set(e){let t=this._schema[`~standard`].validate(e);if(t instanceof Promise)throw Error(`StructuredConfigFile does not support async validation`);if(t.issues){this.reportInvalidConfigFile(t.issues);return}this.data=this.onBeforeSave(e),this.save(e)}get(){return this.data?this.data:this.load()}getKey(e){return this.get()[e]}save(e){let t=this.onBeforeSave(e);try{ae(this.getFilePath()),o.writeFileSync(this.getFilePath(),JSON.stringify(t,void 0,2))}catch{return}return this.data=t}load(){if(!this.fileExists(this.getFilePath())||!this.canReadWriteFile(this.getFilePath())){this.save(this._defaultValue);let e=this.onAfterLoad(this._defaultValue);return this.data=e,e}try{let e=o.readFileSync(this.getFilePath(),`utf8`),t=this.onAfterLoad(this.parseStoredData(e));return this.data=t,t}catch{return this.data=this._defaultValue}}onBeforeSave(e){return e}onAfterLoad(e){return e}getFilePath(){return this._path}parseStoredData(e){try{let t=a(`string.json.parse`).to(`object`)(e);if(t instanceof a.errors)return this._defaultValue;let n=this._schema[`~standard`].validate(t);if(n instanceof Promise)throw Error(`StructuredConfigFile does not support async validation`);return n.issues?this.migrate(t,this._defaultValue):this.migrate(n.value,this._defaultValue)}catch{return this._defaultValue}}canReadWriteFile(e){try{return o.accessSync(e,o.constants.R_OK|o.constants.W_OK),!0}catch{return!1}}fileExists(e){try{return o.accessSync(e,o.constants.F_OK),!0}catch{return!1}}migrate(e,t){let n={...e};for(let r in t)Object.prototype.hasOwnProperty.call(t,r)&&(r in e?e[r]!==null&&t[r]!==null&&typeof e[r]==`object`&&typeof t[r]==`object`&&!Array.isArray(e[r])&&!Array.isArray(t[r])?n[r]=this.migrate(e[r],t[r]):Array.isArray(t[r])&&(n[r]=e[r]):n[r]=t[r]);return n}reportInvalidConfigFile(e){N.getInstance().error(`Invalid config file`,`INVALID_CONFIG_FILE`,{issues:e.map(e=>e.message),filePath:this._path,data:this.data})}};const F=`**/overlayed.config.{js,ts,mts}`;async function I(){let e=R(),t=de(import.meta.url);return(await Promise.all(e.map(async e=>e.endsWith(`.ts`)?[await t.import(e,{default:!0}),e]:[(await import(le.pathToFileURL(e).toString())).default,e]))).filter(([e])=>L(e)).map(([e,t])=>[e,t])}function L(e){return O(e)&&`app`in e}function R(){return h.globSync(F,{absolute:!0,cwd:process.cwd()})}var z=class{pattern;options;constructor(e,t){this.pattern=e,this.options=t}},B=class{defaultIgnore=[`overlayed.config.ts`];patterns=[];constructor(e){this.config=e}addGlobPattern(e,t){let n=this.resolveOptions(t),r=k(e);this.patterns.push(new z(r,n))}async bundle(){let e=new g;for(let t of this.patterns){let n={...t.options},r;r=n.cwd?typeof n.cwd==`string`?n.cwd:ue(n.cwd):process.cwd(),u.isAbsolute(r)||(r=u.resolve(r)),n.absolute=!0,n.cwd=r;let i=await h(t.pattern,n),a=await this.getFilesPathsFromPaths(i);await Promise.all(a.map(async t=>{await this.zipFile(r,e,t)}))}return e}async validateFilePath(e){return Promise.resolve(!0)}async zipFile(e,t,n){let r=u.relative(e,n);if(r=r.replace(/^(\.\.\\|\.\.\/)+/,``),!m(n)){t.file(r,``,{compression:`DEFLATE`});return}if(!await this.validateFilePath(n))return;let i=p(n,`binary`);t.file(r,i,{compression:`DEFLATE`})}resolveOptions(e){return{...e,ignore:[...k(e?.ignore),...this.defaultIgnore]}}async getFilesPathsFromPaths(e){return Promise.all(e.map(async e=>await this.getFilePathsFromPathRecursive(e))).then(e=>e.flat())}async getFilePathsFromPathRecursive(e){if(!m(e)||!se(e).isDirectory())return[e];let t=await fe(e,{withFileTypes:!0});return(await Promise.all(t.map(async t=>{let n=u.join(e,t.name);return t.isDirectory()?await this.getFilePathsFromPathRecursive(n):[n]}))).flat()}},V=class extends B{defaultIgnore=[`overlayed.config.ts`,`**/installer/**`];mainFile;constructor(e){super(e),this.addGlobPattern(`package.json`)}async bundle(){let e=await super.bundle();return await this.validateZip(e),e}async validateFilePath(e){return e.endsWith(`package.json`)&&!e.includes(`node_modules`)&&await this.validatePackageJson(e),!0}async validateZip(e){await Promise.all([this.validateNoInstallFolder(e),this.validateMainFile(e)])}async validatePackageJson(e){if(!e)throw new H;let t=f.readFileSync(e,`utf-8`),n=a(`string.json.parse`).to({main:`string`,devDependencies:{"@overlayed/electron":`string`}})(t);if(n instanceof a.errors)throw new U(n.summary);this.mainFile=n.main}async validateMainFile(e){if(!this.mainFile)throw new H;if(!e.file(l.normalize(this.mainFile)))throw new W(this.mainFile)}async validateNoInstallFolder(e){let t=e.folder(`install`);if(t&&t.length>0)throw new pe}},H=class extends Error{constructor(){super(`package.json not found in bundle`),this.name=`PackageJsonNotFoundError`}},U=class extends Error{constructor(e){super(`package.json is invalid: ${e}`),this.name=`PackageJsonInvalidError`}},W=class extends Error{constructor(e){super(`referenced package.json main file ${e} not found in bundle`),this.name=`MainFileNotFoundError`}},pe=class extends Error{constructor(){super("folder `install` found in bundle is not allowed and is reserved for the installer"),this.name=`InstallFolderFoundError`}};_.create({baseURL:`https://updater.stats.cc`,headers:{Accept:`application/json`,"Accept-Encoding":`gzip, deflate, br`}}).interceptors.request.use(e=>e);const G=_.create({baseURL:`https://api.overlayed.gg`,headers:{Accept:`application/json`,"Accept-Encoding":`gzip, deflate, br`}});let K;function q(e){K=e}G.interceptors.request.use(e=>{let t=K?.();return e.headers??={},t&&(e.headers[`X-Api-Key`]=t),e});function me(e){return e}function he(e){return e?t=>(e(t),t):me}function ge(e,t){return G.post(`/v1/applications/${e}/bundles/jobs/upload`,t)}function _e(e,t){return G.post(`/v1/applications/${e}/bundles/jobs/confirm`,{name:t})}function ve(e,t){return G.get(`/v1/applications/${e}/bundles/jobs/${t}`)}const ye=he();function J(){return G.get(`/v1/auth/me`)}async function be(e,t){if(e.method!==`PUT`)throw Error(`Unsupported presigned request method: ${e.method}`);let n=await fetch(e.url,{method:e.method,headers:{...e.headers,"Content-Length":t.byteLength.toString()},body:t});if(!n.ok){let e=await xe(n);throw Error(`Presigned upload failed (${n.status} ${n.statusText})${e?`: ${e}`:``}`)}}async function xe(e){try{let t=(await e.text()).trim();if(t.length===0)return;let n=2048;return t.length>n?`${t.slice(0,n)}…`:t}catch{return}}const Y=new P({path:l.normalize(`${process.env.APPDATA||process.env.HOME||`.`}/.overlayed/config.json`),default:void 0,schema:i({Account:a({email:`string`,apiKey:`string`})}).type({currentAccount:`Account | undefined`,accounts:`Account[]`}).or(`undefined`)});async function X(){if(!Y.get()?.currentAccount)throw new r(`Not authenticated. Run 'overlayed login' to authenticate.`);try{return(await J()).data.email}catch(e){if(!v(e))throw e;switch(e.response?.status){case 401:case 403:throw new r(`Authentication failed. Your API key may be invalid or expired. Run 'overlayed login' to re-authenticate.`);default:throw Error(`API error occurred. Please try again later.`)}}}async function Se(e){let t=await I();if(t.length===0)throw new r(`No config file found matching `+F);return t.length>1&&e.stdout.write(d.yellow(`
3
- Multiple config files found matching ${F}, using the first one.
4
- `)),t.at(0)}var Ce=class extends B{defaultIgnore=[`overlayed.config.ts`];constructor(e){super(e)}async bundle(){let e=await super.bundle();if(!Object.keys(e.files).some(e=>e.endsWith(`.html`)))throw new we;return e}},we=class extends Error{constructor(){super(`Site bundle must contain at least one HTML file`)}},Te=class extends n{static paths=[[`bundle`]];static usage=n.Usage({category:E.deployment,description:`Bundle the app and site for deployment.`,details:`
5
- Bundle the app and site for deployment.
6
-
7
- [Docs](http://docs.overlayed.gg/cli/bundle)
8
- `,examples:[[`Basic usage`,`$0 bundle`]]});async execute(){await X();let e=await new x([{title:`Authenticating`,task:X},{title:`Locating overlayed.config.ts`,task:async e=>e.config=await this.resolveConfig()},{task:async(e,t)=>{let n=await t.prompt(S).run(...this.getTargetBundles(e.config));e.targetBundles=n,this.context.stdout.write(d.green(`Selected bundles: `)+d.bold(n.join(`, `)))}}]).run();function t(e){return`${e.map(e=>`jobId=${e}`).join(`&`)}`}await new x([{title:`Uploading bundles`,task:e=>new x(e.targetBundles.map((e,t)=>({title:`Uploading ${e} bundle`,task:async n=>{n.jobIds??=[];let r=await this.uploadBundle(n.config,e);r&&(n.jobIds[t]=r.id)}})),{concurrent:!0,rendererOptions:{collapseSubtasks:!1}})},{title:`Waiting for bundles to be processed.`,task:e=>new x(e.jobIds.map((n,r)=>({title:`Waiting for ${d.bold(e.targetBundles[r])} bundle to be processed. This may take a couple minutes - you can wait for the bundles to appear in the dashboard here: ${d.blue(`https://overlay.dev/bundles?applicationId=${e.config.applicationId}&${t(e.jobIds)}`)}`,task:async(e,i)=>{let a=0,o=e.targetBundles[r],s=`https://overlay.dev/bundles?applicationId=${e.config.applicationId}&${t(e.jobIds)}`,c;for(;;){if(a++>100){i.title=`${d.red(`Timeout`)} - ${d.bold(o)} bundle processing is taking longer than expected. Check the dashboard: ${d.blue(s)}`;return}c=await ve(e.config.applicationId,n);let t=c.data.status;if(t===`completed`){i.title=`${d.green(`Completed`)} - ${d.bold(o)} bundle has been processed successfully.`;break}if(t===`failed`){i.title=`${d.red(`Failed`)} - ${d.bold(o)} bundle processing failed.`;break}t===`pending`?i.title=`${d.yellow(`Queued`)} - ${d.bold(o)} bundle is queued for processing. Dashboard: ${d.blue(s)}`:t===`running`&&(i.title=`${d.green(`Running`)} - ${d.bold(o)} bundle is being processed. Dashboard: ${d.blue(s)}`),await new Promise(e=>setTimeout(e,3e3))}if(c.data.status===`failed`){let e=`An unknown error occurred.`;c.data.runs.length>0&&(e=c.data.runs[c.data.runs.length-1].error_message??e),i.title=`${d.red(`Failed`)} - ${d.bold(o)} bundle processing failed: ${e}`}}})),{concurrent:!0,exitOnError:!1,rendererOptions:{collapseSubtasks:!1}})}]).run(e)}async uploadBundle(e,t){let n=await ge(e.applicationId,{type:t}),r=await e[t]?.bundle()??D(Error(`No bundle found for `+t));e.debug&&r.forEach(e=>{console.log(`Zipping: `,e)});let i=await r.generateAsync({type:`nodebuffer`});if(e.debug){let e=`.overlayed/tmp`;ie(e),re(`${e}/debug-${t}.zip`,i);return}return await be(n.data.upload_request,i),(await _e(e.applicationId,n.data.asset_bundle_name)).data}getTargetBundles(e){let t=e=>`(Disabled: No config found for "${e}" in overlayed.config.ts)`;return[y,{message:`Select your desired bundles to create and upload.`,shortcuts:{all:`a`,invert:`i`},choices:[{name:`App`,value:`app`,disabled:e.app?void 0:t(`app`)},{name:`Site`,value:`site`,disabled:e.site?void 0:t(`site`)}]}]}async resolveConfig(){let[e,t]=await Se(this.context);return{...e,applicationId:ye(e.applicationId),app:this.getAppBundler(e,t),site:this.getSiteBundler(e,t)}}getAppBundler(e,t){let n=new V(e),r=this.resolveCwd(t,e.app.baseDir);return e.debug&&console.log(`[App] Bundling files from: `,r),n.addGlobPattern(e.app.include,{ignore:e.app.exclude,cwd:r}),n}getSiteBundler(e,t){if(!e.site)return;let n=new Ce(e),r=this.resolveCwd(t,e.site.baseDir);return e.debug&&console.log(`[Site] Bundling files from: `,r),n.addGlobPattern(e.site.include,{ignore:e.site.exclude,cwd:r}),n}resolveCwd(e,t){return t?l.resolve(l.dirname(e),t):l.dirname(e)}async catch(e){if(v(e)){let t=e.response?.status,n=(e.config?.baseURL??``)+(e.config?.url??``);throw Error(`Request to ${n} failed with status ${t}. ${e.response?.data}`)}throw e}};function Ee(e){return`https://overlay.dev${e}`}const Z={normal:De,success:Oe,error:Q,warning:ke,info:Ae};function De(...e){return d.gray([...e,`
9
- `].join(` `))}function Oe(...e){return[d.green(`✔`),...e,`
10
- `].join(` `)}function Q(...e){return[d.red(`✘`),...e,`
11
- `].join(` `)}function ke(...e){return[d.yellow(`⚠`),...e,`
12
- `].join(` `)}function Ae(...e){return[d.blue(`ℹ`),...e,`
13
- `].join(` `)}const je=w(C);var Me=class extends n{static paths=[[`login`]];static usage=n.Usage({description:`Authenticate with the Overlayed platform.`,details:`
14
- Authenticate with the Overlayed platform.
15
- `,examples:[[`Basic usage`,`$0 login`]]});async execute(){let e=Ee(`/user-settings/api-keys?cli=true`);if(await this.openUrl(e),!await b({message:`To authenticate, you'll need an API Key. We've opened the page to create one in your browser.\n\n${Z.info(e)}\n\nOnce you have it, enter it here:`,async validate(e){q(()=>e);try{let t={email:(await J()).data.email,apiKey:e},n=Y.get();Y.set({...n,currentAccount:t,accounts:n?.accounts.concat(t)??[t]})}catch(e){if(!v(e))return`Unknown error has occurred: ${e}`;switch(e.response?.status){case 401:case 403:return`Invalid API key, please try again.`;default:return`API error, please try again later.`}}return q(()=>Y.get()?.currentAccount?.apiKey),!0}}))throw Error(`Login unknown error - this should never happen`);let t=Y.get();if(!t?.currentAccount)throw Error(`Could not get config after login`);let n=t.currentAccount.email;this.context.stdout.write(Z.success(`Authentication Complete - Logged in as ${n}`))}async openUrl(e){let t=process.platform,n;switch(t){case`darwin`:n=`open "${e}"`;break;case`win32`:n=`start "" "${e}"`;break;default:n=`xdg-open "${e}"`;break}try{await je(n)}catch{}}},Ne=class extends n{static paths=[[`logout`]];static usage=n.Usage({description:`Log out of the currently authenticated account.`,details:`
16
- Log out of the currently authenticated account.
17
- `,examples:[[`Basic usage`,`$0 logout`]]});async execute(){let e=await X(),t=Y.get();if(!t){this.context.stdout.write(Z.normal(`Not currently logged in.`));return}let n=t.accounts.filter(t=>t.email!==e);Y.set({...t,currentAccount:void 0,accounts:n}),this.context.stdout.write(Z.normal(`Logged out from ${e}`))}},Pe=class extends n{static paths=[[`whoami`]];static usage=n.Usage({description:`Display information about the currently authenticated user.`,details:`
18
- Display information about the currently authenticated user.
19
-
20
- [Docs](http://docs.overlayed.gg/cli/whoami)
21
- `,examples:[[`Basic usage`,`$0 whoami`]]});async execute(){let e=await X();this.context.stdout.write(Z.normal(`Logged in as ${e}`))}};const Fe=process.argv.slice(2);(()=>{try{let e=new URL(`../package.json`,import.meta.url),t=JSON.parse(ce(e,`utf8`));typeof t==`object`&&t&&`name`in t&&`version`in t&&typeof t.name==`string`&&typeof t.version==`string`&&T({pkg:{name:t.name,version:t.version}}).notify({defer:!0})}catch{}})();const $=new t({binaryName:`overlayed`,binaryLabel:`Overlayed`});[Te,Me,Ne,Pe,e.HelpCommand].forEach(e=>{$.register(e)}),q(()=>Y.get()?.currentAccount?.apiKey),$.runExit(Fe);export{};
2
+ import{Command as e}from"@commander-js/extra-typings";import{scope as t,type as n}from"arktype";import r,{createWriteStream as i,existsSync as a,mkdirSync as o,readdirSync as s,unlinkSync as c,writeFileSync as l}from"fs";import{ensureDirSync as ee,ensureFileSync as te}from"fs-extra";import u,{join as ne}from"path";import d from"node:path";import f from"picocolors";import re,{createReadStream as p,existsSync as m,lstatSync as h,readFileSync as g}from"node:fs";import{glob as _}from"glob";import v,{fileURLToPath as ie}from"node:url";import{createJiti as ae}from"jiti";import y,{isXiorError as b}from"xior";import oe from"update-notifier";import{readdir as se}from"node:fs/promises";import ce from"jszip";import{checkbox as le,password as ue}from"@inquirer/prompts";import{Listr as x}from"listr2";import{ListrInquirerPromptAdapter as de}from"@listr2/prompt-adapter-inquirer";import{exec as fe}from"child_process";import{promisify as pe}from"util";function S(e){throw e}function C(e){return Object.prototype.toString.call(e)===`[object Object]`}function w(e){return e?Array.isArray(e)?e:[e]:[]}function T(e){return{all:e||=new Map,on:function(t,n){var r=e.get(t);r?r.push(n):e.set(t,[n])},off:function(t,n){var r=e.get(t);r&&(n?r.splice(r.indexOf(n)>>>0,1):e.set(t,[]))},emit:function(t,n){var r=e.get(t);r&&r.slice().map(function(e){e(n)}),(r=e.get(`*`))&&r.slice().map(function(e){e(t,n)})}}}var E=class{emitter;constructor(){this.emitter=T()}on(e,t){w(e).forEach(e=>{this.emitter.on(e,t)})}off(e,t){w(e).forEach(e=>{this.emitter.off(e,t)})}emit(e,t){w(e).forEach(e=>{this.emitter.emit(e,t)})}removeAllListeners(){this.emitter.all.clear()}};function D(e,...t){return class extends e{static _instance;static getInstance(){return this._instance||=new e(...t),this._instance}static clearInstance(){this._instance=void 0}}}const O=D(class extends E{destroy(){this.removeAllListeners()}fatal(e,t,n){this.emit(`fatal`,{code:t,message:e,data:n,timestamp:Date.now()})}error(e,t,n){this.emit(`error`,{code:t,message:e,data:n,timestamp:Date.now()})}warn(e,t,n){this.emit(`warning`,{code:t,message:e,data:n,timestamp:Date.now()})}});var k=class{data;_schema;_defaultValue;_path;constructor(e){let{schema:t,default:n,path:r}=e;this._schema=t,this._defaultValue=n,this._path=r}set(e){let t=this._schema[`~standard`].validate(e);if(t instanceof Promise)throw Error(`StructuredConfigFile does not support async validation`);if(t.issues){this.reportInvalidConfigFile(t.issues);return}this.data=this.onBeforeSave(e),this.save(e)}get(){return this.data?this.data:this.load()}getKey(e){return this.get()[e]}save(e){let t=this.onBeforeSave(e);try{te(this.getFilePath()),r.writeFileSync(this.getFilePath(),JSON.stringify(t,void 0,2))}catch{return}return this.data=t}load(){if(!this.fileExists(this.getFilePath())||!this.canReadWriteFile(this.getFilePath())){this.save(this._defaultValue);let e=this.onAfterLoad(this._defaultValue);return this.data=e,e}try{let e=r.readFileSync(this.getFilePath(),`utf8`),t=this.onAfterLoad(this.parseStoredData(e));return this.data=t,t}catch{return this.data=this._defaultValue}}onBeforeSave(e){return e}onAfterLoad(e){return e}getFilePath(){return this._path}parseStoredData(e){try{let t=n(`string.json.parse`).to(`object`)(e);if(t instanceof n.errors)return this._defaultValue;let r=this._schema[`~standard`].validate(t);if(r instanceof Promise)throw Error(`StructuredConfigFile does not support async validation`);return r.issues?this.migrate(t,this._defaultValue):this.migrate(r.value,this._defaultValue)}catch{return this._defaultValue}}canReadWriteFile(e){try{return r.accessSync(e,r.constants.R_OK|r.constants.W_OK),!0}catch{return!1}}fileExists(e){try{return r.accessSync(e,r.constants.F_OK),!0}catch{return!1}}migrate(e,t){let n={...e};for(let r in t)Object.prototype.hasOwnProperty.call(t,r)&&(r in e?e[r]!==null&&t[r]!==null&&typeof e[r]==`object`&&typeof t[r]==`object`&&!Array.isArray(e[r])&&!Array.isArray(t[r])?n[r]=this.migrate(e[r],t[r]):Array.isArray(t[r])&&(n[r]=e[r]):n[r]=t[r]);return n}reportInvalidConfigFile(e){O.getInstance().error(`Invalid config file`,`INVALID_CONFIG_FILE`,{issues:e.map(e=>e.message),filePath:this._path,data:this.data})}};const A=`**/overlayed.config.{js,ts,mts}`;async function j(){let e=N(),t=ae(import.meta.url);return(await Promise.all(e.map(async e=>e.endsWith(`.ts`)?[await t.import(e,{default:!0}),e]:[(await import(v.pathToFileURL(e).toString())).default,e]))).filter(([e])=>M(e)).map(([e,t])=>[e,t])}function M(e){return C(e)&&`app`in e}function N(){return _.globSync(A,{absolute:!0,cwd:process.cwd()})}const P=new k({path:u.normalize(`${process.env.APPDATA||process.env.HOME||`.`}/.overlayed/config.json`),default:void 0,schema:t({Account:n({email:`string`,apiKey:`string`})}).type({currentAccount:`Account | undefined`,accounts:`Account[]`}).or(`undefined`)});y.create({baseURL:`https://updater.stats.cc`,headers:{Accept:`application/json`,"Accept-Encoding":`gzip, deflate, br`}}).interceptors.request.use(e=>e);const F=y.create({baseURL:`https://api.overlayed.gg`,headers:{Accept:`application/json`,"Accept-Encoding":`gzip, deflate, br`}});let I;function L(e){I=e}F.interceptors.request.use(e=>{let t=I?.();return e.headers??={},t&&(e.headers[`X-Api-Key`]=t),e});function R(e){return e}function z(e){return e?t=>(e(t),t):R}function B(e,t){return F.post(`/v1/applications/${e}/bundles/jobs/upload`,t)}function V(e,t){return F.post(`/v1/applications/${e}/bundles/jobs/confirm`,{name:t})}function me(e,t){return F.get(`/v1/applications/${e}/bundles/jobs/${t}`)}const he=z();function H(){return F.get(`/v1/auth/me`)}async function ge(e,t){if(e.method!==`PUT`)throw Error(`Unsupported presigned request method: ${e.method}`);let n=await fetch(e.url,{method:e.method,headers:{...e.headers,"Content-Length":t.byteLength.toString()},body:t});if(!n.ok){let e=await _e(n);throw Error(`Presigned upload failed (${n.status} ${n.statusText})${e?`: ${e}`:``}`)}}async function _e(e){try{let t=(await e.text()).trim();if(t.length===0)return;let n=2048;return t.length>n?`${t.slice(0,n)}…`:t}catch{return}}var ve=class{pattern;options;constructor(e,t){this.pattern=e,this.options=t}},U=class{defaultIgnore=[`overlayed.config.ts`];patterns=[];constructor(e){this.config=e}addGlobPattern(e,t){let n=this.resolveOptions(t),r=w(e);this.patterns.push(new ve(r,n))}async bundle(){let e=new ce;for(let t of this.patterns){let n={...t.options},r;r=n.cwd?typeof n.cwd==`string`?n.cwd:ie(n.cwd):process.cwd(),d.isAbsolute(r)||(r=d.resolve(r)),n.absolute=!0,n.cwd=r;let i=await _(t.pattern,n),a=await this.getFilesPathsFromPaths(i);await Promise.all(a.map(async t=>{await this.zipFile(r,e,t)}))}return e}async validateFilePath(e){return Promise.resolve(!0)}async zipFile(e,t,n){let r=d.relative(e,n);if(r=r.replace(/^(\.\.\\|\.\.\/)+/,``),!m(n)){t.file(r,``,{compression:`DEFLATE`});return}if(!await this.validateFilePath(n))return;let i=p(n,`binary`);t.file(r,i,{compression:`DEFLATE`})}resolveOptions(e){return{...e,ignore:[...w(e?.ignore),...this.defaultIgnore]}}async getFilesPathsFromPaths(e){return Promise.all(e.map(async e=>await this.getFilePathsFromPathRecursive(e))).then(e=>e.flat())}async getFilePathsFromPathRecursive(e){if(!m(e)||!h(e).isDirectory())return[e];let t=await se(e,{withFileTypes:!0});return(await Promise.all(t.map(async t=>{let n=d.join(e,t.name);return t.isDirectory()?await this.getFilePathsFromPathRecursive(n):[n]}))).flat()}},W=class extends Error{constructor(e){super(e),this.name=`CLIError`}},ye=class extends U{defaultIgnore=[`overlayed.config.ts`,`**/installer/**`];mainFile;constructor(e){super(e),this.addGlobPattern(`package.json`)}async bundle(){let e=await super.bundle();return await this.validateZip(e),e}async validateFilePath(e){return e.endsWith(`package.json`)&&!e.includes(`node_modules`)&&await this.validatePackageJson(e),!0}async validateZip(e){await Promise.all([this.validateNoInstallFolder(e),this.validateMainFile(e)])}async validatePackageJson(e){if(!e)throw new G;let t=re.readFileSync(e,`utf-8`),r=n(`string.json.parse`).to({main:`string`,devDependencies:{"@overlayed/electron":`string`}})(t);if(r instanceof n.errors)throw new be(r.summary);this.mainFile=r.main}async validateMainFile(e){if(!this.mainFile)throw new G;if(!e.file(u.normalize(this.mainFile)))throw new xe(this.mainFile)}async validateNoInstallFolder(e){let t=e.folder(`install`);if(t&&t.length>0)throw new Se}},G=class extends W{constructor(){super(`package.json not found in bundle`),this.name=`PackageJsonNotFoundError`}},be=class extends W{constructor(e){super(`package.json is invalid: ${e}`),this.name=`PackageJsonInvalidError`}},xe=class extends W{constructor(e){super(`referenced package.json main file ${e} not found in bundle`),this.name=`MainFileNotFoundError`}},Se=class extends W{constructor(){super("folder `install` found in bundle is not allowed and is reserved for the installer"),this.name=`InstallFolderFoundError`}};const K=new class{write(e){process.stdout.write(e)}error(e){process.stderr.write(e)}},q={normal:Ce,success:we,error:Te,warning:Ee,info:De};function Ce(...e){return f.gray([...e,`
3
+ `].join(` `))}function we(...e){return[f.green(`✔`),...e,`
4
+ `].join(` `)}function Te(...e){return[f.red(`✘`),...e,`
5
+ `].join(` `)}function Ee(...e){return[f.yellow(`⚠`),...e,`
6
+ `].join(` `)}function De(...e){return[f.blue(`ℹ`),...e,`
7
+ `].join(` `)}async function J(){let e=process.env.OVERLAYED_API_KEY,t=P.get();if(e&&t?.currentAccount&&K.write(q.warning(`Using OVERLAYED_API_KEY environment variable instead of stored credentials.`)),!e&&!t?.currentAccount)throw new W(`Not authenticated. Run 'overlayed login' or set OVERLAYED_API_KEY.`);try{return(await H()).data.email}catch(t){if(!b(t))throw t;switch(t.response?.status){case 401:case 403:throw e?new W(`Authentication failed. Your OVERLAYED_API_KEY may be invalid or expired.`):new W(`Authentication failed. Your API key may be invalid or expired. Run 'overlayed login' to re-authenticate.`);default:throw Error(`API error occurred. Please try again later.`)}}}async function Oe(){let e=await j();if(e.length===0)throw new W(`No config file found matching `+A);return e.length>1&&K.write(f.yellow(`
8
+ Multiple config files found matching ${A}, using the first one.
9
+ `)),e.at(0)}var ke=class extends U{defaultIgnore=[`overlayed.config.ts`];constructor(e){super(e)}async bundle(){let e=await super.bundle();if(!Object.keys(e.files).some(e=>e.endsWith(`.html`)))throw new Ae;return e}},Ae=class extends Error{constructor(){super(`Site bundle must contain at least one HTML file`)}};async function je(e={}){await J();try{await Me(e)}catch(e){if(e instanceof W&&process.exit(1),b(e)){let t=e.response?.status;throw new W(`Request to ${(e.config?.baseURL??``)+(e.config?.url??``)} failed with status ${t}. ${JSON.stringify(e.response?.data,null,2)}`)}throw e}}async function Me(e){let t=await new x([{title:`Locating overlayed.config.ts`,task:async t=>t.config=await Fe(e)},{task:async(t,n)=>{if(e.app||e.site){let n=[];if(e.app&&(t.config.app?n.push(`app`):K.write(q.warning(`${f.bold(`--app`)} flag provided but no app config found in overlayed.config.ts`))),e.site&&(t.config.site?n.push(`site`):K.write(q.warning(`${f.bold(`--site`)} flag provided but no site config found in overlayed.config.ts`))),n.length===0)throw Error(`No valid bundles selected. Ensure the selected bundle types have valid config in overlayed.config.ts`);t.targetBundles=n,K.write(f.green(`Selected bundles: `)+f.bold(n.join(`, `)))}else{let e=await n.prompt(de).run(...Pe(t.config));t.targetBundles=e,K.write(f.green(`Selected bundles: `)+f.bold(e.join(`, `)))}}}]).run();function n(e,t){let n=new URL(`https://overlay.dev/jobs`);n.searchParams.set(`applicationId`,e);for(let e of t)n.searchParams.append(`jobId`,e);return n.toString()}await new x([{title:`Uploading bundles`,task:e=>new x(e.targetBundles.map((e,t)=>({title:`Uploading ${e} bundle`,task:async n=>{n.jobIds??=[];let r=await Ne(n.config,e);r&&(n.jobIds[t]=r.id)}})),{concurrent:!0,rendererOptions:{collapseSubtasks:!1}})},{title:`Waiting for bundles to be processed.`,task:t=>{if(e.wait===!1){if(t.jobIds.length>0){let e=n(t.config.applicationId,t.jobIds);K.write(f.yellow(`Skipping wait. Check bundle status in the dashboard: `)+f.blue(e))}return}return new x(t.jobIds.map((e,r)=>({title:`Waiting for ${f.bold(t.targetBundles[r])} bundle to be processed. This may take a couple minutes - you can wait for the bundles to appear in the dashboard here: ${f.blue(n(t.config.applicationId,t.jobIds))}`,task:async(t,i)=>{let a=0,o=t.targetBundles[r],s=n(t.config.applicationId,t.jobIds),c;for(;;){if(a++>100){i.title=`${f.red(`Timeout`)} - ${f.bold(o)} bundle processing is taking longer than expected. Check the dashboard: ${f.blue(s)}`;return}c=await me(t.config.applicationId,e);let n=c.data.status;if(n===`completed`){i.title=`${f.green(`Completed`)} - ${f.bold(o)} bundle has been processed successfully.`;break}if(n===`failed`){i.title=`${f.red(`Failed`)} - ${f.bold(o)} bundle processing failed.`;break}n===`pending`?i.title=`${f.yellow(`Queued`)} - ${f.bold(o)} bundle is queued for processing. Dashboard: ${f.blue(s)}`:n===`running`&&(i.title=`${f.green(`Running`)} - ${f.bold(o)} bundle is being processed. Dashboard: ${f.blue(s)}`),await new Promise(e=>setTimeout(e,3e3))}if(c.data.status===`failed`){let e=`An unknown error occurred.`;c.data.runs.length>0&&(e=c.data.runs[c.data.runs.length-1].error_message??e),i.title=`${f.red(`Failed`)} - ${f.bold(o)} bundle processing failed: ${e}`}}})),{concurrent:!0,exitOnError:!1,rendererOptions:{collapseSubtasks:!1}})}}]).run(t)}async function Ne(e,t){let n=await B(e.applicationId,{type:t}),r=await e[t]?.bundle()??S(Error(`No bundle found for `+t));e.debug&&r.forEach(e=>{console.log(`Zipping: `,e)});let i=await r.generateAsync({type:`nodebuffer`});if(e.debug){let e=`.overlayed/tmp`;ee(e),l(`${e}/debug-${t}.zip`,i);return}return await ge(n.data.upload_request,i),(await V(e.applicationId,n.data.asset_bundle_name)).data}function Pe(e){let t=e=>`(Disabled: No config found for "${e}" in overlayed.config.ts)`;return[le,{message:`Select your desired bundles to create and upload.`,shortcuts:{all:`a`,invert:`i`},choices:[{name:`App`,value:`app`,disabled:e.app?void 0:t(`app`)},{name:`Site`,value:`site`,disabled:e.site?void 0:t(`site`)}]}]}async function Fe(e){let[t,n]=await Oe();return{...t,applicationId:he(t.applicationId),app:Ie(t,n,e),site:Le(t,n,e),debug:e.debug}}function Ie(e,t,n){let r=new ye(e),i=Y(t,e.app.baseDir);return n.debug&&console.log(`[App] Bundling files from: `,i),r.addGlobPattern(e.app.include,{ignore:e.app.exclude,cwd:i}),r}function Le(e,t,n){if(!e.site)return;let r=new ke(e),i=Y(t,e.site.baseDir);return n.debug&&console.log(`[Site] Bundling files from: `,i),r.addGlobPattern(e.site.include,{ignore:e.site.exclude,cwd:i}),r}function Y(e,t){return t?u.resolve(u.dirname(e),t):u.dirname(e)}const X=new e(`bundle`).description(`Bundle the app and site for deployment.`).addHelpText(`after`,`
10
+ Documentation:
11
+ http://docs.overlayed.gg/cli/bundle
12
+ `).helpCommand(`overlayed bundle`).option(`--debug`,`Enable debug mode`,!1).option(`--app`,`Bundle the app`,!1).option(`--site`,`Bundle the site`,!1).option(`--no-wait`,`Skip waiting for bundle processing to complete`).action(je);function Re(e){return`https://overlay.dev${e}`}const ze=pe(fe);async function Be(){let e=Re(`/user-settings/api-keys?cli=true`);if(await Ve(e),!await ue({message:`To authenticate, you'll need an API Key. We've opened the page to create one in your browser.\n\n${q.info(e)}\n\nOnce you have it, enter it here:`,async validate(e){L(()=>e);try{let t={email:(await H()).data.email,apiKey:e},n=P.get();P.set({...n,currentAccount:t,accounts:n?.accounts.concat(t)??[t]})}catch(e){if(!b(e))return`Unknown error has occurred: ${e}`;switch(e.response?.status){case 401:case 403:return`Invalid API key, please try again.`;default:return`API error, please try again later.`}}return L(()=>P.get()?.currentAccount?.apiKey),!0}}))throw Error(`Login unknown error - this should never happen`);let t=P.get();if(!t?.currentAccount)throw Error(`Could not get config after login`);let n=t.currentAccount.email;K.write(q.success(`Authentication Complete - Logged in as ${n}`))}async function Ve(e){let t=process.platform,n;switch(t){case`darwin`:n=`open "${e}"`;break;case`win32`:n=`start "" "${e}"`;break;default:n=`xdg-open "${e}"`;break}try{await ze(n)}catch{}}const He=new e(`login`).description(`Authenticate with the Overlayed platform.`).helpCommand(`overlayed login`).action(Be);async function Ue(){let e=await J(),t=P.get();if(!t){K.write(q.normal(`Not currently logged in.`));return}let n=t.accounts.filter(t=>t.email!==e);P.set({...t,currentAccount:void 0,accounts:n}),K.write(q.normal(`Logged out from ${e}`))}const We=new e(`logout`).description(`Log out of the currently authenticated account.`).helpCommand(`overlayed logout`).action(Ue);async function Ge(){let e=await J();return K.write(q.normal(`Logged in as ${e}`)),e}const Ke=new e(`whoami`).description(`Display information about the currently authenticated user.`).addHelpText(`after`,`
13
+ Documentation:
14
+ http://docs.overlayed.gg/cli/whoami
15
+ `).helpCommand(`overlayed whoami`).action(()=>{Ge()});let Z=null;function qe(){if(Z)return Z;try{let e=new URL(`../package.json`,import.meta.url),t=JSON.parse(g(e,`utf8`));if(typeof t==`object`&&t&&`name`in t&&`version`in t&&typeof t.name==`string`&&typeof t.version==`string`)return Z={name:t.name,version:t.version},Z;throw Error(`Invalid package.json structure`)}catch{throw Error(`Failed to read package.json`)}}const Q=qe();oe({pkg:Q}).notify({defer:!0}),L(()=>process.env.OVERLAYED_API_KEY??P.get()?.currentAccount?.apiKey);const $=new e;$.name(`overlayed`).description(`Overlayed CLI`).version(Q.version,`-v, --version`).addHelpText(`before`,`
16
+ ${f.bold(f.cyan(`Overlayed`))} is revolutionizing game overlays.
17
+ `).action(()=>$.help()),$.addCommand(X).addCommand(He).addCommand(We).addCommand(Ke).configureOutput({writeErr:e=>process.stderr.write(e)});try{await $.parseAsync(process.argv)}catch(e){e instanceof W&&(process.stderr.write(q.error(e.message)),process.exit(1)),console.error(`Unexpected error:`),console.error(e),process.exit(1)}export{};
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "keywords": [
11
11
  "typescript"
12
12
  ],
13
- "version": "0.6.7",
13
+ "version": "0.6.9",
14
14
  "description": "Overlayed CLI",
15
15
  "type": "module",
16
16
  "bin": {
@@ -30,21 +30,22 @@
30
30
  "typescript": "^5.9.3",
31
31
  "vitest": "^3.2.4",
32
32
  "@overlayed/api": "0.0.5",
33
+ "@overlayed/shared-node": "0.0.4",
33
34
  "@overlayed/utils": "0.0.4",
34
- "@overlayed/utils-node": "0.0.4",
35
- "@overlayed/shared-node": "0.0.4"
35
+ "@overlayed/utils-node": "0.0.4"
36
36
  },
37
37
  "dependencies": {
38
+ "@commander-js/extra-typings": "^14.0.0",
38
39
  "@inquirer/prompts": "^7.10.1",
39
40
  "@listr2/prompt-adapter-inquirer": "^3.0.5",
40
41
  "arktype": "^2.1.29",
41
- "picocolors": "^1.1.1",
42
- "clipanion": "4.0.0-rc.4",
42
+ "commander": "^14.0.2",
43
43
  "fs-extra": "^11.3.2",
44
44
  "glob": "^11.1.0",
45
45
  "jiti": "^2.6.1",
46
46
  "jszip": "^3.10.1",
47
47
  "listr2": "^9.0.5",
48
+ "picocolors": "^1.1.1",
48
49
  "update-notifier": "^7.3.1",
49
50
  "xior": "^0.7.8"
50
51
  },