dashnex 0.7.6 → 0.8.0
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/README.md +68 -2
- package/dist/chunks/{dashnex-CFNkxHra.js → dashnex-BKNuCWid.js} +1 -1
- package/dist/chunks/project-guard-Z_W1-Bfr.js +1 -0
- package/dist/cli.js +1 -1
- package/dist/client.js +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/server.js +1 -1
- package/dist/src/commands/install.d.ts +1 -0
- package/dist/src/commands/module/index.d.ts +3 -0
- package/dist/src/commands/module/init.d.ts +9 -0
- package/dist/src/commands/module/shared.d.ts +8 -0
- package/dist/src/commands/module/submit.d.ts +8 -0
- package/dist/src/commands/switch.d.ts +0 -1
- package/dist/src/lib/api.d.ts +2 -1
- package/dist/src/lib/exit.d.ts +22 -0
- package/dist/src/lib/module-build.d.ts +27 -0
- package/dist/src/lib/module-template.d.ts +4 -0
- package/dist/src/lib/npmrc.d.ts +8 -0
- package/dist/src/lib/project.d.ts +20 -0
- package/dist/src/lib/semver.d.ts +1 -0
- package/dist/src/lib/settings.d.ts +40 -0
- package/dist/src/services/auth.d.ts +2 -0
- package/dist/src/services/business-session.d.ts +5 -4
- package/dist/src/services/developers.d.ts +67 -0
- package/dist/src/services/project-guard.d.ts +11 -0
- package/package.json +2 -1
- package/dist/chunks/auth-9zeRR7R-.js +0 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ dashnex <command> [options]
|
|
|
24
24
|
| `login` | Log in to your DashNex account |
|
|
25
25
|
| `logout` | Log out and remove local credentials |
|
|
26
26
|
| `switch` | Switch to another business |
|
|
27
|
-
| `whoami` | Show
|
|
27
|
+
| `whoami` | Show the logged-in business (with its ID), user and session file |
|
|
28
28
|
| `dev` | Run the development server (run in DashNex application folder); `-p/--port`, `-H/--host`, `--https` |
|
|
29
29
|
| `check` | Run build-time validations (run in DashNex application folder) |
|
|
30
30
|
| `install` | Install dependencies using pnpm or npm (run in DashNex application folder) |
|
|
@@ -33,11 +33,46 @@ dashnex <command> [options]
|
|
|
33
33
|
| `app push` | Push local application to DashNex |
|
|
34
34
|
| `app delete` | Delete application from DashNex |
|
|
35
35
|
| `app deploy` | Deploy application to DashNex |
|
|
36
|
-
| `app status` | Show
|
|
36
|
+
| `app status` | Show the business, application ID, status, URL and session file |
|
|
37
37
|
| `app clear` | Delete local dependencies, caches and build output |
|
|
38
38
|
| `secrets list` | List all secret names bound to the worker |
|
|
39
39
|
| `secrets set` | Create or update a secret |
|
|
40
40
|
| `secrets delete` | Delete a secret |
|
|
41
|
+
| `module init` | Create reserved marketplace modules in `packages/` from the module template (run in DashNex application folder) |
|
|
42
|
+
| `module submit <address>` | Build, publish and submit a marketplace module for review (run in DashNex application folder) |
|
|
43
|
+
|
|
44
|
+
### Sessions
|
|
45
|
+
|
|
46
|
+
The session lives in `.dashnex/settings.json`. Commands use the nearest one,
|
|
47
|
+
looking in the current folder first and then in each parent folder; files are
|
|
48
|
+
never merged. An application folder with its own `.dashnex/settings.json`
|
|
49
|
+
therefore has its own session, independent of the one in your home folder.
|
|
50
|
+
`login`, `logout`, `switch`, `whoami` and `app status` all act on that same
|
|
51
|
+
file and print its path.
|
|
52
|
+
|
|
53
|
+
The CLI keeps its session under two keys and leaves every other key in the
|
|
54
|
+
file alone:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"business": { "token": "…", "refreshToken": "…", "id": "103602" },
|
|
59
|
+
"id": { "token": "…", "refreshToken": "…", "expiresAt": 1793270400000 }
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`business` is the token for the selected business; `id` is the DashNex ID
|
|
64
|
+
session `switch` uses. Files written by CLI 0.7.x and older (flat `token`,
|
|
65
|
+
`businessId`, `idToken`… keys) are still read and are moved into this layout
|
|
66
|
+
the next time the CLI saves the file.
|
|
67
|
+
|
|
68
|
+
Inside an application, the CLI checks the session against the `BUSINESS_ID`
|
|
69
|
+
(and, where it matters, `APPLICATION_ID`) of the application. Each ID is read
|
|
70
|
+
from the first of `.env.production`, `.env.development` and `.env` that names
|
|
71
|
+
it.
|
|
72
|
+
`app push`, `app deploy`, `app delete`, `app pull`, `app tail`, `secrets`
|
|
73
|
+
and module commands that use the session (such as `db migrate --remote`)
|
|
74
|
+
refuse to run for another business. `whoami` and `app status` show a warning
|
|
75
|
+
instead.
|
|
41
76
|
|
|
42
77
|
### Options
|
|
43
78
|
|
|
@@ -49,6 +84,15 @@ Sign-in goes through DashNex ID in your browser: approve the request, copy the
|
|
|
49
84
|
code you are shown, and paste it back into the CLI. Codes are valid for 10
|
|
50
85
|
minutes and can only be used once.
|
|
51
86
|
|
|
87
|
+
Inside an application, `login` selects the business named by `BUSINESS_ID`
|
|
88
|
+
(unless `--business` is given; see [Sessions](#sessions)), and switches to it when the current
|
|
89
|
+
session is for another business.
|
|
90
|
+
|
|
91
|
+
**logout**:
|
|
92
|
+
|
|
93
|
+
Removes the session from the settings file the other commands use (see
|
|
94
|
+
[Sessions](#sessions)) and prints its path.
|
|
95
|
+
|
|
52
96
|
**switch**:
|
|
53
97
|
- `[businessId]` — Business to switch to; without it, pick from the list of your businesses
|
|
54
98
|
|
|
@@ -88,6 +132,28 @@ the database is cleared along with everything else.
|
|
|
88
132
|
**secrets delete**:
|
|
89
133
|
- `--name <name>` — Secret name (prompted if omitted)
|
|
90
134
|
|
|
135
|
+
**module init**:
|
|
136
|
+
- `[address]` — Module to create, e.g. `crm` or `@dashnex/crm`. Without it, a single missing reserved module is created directly; with several, you pick from a checklist
|
|
137
|
+
- `--all` — Create every reserved module that is not in `packages/` yet
|
|
138
|
+
- `--ci` — No prompts. With several missing modules, pass an address or `--all`
|
|
139
|
+
|
|
140
|
+
**module submit**:
|
|
141
|
+
- `<address>` — Module to submit, e.g. `crm` or `@dashnex/crm`
|
|
142
|
+
- `--ci` — Disable spinner (CI mode)
|
|
143
|
+
|
|
144
|
+
## Developing a marketplace module
|
|
145
|
+
|
|
146
|
+
Marketplace modules are `@dashnex/*` packages that extend your DashNex application. To build one:
|
|
147
|
+
|
|
148
|
+
1. Join the Developers program and reserve a module name on business.dashnex.com.
|
|
149
|
+
2. Deploy your application once. This writes the DashNex registry `.npmrc`.
|
|
150
|
+
3. Run `dashnex app pull` in the application folder.
|
|
151
|
+
4. Run `dashnex module init`. It creates `packages/<address>` from the module template, sets the package name to `@dashnex/<address>` and links the module into your application's `package.json`.
|
|
152
|
+
5. Build and develop: `cd packages/<address> && pnpm install && pnpm build`, then `dashnex install && dashnex dev` in the application folder.
|
|
153
|
+
6. When the module is ready, run `dashnex module submit <address>`. It builds and publishes the module to the DashNex registry under the `review` dist-tag, then submits the version for review (`latest` moves only once the version is approved). The local version must be higher than the last submitted one, and no other version may be in review.
|
|
154
|
+
|
|
155
|
+
The pulled application's `.npmrc` is copied into the module folder for publishing. It is gitignored there, and the CLI never puts the registry token on a command line.
|
|
156
|
+
|
|
91
157
|
## Creating your own command
|
|
92
158
|
|
|
93
159
|
The CLI auto-discovers commands from every installed `@dashnex/*` module (and your app). To add a command, a module exports a **default array of `CliCommandConfig`** from `src/commands/index.ts`. On startup the CLI looks for the command file in each module, in this order:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var e={$schema:"https://json.schemastore.org/package",name:"@dashnex/cli",version:"0.
|
|
1
|
+
var e={$schema:"https://json.schemastore.org/package",name:"@dashnex/cli",version:"0.8.0",dashnex:{minAppVersion:"1.1.0"},description:"Command-line interface for DashNex framework",homepage:"https://dashnex.io",type:"module",bin:{dashnex:"bin/dashnex"},engines:{node:">=22"},exports:{".":{types:"./dist/server.d.ts",default:"./dist/server.js"},"./client":{types:"./dist/client.d.ts",default:"./dist/client.js"}},scripts:{build:"vite build",dev:"vite build --watch --mode development",test:"vitest run"},keywords:["dashnex","cli"],author:"Dashnex Team",license:"UNLICENSED",dependencies:{"adm-zip":"^0.5.17",chalk:"^5.6.2",commander:"^14.0.3",diff:"^8.0.4",dotenv:"^17.4.2","fs-extra":"^11.3.5",ignore:"^7.0.5",inquirer:"^13.4.3",jiti:"^2.7.0",partysocket:"^1.3.0",tar:"^7.5.22",ws:"^8.21.0"},devDependencies:{"@dashnex/types":"^1.6.0","@types/adm-zip":"^0.5.8","@types/fs-extra":"^11.0.4","@types/inquirer":"^9.0.9","@types/node":"^25.7.0","@types/ws":"^8.18.1",eslint:"^9.39.4","rollup-plugin-preserve-directives":"^0.4.0",terser:"^5.47.1",typescript:"^5.9.3",vite:"^8.0.8","vite-plugin-dts":"5.0.0-beta.6",vitest:"^4.1.6"},publishConfig:{registry:"https://npm.dashnex.com/"},files:["dist","bin","AGENTS.md","CLAUDE.md"]},s={};export{e as n,s as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"chalk";import s from"fs-extra";import n from"path";import t from"dotenv";var r=e=>e instanceof Error&&("ExitPromptError"===e.name||("AbortError"===e.name||("User force closed the prompt"===e.message||!!e.message.includes("prompt was closed")))),o="Command interrupted.",a=class extends Error{constructor(e){super(`EXIT:${e}`),this.code=e,this.name="ExitSignal"}};function i(e=0){throw new a(e)}var c=e=>{process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()},u=e=>{const s=e.toLowerCase();return s.includes("token")||s.includes("password")||"auth_code"===s},l=e=>{if(null===e||"object"!=typeof e)return e;if(Array.isArray(e))return e.map(l);const s={};for(const[n,t]of Object.entries(e))u(n)?s[n]="[REDACTED]":s[n]=l(t);return s},p=s=>{process.env.DEBUG&&console.log(e.dim(s))},d=(s,n)=>{if(process.env.DEBUG){const t=l(n);console.log(e.dim(`${s}: ${JSON.stringify(t,null,2)}`))}},h=s=>{process.env.DEBUG&&s instanceof Error&&(console.log(e.dim(`Error: ${s.message}`)),s.stack&&console.log(e.dim(`Stack: ${s.stack}`)))},f=()=>"qa"===process.env.APPLICATION_ENV?"https://api.qa.dashnex.com":"https://api.dashnex.com",v=()=>process.env.DASHNEX_BUSINESS_API_URL?process.env.DASHNEX_BUSINESS_API_URL.replace(/\/+$/,""):"qa"===process.env.APPLICATION_ENV?"https://api.business.qa.dashnex.com":"https://api.business.dashnex.com",k=()=>"qa"===process.env.APPLICATION_ENV?"https://business.qa.dashnex.com":"https://business.dashnex.com",m={qa:"https://app.qa.dashnex.com",live:"https://app.dashnex.com"},w={qa:"hacmrrpg1wnjv12oq22p8lfm",live:"paijq1vhr6kxnbm22wjoftep"},I=()=>process.env.DASHNEX_ID_URL?process.env.DASHNEX_ID_URL.replace(/\/+$/,""):m[process.env.APPLICATION_ENV||"live"]||m.live,b=()=>process.env.DASHNEX_OAUTH_CLIENT_ID?process.env.DASHNEX_OAUTH_CLIENT_ID:w[process.env.APPLICATION_ENV||"live"]??"",y={qa:"https://3atfynmu2w.us-east-1.awsapprunner.com",live:"https://rkwbzvhcwh.us-east-1.awsapprunner.com"},T=()=>y[process.env.APPLICATION_ENV||"live"]||y.live,N=async(e,s={})=>{const n=await fetch(e,{...s,headers:{"Content-Type":"application/json",...s.headers}});return{response:n,body:await n.json().catch(()=>({}))}},E=process.env.DASHNEX_DIR||".dashnex",g=e=>n.join(e,E,"settings.json"),x={token:"token",refreshToken:"refreshToken",businessId:"id"},S={idToken:"token",idRefreshToken:"refreshToken",idExpiresAt:"expiresAt"},A=["issuer"],D=e=>"object"==typeof e&&null!==e&&!Array.isArray(e),$=async e=>{try{return(e=>{const s=D(e)?{...e}:{},n=[...Object.keys(x),...Object.keys(S)].some(e=>void 0!==s[e]);!n&&D(s.business)||delete s.business,!n&&D(s.id)||delete s.id;const t=(e,n)=>{const t=Object.keys(e).filter(e=>void 0!==s[e]);if(0===t.length)return;const r={};for(const o of t)r[e[o]]=s[o],delete s[o];s[n]=r};t(x,"business"),t(S,"id");for(const r of A)delete s[r];return s})(await s.readJson(e))}catch{return null}},_=async(e,n)=>{const t=await $(e)??{},r=n(t)??t;return await s.writeJson(e,r,{spaces:2}),r},P=e=>Date.now()+1e3*(e??86400),O=async e=>{const s=(await $(e))?.id;if(!s?.refreshToken)return p("No DashNex ID refresh token saved"),null;const n=`${I()}/api/auth/refresh`;p(`POST ${n}`);let t=!1;try{const{response:r,body:o}=await N(n,{method:"POST",body:JSON.stringify({refreshToken:s.refreshToken})});p(`Response: ${r.status}`);const a=o;if(r.ok&&a.token&&a.refreshToken)return await _(e,e=>{e.id={token:a.token,refreshToken:a.refreshToken,expiresAt:P()}}),p("Refreshed DashNex ID session"),a.token;p(`DashNex ID session refresh rejected: ${a.error??"no tokens in response"}`),t=401===r.status}catch(o){h(o)}const r=(await $(e))?.id;return r?.token&&r.refreshToken!==s.refreshToken?(p("DashNex ID session was refreshed by another process"),r.token):(t&&r&&await _(e,e=>{e.id&&delete e.id.refreshToken}),null)},j=async e=>{const s=(await $(e))?.id;if(!s?.token)return null;const n=s.expiresAt;return"number"==typeof n&&n-3e5>Date.now()?s.token:(p(n?"DashNex ID session expired or expiring soon":"DashNex ID session expiry unknown"),await O(e)??s.token)},U=()=>{console.error(e.red("Please run 'dashnex login' to authenticate.")),console.error(e.green(`If you don't have an account yet, create one at ${I()}`))},C=e=>Boolean(e.id?.token),L=async e=>{let t=n.resolve(e);for(;;){const e=g(t);if(await s.pathExists(e))return e;const r=n.dirname(t);if(r===t)return null;t=r}},B=e=>{try{const s=e.split(".");if(3!==s.length)return null;const n=JSON.parse(Buffer.from(s[1],"base64url").toString());return void 0!==n.id?String(n.id):null}catch{return null}},R=(e,s=30)=>{try{const n=e.split(".");if(3!==n.length)return!1;const t=JSON.parse(Buffer.from(n[1],"base64url").toString());if("number"!=typeof t.exp)return!1;const r=Math.floor(Date.now()/1e3),o=t.exp-s<=r;return o&&p(`Token expired or expiring soon (exp: ${t.exp}, now: ${r})`),o}catch{return!1}},X=async(e,s,n)=>{const t=C(n)?`${v()}/business/v1/token/refresh`:`${f()}/auth/v1/token/refresh`;p(`POST ${t}`);const{response:r,body:o}=await N(t,{method:"POST",body:JSON.stringify({refreshToken:e})});if(p(`Response: ${r.status}`),d("Refresh response",o),!r.ok)return p("Token refresh failed"),null;const a=o;if(!a.token||!a.refreshToken)return p("Refresh response missing tokens"),null;const i=await _(s,e=>{e.business={...e.business,token:a.token,refreshToken:a.refreshToken}});return p("Updated settings.json with new tokens"),H(a.token,a.refreshToken,s,i)},H=async(e,s,n,t)=>{if(R(e)){if(!s)return p("Token expired, no refreshToken available"),null;p("Token expired, refreshing proactively");try{return await X(s,n,t)}catch(o){return h(o),null}}const r=C(t)?`${v()}/business/v1/`:`${f()}/users/v1/`;p(`GET ${r}`);try{const{response:o,body:a}=await N(r,{headers:{Authorization:`Bearer ${e}`}});if(p(`Response: ${o.status}`),d("Validation response",a),o.ok){const s=a,n=s.name??s.companyName,r=t.business?.id??(void 0!==s.id?String(s.id):void 0);if("string"==typeof n&&void 0!==r)return p(`Token valid, Already logged in as ${n}`),{token:e,businessId:String(r),userName:n}}return s?(p("Validation endpoint rejected token, refreshing"),await X(s,n,t)):(p("Token invalid, no refreshToken available"),null)}catch(o){return h(o),null}},q=async(e=process.cwd())=>{const s=await L(e);if(!s)return!1;const n=await $(s);return!!n&&C(n)},V=async(n={})=>{const{exitOnFailure:t=!0,dashnexPath:r}=n,o=process.env.DASHNEX_TOKEN;if(o)return R(o)?(p("DASHNEX_TOKEN is expired"),console.error(e.red("DASHNEX_TOKEN is expired. Provide a freshly issued token.")),t&&i(1),null):(p("Using DASHNEX_TOKEN from environment"),{token:o,businessId:B(o)??""});const a=r??await L(process.cwd());if(!a||!(await s.pathExists(a)))return p(".dashnex/settings.json not found"),t&&(U(),i(1)),null;p(`Found settings at ${a}`);const c=await $(a);if(!c)return p("settings.json parse failed"),t&&(U(),i(1)),null;const{token:u,refreshToken:l}=c.business??{};if(!u)return p("settings.json missing token"),t&&(U(),i(1)),null;const d=await H(u,l,a,c);return d?(C(c)&&await j(a),{...d,settingsPath:a}):(t&&(U(),i(1)),null)},J=[".env.production",".env.development",".env"],K=async(e=process.cwd())=>{const r=await(async e=>{let t=n.resolve(e);for(;;){if(await s.pathExists(n.join(t,"dashnex.json")))return t;const e=n.dirname(t);if(e===t)return null;t=e}})(e);if(!r)return null;const o=[];for(const u of J){const e=n.join(r,u);try{o.push({path:e,env:t.parse(await s.readFile(e))})}catch{}}const a=(...e)=>{for(const s of o){const n=e.map(e=>s.env[e]?.trim()).find(e=>Boolean(e));if(n)return{value:n,path:s.path}}return{path:n.join(r,".env")}},i=a("BUSINESS_ID","NEXT_PUBLIC_BUSINESS_ID"),c=a("APPLICATION_ID","NEXT_PUBLIC_APPLICATION_ID");return{root:r,envPath:i.path,applicationEnvPath:c.path,...i.value&&{businessId:i.value},...c.value&&{applicationId:c.value}}},G=(e,s)=>void 0!==e&&void 0!==s&&String(e).trim()===String(s).trim(),z=e=>{const s=e.userName?` (${e.userName})`:"";return e.settingsPath?`you are signed in to business ${e.businessId}${s}.`:`DASHNEX_TOKEN is issued for business ${e.businessId}${s}.`},F=async(e,s,n)=>{const t=await K(n);if(!t?.businessId||!e.businessId)return null;if(!G(t.businessId,e.businessId)){const s=[`This folder belongs to business ${t.businessId} (BUSINESS_ID in ${t.envPath}),`,`but ${z(e)}`];return e.settingsPath&&s.push(`Session: ${e.settingsPath}`),{problem:s,hint:e.settingsPath?`Run 'dashnex switch ${t.businessId}' or 'dashnex login' in this folder, then try again.`:`Use a DASHNEX_TOKEN issued for business ${t.businessId}.`}}if(!t.applicationId||!s)return null;const r="function"==typeof s?await s():s.id;return void 0===r||G(t.applicationId,r)?null:{problem:[`This folder belongs to application ${t.applicationId} (APPLICATION_ID in ${t.applicationEnvPath}),`,`but the application of business ${e.businessId} is ${r}.`],hint:"Run 'dashnex app pull' to bring this folder up to date with that application, then try again."}},M=async(s,n={})=>{const t=await F(s,n.checkApplication?()=>(async e=>{const s=`${v()}/business/v1/applications`;p(`GET ${s}`);try{const n=await fetch(s,{headers:{Authorization:`Bearer ${e}`}});if(p(`Response: ${n.status}`),!n.ok)return;const t=await n.json().catch(()=>({}));return void 0!==t.id?String(t.id):void 0}catch(n){return void h(n)}})(s.token):void 0,n.projectDir);if(t){p("Session does not match the project's .env");for(const s of t.problem)console.error(e.red(s));console.error(e.yellow(t.hint)),i(1)}},W=async(s,n)=>{const t=await F(s,void 0!==n?{id:n}:void 0);t&&(console.warn(e.yellow(`Warning: ${t.problem.join(" ")}`)),console.warn(e.yellow(t.hint)))};export{h as C,c as D,i as E,o as O,p as S,a as T,v as _,V as a,T as b,j as c,$ as d,_ as f,f as g,N as h,G as i,r as k,P as l,g as m,W as n,L as o,E as p,K as r,q as s,M as t,O as u,k as v,d as w,b as x,I as y};
|
package/dist/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
2
|
+
import{D as n,O as o,T as e,_ as s,a as t,k as i,t as a}from"./chunks/project-guard-Z_W1-Bfr.js";import{Command as c}from"commander";import r from"chalk";import d from"fs-extra";import l from"path";import{fileURLToPath as m,pathToFileURL as p}from"url";import{execSync as f}from"child_process";import{createJiti as u}from"jiti";import g from"dotenv";var h=new c,y=l.dirname(m(import.meta.url)),{version:j,description:x}=JSON.parse(d.readFileSync(l.resolve(y,"..","package.json"),"utf8"));h.name("dashnex").description(x).version(j).option("--ci","Disable spinner and interactive prompts (CI mode)").arguments("[command...]").action(n=>{if(n&&n.length>0){const o=n[0];if(!h.commands.map(n=>n.name()).includes(o)){console.error(r.red(`Unknown command: ${o}`));const n=process.cwd(),e=d.existsSync(l.join(n,"dashnex.json")),s=d.existsSync(l.join(n,"node_modules"));["build","router","bindings","env","db"].includes(o)?e&&!s?console.log(r.yellow(`\`${o}\` is provided by installed modules. Run \`dashnex install\` first.`)):e?console.log(r.green("Run `dashnex` to see available commands.")):console.log(r.yellow(`\`${o}\` must be run inside a DashNex application folder (containing dashnex.json).`)):console.log(r.green("Run `dashnex` to see available commands.")),process.exit(1)}}!function(n){const o=[];o.push("Usage: dashnex [options] [command]"),o.push(""),o.push(n.description()),o.push(""),o.push("Options:"),n.options.forEach(n=>{const e=n.flags.padEnd(20);o.push(` ${e} ${n.description}`)}),o.push(""),o.push("Commands:"),n.commands.forEach(n=>{const e=` ${n.name().padEnd(20)} ${n.description()}`;o.push(e),n.commands.length>0&&n.commands.forEach(n=>{const e=` ${n.name().padEnd(20)} ${n.description()}`;o.push(e)})}),console.log(o.join("\n"))}(h)});var S=async()=>{try{const n=await t();return n&&await a(n),n}catch(n){throw n instanceof e&&process.exit(n.code),n}},v=null;var E=null;async function $(n,o){const e=[];try{const s=function(n,o){const e=l.join(o,"package.json");if(d.existsSync(e))try{const s=JSON.parse(d.readFileSync(e,"utf8"));if(s.name===n)return o;const t={...s.dependencies,...s.devDependencies}[n];if(t&&(t.startsWith("link:")||t.startsWith("file:"))){const n=l.resolve(o,t.replace(/^(link:|file:)/,""));if(d.existsSync(n))return n}}catch{}const s=l.join(o,"node_modules",n);if(d.existsSync(s))return d.realpathSync(s);if(v){const o=l.join(v,n);if(d.existsSync(o))return o}return s}(n,o),t=[l.join(s,"src","commands","index.ts"),l.join(s,"src","commands","index.js")],i=l.join(s,"dist","commands","index.js"),a=(function(n,o){const e=l.join(o,"package.json");if(!d.existsSync(e))return!1;try{const o=JSON.parse(d.readFileSync(e,"utf8")),s={...o.dependencies,...o.devDependencies}[n];return"string"==typeof s&&(s.startsWith("link:")||s.startsWith("file:"))}catch{return!1}}(n,o)?[...t,i]:[i,...t]).find(n=>d.existsSync(n));if(a){process.env.DEBUG&&console.log(`Loading ${n} commands from: ${a}`);const o=await async function(n){return/\.(ts|mts|cts|tsx)$/.test(n)?(E??=u(import.meta.url,{interopDefault:!1}),await E.import(n)):await import(p(n).href)}(a);let s=[];Array.isArray(o)?s=o:o.default?s=Array.isArray(o.default)?o.default:[]:o.commands&&(s=Array.isArray(o.commands)?o.commands:[]),s.length>0&&e.push(...s)}}catch(s){if(process.env.DEBUG){const o=s instanceof Error?s.message:String(s);console.log(r.yellow(`Module ${n} has no commands or failed to load: ${o}`))}}return e}function w(t,a,c={}){const d=t.command(a.name).description(a.description);a.arguments&&a.arguments.forEach(n=>{const o=n.required?`<${n.name}>`:`[${n.name}]`;void 0!==n.defaultValue?d.argument(o,n.description,n.defaultValue):d.argument(o,n.description)}),d.option("--ci","Disable spinner (CI mode)"),a.options&&a.options.forEach(n=>{void 0!==n.defaultValue?"number"==typeof n.defaultValue?d.option(n.flags,n.description,String(n.defaultValue)):d.option(n.flags,n.description,n.defaultValue):d.option(n.flags,n.description)}),a.subcommands&&a.subcommands.forEach(n=>{w(d,n,{...c,...a.options})}),a.subcommands&&a.subcommands.length>0?d.action(async()=>{d.outputHelp()}):d.action(async(...t)=>{try{const n=t[t.length-1],o=n.opts(),e=n.processedArgs,i={...c,...o};a.arguments?.forEach((n,o)=>{void 0!==e[o]&&(i[n.name]=e[o])}),i.context={auth:{getSession:S},businessApiBase:s()},await a.handler.execute(i)}catch(d){if(d instanceof e)return void n(d.code);if(i(d))return void console.log(r.yellow(o));console.error(r.red(`❌ Command failed in ${a.name}:`),d),process.exit(1)}})}(async function(){g.config({quiet:!0}),process.env.DASHNEX_CLI="1",function(){try{v=f("npm root -g",{encoding:"utf8"}).trim()}catch{v=null}}(),process.argv.includes("--ci")&&(process.env.DASHNEX_CI="1");let n=process.cwd();const o=l.dirname(m(import.meta.url)),e=l.resolve(o,"..");let s="@dashnex/cli";try{const n=JSON.parse(await d.readFile(l.join(e,"package.json"),"utf8"));n.name&&(s=n.name)}catch{}process.env.DEBUG&&(console.log(`CLI started from directory: ${n}`),console.log(`CLI package directory: ${e}`),console.log(`Global node_modules: ${v??"not found"}`));const t=await async function(n){try{const e=[],s=/* @__PURE__ */new Set,t=n=>{s.has(n)||(s.add(n),e.push(n))},i=l.join(n,"dashnex.json");if(d.existsSync(i)){const o=l.join(n,"package.json");if(d.existsSync(o)){const n=JSON.parse(d.readFileSync(o,"utf8"));n.name&&t(n.name)}}const a=l.join(n,"package.json");if(d.existsSync(a)){const o=JSON.parse(d.readFileSync(a,"utf8")),e={...o.dependencies,...o.devDependencies,...o.optionalDependencies};for(const[s,i]of Object.entries(e)){if("string"==typeof i&&(i.startsWith("link:")||i.startsWith("file:"))){const o=l.resolve(n,i.replace(/^(link:|file:)/,""));if(d.existsSync(l.join(o,"dashnex.json"))){t(s);continue}}const o=l.join(n,"node_modules",s);d.existsSync(l.join(o,"dashnex.json"))&&t(s)}}if(v&&d.existsSync(v))try{const n=d.readdirSync(v);for(const o of n)if(o.startsWith("@")){const n=l.join(v,o);try{if(d.statSync(n).isDirectory()){const e=d.readdirSync(n);for(const n of e){const e=`${o}/${n}`,s=l.join(v,e);d.existsSync(l.join(s,"dashnex.json"))&&t(e)}}}catch{}}else{const n=l.join(v,o);d.existsSync(l.join(n,"dashnex.json"))&&t(o)}}catch(o){if(process.env.DEBUG){const n=o instanceof Error?o.message:String(o);console.log("⚠ Error scanning global node_modules:",n)}}return e}catch(o){if(process.env.DEBUG){const n=o instanceof Error?o.message:String(o);console.log("⚠ Error reading app package.json:",n)}return[]}}(n);t.includes(s)||t.push(s),process.env.DEBUG&&(console.log(`CLI package directory: ${e}`),console.log(`Discovered modules: ${t.join(", ")}`));for(const c of t){let o=[];if(c===s)try{const n=l.join(e,"dist","commands","index.js");if(process.env.DEBUG&&console.log(`Loading ${s} commands from: ${n}`),await d.pathExists(n)){const e=await import(p(n).href);e.default?o=Array.isArray(e.default)?e.default:[]:e.commands&&(o=Array.isArray(e.commands)?e.commands:[]),process.env.DEBUG&&console.log(`Loaded ${o.length} commands from ${s}`)}}catch(a){if(process.env.DEBUG){const n=a instanceof Error?a.message:String(a);console.log(r.yellow(`Failed to load ${s} commands: ${n}`))}}else o=await $(c,n);for(const n of o)w(h,n)}const i=l.join(n,"dist","commands","index.js");if(await d.pathExists(i))try{const{default:n}=await import(p(i).href);if(n&&Array.isArray(n))for(const o of n)w(h,o)}catch(a){if(process.env.DEBUG){const n=a instanceof Error?a.message:String(a);console.error(r.yellow(`⚠ Failed to load application commands: ${n}`))}}h.parse(process.argv)})().catch(s=>{s instanceof e?n(s.code):(i(s)&&(console.log(r.yellow(o)),process.exit(0)),console.error(r.red("❌ CLI failed:"),s),process.exit(1))});
|
package/dist/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as s,t as e}from"./chunks/dashnex-
|
|
1
|
+
import{n as s,t as e}from"./chunks/dashnex-BKNuCWid.js";var n={name:s.name,version:s.version,description:s.description,...e};export{n as default};
|
package/dist/commands/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as e,a as t,c as o,d as s,f as n,g as r,h as a,i,l as c,m as l,n as d,o as p,p as u,r as h,s as m,t as g,u as f,v as w,y}from"../chunks/auth-9zeRR7R-.js";import $ from"chalk";import v from"fs-extra";import x from"path";import{fileURLToPath as k}from"url";import{execFileSync as b,spawn as S,spawnSync as E}from"child_process";import D from"fs";import T from"inquirer";import{X509Certificate as C,createHash as P,createPrivateKey as A,randomBytes as j}from"crypto";import N from"net";import I from"os";import F from"adm-zip";import{diffLines as R}from"diff";import O from"ignore";import{WebSocket as B}from"partysocket";import z from"ws";import{inspect as _}from"node:util";import{rm as L}from"node:fs/promises";var U=e=>e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,""),M=async e=>{const t=(e=>{switch(process.platform){case"darwin":return{command:"open",args:[e]};case"win32":return{command:"cmd",args:["/c","start","",e]};default:return{command:"xdg-open",args:[e]}}})(e);return!!t&&new Promise(e=>{try{const o=S(t.command,t.args,{stdio:"ignore",detached:!0});o.on("error",t=>{r(t),e(!1)}),o.on("spawn",()=>{a(`Opened browser via ${t.command}`),o.unref(),e(!0)})}catch(o){r(o),e(!1)}})},W="urn:ietf:wg:oauth:2.0:oob",J=class extends Error{constructor(e,t){super(e),this.code=t,this.name="OAuthError"}},H=class extends Error{},X=class extends H{},G=e=>{const t=e.data;return t&&"object"==typeof t?t:e},q=e=>{const t=e.items??[];return Array.isArray(t)?t.map(e=>{if(!e||"object"!=typeof e)return null;const t=e,o=Number(t.id);return!Number.isFinite(o)||o<=0?null:{id:o,name:String(t.name??t.companyName??`Business ${o}`).trim()}}).filter(e=>null!==e):[]},Y=async(t,o)=>{const s=[];let r=1,i=0;do{const l=`${n()}${t}?page=${r}&perPage=100`;a(`GET ${l}`);const{response:d,body:p}=await c(l,{headers:{Authorization:`Bearer ${o}`}});if(a(`Response: ${d.status}`),e(`Businesses response (${t})`,p),!d.ok){if(401===d.status)throw new X("Your DashNex ID session has expired.");if(404===d.status)throw new H(`Your DashNex ID account is not linked to a contact yet. Finish signing up at ${n()} and try again.`);throw new H("Failed to fetch businesses. Please try again.")}const u=G(p),h=q(u);if(i="number"==typeof u.total?u.total:h.length,s.push(...h),h.length<100||s.length>=i)break;r++}while(s.length<i);return s},V=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"],K=(e="")=>{if(process.env.DASHNEX_CI)return{start:()=>{},stop:()=>{},text:()=>{}};let t=null,o=0,s=e;const n=()=>{const e=V[o%V.length];process.stderr.write(`\r[K${e} ${s}`),o++};return{start:e=>{void 0!==e&&(s=e),t||(o=0,n(),t=setInterval(n,80))},stop:()=>{t&&(clearInterval(t),t=null),process.stderr.write("\r[K\n")},text:e=>{s=e,t&&n()}}},Z=["issuer"],Q=async(t,o,s={})=>{const r=K();let i;r.start("Fetching businesses...");try{i=await(async e=>{const t=await Y("/api/businesses/my",e),o=await Y("/api/businesses/other",e),s=/* @__PURE__ */new Map;for(const n of t)s.set(n.id,n);for(const n of o)s.has(n.id)||s.set(n.id,n);return a(`Businesses (my: ${t.length}, other: ${o.length}, merged: ${s.size})`),[...s.values()]})(t)}finally{r.stop()}0===i.length&&(console.error($.red("No businesses or teams found for your account!")),console.log($.green(`Create your first business at ${n()}`)),process.exit(1));const l=await ee(i,s);let d;r.start("Switching business...");try{d=await(async(t,o)=>{const s=`${n()}/api/businesses/${o}/switch`;a(`POST ${s}`);const{response:r,body:i}=await c(s,{method:"POST",headers:{Authorization:`Bearer ${t}`}});if(a(`Response: ${r.status}`),e("Switch response",i),!r.ok){if(401===r.status)throw new X("Your DashNex ID session has expired.");if(403===r.status)throw new H("You do not have access to that business.");throw new H("Failed to switch to business. Please try again.")}const l=G(i);if(!l.token||!l.refreshToken)throw new H("Invalid response from server. Please try again.");return{token:l.token,refreshToken:l.refreshToken,businessId:o}})(t,l.id)}finally{r.stop()}return await oe(o,{token:d.token,refreshToken:d.refreshToken,businessId:d.businessId,...await te(d.token)}),await se(),l},ee=async(e,t)=>{if(void 0!==t.businessId){const o=e.find(e=>String(e.id)===String(t.businessId).trim());return o||(console.error($.red(`Business "${t.businessId}" not found.`)),process.exit(1)),a(`Selected business via argument: ${o.id} ${o.name}`),o}if(1===e.length)return a(`Selected business: ${e[0].id} ${e[0].name}`),e[0];const o=e.find(e=>String(e.id)===String(t.currentBusinessId)),{businessId:s}=await T.prompt([{type:"select",name:"businessId",message:"Select business:",choices:e.map(e=>({name:e===o?`${e.name} ${$.dim("(current)")}`:e.name,value:e.id})),...o&&{default:o.id}}]),n=e.find(e=>e.id===s)||e[0];return a(`Selected business: ${n.id} ${n.name}`),n},te=async t=>{try{const o=`${s()}/business/v1/cli/settings`;a(`GET ${o}`);const{response:n,body:r}=await c(o,{headers:{Authorization:`Bearer ${t}`}});if(a(`Response: ${n.status}`),e("CLI settings response",r),n.ok&&r&&"object"==typeof r)return r}catch(o){r(o)}return{}},oe=async(e,t)=>{const o=x.dirname(e);await v.pathExists(o)&&(await v.stat(o)).isFile()&&await v.remove(o),await v.ensureDir(o);let s=t;if(await v.pathExists(e))try{s={...await v.readJson(e),...t};for(const e of Z)delete s[e]}catch{a("Existing settings.json could not be parsed, overwriting")}await v.writeJson(e,s,{spaces:2}),a("Saved credentials to .dashnex/settings.json")},se=async()=>{const e=x.join(process.cwd(),".gitignore");if(!(await v.pathExists(e)))return;let t=await v.readFile(e,"utf8");t.includes(".dashnex")||(t=t.trimEnd(),t.endsWith("\n")||(t+="\n"),t+="\n.dashnex\n",await v.writeFile(e,t))},ne="v1.4.4",re=x.join(m,"certificates"),ae=["localhost","127.0.0.1","::1"],ie="win32"===process.platform,ce=()=>0===E("mkcert",["-version"],{stdio:"ignore",shell:ie}).status?"mkcert":null,le=async(e={})=>{const t=(e.lookupPath??ce)();if(t)return t;const o=e.cacheDir??x.join(I.homedir(),".dashnex","mkcert"),s=((e=process.platform,t=process.arch)=>{const o="x64"===t?"amd64":t;if("win32"===e)return`mkcert-${ne}-windows-${o}.exe`;if("darwin"===e)return`mkcert-${ne}-darwin-${o}`;if("linux"===e)return`mkcert-${ne}-linux-${o}`;throw new Error(`Unsupported platform for mkcert: ${e}`)})(),n=x.join(o,s);if(await v.pathExists(n))return n;const r=`https://github.com/FiloSottile/mkcert/releases/download/${ne}/${s}`;e.log?.(`mkcert not found, downloading ${ne} to ${n}`);const a=await(e.fetchImpl??fetch)(r);if(!a.ok)throw new Error(`Failed to download mkcert from ${r}: HTTP ${a.status}`);await v.ensureDir(o);const i=`${n}.download`;return await v.writeFile(i,Buffer.from(await a.arrayBuffer())),await v.chmod(i,493),await v.move(i,n,{overwrite:!0}),n},de=(e,t,o)=>{try{const s=new C(v.readFileSync(t)),n=A(v.readFileSync(e));return!!s.checkPrivateKey(n)&&(!(new Date(s.validTo).getTime()<=Date.now())&&o.every(e=>((e,t)=>N.isIP(t)?Boolean(e.checkIP(t)):Boolean(e.checkHost(t)))(s,e)))}catch{return!1}},pe=async(e,t={})=>{const o=t.log??(()=>{}),s=t.certDir??re,n=x.resolve(e,s),a=x.join(n,"localhost-key.pem"),i=x.join(n,"localhost.pem"),c=(e=>{const t=(e??"").trim();return t&&!ae.includes(t)?[...ae,t]:[...ae]})(t.host);try{if(de(a,i,c))return o(`Using existing local certificate from ${x.relative(e,n)||"."}`),{key:a,cert:i};const r=await(t.resolveMkcert??(()=>le({log:o})))();if(await v.ensureDir(n),o("Generating a trusted local certificate with mkcert (this may prompt for your password once)..."),b(r,["-install","-key-file",a,"-cert-file",i,...c],{stdio:"inherit",shell:ie}),!(await v.pathExists(a))||!(await v.pathExists(i)))throw new Error("mkcert finished but the certificate files were not created");let l;await(async(e,t)=>{const o=x.join(e,".gitignore");if(!(await v.pathExists(o)))return!1;const s=await v.readFile(o,"utf8"),n=s.split(/\r?\n/).map(e=>e.trim()),r=new Set([t,`/${t}`,`${t}/`,`/${t}/`]);if(n.some(e=>r.has(e)))return!1;const a=0===s.length||s.endsWith("\n")?"":"\n";return await v.appendFile(o,`${a}${t}\n`),!0})(e,s.split(x.sep)[0]);try{const e=b(r,["-CAROOT"],{encoding:"utf8",shell:ie}).trim();e&&(l=x.join(e,"rootCA.pem"))}catch{}return o(`Certificates created in ${x.relative(e,n)||"."}`),{key:a,cert:i,...l?{rootCA:l}:{}}}catch(l){return r(l),o(`Failed to set up HTTPS certificates: ${l instanceof Error?l.message:String(l)}`),null}},ue=["Using (vars|secrets) defined in ","\\(node:\\d+\\) MaxListenersExceededWarning:","\\(Use `node --trace-warnings","\\[DEP0205\\] DeprecationWarning:","\\(Use `node --trace-deprecation","Detected multiple renderers concurrently rendering the same context provider","\\[WARNING\\].*You have defined bindings to the following internal Durable Objects","You have defined bindings to the following internal Durable Objects",'\\{"name":".+","class_name":".+"\\}',"These will not work in local development, but they should work in production","If you want to develop these locally, you can define your DO in a separate Worker","For detailed instructions, refer to the Durable Objects section here:","developers.cloudflare.com/workers/wrangler/api#supported-bindings","workerd/server/server\\.c\\+\\+.*warning:.*DurableObjectNamespace"],he=(e,t)=>t.length>0&&t.some(t=>t.test(e)),me=(e,t)=>{let o="";return{write:s=>{const n="string"==typeof s?s:s.toString();o+=n;const r=o.split(/\r?\n/);o=r.pop()??"";for(const o of r){const s=o.trim();s?he(s,t)?a(`Filtered: ${s}`):e.write(o+"\n"):a("Filtered: <empty line>")}},flush:()=>{const s=o.trim();if(s){if(he(s,t))return a(`Filtered: ${s}`),void(o="");e.write(o),o=""}else o=""}}},ge=async(e,t={})=>{const o=x.join(e,"dashnex.json"),s=x.join(e,"package.json");if(a(`Checking for dashnex.json at: ${o}`),!(await v.pathExists(o)))throw new Error("Run this command in a DashNex Web application folder.");if(a("dashnex.json found, checking for package.json"),!(await v.pathExists(s)))throw new Error("package.json is missing.");let n;a("package.json found, checking for dev script");try{n=await v.readJson(s)}catch(h){throw r(h),new Error("Failed to read package.json.")}if(!n.scripts||!n.scripts.dev)throw new Error('The "dev" script is missing from package.json. Please add a "dev" script to package.json.');const i=((e,t=process.env)=>{const o=e.port??t.PORT;if(null==o||""===String(o).trim())return;const s=Number(o);if(!Number.isInteger(s)||s<1||s>65535)throw new Error(`Invalid port: ${o}. Use a number between 1 and 65535.`);return s})(t),c=((e,t=process.env)=>{const o=(e.host??t.HOST??"").trim();return""===o?void 0:o})(t);a("dev script found, checking for npm availability");const l=await(async e=>new Promise(t=>{const o=S(`${e} --version`,{stdio:"ignore",shell:!0});o.on("error",()=>{t(!1)}),o.on("close",e=>{t(0===e)})}))("npm");if(a(`npm available: ${l}`),!l)throw new Error("npm is not available. Please install npm to continue.");const d={};if(t.https){const t=await pe(e,{host:c,log:e=>console.log($.dim(e))});t?(d.DASHNEX_DEV_HTTPS_KEY=t.key,d.DASHNEX_DEV_HTTPS_CERT=t.cert,console.log($.green("HTTPS enabled for the dev server"+(i?` on https://${c??"localhost"}:${i}`:""))),t.rootCA&&console.log($.dim(`Local CA: ${t.rootCA} (import it in browsers on other machines, e.g. when the app runs in Docker)`))):console.log($.yellow("HTTPS is not available, starting the dev server over HTTP instead."))}const p=[];void 0!==i&&p.push("--port",String(i)),void 0!==c&&p.push("--hostname",c);const u=p.length?["--",...p]:[];return a(`npm available, executing: npm run dev${u.length?` ${u.join(" ")}`:""} in ${e}`),(async(e,t=[],o=process.cwd(),s=[],n={})=>{const a=t.length?`${e} ${t.join(" ")}`:e,i=s.length>0;return new Promise((e,t)=>{const c=S(a,{stdio:i?["inherit","pipe","pipe"]:"inherit",cwd:o,shell:!0,env:{...process.env,NODE_ENV:"development",FORCE_COLOR:"1",...n}});if(c.on("error",e=>{r(e),t(e)}),i&&c.stdout&&c.stderr){const t=me(process.stdout,s),o=me(process.stderr,s);c.stdout.on("data",e=>t.write(e)),c.stderr.on("data",e=>o.write(e)),c.on("close",s=>{t.flush(),o.flush(),e(null===s?0:s)})}else c.on("close",t=>{e(null===t?0:t)})})})("npm run dev",u,e,(()=>{const e=[];for(const t of ue)try{e.push(new RegExp(t))}catch{a(`Invalid dev output filter pattern ignored: ${t}`)}return e})(),d)},fe=async e=>new Promise(t=>{const o=S(`${e} --version`,{stdio:"ignore",shell:!0});o.on("error",()=>{t(!1)}),o.on("close",e=>{t(0===e)})}),we=async e=>{let t;if(await fe("pnpm"))t="pnpm install --config.strict-dep-builds=false",a("Using package manager: pnpm");else{if(!(await fe("npm")))throw new Error("Neither pnpm nor npm is available. Install Node.js (which bundles npm) or pnpm to continue.");t="npm install --legacy-peer-deps",a("Using package manager: npm (with --legacy-peer-deps for DashNex peer-dep overlap)")}return a(`Executing: ${t} in ${e}`),new Promise((o,s)=>{const n=[],a=[],i=S(t,{stdio:["ignore","pipe","pipe"],cwd:e,shell:!0,env:{...process.env,NPM_CONFIG_LOGLEVEL:"error"}});i.stdout&&i.stdout.on("data",e=>n.push(e)),i.stderr&&i.stderr.on("data",e=>a.push(e)),i.on("error",e=>{r(e),s(e)}),i.on("close",e=>{const t=null===e?0:e;0!==t&&(n.length&&process.stdout.write(Buffer.concat(n)),a.length&&process.stderr.write(Buffer.concat(a))),o(t)})})},ye="Run this command in a DashNex Web application folder.",$e=e=>{const t=x.join(e,"dashnex.json");return v.pathExistsSync(t)},ve=(e,t=!1)=>{t||console.log($.cyan(e))},xe=(e,t,o={})=>{const s=`${e} ${t.join(" ")}`,n=!0===o.silent;return new Promise((t,o)=>{a(`Running: ${s}`);const i=S(s,{stdio:n?["ignore","pipe","pipe"]:"inherit",cwd:process.cwd(),shell:!0});i.on("error",e=>{r(e),o(e)}),i.on("close",o=>{a(`${e} exited with code ${o}`),t(null===o?1:o)})})},ke=(e,t)=>new Promise((o,s)=>{a(`Running: ${e} ${t.join(" ")}`);const n=S(e,t,{cwd:process.cwd()});let i="";n.stdout?.on("data",e=>{i+=e.toString()}),n.stderr?.on("data",e=>{i+=e.toString()}),n.on("error",e=>{r(e),s(e)}),n.on("close",t=>{a(`${e} exited with code ${t}`),o({exitCode:null===t?1:t,output:i})})}),be=async e=>{a("Running check: install");try{return 0===await we(process.cwd())}catch(t){return r(t),!e&&t instanceof Error&&console.error($.red(t.message)),!1}},Se=async()=>{try{const{output:e}=await ke(process.execPath,[process.argv[1],"db","migrate","--help"]);return e.includes("Usage: dashnex db migrate")}catch(e){return r(e),!1}},Ee=async e=>{a("Running check: db migrate");try{const{exitCode:t,output:o}=await ke(process.execPath,[process.argv[1],"db","migrate"]);return 0===t||(o.includes("Unknown command")?(a("db migrate command is not available, skipping"),!0):(!e&&o.trim().length>0&&process.stderr.write(o),!1))}catch(t){return r(t),!0}},De=e=>new Promise(t=>{const o=S(`${e} --version`,{stdio:"ignore",shell:!0});o.on("error",()=>t(!1)),o.on("close",e=>t(0===e))}),Te=[{name:"TypeScript",command:"npx",args:["tsc","--noEmit"],notFoundMessage:"TypeScript is not available. Please install it with: npm install -D typescript"}],Ce=class{async execute(){a("Check flow started"),$e(process.cwd())||(console.error($.red(ye)),process.exit(1)),ve("Checking dependencies..."),await be(!1)||(console.error($.red("Check failed.")),process.exit(1)),a(`Running ${Te.length} check(s)`);for(const t of Te){a(`Running check: ${t.name}`),ve(`Checking ${t.name}...`),await De(t.command)||(console.error($.red(t.notFoundMessage)),process.exit(1));try{0!==await xe(t.command,t.args)&&(console.error($.red("Check failed.")),process.exit(1))}catch(e){r(e),console.error($.red(t.notFoundMessage)),process.exit(1)}}await Se()?(ve("Checking database migrations..."),await Ee(!1)||(console.error($.red("Check failed.")),process.exit(1))):a("db migrate command is not available, skipping"),console.log($.green("All checks passed."))}async run(e=!1){if(a("Running checks (called from push)"),!$e(process.cwd()))return e||console.error($.red(ye)),!1;if(ve("Checking dependencies...",e),!(await be(e)))return!1;const t=e?{silent:!0}:void 0;for(const s of Te){if(a(`Running check: ${s.name}`),ve(`Checking ${s.name}...`,e),!(await De(s.command)))return console.error($.red(s.notFoundMessage)),!1;try{if(0!==await xe(s.command,s.args,t))return!1}catch(o){return r(o),console.error($.red(s.notFoundMessage)),!1}}if(await Se()){if(ve("Checking database migrations...",e),!(await Ee(e)))return!1}else a("db migrate command is not available, skipping");return e||console.log($.green("All checks passed.")),!0}},Pe=" │ ",Ae=e=>e.subarray(0,8192).includes(0),je=e=>{const t=e.split("\n");return""===t[t.length-1]&&t.pop(),t},Ne=(e,t)=>{if(null===e)return" ".repeat(t);const o=e.replace(/\t/g," ");return o.length<=t?o.padEnd(t):`${o.slice(0,Math.max(0,t-1))}…`},Ie=(e,t,o)=>{const s=Math.max(o??process.stdout.columns??120,43),n=Math.floor((s-3)/2),r=((e,t)=>{const o=[],s=R(e,t);for(let n=0;n<s.length;n+=1){const e=s[n];if(e.removed){const t=je(e.value),r=s[n+1],a=r?.added?je(r.value):[];r?.added&&(n+=1);const i=Math.max(t.length,a.length);for(let e=0;e<i;e+=1)o.push({left:t[e]??null,right:a[e]??null,changed:!0})}else if(e.added)for(const t of je(e.value))o.push({left:null,right:t,changed:!0});else for(const t of je(e.value))o.push({left:t,right:t,changed:!1})}return o})(e,t),a=/* @__PURE__ */new Set;r.forEach((e,t)=>{if(e.changed)for(let o=Math.max(0,t-3);o<=Math.min(r.length-1,t+3);o+=1)a.add(o)});const i=[];i.push($.bold(Ne("local (current)",n))+$.dim(Pe)+$.bold("incoming"));let c=-1,l=0,d=0;for(let p=0;p<r.length;p+=1){if(!a.has(p))continue;if(l>=200){d+=1;continue}-1!==c&&p>c+1&&i.push($.dim("···")),c=p,l+=1;const e=r[p],t=Ne(e.left,n),o=Ne(e.right,n);if(!e.changed){i.push($.dim(t)+$.dim(Pe)+$.dim(o));continue}const s=null!==e.left?$.red(t):t,u=null!==e.right?$.green(o):o;i.push(s+$.dim(Pe)+u)}return d>0&&i.push($.dim(`… ${d} more diff line(s) not shown`)),i.join("\n")},Fe=[".dashnex",".git",".vinext",".wrangler","wrangler.jsonc","wrangler.toml",".dev.vars",".github"],Re=[".env","wrangler.jsonc"],Oe=e=>x.join(e,m,"sync.json"),Be=e=>P("sha1").update(`blob ${e.length}\0`).update(e).digest("hex"),ze=async e=>{const t=O();t.add(Fe);const o=x.join(e,".gitignore");return await v.pathExists(o)&&t.add(await v.readFile(o,"utf8")),t},_e=e=>e.split(x.sep).join("/"),Le=e=>Fe.some(t=>e===t||e.startsWith(`${t}/`)),Ue=async(e,t={})=>{const o={},s=await(async e=>{const t=await ze(e),o=[],s=async n=>{const r=await v.readdir(n,{withFileTypes:!0});for(const a of r){const r=x.join(n,a.name),i=_e(x.relative(e,r));Le(i)||t.ignores(a.isDirectory()?`${i}/`:i)||(a.isDirectory()?await s(r):a.isFile()&&o.push(i))}};return await s(e),o})(e);for(const n of s)o[n]=Be(await v.readFile(x.join(e,n)));if(t.includeGenerated)for(const n of Re){if(n in o)continue;const t=x.join(e,n);(await v.lstat(t).catch(()=>null))?.isFile()&&(o[n]=Be(await v.readFile(t)))}return o},Me=async e=>{try{const t=await v.readFile(Oe(e),"utf8"),o=JSON.parse(t);return"string"!=typeof o?.commit||"object"!=typeof o?.manifest||null===o.manifest?null:o}catch{return null}},We=async(e,t,o)=>{const s={commit:t,manifest:o,updatedAt:/* @__PURE__ */(new Date).toISOString()},n=Oe(e);try{await v.ensureDir(x.dirname(n)),await v.writeFile(n,`${JSON.stringify(s,null,2)}\n`),a(`Sync state written: commit ${t}, ${Object.keys(o).length} files`)}catch(r){a(`Failed to write sync state: ${r instanceof Error?r.message:"unknown"}`)}},Je=1048576,He=async(e,t)=>{const{install:o}=await T.prompt([{type:"confirm",name:"install",message:"Install dependencies?",default:!0}]);if(!o){if(x.resolve(e)!==x.resolve(t)){const o=x.relative(t,e);console.log($.cyan(`Run "cd ${o}" to change to the application folder.`))}return}try{const t=K();t.start("Installing dependencies...");try{const o=await we(e);if(t.stop(),0!==o)return void a(`install exited with code ${o}`)}catch(i){throw t.stop(),i}}catch(i){return r(i),void console.error($.red(i instanceof Error?i.message:"Failed to run install."))}const{migrate:s}=await T.prompt([{type:"confirm",name:"migrate",message:"Create or update database?",default:!0}]);if(!s)return;a(`Running dashnex db migrate in ${e}`);try{const t=K();t.start("Creating or updating database...");try{const o=await((e,t,o)=>{const s=`${x.join(e,"node_modules",".bin")}${x.delimiter}${process.env.PATH??""}`,n={...process.env,PATH:s};return new Promise((s,a)=>{const i=S(t,{cwd:e,shell:!0,stdio:"inherit",env:o?{...n,...o}:n});i.on("error",e=>{r(e),a(e)}),i.on("close",e=>{s(null===e?0:e)})})})(e,"dashnex db migrate");if(t.stop(),0!==o)return void a(`dashnex db migrate exited with code ${o}`)}catch(i){throw t.stop(),i}}catch(i){return r(i),void console.error($.red("Failed to run dashnex db migrate."))}const{dev:n}=await T.prompt([{type:"confirm",name:"dev",message:"Run development server?",default:!0}]);if(n)try{await ge(e)}catch(i){r(i),console.error($.red(i instanceof Error?i.message:"Failed to run dev server."))}},Xe=(e,t)=>{if(e.length!==t.length)return`local ${e.length} bytes, incoming ${t.length} bytes`;const o=e.length;for(let s=0;s<o;s+=1)if(e[s]!==t[s]){return`size ${o} matches; first diff at byte ${s} (local=0x${e[s].toString(16).padStart(2,"0")} incoming=0x${t[s].toString(16).padStart(2,"0")})`}return`size ${o} matches; no byte difference (unexpected)`},Ge=async(e,t)=>{let o;try{o=await v.lstat(e)}catch{return{kind:"new"}}if(o.isDirectory())return{kind:"different",reason:"target is a directory"};if(o.size!==t.length)return{kind:"different",reason:`local ${o.size} bytes, incoming ${t.length} bytes`};const s=await v.readFile(e);return 0===Buffer.compare(s,t)?{kind:"identical"}:{kind:"different",reason:Xe(s,t)}},qe=async(e,t,o)=>{console.log(`\n${$.bold(t)}`);try{const t=await v.readFile(e);if(t.length>Je||o.length>Je||Ae(t)||Ae(o))return void console.log($.dim(`(no preview: ${Xe(t,o)})`));console.log(Ie(t.toString("utf8"),o.toString("utf8")))}catch(s){r(s),console.log($.dim("(no preview available)"))}},Ye=async e=>{const{choice:t}=await T.prompt([{type:"expand",name:"choice",message:`Overwrite ${e}?`,default:"y",choices:[{key:"y",name:"yes (overwrite this file)",value:"yes"},{key:"n",name:"no (keep local version)",value:"no"},{key:"a",name:"overwrite all remaining differing files",value:"all"},{key:"s",name:"skip all remaining differing files",value:"skip"}]}]);return t},Ve=async(e,t)=>{const o=t?$.yellow(" (has local changes!)"):"",{choice:s}=await T.prompt([{type:"expand",name:"choice",message:`Delete ${e}? It no longer exists in the application.${o}`,default:"y",choices:[{key:"y",name:"yes (delete this file)",value:"yes"},{key:"n",name:"no (keep this file)",value:"no"},{key:"a",name:"delete all remaining removed files",value:"all"},{key:"s",name:"keep all remaining removed files",value:"skip"}]}]);return s},Ke=async(e,t)=>{let o=x.dirname(e);const s=x.resolve(t);for(;x.resolve(o)!==s;)try{if((await v.readdir(o)).length>0)return;await v.rmdir(o),o=x.dirname(o)}catch{return}},Ze=(e,t)=>x.resolve(e)===x.resolve(t)?"Application is successfully pulled.":`Application is successfully pulled into ${x.relative(t,e)}.`,Qe=class{async execute(e={}){a("Pull flow started");const t=await g();if(!t)return;const o=process.cwd();let s;if(e.folder)s=e.folder.trim()||".",a(`Folder argument provided: ${s}`);else{a("No folder argument, prompting user");const{folder:e}=await T.prompt([{type:"input",name:"folder",message:"Which folder to pull the application to?",default:"."}]);s=e.trim()||"."}const n=x.resolve(o,s);a(`Target folder resolved: ${n}`);try{const e=await v.lstat(n).catch(()=>null);e&&!e.isDirectory()&&(console.error($.red(`Cannot pull into ${n}: path exists and is not a directory.`)),process.exit(1)),await v.ensureDir(n),a(`Target folder ensured: ${n}`)}catch(l){r(l);const e=l instanceof Error?l.message:"Unknown error";console.error($.red(`Cannot create folder ${n}: ${e}`)),process.exit(1)}const i=x.join(I.tmpdir(),`dashnex-pull-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`),c=K();c.start("Analyzing local files...");try{const s=await Me(n),d=await Ue(n,{includeGenerated:!0});a(`Local manifest has ${Object.keys(d).length} files`),c.text("Pulling application...");const p=await this.download(t.token,d);c.text("Downloading..."),await v.writeFile(i,p.buffer),a(`Zip written to temp file (${p.viaV2?"v2 diff":"legacy full"} pull)`),c.text("Extracting...");const u=new F(i).getEntries().filter(e=>!e.isDirectory);let h=null;const m=u.find(e=>".dashnex/pull.json"===e.entryName);if(m)try{h=JSON.parse(m.getData().toString("utf8"))}catch(l){r(l)}const g=u.filter(e=>".dashnex"!==e.entryName&&!e.entryName.startsWith(".dashnex/"));a(`Zip has ${g.length} file entries${h?`, meta commit ${h.commit}`:", no meta"}`);const f=e.yes||"1"===process.env.DASHNEX_CI;let w=f?"all":"prompt",y=!1,k=0,b=0,S=0,E=0,D=0;const T=async(e,t,o)=>{try{return await v.ensureDir(x.dirname(e)),await v.writeFile(e,t),o&&await v.chmod(e,o),!0}catch(l){return r(l),!1}};for(const e of g){const t=e.getData(),o=x.join(n,e.entryName),s=x.relative(n,o)||e.entryName,r=(e.header.attr??0)>>16&4095,i=await Ge(o,t);if(a(`Entry ${e.entryName}: ${i.kind}${i.reason?` (${i.reason})`:""}`),"identical"===i.kind){E+=1;continue}if("new"===i.kind){await T(o,t,r)?k+=1:D+=1;continue}let l=!1;if("all"===w)l=!0;else if("none"===w)l=!1;else{c.stop(),await qe(o,s,t),y||(console.log($.dim("Y = yes (default), n = skip, a = overwrite all, s = skip all, h = help")),y=!0);const n=await Ye(s);a(`Entry ${e.entryName}: user chose ${n}`),"yes"===n?l=!0:"no"===n?l=!1:"all"===n?(w="all",l=!0):(w="none",l=!1),c.start("Extracting...")}l?await T(o,t,r)?b+=1:D+=1:S+=1}c.stop();let C=0,P=0;if(h&&h.deleted.length>0){const e=await this.applyDeletions(n,h.deleted,s,f);C=e.deleted,P=e.kept}h&&(E+=Math.max(0,Object.keys(h.manifest).length-g.length)),a(`Pull summary: ${k} new, ${b} overwritten, ${S} skipped, ${C} deleted, ${E} unchanged, ${D} failed`);const A=[`${k} new`,`${b} overwritten`,`${S} skipped`,`${E} unchanged`];if((C>0||P>0)&&A.splice(3,0,`${C} deleted`),D>0&&A.push(`${D} failed`),console.log($.cyan(`Pulled: ${A.join(", ")}`)),h){const e=await Ue(n,{includeGenerated:!0});await We(n,h.commit,e)}x.resolve(n)!==x.resolve(o)?(console.log($.green(Ze(n,o))),process.chdir(n),a(`Changed working directory to ${n}`)):console.log($.green(Ze(n,o))),D>0&&(console.error($.red(`${D} file(s) failed to write. See DEBUG output for details.`)),process.exit(1)),f?a("Non-interactive mode (--yes or --ci), skipping post-pull child commands"):await He(n,o)}catch(l){if(c.stop(),r(l),l instanceof Error&&l.message.startsWith("EXIT:"))throw l;if(y(l))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}finally{await v.remove(i).catch(()=>{}),a("Temp zip removed")}}async download(e,t){const o=`${s()}/business/v1/cli/v2/pull`;a(`POST ${o}`);const n=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({manifest:t})});if(a(`v2 pull response: ${n.status}`),n.ok)return{buffer:Buffer.from(await n.arrayBuffer()),viaV2:!0};const r=await n.json().catch(()=>({}));var i,c;i=n.status,c=r,404===i&&"string"==typeof c.message&&c.message.startsWith("Cannot ")||this.failFromResponse(n.status,r),a("v2 pull endpoint unavailable, falling back to legacy pull");const l=`${s()}/business/v1/cli/pull`;a(`GET ${l}`);const d=await fetch(l,{headers:{Authorization:`Bearer ${e}`}});if(a(`Legacy pull response: ${d.status}`),!d.ok){const e=await d.json().catch(()=>({}));this.failFromResponse(d.status,e)}return{buffer:Buffer.from(await d.arrayBuffer()),viaV2:!1}}failFromResponse(e,t){let o="Failed to pull application.";if(401===e||403===e)o="Please run 'dashnex login' to authenticate.";else if(404===e)o="Business has no application. Run 'dashnex app create' to create one.";else{const e=(e=>"string"==typeof e.error?e.error:"string"==typeof e.message?e.message:void 0)(t);e&&(o=e)}console.error($.red(o)),process.exit(1)}async applyDeletions(e,t,o,s){let n=0,i=0;if(!o){const e=t.slice(0,20);console.log($.yellow("These local files do not exist in the application (kept — no sync state yet):"));for(const t of e)console.log($.yellow(` ${t}`));return t.length>e.length&&console.log($.yellow(` … and ${t.length-e.length} more`)),{deleted:n,kept:t.length}}let c=s?"all":"prompt";for(const d of t){if(!(d in o.manifest)){a(`Deletion candidate ${d} was never synced, keeping`);continue}const t=x.join(e,d),s=await v.readFile(t).catch(()=>null);if(null===s)continue;const p=Be(s)!==o.manifest[d];let u=!1;if("all"===c)u=!0;else if("none"===c)u=!1;else{const e=await Ve(d,p);a(`Deletion ${d}: user chose ${e}`),"yes"===e?u=!0:"no"===e?u=!1:"all"===e?(c="all",u=!0):(c="none",u=!1)}if(u)try{await v.remove(t),await Ke(t,e),n+=1}catch(l){r(l),i+=1}else i+=1}return{deleted:n,kept:i}}},et=async e=>{const t=`${s()}/business/v1/agreements/payment-processing/status`;a(`GET ${t}`);const o=await fetch(t,{headers:{Authorization:`Bearer ${e}`}});if(a(`Payment agreement response: ${o.status}`),!o.ok)return!0;if((await o.json().catch(()=>({}))).hasAccepted)return!0;const n=`${await(async()=>"qa"===process.env.APPLICATION_ENV?"https://business.qa.dashnex.com":"https://business.dashnex.com")()}/money/agreement/`;return console.error($.red(`You need to accept Master Payment Processing Agreement first by visiting this link: ${n}`)),!1},tt=async(e,t={})=>{const o=t.ciMode??!1;return!!(await et(e))&&!!(await(async(e,t)=>{const o=`${s()}/business/v1/tos/latest`;a(`GET ${o}`);const n=await fetch(o,{headers:{Authorization:`Bearer ${e}`}});if(a(`TOS latest response: ${n.status}`),n.ok)return!0;if(404!==n.status)return!0;const i=`${s()}/business/v1/tos/current`;a(`GET ${i}`);const c=await fetch(i,{headers:{Authorization:`Bearer ${e}`}});if(a(`TOS current response: ${c.status}`),!c.ok)return a("Could not fetch current TOS, skipping check"),!0;const l=await c.json().catch(()=>({})),d=l.version??"unknown";if(l.text&&(console.log($.dim("─".repeat(60))),console.log($.bold(`Terms of Service ${d}`)),console.log($.dim("─".repeat(60))),console.log(l.text.replace(/<br\s*\/?>/gi,"\n").replace(/<\/p>/gi,"\n\n").replace(/<\/div>/gi,"\n").replace(/<\/li>/gi,"\n").replace(/<[^>]+>/g,"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/ /g," ").replace(/\n{3,}/g,"\n\n").trim()),console.log($.dim("─".repeat(60)))),t)return console.log($.yellow(`By proceeding you accept the Terms of Service ${d}.`)),!0;const{accept:p}=await T.prompt([{type:"confirm",name:"accept",message:`By creating an application you accept the Terms of Service ${d}. Continue?`,default:!0}]);if(!p)return console.log($.yellow("Cancelled.")),!1;try{const t=`${s()}/business/v1/tos/${l.id}/accept`;a(`POST ${t}`),await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({})})}catch(u){r(u)}return!0})(e,o))},ot=e=>Array.isArray(e.message)&&e.message.length>0?e.message.join("\n"):"string"==typeof e.message?e.message:"string"==typeof e.error?e.error:void 0,st=e=>new Promise(t=>setTimeout(t,e)),nt=e=>e.appFullCustomDomain?e.appFullCustomDomain:e.subdomain?"qa"===(process.env.APPLICATION_ENV??"")?`https://${e.subdomain}.dashnex.dev`:`https://${e.subdomain}.dashnexcloud.com`:null,rt=3e5,at=99,it=e=>new Promise(t=>setTimeout(t,e)),ct=async(e,t,o,s)=>{const n=await fetch(e,{method:"GET",headers:{Authorization:`Bearer ${t}`,Accept:"text/event-stream"}});if(a(`SSE connection opened: ${n.status}`),!n.ok){const e=n.status;let t="Failed to start deployment.";if(401===e||403===e)t="Please run 'dashnex login' to authenticate.";else try{const e=await n.text(),o=JSON.parse(e);"string"==typeof o.error?t=o.error:"string"==typeof o.message&&(t=o.message)}catch{}throw console.error($.red(t)),new Error(`HTTP ${e}: ${t}`)}if(!n.body)throw new Error("Response body is null");a("SSE connection established successfully"),s?.();const i=n.body.getReader();try{for await(const e of async function*(e){const t=new TextDecoder("utf-8");let o="";for(;;){const{value:s,done:n}=await e.read();if(n)break;o+=t.decode(s,{stream:!0});const r=o.split("\n");o=r.pop()||"";let a="";for(const e of r)e.startsWith("data: ")?a=e.slice(6):""===e&&a&&(yield{data:a},a="")}if(o.startsWith("data: ")){const e=o.slice(6);e&&(yield{data:e})}}(i))try{if(!e.data){a("Received empty SSE event");continue}const t=JSON.parse(e.data);a(`SSE message received: ${JSON.stringify(t)}`),o(t)}catch(c){r(c),a(`Failed to parse SSE event data: ${e.data}`)}}finally{i.releaseLock()}},lt=class{async execute(){a("Deploy flow started");const e=await g();if(!e)return;if(!(await tt(e.token,{ciMode:!0})))return;const t=`${u()}/business/v1/applications/deploy`;a(`GET ${t} (SSE)`);let o=0,s="",n=!1,i=0,c=!1;const l=K();let d=0,p=0,h=null;const m=(e,t)=>{const o=Math.round(e);l.text(`[${o}%] ${t}`)},f=()=>{v();const e=o,t=Date.now(),n=rt*((at-e)/at);h=setInterval(()=>{const r=Date.now()-t;let a;if(p>t){const e=Date.now()-p,t=at-d,o=3030.3030303030305*t;a=Math.min(d+e/o*t,at)}else a=Math.min(e+r/n*(at-e),at);a>o&&(o=a,m(o,s))},200)},v=()=>{h&&(clearInterval(h),h=null)};let x=!1;const k=e=>{if(c=!0,"string"==typeof e.error)return v(),l.stop(),console.error($.red(e.error)),n=!0,void(x=!0);"string"==typeof e.message&&(s=e.message),"number"==typeof e.progress&&(d=e.progress,p=Date.now(),e.progress>o&&(o=e.progress)),"string"!=typeof e.message&&"number"!=typeof e.progress||(o>=100?(v(),l.stop(),n=!0):m(o,s))};for(;i<=5;)try{return c?m(o,s):l.start("Connecting..."),f(),await ct(t,e.token,k,()=>{c||(l.text(`[${Math.round(o)}%] Deployment in progress...`),s="Deployment in progress...")}),v(),l.stop(),a("SSE connection closed"),x&&process.exit(1),void(n&&await this.showDeployedUrl(e.token))}catch(b){if(v(),r(b),a(`SSE connection error: ${b}`),n)return l.stop(),void(x&&process.exit(1));if(y(b))return l.stop(),void console.log($.yellow(w));b instanceof Error&&b.message.startsWith("HTTP")&&(l.stop(),console.error($.red("Could not complete deployment. Check your connection and try again.")),process.exit(1)),i++,i>5&&(l.stop(),console.error($.red("Deployment failed. Maximum reconnection attempts exceeded.")),process.exit(1));const e=Math.min(1e3*Math.pow(2,i-1),3e4);a(`Connection error. Reconnecting in ${e/1e3}s... (attempt ${i}/5)`),await it(e)}}async showDeployedUrl(e){try{const t=`${s()}/business/v1/applications`;a(`GET ${t} (for deployed URL)`);const o=await fetch(t,{headers:{Authorization:`Bearer ${e}`}});if(!o.ok)return;const n=await o.json().catch(()=>({})),r=nt({subdomain:n.subdomain,appFullCustomDomain:n.appFullCustomDomain});r&&console.log(`Application is deployed to ${$.cyan(r)}`)}catch(t){r(t)}}},dt=104857600,pt=class extends Error{},ut=class extends Error{},ht=class extends Error{},mt="/business/v1/cli/tail-token";async function gt(e,t={}){const o=`${s()}${mt}${t.debug?"?debug=1":""}`;try{await fetch(o,{method:"DELETE",headers:{Authorization:`Bearer ${e}`}})}catch{}}var ft=["log","info","warn","error","debug"],wt={error:4,warn:3,log:2,info:2,debug:1};function yt(e){return"string"==typeof e&&ft.includes(e)}function $t(e){return e.map(e=>"string"==typeof e?e:function(e){try{return JSON.stringify(e)}catch{return String(e)}}(e)).join(" ")}function vt(e){return e<10?`0${e}`:String(e)}function xt(e){const t=new Date(e);return`${vt(t.getHours())}:${vt(t.getMinutes())}:${vt(t.getSeconds())}.${o=t.getMilliseconds(),o<10?`00${o}`:o<100?`0${o}`:String(o)}`;var o}function kt(e,t){if("string"==typeof e)return e;if("object"==typeof(o=e)&&null!==o&&"string"==typeof o.name&&"string"==typeof o.message&&Object.keys(o).every(e=>"name"===e||"message"===e||"stack"===e)){const t=`${e.name}: ${e.message}`;return e.stack?`${t}\n${e.stack}`:t}var o;return _(e,{colors:t,depth:2,breakLength:120})}var bt={error:e=>$.red(e),warn:e=>$.yellow(e),debug:e=>$.dim(e),log:e=>e,info:e=>e};function St(e,t){const o=t.color?$.dim(xt(e.ts)):xt(e.ts),s=e.level.toUpperCase().padEnd(5),n=[o,t.color?bt[e.level](s):s];n.push(e.args.map(e=>kt(e,t.color)).join(" "));let r=n.join(" ");if(e.truncated){const e=" …(truncated)";r+=t.color?$.dim(e):e}return r}function Et(e,t,o){let s;try{s=JSON.parse(e)}catch{return{kind:"malformed",raw:e}}if("object"!=typeof s||null===s||!yt(s.level))return{kind:"malformed",raw:e};const n=s;return Array.isArray(n.args)||(n.args=void 0===n.args?[]:[n.args]),r=n.level,a=t.minLevel,wt[r]>=wt[a]&&function(e,t){return!t||t.test($t(e.args))}(n,t.filter)?{kind:"line",line:St(n,o)}:{kind:"filtered"};var r,a}var Dt=new Set([4401,4403]);var Tt=[{label:"node_modules",segments:["node_modules"],description:"installed dependencies"},{label:"node_modules/.vite",segments:["node_modules",".vite"],description:"vite cache"},{label:"node_modules/.vite-temp",segments:["node_modules",".vite-temp"],description:"vite cache"},{label:"src/dashnex",segments:["src","dashnex"],description:"generated module registry"},{label:"dist",segments:["dist"],description:"build output"}],Ct={label:".wrangler",segments:[".wrangler"],description:"local database"},Pt=async e=>{await L(e,{recursive:!0,force:!0,maxRetries:5,retryDelay:100})},At={name:"app",description:"Manage DashNex applications (should be executed in application folder)",handler:{execute:async()=>{}},options:[],subcommands:[{name:"create",description:"Create a new DashNex application",handler:new class{async pollApplicationCreated(e,t,o=5e3,s=6e4){const n=Date.now();let i=0;for(;Date.now()-n<s;){i++,a(`Polling application status (attempt ${i})...`);try{const o=await fetch(e,{headers:{Authorization:`Bearer ${t}`}});if(a(`Poll response: ${o.status}`),200===o.status){const e=await o.json();if(a(`Poll response body status: ${e.status}`),"creating"!==e.status)return a(`Application status is "${e.status}", creation complete`),{ready:!0,status:e.status};a('Application status is still "creating", continuing to poll...')}}catch(c){r(c),a(`Poll attempt ${i} failed with network error`)}await st(o)}return{ready:!1}}async execute(e={}){a("Create flow started");const t=await g();if(!t)return;a("Auth valid, checking if business already has application");const o=K(),n=`${s()}/business/v1/applications`;a(`GET ${n}`),o.start("Checking application status...");try{const e=await fetch(n,{headers:{Authorization:`Bearer ${t.token}`}});o.stop(),a(`Applications response: ${e.status}`),a(`Applications response body: ${await e.text()}`),404!==e.status&&(console.error($.red("Business already has an application. Run 'dashnex app pull' to pull the existing one.")),process.exit(1))}catch(m){if(o.stop(),r(m),m instanceof Error&&m.message.startsWith("EXIT:"))throw m;if(y(m))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}a("No existing application, checking agreements");const i=void 0!==e.subdomain;if(!(await tt(t.token,{ciMode:i})))return;let c;a("Agreements checked, proceeding to subdomain prompt");const l=e=>`${s()}/business/v1/applications/check-subdomain?subdomain=${encodeURIComponent(e)}`;let d=e.subdomain?.trim();for(;;){if(d)c=d,d=void 0,a(`Subdomain provided via flag: ${c}`);else{if(i)return console.error($.red("Subdomain is not available.")),void process.exit(1);{const{sub:e}=await T.prompt([{type:"input",name:"sub",message:"Pick your subdomain:",validate:e=>!!e.trim()||"Subdomain is required"}]);c=e.trim()}}a(`Checking subdomain: ${c}`);const e=l(c);a(`GET ${e}`),o.start("Checking subdomain...");try{const s=await fetch(e,{headers:{Authorization:`Bearer ${t.token}`}});o.stop(),a(`Check subdomain response: ${s.status}`);const n=await s.text();if(a(`Check subdomain response body: ${n}`),!s.ok){const e=s.headers.get("content-type")??"";let t="Failed to check subdomain availability.";if(401!==s.status&&403!==s.status||(t="Please run 'dashnex login' to authenticate.",console.error($.red(t)),process.exit(1)),e.includes("application/json")){const e=JSON.parse(n);a(`Check subdomain error body: ${JSON.stringify(e)}`);const o=ot(e);o&&(t=o)}console.error($.red(t));continue}const r=JSON.parse(n);if(a(`Check subdomain parsed body: ${JSON.stringify(r)}`),r.available){a(`Subdomain ${c} is available`);break}console.error($.red("Subdomain is not available. Please choose a different one."))}catch(m){if(o.stop(),r(m),m instanceof Error&&m.message.startsWith("EXIT:"))throw m;if(y(m))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}}const p=`${s()}/business/v1/cli/create`;a(`POST ${p}`);const u={subdomain:c};e.template&&(u.template=e.template,a(`Template: ${e.template}`));const h=JSON.stringify(u);a(`Create request body: ${h}`),o.start("Creating application...");try{const n=await fetch(p,{method:"POST",headers:{Authorization:`Bearer ${t.token}`,"Content-Type":"application/json"},body:h});o.stop(),a(`Create response: ${n.status}`);const c=await n.text();if(a(`Create response body: ${c}`),503===n.status){a("Create returned 503, polling for application creation..."),o.start("Waiting for application to be ready...");const e=`${s()}/business/v1/applications`,n=await this.pollApplicationCreated(e,t.token);o.stop(),n.ready||(console.error($.red("Application creation is taking longer than expected. Try again later or run 'dashnex app pull' to check.")),process.exit(1)),"failed"===n.status&&(console.error($.red("Application creation failed. Please try again.")),process.exit(1)),a("Application found after polling")}else if(!n.ok){const e=n.headers.get("content-type")??"";let t="Failed to create application.";if(401===n.status||403===n.status)t="Please run 'dashnex login' to authenticate.";else if(e.includes("application/json")){const e=JSON.parse(c);a(`Create error body parsed: ${JSON.stringify(e)}`);const o=ot(e);o&&(t=o)}console.error($.red(t)),process.exit(1)}let l;if(a("Application created successfully"),!0===e.pull?(l=!0,a("--pull flag set, pulling without prompting")):!1===e.pull?(l=!1,a("--no-pull flag set, skipping pull")):i?(l=!1,a("CI mode (--subdomain), skipping pull")):(l=(await T.prompt([{type:"confirm",name:"pull",message:"Pull the application locally?",default:!0}])).pull,a(`User chose to pull: ${l}`)),l){a("Running pull flow");try{await(new Qe).execute()}catch(m){if(r(m),m instanceof Error&&m.message.startsWith("EXIT:"))throw m;if(y(m))return void console.log($.yellow(w));console.error($.red("Failed to pull application."))}}else console.log($.green("Application is created successfully. Run 'dashnex app pull' to pull it to your computer or 'dashnex app deploy' to deploy it to the cloud."))}catch(m){if(o.stop(),r(m),m instanceof Error&&m.message.startsWith("EXIT:"))throw m;if(y(m))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}}},options:[{flags:"-t, --template <template>",description:"Template (name@branch-or-tag)",defaultValue:"webapp-base@live"},{flags:"--subdomain <subdomain>",description:"Subdomain (CI mode, skip prompts)"},{flags:"--pull",description:"Auto-pull after creation (skip prompt)"},{flags:"--no-pull",description:"Skip the pull prompt and show the success message"}]},{name:"pull",description:"Pull existing application to specified folder",handler:new Qe,arguments:[{name:"folder",description:"Target folder (defaults to interactive prompt)"}],options:[{flags:"-y, --yes",description:"Skip prompts and child commands (CI mode)"}]},{name:"push",description:"Push local application to DashNex",handler:new class{async execute(e={}){a("Push flow started"),await v.pathExists(x.join(process.cwd(),"dashnex.json"))||(console.error($.red("Not a DashNex application folder (dashnex.json not found).")),console.error($.red("Run this command from your application root.")),process.exit(1)),!1!==e.check?(a("Running pre-push checks"),await(new Ce).run(!1)||(console.error($.red("Push aborted: checks failed. Re-run with --no-check to skip.")),process.exit(1))):a("Skipping pre-push checks (--no-check)");const t=await g();if(!t)return;const o=process.cwd(),s=K();try{s.start("Analyzing local files...");const n=await Me(o),r=await Ue(o);a(`Local manifest has ${Object.keys(r).length} files`),s.text("Comparing with remote...");const i=await this.requestPlan(t.token,r,n?.commit);s.stop(),"legacy"===i?(a("v2 push endpoints unavailable, falling back to legacy zip push"),await this.pushLegacy(t.token,o,s)):await this.pushDiff(t.token,o,r,i,e,s),await this.promptAndDeploy(e)}catch(n){if(s.stop(),r(n),n instanceof Error&&n.message.startsWith("EXIT:"))throw n;if(y(n))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}}async requestPlan(e,t,o){const n=`${s()}/business/v1/cli/v2/push/plan`;a(`POST ${n}`);const r=await fetch(n,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({manifest:t,lastSyncCommit:o})});if(a(`Plan response: ${r.status}`),r.ok)return await r.json();const i=await r.json().catch(()=>({}));if(((e,t)=>404===e&&"string"==typeof t.message&&t.message.startsWith("Cannot "))(r.status,i))return"legacy";this.failFromResponse(r.status,i,"Failed to push application.")}async pushDiff(e,t,o,s,n,r){let i=s;if(i.remoteChanged.length>0&&!n.force){console.error($.red("Remote application has changes you don't have locally:"));for(const e of i.remoteChanged.slice(0,20))console.error($.red(` ${e}`));i.remoteChanged.length>20&&console.error($.red(` … and ${i.remoteChanged.length-20} more`)),console.error($.red("Run 'dashnex app pull' first, or push anyway with --force.")),process.exit(1)}const c=i.upload.filter(e=>e in o),l=await ze(t);let d=i.delete.filter(e=>!l.ignores(e));if(0!==c.length||0!==d.length){if(d.length>0){const e=await Me(t),o=n.yes||"1"===process.env.DASHNEX_CI;console.log($.yellow(`${d.length} file(s) will be deleted from the application repository:`));for(const t of d.slice(0,20))console.log($.yellow(` ${t}`));if(d.length>20&&console.log($.yellow(` … and ${d.length-20} more`)),!o){const{confirmDelete:t}=await T.prompt([{type:"confirm",name:"confirmDelete",message:"Delete these files from the remote repository?",default:null!==e}]);t||(a("User declined remote deletions, pushing without them"),d=[])}}if(0!==c.length||0!==d.length)for(let s=1;;s+=1){r.start(1===s?"Uploading changes...":"Remote changed, retrying...");const l=await this.applyPush(e,t,c,d,i.baseCommit);if(r.stop(),"ok"===l.status){await We(t,l.commit,o);const e=[`${c.length} file(s) updated`];return d.length>0&&e.push(`${d.length} deleted`),console.log($.cyan(`Pushed: ${e.join(", ")}`)),void console.log($.green("Application pushed successfully."))}s>=2&&(console.error($.red("Remote repository keeps changing, please retry the push.")),process.exit(1)),a("Push apply conflicted, re-planning");const p=await Me(t),u=await this.requestPlan(e,o,p?.commit);"legacy"===u&&(console.error($.red("Failed to push application.")),process.exit(1)),u.remoteChanged.length>0&&!n.force&&(console.error($.red("Remote application changed during the push. Run 'dashnex app pull' first, or push with --force.")),process.exit(1)),i=u}else console.log($.green("Everything up to date."))}else console.log($.green("Everything up to date."))}async applyPush(e,t,o,n,r){const i=new F;for(const s of o){const e=x.join(t,...s.split("/")),o=x.posix.dirname(s);i.addLocalFile(e,"."===o?"":o,x.posix.basename(s))}const c=x.join(I.tmpdir(),`dashnex-push-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`);try{i.writeZip(c),a(`Diff zip written to ${c} (${o.length} files, ${n.length} deletions)`),await this.ensureZipWithinLimit(c);const t=`${s()}/business/v1/cli/v2/push/apply`;a(`POST ${t}`);const l=await v.readFile(c),d=new FormData;d.append("file",new Blob([l],{type:"application/zip"}),"diff.zip"),d.append("baseCommit",r),d.append("deletions",JSON.stringify(n));const p=await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${e}`},body:d});if(a(`Apply response: ${p.status}`),409===p.status)return{status:"conflict"};if(!p.ok){const e=await p.json().catch(()=>({}));this.failFromResponse(p.status,e,"Failed to push application.")}return{status:"ok",commit:(await p.json().catch(()=>({}))).commit??""}}finally{await v.remove(c).catch(()=>{})}}async pushLegacy(e,t,o){const n=await Ue(t),r=Object.keys(n);a(`Legacy push including ${r.length} files`);const i=new F;for(const s of r){const e=x.join(t,...s.split("/")),o=x.posix.dirname(s);i.addLocalFile(e,"."===o?"":o,x.posix.basename(s))}const c=x.join(I.tmpdir(),`dashnex-push-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`);try{o.start("Packaging application..."),i.writeZip(c),a(`Zip written to ${c}`),await this.ensureZipWithinLimit(c);const t=`${s()}/business/v1/cli/push`;a(`POST ${t}`);const n=await v.readFile(c),r=new FormData;r.append("file",new Blob([n],{type:"application/zip"}),"archive.zip"),o.text("Uploading...");const l=await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${e}`},body:r});if(o.stop(),a(`Response: ${l.status}`),!l.ok){const e=await l.json().catch(()=>({}));this.failFromResponse(l.status,e,"Failed to push application.")}console.log($.green("Application pushed successfully."))}finally{await v.remove(c).catch(()=>{}),a("Temp zip removed")}}async ensureZipWithinLimit(e){const{size:t}=await v.stat(e);if(t>dt){const e=e=>(e/1048576).toFixed(1);console.error($.red(`Push package is ${e(t)} MB — the maximum is ${e(dt)} MB.`)),console.error($.red("Remove or .gitignore large files and try again.")),process.exit(1)}}failFromResponse(e,t,o){let s=o;if(401===e||403===e)s="Please run 'dashnex login' to authenticate.";else{const e=(e=>"string"==typeof e.error?e.error:"string"==typeof e.message?e.message:void 0)(t);e&&(s=e)}console.error($.red(s)),process.exit(1)}async promptAndDeploy(e){let t;void 0!==e.deploy?(t=e.deploy,a(`Deploy decided via flag: ${t}`)):"1"===process.env.DASHNEX_CI?(t=!1,a("CI mode: skipping deploy prompt (use --deploy to deploy)")):(t=(await T.prompt([{type:"confirm",name:"deploy",message:"Deploy the application?",default:!0}])).deploy,a(`User chose to deploy: ${t}`)),t&&(a("Running deploy flow"),await(new lt).execute())}},options:[{flags:"--deploy",description:"Auto-deploy after push (skip prompt)"},{flags:"--no-deploy",description:"Skip deploy prompt"},{flags:"--no-check",description:"Skip pre-push checks"},{flags:"-f, --force",description:"Push even when the remote has changes you don't have locally"},{flags:"-y, --yes",description:"Skip prompts (CI mode)"}]},{name:"delete",description:"Delete application from DashNex",handler:new class{async execute(e={}){if(a("Delete flow started"),!e.yes){const{confirm:e}=await T.prompt([{type:"confirm",name:"confirm",message:"Are you sure you want to delete this application? This action cannot be undone.",default:!1}]);if(!e)return a("User cancelled deletion"),void console.log($.yellow("Deletion cancelled."))}const t=await g();if(!t)return;const o=`${s()}/business/v1/applications`;a(`DELETE ${o}`);const n=K();n.start("Deleting application...");try{const e=await fetch(o,{method:"DELETE",headers:{Authorization:`Bearer ${t.token}`}});if(n.stop(),a(`Response: ${e.status}`),!e.ok){const t=e.headers.get("content-type")??"";let o="Failed to delete application.";if(401===e.status||403===e.status)o="Please run 'dashnex login' to authenticate.";else if(t.includes("application/json")){const t=(e=>"string"==typeof e.error?e.error:"string"==typeof e.message?e.message:void 0)(await e.json().catch(()=>({})));t&&(o=t)}console.error($.red(o)),process.exit(1)}const s=await e.json().catch(()=>({})),r="string"==typeof s.message?s.message:"Application deleted successfully.";a("Application deleted successfully"),console.log($.green(r))}catch(i){if(n.stop(),r(i),i instanceof Error&&i.message.startsWith("EXIT:"))throw i;if(y(i))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}}},options:[{flags:"-y, --yes",description:"Skip confirmation prompt"}]},{name:"deploy",description:"Deploy application to DashNex",handler:new lt,options:[]},{name:"status",description:"Show current application status",handler:new class{async execute(){a("Status flow started");const e=await g();if(!e)return;const t=`${s()}/business/v1/applications`;a(`GET ${t}`);const o=K();o.start("Fetching application status...");try{const s=await fetch(t,{headers:{Authorization:`Bearer ${e.token}`}});if(o.stop(),a(`Response: ${s.status}`),404===s.status)return void console.log($.yellow("Application not created"));if(!s.ok){const e=s.headers.get("content-type")??"";let t="Failed to fetch application status.";if(401===s.status||403===s.status)t="Please run 'dashnex login' to authenticate.";else if(e.includes("application/json")){const e=await s.json().catch(()=>({}));"string"==typeof e.error?t=e.error:"string"==typeof e.message&&(t=e.message)}console.error($.red(t)),process.exit(1)}const n=await s.json().catch(()=>({}));a(`Application body: ${JSON.stringify(n)}`);const r=n.status??"unknown";console.log(`Status: ${$.cyan(r)}`);const i=nt(n);i&&console.log(`URL: ${$.cyan(i)}`)}catch(n){if(o.stop(),r(n),n instanceof Error&&n.message.startsWith("EXIT:"))throw n;if(y(n))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}}},options:[]},{name:"clear",description:"Delete local dependencies, caches and build output",handler:new class{async execute(e={}){a("Clear flow started");const t=process.cwd();v.pathExistsSync(x.join(t,"dashnex.json"))||(console.error($.red("Run this command in a DashNex Web application folder.")),process.exit(1));const o=e.yes||"1"===process.env.DASHNEX_CI;a(`Non-interactive: ${o}`);try{const e=await this.collectTargets(t),n=x.join(t,...Ct.segments),i=await v.pathExists(n),c=i&&!o;if(i&&o&&e.push(Ct),0===e.length&&!c)return void console.log($.yellow("Nothing to clear."));const l=[];if(e.length>0){console.log("The following will be deleted:");for(const t of e)console.log(` ${$.cyan(t.label)} ${$.dim(`(${t.description})`)}`);if(!o){const{confirm:e}=await T.prompt([{type:"confirm",name:"confirm",message:"Delete these files?",default:!1}]);if(!e)return a("User cancelled clear"),void console.log($.yellow("Cancelled."))}for(const o of e){const e=x.join(t,...o.segments);a(`Removing ${e}`);try{await Pt(e),console.log($.green(`Removed ${o.label}`))}catch(s){r(s),l.push(o.label)}}}if(c){const{confirm:e}=await T.prompt([{type:"confirm",name:"confirm",message:"Delete the local database as well?",default:!1}]);if(e){a(`Removing ${n}`);try{await Pt(n),console.log($.green(`Removed ${Ct.label}`))}catch(s){r(s),l.push(Ct.label)}}}if(l.length>0){for(const e of l)console.error($.red(`Could not delete ${e} — it may be in use. Stop 'dashnex dev' and try again.`));process.exit(1)}console.log($.green("Clear complete. Run 'dashnex install' to set the application up again."))}catch(s){if(s instanceof Error&&s.message.startsWith("EXIT:"))throw s;if(r(s),y(s))return void console.log($.yellow(w));throw s}}async collectTargets(e){const t=[];for(const o of Tt)await v.pathExists(x.join(e,...o.segments))&&t.push(o);return t.filter(o=>!t.some(t=>t!==o&&((e,t)=>{const o=x.relative(t,e);return""!==o&&!o.startsWith("..")&&!x.isAbsolute(o)})(x.join(e,...o.segments),x.join(e,...t.segments))))}},options:[{flags:"-y, --yes",description:"Skip prompts and keep the local database (CI mode)"}]},{name:"tail",description:"Stream live console/debug logs from the deployed worker (Ctrl+C to stop)",handler:new class{async execute(e={}){const t=e.level??"log";yt(t)||(console.error($.red(`Invalid --level '${e.level}'. Use one of: log, info, warn, error, debug.`)),process.exit(1));let o=null;if(e.filter)try{o=new RegExp(e.filter)}catch{console.error($.red(`Invalid --filter regular expression: ${e.filter}`)),process.exit(1)}const n=await g();if(!n)return;const i=!!e.debug;let c;try{c=await async function(e,t={}){const o=`${s()}${mt}`;a(`POST ${o}${t.debug?" (debug)":""}`);const n=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({debug:!!t.debug})});if(a(`Response: ${n.status}`),401===n.status||403===n.status)throw new pt("Not authorised to tail this application.");if(404===n.status)throw new ut("Business has no application, or the tail endpoint is unavailable.");if(!n.ok)throw new ht(`Failed to start tail (status ${n.status}).`);const r=await n.json().catch(()=>({}));if(!r.token||!r.workerUrl)throw new ht("Malformed tail-token response from the platform.");return{token:r.token,workerUrl:r.workerUrl,expiresAt:"number"==typeof r.expiresAt?r.expiresAt:0}}(n.token,{debug:i})}catch(x){r(x),x instanceof pt&&(console.error($.red(x.message)),process.exit(1)),x instanceof ut&&(console.error($.red(x.message)),process.exit(2)),console.error($.red(x instanceof Error?x.message:"Could not start tail. Check your connection.")),process.exit(3)}const l={color:!!process.stdout.isTTY},d={minLevel:t,filter:o},p=(u=c.workerUrl,`${/^(localhost|127\.0\.0\.1)/.test(u)?"ws":"wss"}://${u}/api/realtime/tail`);var u;const h=`dashnex.tail.token.${c.token}`;console.log($.dim(`Tailing ${c.workerUrl} — Ctrl+C to stop.`)),i&&console.log($.dim("Debug logging enabled on the worker (DEBUG=1); may take a minute to take effect."));let m=!1,f=Date.now();const w=setInterval(()=>{m||Date.now()-f>=3e4&&(console.log($.dim("(no logs in 30s)")),f=Date.now())},5e3),y=new B(p,h,{WebSocket:z,connectionTimeout:1e4,maxReconnectionDelay:16e3}),v=async e=>{if(!m){m=!0,clearInterval(w);try{y.close()}catch{}await Promise.race([gt(n.token,{debug:i}),new Promise(e=>setTimeout(e,3e3))]),process.exit(e)}};process.on("SIGINT",()=>{console.log(),v(0)}),y.binaryType="arraybuffer",y.addEventListener("message",e=>{f=Date.now();const t=Et("string"==typeof e.data?e.data:"",d,l);"line"===t.kind?console.log(t.line):"malformed"===t.kind&&console.error($.dim(`[malformed frame] ${t.raw}`))}),y.addEventListener("close",e=>{if(!m)return Dt.has(e.code)?(console.error($.red("Tail token was rejected (auth). Exiting.")),void v(1)):void a(`socket closed (code ${e.code}); reconnecting…`)}),y.addEventListener("error",e=>{a(`socket error: ${e.message??"unknown"}`)}),await new Promise(()=>{})}},options:[{flags:"--level <level>",description:"Minimum level: log, info, warn, error, debug"},{flags:"--filter <regex>",description:"Only show records whose args match this regex"},{flags:"--debug",description:"Also enable DEBUG=1 on the worker so debug-gated logs are shown"}]}]},jt={name:"secrets",description:"Manage DashNex secrets",handler:{execute:async()=>{}},options:[],subcommands:[{name:"list",description:"List all secret names bound to the worker",handler:new class{async execute(){a("Secrets list flow started");const e=await g();if(!e)return;const t=`${s()}/business/v1/cli/secrets`;a(`GET ${t}`);const o=K();o.start("Fetching secrets...");try{const s=await fetch(t,{headers:{Authorization:`Bearer ${e.token}`}});if(o.stop(),a(`Response: ${s.status}`),!s.ok){let e="Failed to fetch secrets.";401===s.status||403===s.status?e="Please run 'dashnex login' to authenticate.":404===s.status?e="Business has no application.":502===s.status&&(e="Error fetching secrets. Make sure your application is deployed."),console.error($.red(e)),process.exit(1)}const n=(await s.json()).secrets??[];if(0===n.length)return void console.log($.yellow("No secrets set."));console.log($.bold(`\nSecrets (${n.length}):\n`)),n.forEach(({name:e})=>{console.log(` ${$.cyan(e)}`)}),console.log("")}catch(n){if(o.stop(),r(n),n instanceof Error&&n.message.startsWith("EXIT:"))throw n;if(y(n))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}}},options:[]},{name:"set",description:"Create or update a secret (prompts for missing KEY/VALUE)",handler:new class{async execute(e={}){a("Secrets set flow started");let{name:t,value:o}=e;const n="1"===process.env.DASHNEX_CI;t||(n&&(console.error($.red("--name is required in CI mode (--ci).")),process.exit(1)),t=(await T.prompt([{type:"input",name:"name",message:"Secret name:",validate:e=>""!==e.trim()||"Name is required"}])).name.trim()),o||(n&&(console.error($.red("--value is required in CI mode (--ci).")),process.exit(1)),o=(await T.prompt([{type:"password",name:"value",message:`Enter value for ${t}:`,mask:"*",validate:e=>""!==e||"Value is required"}])).value);const i=await g();if(!i)return;const c=`${s()}/business/v1/cli/secrets`;a(`PUT ${c}`);const l=K();l.start(`Setting secret '${t}'...`);try{const e=await fetch(c,{method:"PUT",headers:{Authorization:`Bearer ${i.token}`,"Content-Type":"application/json"},body:JSON.stringify({name:t,value:o})});if(l.stop(),a(`Response: ${e.status}`),!e.ok){let t="Failed to set secret.";401===e.status||403===e.status?t="Please run 'dashnex login' to authenticate.":400===e.status?t="Missing or empty name / value.":404===e.status?t="Business has no application.":502===e.status&&(t="Error setting secret. Make sure your application is deployed."),console.error($.red(t)),process.exit(1)}a(`Secret '${t}' set successfully`),console.log($.green(`Secret '${t}' set successfully.`))}catch(d){if(l.stop(),r(d),d instanceof Error&&d.message.startsWith("EXIT:"))throw d;if(y(d))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}}},arguments:[{name:"name",description:"Secret name (KEY)"},{name:"value",description:"Secret value"}],options:[{flags:"--name <name>",description:"Secret name (alternative to positional KEY)"},{flags:"--value <value>",description:"Secret value (alternative to positional VALUE)"}]},{name:"delete",description:"Delete a secret (prompts for missing KEY)",handler:new class{async execute(e={}){a("Secrets delete flow started");let{name:t}=e;const o="1"===process.env.DASHNEX_CI;t||(o&&(console.error($.red("--name is required in CI mode (--ci).")),process.exit(1)),t=(await T.prompt([{type:"input",name:"name",message:"Secret name:",validate:e=>""!==e.trim()||"Name is required"}])).name.trim());const n=await g();if(!n)return;const i=`${s()}/business/v1/cli/secrets/${encodeURIComponent(t)}`;a(`DELETE ${i}`);const c=K();c.start(`Deleting secret '${t}'...`);try{const e=await fetch(i,{method:"DELETE",headers:{Authorization:`Bearer ${n.token}`}});if(c.stop(),a(`Response: ${e.status}`),!e.ok){let o="Failed to delete secret.";401===e.status||403===e.status?o="Please run 'dashnex login' to authenticate.":404===e.status?o=`Secret '${t}' not found.`:502===e.status&&(o="Error deleting secret. Make sure your application is deployed."),console.error($.red(o)),process.exit(1)}a(`Secret '${t}' deleted successfully`),console.log($.green(`Secret '${t}' deleted.`))}catch(l){if(c.stop(),r(l),l instanceof Error&&l.message.startsWith("EXIT:"))throw l;if(y(l))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}}},arguments:[{name:"name",description:"Secret name (KEY)"}],options:[{flags:"--name <name>",description:"Secret name (alternative to positional KEY)"}]}]},Nt=[{name:"version",description:"Display the version of the CLI",handler:new class{async execute(e){const t=x.dirname(k(import.meta.url)),o=JSON.parse(D.readFileSync(x.resolve(t,"..","..","package.json"),"utf8"));console.log(o.version)}},options:[]},{name:"login",description:"Log in to your DashNex account",handler:new class{async execute(e={}){a("Login flow started");const s=o(process.cwd()),n=await g({exitOnFailure:!1,dashnexPath:s});if(n)console.log($.green(`Already logged in as ${n.userName??"user"}`));else{l()||(console.error($.red("No DashNex ID OAuth client is configured for this environment.")),console.error($.yellow("Set DASHNEX_OAUTH_CLIENT_ID, or use APPLICATION_ENV=qa.")),process.exit(1));try{const o=await v.pathExists(s)?await i(s):null;if(o){a("Resuming with saved DashNex ID session");try{return void(await this.selectBusiness(o,s,e.business))}catch(c){if(!(c instanceof X))throw c;a("Saved DashNex ID session rejected, starting a new sign-in")}}const n=await this.authorize(!1!==e.browser);await oe(s,{idToken:n.token,idExpiresAt:t(n.expiresIn),...n.refreshToken&&{idRefreshToken:n.refreshToken}}),await this.selectBusiness(n.token,s,e.business)}catch(c){if(r(c),y(c))return void console.log($.yellow(w));if((c instanceof J||c instanceof H)&&(console.error($.red(c.message)),process.exit(1)),c instanceof Error&&c.message.startsWith("EXIT:"))throw c;console.error($.red("Could not reach DashNex. Check your connection and try again.")),process.exit(1)}}}async authorize(t){const{verifier:o,challenge:s}=(()=>{const e=U(j(32));return{verifier:e,challenge:U(P("sha256").update(e).digest())}})(),r=(e=>{const t=new URL(`${n()}/api/oauth/authorize`);return t.searchParams.set("client_id",l()),t.searchParams.set("redirect_uri",W),t.searchParams.set("response_type","code"),t.searchParams.set("code_challenge",e),t.searchParams.set("code_challenge_method","S256"),t.toString()})(s);a(`Authorize URL: ${r}`);const i=!!t&&await M(r);console.log(""),i?(console.log($.green("Opened your browser to finish signing in.")),console.log($.dim("If nothing opened, use this URL:"))):console.log($.bold("Open this URL to sign in:")),console.log($.cyan(r)),console.log(""),console.log($.dim("Approve the request, then copy the code shown and paste it below.")),console.log("");const{code:d}=await T.prompt([{type:"input",name:"code",message:"Paste the code:",validate:e=>!!e.trim()||"Code is required"}]),p=K();p.start("Verifying code...");try{return await(async(t,o)=>{const s=`${n()}/api/oauth/token`;a(`POST ${s}`);const{response:r,body:i}=await c(s,{method:"POST",body:JSON.stringify({grant_type:"authorization_code",client_id:l(),redirect_uri:W,code:t.trim(),code_verifier:o})});if(a(`Response: ${r.status}`),e("Token response",i),!r.ok){const e="string"==typeof i.error?i.error:void 0,t="string"==typeof i.error_description?i.error_description:void 0;if("invalid_grant"===e)throw new J("That code is not valid anymore. Codes expire 10 minutes after they are issued and can only be used once.",e);throw new J(t??"Could not exchange the code for a token.",e)}const d=i;if(!d.access_token)throw new J("The authorization server did not return an access token.");return{token:d.access_token,refreshToken:d.refresh_token,expiresIn:d.expires_in}})(d,o)}finally{p.stop()}}async selectBusiness(e,t,o){const s=await Q(e,t,{businessId:o});console.log($.green(`Logged in as ${s.name}`))}},options:[{flags:"--business <id>",description:"Business ID to select (skip prompt)"},{flags:"--no-browser",description:"Print the sign-in URL instead of opening a browser"}]},{name:"logout",description:"Log out and remove local credentials",handler:new class{async execute(){const e=o(process.cwd());if(await v.pathExists(e)){const t=await v.readJson(e);delete t.token,delete t.refreshToken,delete t.businessId,delete t.idToken,delete t.idRefreshToken,delete t.idExpiresAt,await v.writeJson(e,t,{spaces:2})}console.log($.green("Logged out."))}},options:[]},{name:"switch",description:"Switch to another business (prompts when no ID is given)",handler:new class{async execute(e={}){a("Switch flow started");const t=await d(process.cwd()),o=t?await this.readSession(t):null;t&&o?.token||(console.error($.red("Please run 'dashnex login' to authenticate.")),process.exit(1)),o.idToken||(console.error($.red("Switching businesses requires signing in with DashNex ID.")),console.error($.yellow("Run 'dashnex logout' and then 'dashnex login' to sign in again.")),process.exit(1));const s=s=>Q(s,t,{businessId:e.businessId,currentBusinessId:o.businessId});try{const e=await i(t)??o.idToken;let r;try{r=await s(e)}catch(n){if(!(n instanceof X))throw n;a("DashNex ID session rejected, refreshing");const e=await p(t);if(!e)throw n;r=await s(e)}console.log($.green(`Switched to ${r.name}`))}catch(n){if(r(n),y(n))return void console.log($.yellow(w));if(n instanceof X&&(console.error($.red(n.message)),console.error($.yellow("Run 'dashnex logout' and then 'dashnex login' to sign in again.")),process.exit(1)),n instanceof H&&(console.error($.red(n.message)),process.exit(1)),n instanceof Error&&n.message.startsWith("EXIT:"))throw n;console.error($.red("Could not reach DashNex. Check your connection and try again.")),process.exit(1)}}async readSession(e){try{return await v.readJson(e)}catch{return null}}},arguments:[{name:"businessId",description:"Business ID to switch to (skip prompt)"}],options:[]},{name:"whoami",description:"Show current logged-in business and user",handler:new class{async execute(){a("Whoami flow started");const e=await g();if(!e)return;const t=await h()?`${s()}/business/v1/`:`${f()}/users/v1/`;a(`GET ${t}`);const o=K();o.start("Fetching user info...");try{const s=await fetch(t,{headers:{Authorization:`Bearer ${e.token}`}});if(o.stop(),a(`Response: ${s.status}`),!s.ok){const e=s.headers.get("content-type")??"";let t="Failed to fetch user info.";if(401===s.status||403===s.status)t="Please run 'dashnex login' to authenticate.";else if(e.includes("application/json")){const e="string"==typeof(n=await s.json().catch(()=>({}))).error?n.error:"string"==typeof n.message?n.message:void 0;e&&(t=e)}console.error($.red(t)),process.exit(1)}const r=await s.json().catch(()=>({})),i="string"==typeof r.name?r.name:"string"==typeof r.companyName?r.companyName:"Unknown",c=r.memberUser,l=c&&"string"==typeof c.fullName?c.fullName:c&&"string"==typeof c.name?c.name:"Unknown";a(`Whoami: ${i} (${l})`),console.log($.green(`You are logged in as ${i} (${l})`))}catch(i){if(o.stop(),r(i),i instanceof Error&&i.message.startsWith("EXIT:"))throw i;if(y(i))return void console.log($.yellow(w));console.error($.red("Could not reach DashNex API. Check your connection and try again.")),process.exit(1)}var n}},options:[]},{name:"dev",description:"Run the development server (run in DashNex application folder)",handler:new class{async execute(e={}){a("Dev flow started");try{const t=await ge(process.cwd(),e);process.exit(t)}catch(t){if(t instanceof Error&&t.message.startsWith("EXIT:"))throw t;r(t),t instanceof Error?console.error($.red(t.message)):console.error($.red("Failed to run npm run dev")),process.exit(1)}}},options:[{flags:"-p, --port <port>",description:"Port for the dev server (default: PORT env or 3000)"},{flags:"-H, --host <host>",description:"Host to bind, e.g. 0.0.0.0 inside Docker (default: HOST env or localhost)"},{flags:"--https",description:"Serve over HTTPS with a trusted local certificate (mkcert)"}]},{name:"check",description:"Run build-time validations (run in DashNex application folder)",handler:new Ce,options:[]},{name:"install",description:"Install dependencies using pnpm (preferred) or npm (run in DashNex application folder)",handler:new class{async execute(){a("Install flow started");const e=x.join(process.cwd(),"dashnex.json");await v.pathExists(e)||(console.error($.red("Run this command in a DashNex Web application folder.")),process.exit(1));const t=process.cwd(),o=K();o.start("Installing dependencies...");try{const e=await we(t);o.stop(),0!==e&&process.exit(e),console.log($.green("Dependencies installed.")),process.exit(0)}catch(s){o.stop(),r(s),s instanceof Error?console.error($.red(s.message)):console.error($.red("Failed to run install")),process.exit(1)}}},options:[]},At,jt];export{Nt as default};
|
|
1
|
+
import{C as e,E as t,O as s,S as n,_ as o,a as r,b as a,c as i,d as c,f as l,g as d,h as p,i as u,k as h,l as m,m as g,n as f,o as w,p as y,r as $,s as v,t as b,u as k,v as x,w as S,x as E,y as D}from"../chunks/project-guard-Z_W1-Bfr.js";import T from"chalk";import j from"fs-extra";import C from"path";import{fileURLToPath as N}from"url";import{execFileSync as P,spawn as I,spawnSync as A}from"child_process";import F from"fs";import R from"inquirer";import{X509Certificate as O,createHash as B,createPrivateKey as _,randomBytes as z}from"crypto";import W from"net";import L from"os";import M from"adm-zip";import{diffLines as U}from"diff";import X from"ignore";import{WebSocket as H}from"partysocket";import J from"ws";import{inspect as G}from"node:util";import{rm as q}from"node:fs/promises";import*as Y from"tar";var V=e=>e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,""),K=async t=>{const s=(e=>{switch(process.platform){case"darwin":return{command:"open",args:[e]};case"win32":return{command:"cmd",args:["/c","start","",e]};default:return{command:"xdg-open",args:[e]}}})(t);return!!s&&new Promise(t=>{try{const o=I(s.command,s.args,{stdio:"ignore",detached:!0});o.on("error",s=>{e(s),t(!1)}),o.on("spawn",()=>{n(`Opened browser via ${s.command}`),o.unref(),t(!0)})}catch(o){e(o),t(!1)}})},Z="urn:ietf:wg:oauth:2.0:oob",Q=class extends Error{constructor(e,t){super(e),this.code=t,this.name="OAuthError"}},ee=class extends Error{},te=class extends ee{},se=e=>{const t=e.data;return t&&"object"==typeof t?t:e},ne=e=>{const t=e.items??[];return Array.isArray(t)?t.map(e=>{if(!e||"object"!=typeof e)return null;const t=e,s=Number(t.id);return!Number.isFinite(s)||s<=0?null:{id:s,name:String(t.name??t.companyName??`Business ${s}`).trim()}}).filter(e=>null!==e):[]},oe=async(e,t)=>{const s=[];let o=1,r=0;do{const a=`${D()}${e}?page=${o}&perPage=100`;n(`GET ${a}`);const{response:i,body:c}=await p(a,{headers:{Authorization:`Bearer ${t}`}});if(n(`Response: ${i.status}`),S(`Businesses response (${e})`,c),!i.ok){if(401===i.status)throw new te("Your DashNex ID session has expired.");if(404===i.status)throw new ee(`Your DashNex ID account is not linked to a contact yet. Finish signing up at ${D()} and try again.`);throw new ee("Failed to fetch businesses. Please try again.")}const l=se(c),d=ne(l);if(r="number"==typeof l.total?l.total:d.length,s.push(...d),d.length<100||s.length>=r)break;o++}while(s.length<r);return s},re=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"],ae=(e="")=>{if(process.env.DASHNEX_CI)return{start:()=>{},stop:()=>{},text:()=>{}};let t=null,s=0,n=e;const o=()=>{const e=re[s%re.length];process.stderr.write(`\r[K${e} ${n}`),s++};return{start:e=>{void 0!==e&&(n=e),t||(s=0,o(),t=setInterval(o,80))},stop:()=>{t&&(clearInterval(t),t=null),process.stderr.write("\r[K\n")},text:e=>{n=e,t&&o()}}},ie=async(e,s,o={})=>{const r=ae();let a;r.start("Fetching businesses...");try{a=await(async e=>{const t=await oe("/api/businesses/my",e),s=await oe("/api/businesses/other",e),o=/* @__PURE__ */new Map;for(const n of t)o.set(n.id,n);for(const n of s)o.has(n.id)||o.set(n.id,n);return n(`Businesses (my: ${t.length}, other: ${s.length}, merged: ${o.size})`),[...o.values()]})(e)}finally{r.stop()}0===a.length&&(console.error(T.red("No businesses or teams found for your account!")),console.log(T.green(`Create your first business at ${D()}`)),t(1));const i=await ce(a,o);let c;r.start("Switching business...");try{c=await(async(e,t)=>{const s=`${D()}/api/businesses/${t}/switch`;n(`POST ${s}`);const{response:o,body:r}=await p(s,{method:"POST",headers:{Authorization:`Bearer ${e}`}});if(n(`Response: ${o.status}`),S("Switch response",r),!o.ok){if(401===o.status)throw new te("Your DashNex ID session has expired.");if(403===o.status)throw new ee("You do not have access to that business.");throw new ee("Failed to switch to business. Please try again.")}const a=se(r);if(!a.token||!a.refreshToken)throw new ee("Invalid response from server. Please try again.");return{token:a.token,refreshToken:a.refreshToken,businessId:t}})(e,i.id)}finally{r.stop()}return await de(s,{...await le(c.token),business:{token:c.token,refreshToken:c.refreshToken,id:c.businessId}}),await pe(),i},ce=async(e,s)=>{if(void 0!==s.businessId){const o=e.find(e=>String(e.id)===String(s.businessId).trim());return o||(console.error(T.red(`Business "${s.businessId}" not found.`)),t(1)),n(`Selected business via argument: ${o.id} ${o.name}`),o}if(1===e.length)return n(`Selected business: ${e[0].id} ${e[0].name}`),e[0];const o=e.find(e=>String(e.id)===String(s.currentBusinessId)),{businessId:r}=await R.prompt([{type:"select",name:"businessId",message:"Select business:",choices:e.map(e=>({name:e===o?`${e.name} ${T.dim("(current)")}`:e.name,value:e.id})),...o&&{default:o.id}}]),a=e.find(e=>e.id===r)||e[0];return n(`Selected business: ${a.id} ${a.name}`),a},le=async t=>{try{const e=`${o()}/business/v1/cli/settings`;n(`GET ${e}`);const{response:s,body:r}=await p(e,{headers:{Authorization:`Bearer ${t}`}});if(n(`Response: ${s.status}`),S("CLI settings response",r),s.ok&&r&&"object"==typeof r)return r}catch(s){e(s)}return{}},de=async(e,t)=>{const s=C.dirname(e);await j.pathExists(s)&&(await j.stat(s)).isFile()&&await j.remove(s),await j.ensureDir(s),await l(e,e=>({...e,...t})),n("Saved credentials to .dashnex/settings.json")},pe=async()=>{const e=C.join(process.cwd(),".gitignore");if(!(await j.pathExists(e)))return;let t=await j.readFile(e,"utf8");t.includes(".dashnex")||(t=t.trimEnd(),t.endsWith("\n")||(t+="\n"),t+="\n.dashnex\n",await j.writeFile(e,t))},ue="v1.4.4",he=C.join(y,"certificates"),me=["localhost","127.0.0.1","::1"],ge="win32"===process.platform,fe=()=>0===A("mkcert",["-version"],{stdio:"ignore",shell:ge}).status?"mkcert":null,we=async(e={})=>{const t=(e.lookupPath??fe)();if(t)return t;const s=e.cacheDir??C.join(L.homedir(),".dashnex","mkcert"),n=((e=process.platform,t=process.arch)=>{const s="x64"===t?"amd64":t;if("win32"===e)return`mkcert-${ue}-windows-${s}.exe`;if("darwin"===e)return`mkcert-${ue}-darwin-${s}`;if("linux"===e)return`mkcert-${ue}-linux-${s}`;throw new Error(`Unsupported platform for mkcert: ${e}`)})(),o=C.join(s,n);if(await j.pathExists(o))return o;const r=`https://github.com/FiloSottile/mkcert/releases/download/${ue}/${n}`;e.log?.(`mkcert not found, downloading ${ue} to ${o}`);const a=await(e.fetchImpl??fetch)(r);if(!a.ok)throw new Error(`Failed to download mkcert from ${r}: HTTP ${a.status}`);await j.ensureDir(s);const i=`${o}.download`;return await j.writeFile(i,Buffer.from(await a.arrayBuffer())),await j.chmod(i,493),await j.move(i,o,{overwrite:!0}),o},ye=(e,t,s)=>{try{const n=new O(j.readFileSync(t)),o=_(j.readFileSync(e));return!!n.checkPrivateKey(o)&&(!(new Date(n.validTo).getTime()<=Date.now())&&s.every(e=>((e,t)=>W.isIP(t)?Boolean(e.checkIP(t)):Boolean(e.checkHost(t)))(n,e)))}catch{return!1}},$e=async(t,s={})=>{const n=s.log??(()=>{}),o=s.certDir??he,r=C.resolve(t,o),a=C.join(r,"localhost-key.pem"),i=C.join(r,"localhost.pem"),c=(e=>{const t=(e??"").trim();return t&&!me.includes(t)?[...me,t]:[...me]})(s.host);try{if(ye(a,i,c))return n(`Using existing local certificate from ${C.relative(t,r)||"."}`),{key:a,cert:i};const e=await(s.resolveMkcert??(()=>we({log:n})))();if(await j.ensureDir(r),n("Generating a trusted local certificate with mkcert (this may prompt for your password once)..."),P(e,["-install","-key-file",a,"-cert-file",i,...c],{stdio:"inherit",shell:ge}),!(await j.pathExists(a))||!(await j.pathExists(i)))throw new Error("mkcert finished but the certificate files were not created");let l;await(async(e,t)=>{const s=C.join(e,".gitignore");if(!(await j.pathExists(s)))return!1;const n=await j.readFile(s,"utf8"),o=n.split(/\r?\n/).map(e=>e.trim()),r=new Set([t,`/${t}`,`${t}/`,`/${t}/`]);if(o.some(e=>r.has(e)))return!1;const a=0===n.length||n.endsWith("\n")?"":"\n";return await j.appendFile(s,`${a}${t}\n`),!0})(t,o.split(C.sep)[0]);try{const t=P(e,["-CAROOT"],{encoding:"utf8",shell:ge}).trim();t&&(l=C.join(t,"rootCA.pem"))}catch{}return n(`Certificates created in ${C.relative(t,r)||"."}`),{key:a,cert:i,...l?{rootCA:l}:{}}}catch(l){return e(l),n(`Failed to set up HTTPS certificates: ${l instanceof Error?l.message:String(l)}`),null}},ve=["Using (vars|secrets) defined in ","\\(node:\\d+\\) MaxListenersExceededWarning:","\\(Use `node --trace-warnings","\\[DEP0205\\] DeprecationWarning:","\\(Use `node --trace-deprecation","Detected multiple renderers concurrently rendering the same context provider","\\[WARNING\\].*You have defined bindings to the following internal Durable Objects","You have defined bindings to the following internal Durable Objects",'\\{"name":".+","class_name":".+"\\}',"These will not work in local development, but they should work in production","If you want to develop these locally, you can define your DO in a separate Worker","For detailed instructions, refer to the Durable Objects section here:","developers.cloudflare.com/workers/wrangler/api#supported-bindings","workerd/server/server\\.c\\+\\+.*warning:.*DurableObjectNamespace"],be=(e,t)=>t.length>0&&t.some(t=>t.test(e)),ke=(e,t)=>{let s="";return{write:o=>{const r="string"==typeof o?o:o.toString();s+=r;const a=s.split(/\r?\n/);s=a.pop()??"";for(const s of a){const o=s.trim();o?be(o,t)?n(`Filtered: ${o}`):e.write(s+"\n"):n("Filtered: <empty line>")}},flush:()=>{const o=s.trim();if(o){if(be(o,t))return n(`Filtered: ${o}`),void(s="");e.write(s),s=""}else s=""}}},xe=async(t,s={})=>{const o=C.join(t,"dashnex.json"),r=C.join(t,"package.json");if(n(`Checking for dashnex.json at: ${o}`),!(await j.pathExists(o)))throw new Error("Run this command in a DashNex Web application folder.");if(n("dashnex.json found, checking for package.json"),!(await j.pathExists(r)))throw new Error("package.json is missing.");let a;n("package.json found, checking for dev script");try{a=await j.readJson(r)}catch(h){throw e(h),new Error("Failed to read package.json.")}if(!a.scripts||!a.scripts.dev)throw new Error('The "dev" script is missing from package.json. Please add a "dev" script to package.json.');const i=((e,t=process.env)=>{const s=e.port??t.PORT;if(null==s||""===String(s).trim())return;const n=Number(s);if(!Number.isInteger(n)||n<1||n>65535)throw new Error(`Invalid port: ${s}. Use a number between 1 and 65535.`);return n})(s),c=((e,t=process.env)=>{const s=(e.host??t.HOST??"").trim();return""===s?void 0:s})(s);n("dev script found, checking for npm availability");const l=await(async e=>new Promise(t=>{const s=I(`${e} --version`,{stdio:"ignore",shell:!0});s.on("error",()=>{t(!1)}),s.on("close",e=>{t(0===e)})}))("npm");if(n(`npm available: ${l}`),!l)throw new Error("npm is not available. Please install npm to continue.");const d={};if(s.https){const e=await $e(t,{host:c,log:e=>console.log(T.dim(e))});e?(d.DASHNEX_DEV_HTTPS_KEY=e.key,d.DASHNEX_DEV_HTTPS_CERT=e.cert,console.log(T.green("HTTPS enabled for the dev server"+(i?` on https://${c??"localhost"}:${i}`:""))),e.rootCA&&console.log(T.dim(`Local CA: ${e.rootCA} (import it in browsers on other machines, e.g. when the app runs in Docker)`))):console.log(T.yellow("HTTPS is not available, starting the dev server over HTTP instead."))}const p=[];void 0!==i&&p.push("--port",String(i)),void 0!==c&&p.push("--hostname",c);const u=p.length?["--",...p]:[];return n(`npm available, executing: npm run dev${u.length?` ${u.join(" ")}`:""} in ${t}`),(async(t,s=[],n=process.cwd(),o=[],r={})=>{const a=s.length?`${t} ${s.join(" ")}`:t,i=o.length>0;return new Promise((t,s)=>{const c=I(a,{stdio:i?["inherit","pipe","pipe"]:"inherit",cwd:n,shell:!0,env:{...process.env,NODE_ENV:"development",FORCE_COLOR:"1",...r}});if(c.on("error",t=>{e(t),s(t)}),i&&c.stdout&&c.stderr){const e=ke(process.stdout,o),s=ke(process.stderr,o);c.stdout.on("data",t=>e.write(t)),c.stderr.on("data",e=>s.write(e)),c.on("close",n=>{e.flush(),s.flush(),t(null===n?0:n)})}else c.on("close",e=>{t(null===e?0:e)})})})("npm run dev",u,t,(()=>{const e=[];for(const t of ve)try{e.push(new RegExp(t))}catch{n(`Invalid dev output filter pattern ignored: ${t}`)}return e})(),d)},Se=async e=>new Promise(t=>{const s=I(`${e} --version`,{stdio:"ignore",shell:!0});s.on("error",()=>{t(!1)}),s.on("close",e=>{t(0===e)})}),Ee=async t=>{let s;if(await Se("pnpm"))s="pnpm install --config.strict-dep-builds=false",n("Using package manager: pnpm");else{if(!(await Se("npm")))throw new Error("Neither pnpm nor npm is available. Install Node.js (which bundles npm) or pnpm to continue.");s="npm install --legacy-peer-deps",n("Using package manager: npm (with --legacy-peer-deps for DashNex peer-dep overlap)")}return n(`Executing: ${s} in ${t}`),new Promise((n,o)=>{const r=[],a=[],i=I(s,{stdio:["ignore","pipe","pipe"],cwd:t,shell:!0,env:{...process.env,NPM_CONFIG_LOGLEVEL:"error"}});i.stdout&&i.stdout.on("data",e=>r.push(e)),i.stderr&&i.stderr.on("data",e=>a.push(e)),i.on("error",t=>{e(t),o(t)}),i.on("close",e=>{const t=null===e?0:e;0!==t&&(r.length&&process.stdout.write(Buffer.concat(r)),a.length&&process.stderr.write(Buffer.concat(a))),n(t)})})},De="Run this command in a DashNex Web application folder.",Te=e=>{const t=C.join(e,"dashnex.json");return j.pathExistsSync(t)},je=(e,t=!1)=>{t||console.log(T.cyan(e))},Ce=(t,s,o={})=>{const r=`${t} ${s.join(" ")}`,a=!0===o.silent;return new Promise((s,o)=>{n(`Running: ${r}`);const i=I(r,{stdio:a?["ignore","pipe","pipe"]:"inherit",cwd:process.cwd(),shell:!0});i.on("error",t=>{e(t),o(t)}),i.on("close",e=>{n(`${t} exited with code ${e}`),s(null===e?1:e)})})},Ne=(t,s)=>new Promise((o,r)=>{n(`Running: ${t} ${s.join(" ")}`);const a=I(t,s,{cwd:process.cwd()});let i="";a.stdout?.on("data",e=>{i+=e.toString()}),a.stderr?.on("data",e=>{i+=e.toString()}),a.on("error",t=>{e(t),r(t)}),a.on("close",e=>{n(`${t} exited with code ${e}`),o({exitCode:null===e?1:e,output:i})})}),Pe=async t=>{n("Running check: install");try{return 0===await Ee(process.cwd())}catch(s){return e(s),!t&&s instanceof Error&&console.error(T.red(s.message)),!1}},Ie=async()=>{try{const{output:e}=await Ne(process.execPath,[process.argv[1],"db","migrate","--help"]);return e.includes("Usage: dashnex db migrate")}catch(t){return e(t),!1}},Ae=async t=>{n("Running check: db migrate");try{const{exitCode:e,output:s}=await Ne(process.execPath,[process.argv[1],"db","migrate"]);return 0===e||(s.includes("Unknown command")?(n("db migrate command is not available, skipping"),!0):(!t&&s.trim().length>0&&process.stderr.write(s),!1))}catch(s){return e(s),!0}},Fe=e=>new Promise(t=>{const s=I(`${e} --version`,{stdio:"ignore",shell:!0});s.on("error",()=>t(!1)),s.on("close",e=>t(0===e))}),Re=[{name:"TypeScript",command:"npx",args:["tsc","--noEmit"],notFoundMessage:"TypeScript is not available. Please install it with: npm install -D typescript"}],Oe=class{async execute(){n("Check flow started"),Te(process.cwd())||(console.error(T.red(De)),t(1)),je("Checking dependencies..."),await Pe(!1)||(console.error(T.red("Check failed.")),t(1)),n(`Running ${Re.length} check(s)`);for(const o of Re){n(`Running check: ${o.name}`),je(`Checking ${o.name}...`),await Fe(o.command)||(console.error(T.red(o.notFoundMessage)),t(1));try{0!==await Ce(o.command,o.args)&&(console.error(T.red("Check failed.")),t(1))}catch(s){if(s instanceof Error&&s.message.startsWith("EXIT:"))throw s;e(s),console.error(T.red(o.notFoundMessage)),t(1)}}await Ie()?(je("Checking database migrations..."),await Ae(!1)||(console.error(T.red("Check failed.")),t(1))):n("db migrate command is not available, skipping"),console.log(T.green("All checks passed."))}async run(t=!1){if(n("Running checks (called from push)"),!Te(process.cwd()))return t||console.error(T.red(De)),!1;if(je("Checking dependencies...",t),!(await Pe(t)))return!1;const s=t?{silent:!0}:void 0;for(const r of Re){if(n(`Running check: ${r.name}`),je(`Checking ${r.name}...`,t),!(await Fe(r.command)))return console.error(T.red(r.notFoundMessage)),!1;try{if(0!==await Ce(r.command,r.args,s))return!1}catch(o){return e(o),console.error(T.red(r.notFoundMessage)),!1}}if(await Ie()){if(je("Checking database migrations...",t),!(await Ae(t)))return!1}else n("db migrate command is not available, skipping");return t||console.log(T.green("All checks passed.")),!0}},Be=" │ ",_e=e=>e.subarray(0,8192).includes(0),ze=e=>{const t=e.split("\n");return""===t[t.length-1]&&t.pop(),t},We=(e,t)=>{if(null===e)return" ".repeat(t);const s=e.replace(/\t/g," ");return s.length<=t?s.padEnd(t):`${s.slice(0,Math.max(0,t-1))}…`},Le=(e,t,s)=>{const n=Math.max(s??process.stdout.columns??120,43),o=Math.floor((n-3)/2),r=((e,t)=>{const s=[],n=U(e,t);for(let o=0;o<n.length;o+=1){const e=n[o];if(e.removed){const t=ze(e.value),r=n[o+1],a=r?.added?ze(r.value):[];r?.added&&(o+=1);const i=Math.max(t.length,a.length);for(let e=0;e<i;e+=1)s.push({left:t[e]??null,right:a[e]??null,changed:!0})}else if(e.added)for(const t of ze(e.value))s.push({left:null,right:t,changed:!0});else for(const t of ze(e.value))s.push({left:t,right:t,changed:!1})}return s})(e,t),a=/* @__PURE__ */new Set;r.forEach((e,t)=>{if(e.changed)for(let s=Math.max(0,t-3);s<=Math.min(r.length-1,t+3);s+=1)a.add(s)});const i=[];i.push(T.bold(We("local (current)",o))+T.dim(Be)+T.bold("incoming"));let c=-1,l=0,d=0;for(let p=0;p<r.length;p+=1){if(!a.has(p))continue;if(l>=200){d+=1;continue}-1!==c&&p>c+1&&i.push(T.dim("···")),c=p,l+=1;const e=r[p],t=We(e.left,o),s=We(e.right,o);if(!e.changed){i.push(T.dim(t)+T.dim(Be)+T.dim(s));continue}const n=null!==e.left?T.red(t):t,u=null!==e.right?T.green(s):s;i.push(n+T.dim(Be)+u)}return d>0&&i.push(T.dim(`… ${d} more diff line(s) not shown`)),i.join("\n")},Me=[".dashnex",".git",".vinext",".wrangler","wrangler.jsonc","wrangler.toml",".dev.vars",".github"],Ue=[".env","wrangler.jsonc"],Xe=e=>C.join(e,y,"sync.json"),He=e=>B("sha1").update(`blob ${e.length}\0`).update(e).digest("hex"),Je=async e=>{const t=X();t.add(Me);const s=C.join(e,".gitignore");return await j.pathExists(s)&&t.add(await j.readFile(s,"utf8")),t},Ge=e=>e.split(C.sep).join("/"),qe=e=>Me.some(t=>e===t||e.startsWith(`${t}/`)),Ye=async(e,t={})=>{const s={},n=await(async e=>{const t=await Je(e),s=[],n=async o=>{const r=await j.readdir(o,{withFileTypes:!0});for(const a of r){const r=C.join(o,a.name),i=Ge(C.relative(e,r));qe(i)||t.ignores(a.isDirectory()?`${i}/`:i)||(a.isDirectory()?await n(r):a.isFile()&&s.push(i))}};return await n(e),s})(e);for(const o of n)s[o]=He(await j.readFile(C.join(e,o)));if(t.includeGenerated)for(const o of Ue){if(o in s)continue;const t=C.join(e,o);(await j.lstat(t).catch(()=>null))?.isFile()&&(s[o]=He(await j.readFile(t)))}return s},Ve=async e=>{try{const t=await j.readFile(Xe(e),"utf8"),s=JSON.parse(t);return"string"!=typeof s?.commit||"object"!=typeof s?.manifest||null===s.manifest?null:s}catch{return null}},Ke=async(e,t,s)=>{const o={commit:t,manifest:s,updatedAt:/* @__PURE__ */(new Date).toISOString()},r=Xe(e);try{await j.ensureDir(C.dirname(r)),await j.writeFile(r,`${JSON.stringify(o,null,2)}\n`),n(`Sync state written: commit ${t}, ${Object.keys(s).length} files`)}catch(a){n(`Failed to write sync state: ${a instanceof Error?a.message:"unknown"}`)}},Ze=1048576,Qe=async(t,s)=>{const{install:o}=await R.prompt([{type:"confirm",name:"install",message:"Install dependencies?",default:!0}]);if(!o){if(C.resolve(t)!==C.resolve(s)){const e=C.relative(s,t);console.log(T.cyan(`Run "cd ${e}" to change to the application folder.`))}return}try{const e=ae();e.start("Installing dependencies...");try{const s=await Ee(t);if(e.stop(),0!==s)return void n(`install exited with code ${s}`)}catch(i){throw e.stop(),i}}catch(i){return e(i),void console.error(T.red(i instanceof Error?i.message:"Failed to run install."))}const{migrate:r}=await R.prompt([{type:"confirm",name:"migrate",message:"Create or update database?",default:!0}]);if(!r)return;n(`Running dashnex db migrate in ${t}`);try{const s=ae();s.start("Creating or updating database...");try{const o=await((t,s,n)=>{const o=`${C.join(t,"node_modules",".bin")}${C.delimiter}${process.env.PATH??""}`,r={...process.env,PATH:o};return new Promise((o,a)=>{const i=I(s,{cwd:t,shell:!0,stdio:"inherit",env:n?{...r,...n}:r});i.on("error",t=>{e(t),a(t)}),i.on("close",e=>{o(null===e?0:e)})})})(t,"dashnex db migrate");if(s.stop(),0!==o)return void n(`dashnex db migrate exited with code ${o}`)}catch(i){throw s.stop(),i}}catch(i){return e(i),void console.error(T.red("Failed to run dashnex db migrate."))}const{dev:a}=await R.prompt([{type:"confirm",name:"dev",message:"Run development server?",default:!0}]);if(a)try{await xe(t)}catch(i){e(i),console.error(T.red(i instanceof Error?i.message:"Failed to run dev server."))}},et=(e,t)=>{if(e.length!==t.length)return`local ${e.length} bytes, incoming ${t.length} bytes`;const s=e.length;for(let n=0;n<s;n+=1)if(e[n]!==t[n]){return`size ${s} matches; first diff at byte ${n} (local=0x${e[n].toString(16).padStart(2,"0")} incoming=0x${t[n].toString(16).padStart(2,"0")})`}return`size ${s} matches; no byte difference (unexpected)`},tt=async(e,t)=>{let s;try{s=await j.lstat(e)}catch{return{kind:"new"}}if(s.isDirectory())return{kind:"different",reason:"target is a directory"};if(s.size!==t.length)return{kind:"different",reason:`local ${s.size} bytes, incoming ${t.length} bytes`};const n=await j.readFile(e);return 0===Buffer.compare(n,t)?{kind:"identical"}:{kind:"different",reason:et(n,t)}},st=async(t,s,n)=>{console.log(`\n${T.bold(s)}`);try{const e=await j.readFile(t);if(e.length>Ze||n.length>Ze||_e(e)||_e(n))return void console.log(T.dim(`(no preview: ${et(e,n)})`));console.log(Le(e.toString("utf8"),n.toString("utf8")))}catch(o){e(o),console.log(T.dim("(no preview available)"))}},nt=async e=>{const{choice:t}=await R.prompt([{type:"expand",name:"choice",message:`Overwrite ${e}?`,default:"y",choices:[{key:"y",name:"yes (overwrite this file)",value:"yes"},{key:"n",name:"no (keep local version)",value:"no"},{key:"a",name:"overwrite all remaining differing files",value:"all"},{key:"s",name:"skip all remaining differing files",value:"skip"}]}]);return t},ot=async(e,t)=>{const s=t?T.yellow(" (has local changes!)"):"",{choice:n}=await R.prompt([{type:"expand",name:"choice",message:`Delete ${e}? It no longer exists in the application.${s}`,default:"y",choices:[{key:"y",name:"yes (delete this file)",value:"yes"},{key:"n",name:"no (keep this file)",value:"no"},{key:"a",name:"delete all remaining removed files",value:"all"},{key:"s",name:"keep all remaining removed files",value:"skip"}]}]);return n},rt=async(e,t)=>{let s=C.dirname(e);const n=C.resolve(t);for(;C.resolve(s)!==n;)try{if((await j.readdir(s)).length>0)return;await j.rmdir(s),s=C.dirname(s)}catch{return}},at=(e,t)=>C.resolve(e)===C.resolve(t)?"Application is successfully pulled.":`Application is successfully pulled into ${C.relative(t,e)}.`,it=class{async execute(o={}){n("Pull flow started");const a=await r();if(!a)return;const i=process.cwd();let c;if(o.folder)c=o.folder.trim()||".",n(`Folder argument provided: ${c}`);else{n("No folder argument, prompting user");const{folder:e}=await R.prompt([{type:"input",name:"folder",message:"Which folder to pull the application to?",default:"."}]);c=e.trim()||"."}const l=C.resolve(i,c);n(`Target folder resolved: ${l}`),await b(a,{projectDir:l});try{const e=await j.lstat(l).catch(()=>null);e&&!e.isDirectory()&&(console.error(T.red(`Cannot pull into ${l}: path exists and is not a directory.`)),t(1)),await j.ensureDir(l),n(`Target folder ensured: ${l}`)}catch(u){if(u instanceof Error&&u.message.startsWith("EXIT:"))throw u;e(u);const s=u instanceof Error?u.message:"Unknown error";console.error(T.red(`Cannot create folder ${l}: ${s}`)),t(1)}const d=C.join(L.tmpdir(),`dashnex-pull-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`),p=ae();p.start("Analyzing local files...");try{const s=await Ve(l),r=await Ye(l,{includeGenerated:!0});n(`Local manifest has ${Object.keys(r).length} files`),p.text("Pulling application...");const c=await this.download(a.token,r);p.text("Downloading..."),await j.writeFile(d,c.buffer),n(`Zip written to temp file (${c.viaV2?"v2 diff":"legacy full"} pull)`),p.text("Extracting...");const h=new M(d).getEntries().filter(e=>!e.isDirectory);let m=null;const g=h.find(e=>".dashnex/pull.json"===e.entryName);if(g)try{m=JSON.parse(g.getData().toString("utf8"))}catch(u){e(u)}const f=h.filter(e=>".dashnex"!==e.entryName&&!e.entryName.startsWith(".dashnex/"));n(`Zip has ${f.length} file entries${m?`, meta commit ${m.commit}`:", no meta"}`);const w=o.yes||"1"===process.env.DASHNEX_CI;let y=w?"all":"prompt",$=!1,v=0,b=0,k=0,x=0,S=0;const E=async(t,s,n)=>{try{return await j.ensureDir(C.dirname(t)),await j.writeFile(t,s),n&&await j.chmod(t,n),!0}catch(u){return e(u),!1}};for(const e of f){const t=e.getData(),s=C.join(l,e.entryName),o=C.relative(l,s)||e.entryName,r=(e.header.attr??0)>>16&4095,a=await tt(s,t);if(n(`Entry ${e.entryName}: ${a.kind}${a.reason?` (${a.reason})`:""}`),"identical"===a.kind){x+=1;continue}if("new"===a.kind){await E(s,t,r)?v+=1:S+=1;continue}let i=!1;if("all"===y)i=!0;else if("none"===y)i=!1;else{p.stop(),await st(s,o,t),$||(console.log(T.dim("Y = yes (default), n = skip, a = overwrite all, s = skip all, h = help")),$=!0);const r=await nt(o);n(`Entry ${e.entryName}: user chose ${r}`),"yes"===r?i=!0:"no"===r?i=!1:"all"===r?(y="all",i=!0):(y="none",i=!1),p.start("Extracting...")}i?await E(s,t,r)?b+=1:S+=1:k+=1}p.stop();let D=0,N=0;if(m&&m.deleted.length>0){const e=await this.applyDeletions(l,m.deleted,s,w);D=e.deleted,N=e.kept}m&&(x+=Math.max(0,Object.keys(m.manifest).length-f.length)),n(`Pull summary: ${v} new, ${b} overwritten, ${k} skipped, ${D} deleted, ${x} unchanged, ${S} failed`);const P=[`${v} new`,`${b} overwritten`,`${k} skipped`,`${x} unchanged`];if((D>0||N>0)&&P.splice(3,0,`${D} deleted`),S>0&&P.push(`${S} failed`),console.log(T.cyan(`Pulled: ${P.join(", ")}`)),m){const e=await Ye(l,{includeGenerated:!0});await Ke(l,m.commit,e)}C.resolve(l)!==C.resolve(i)?(console.log(T.green(at(l,i))),process.chdir(l),n(`Changed working directory to ${l}`)):console.log(T.green(at(l,i))),S>0&&(console.error(T.red(`${S} file(s) failed to write. See DEBUG output for details.`)),t(1)),w?n("Non-interactive mode (--yes or --ci), skipping post-pull child commands"):await Qe(l,i)}catch(u){if(p.stop(),e(u),u instanceof Error&&u.message.startsWith("EXIT:"))throw u;if(h(u))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}finally{await j.remove(d).catch(()=>{}),n("Temp zip removed")}}async download(e,t){const s=`${o()}/business/v1/cli/v2/pull`;n(`POST ${s}`);const r=await fetch(s,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({manifest:t})});if(n(`v2 pull response: ${r.status}`),r.ok)return{buffer:Buffer.from(await r.arrayBuffer()),viaV2:!0};const a=await r.json().catch(()=>({}));var i,c;i=r.status,c=a,404===i&&"string"==typeof c.message&&c.message.startsWith("Cannot ")||this.failFromResponse(r.status,a),n("v2 pull endpoint unavailable, falling back to legacy pull");const l=`${o()}/business/v1/cli/pull`;n(`GET ${l}`);const d=await fetch(l,{headers:{Authorization:`Bearer ${e}`}});if(n(`Legacy pull response: ${d.status}`),!d.ok){const e=await d.json().catch(()=>({}));this.failFromResponse(d.status,e)}return{buffer:Buffer.from(await d.arrayBuffer()),viaV2:!1}}failFromResponse(e,s){let n="Failed to pull application.";if(401===e||403===e)n="Please run 'dashnex login' to authenticate.";else if(404===e)n="Business has no application. Run 'dashnex app create' to create one.";else{const e=(e=>"string"==typeof e.error?e.error:"string"==typeof e.message?e.message:void 0)(s);e&&(n=e)}console.error(T.red(n)),t(1)}async applyDeletions(t,s,o,r){let a=0,i=0;if(!o){const e=s.slice(0,20);console.log(T.yellow("These local files do not exist in the application (kept — no sync state yet):"));for(const t of e)console.log(T.yellow(` ${t}`));return s.length>e.length&&console.log(T.yellow(` … and ${s.length-e.length} more`)),{deleted:a,kept:s.length}}let c=r?"all":"prompt";for(const d of s){if(!(d in o.manifest)){n(`Deletion candidate ${d} was never synced, keeping`);continue}const s=C.join(t,d),r=await j.readFile(s).catch(()=>null);if(null===r)continue;const p=He(r)!==o.manifest[d];let u=!1;if("all"===c)u=!0;else if("none"===c)u=!1;else{const e=await ot(d,p);n(`Deletion ${d}: user chose ${e}`),"yes"===e?u=!0:"no"===e?u=!1:"all"===e?(c="all",u=!0):(c="none",u=!1)}if(u)try{await j.remove(s),await rt(s,t),a+=1}catch(l){e(l),i+=1}else i+=1}return{deleted:a,kept:i}}},ct=async e=>{const t=`${o()}/business/v1/agreements/payment-processing/status`;n(`GET ${t}`);const s=await fetch(t,{headers:{Authorization:`Bearer ${e}`}});if(n(`Payment agreement response: ${s.status}`),!s.ok)return!0;if((await s.json().catch(()=>({}))).hasAccepted)return!0;const r=`${await(async()=>x())()}/money/agreement/`;return console.error(T.red(`You need to accept Master Payment Processing Agreement first by visiting this link: ${r}`)),!1},lt=async(t,s={})=>{const r=s.ciMode??!1;return!!(await ct(t))&&!!(await(async(t,s)=>{const r=`${o()}/business/v1/tos/latest`;n(`GET ${r}`);const a=await fetch(r,{headers:{Authorization:`Bearer ${t}`}});if(n(`TOS latest response: ${a.status}`),a.ok)return!0;if(404!==a.status)return!0;const i=`${o()}/business/v1/tos/current`;n(`GET ${i}`);const c=await fetch(i,{headers:{Authorization:`Bearer ${t}`}});if(n(`TOS current response: ${c.status}`),!c.ok)return n("Could not fetch current TOS, skipping check"),!0;const l=await c.json().catch(()=>({})),d=l.version??"unknown";if(l.text&&(console.log(T.dim("─".repeat(60))),console.log(T.bold(`Terms of Service ${d}`)),console.log(T.dim("─".repeat(60))),console.log(l.text.replace(/<br\s*\/?>/gi,"\n").replace(/<\/p>/gi,"\n\n").replace(/<\/div>/gi,"\n").replace(/<\/li>/gi,"\n").replace(/<[^>]+>/g,"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/ /g," ").replace(/\n{3,}/g,"\n\n").trim()),console.log(T.dim("─".repeat(60)))),s)return console.log(T.yellow(`By proceeding you accept the Terms of Service ${d}.`)),!0;const{accept:p}=await R.prompt([{type:"confirm",name:"accept",message:`By creating an application you accept the Terms of Service ${d}. Continue?`,default:!0}]);if(!p)return console.log(T.yellow("Cancelled.")),!1;try{const e=`${o()}/business/v1/tos/${l.id}/accept`;n(`POST ${e}`),await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify({})})}catch(u){e(u)}return!0})(t,r))},dt=e=>Array.isArray(e.message)&&e.message.length>0?e.message.join("\n"):"string"==typeof e.message?e.message:"string"==typeof e.error?e.error:void 0,pt=e=>new Promise(t=>setTimeout(t,e)),ut=e=>e.appFullCustomDomain?e.appFullCustomDomain:e.subdomain?"qa"===(process.env.APPLICATION_ENV??"")?`https://${e.subdomain}.dashnex.dev`:`https://${e.subdomain}.dashnexcloud.com`:null,ht=3e5,mt=99,gt=e=>new Promise(t=>setTimeout(t,e)),ft=async(t,s,o,r)=>{const a=await fetch(t,{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"text/event-stream"}});if(n(`SSE connection opened: ${a.status}`),!a.ok){const e=a.status;let t="Failed to start deployment.";if(401===e||403===e)t="Please run 'dashnex login' to authenticate.";else try{const e=await a.text(),s=JSON.parse(e);"string"==typeof s.error?t=s.error:"string"==typeof s.message&&(t=s.message)}catch{}throw console.error(T.red(t)),new Error(`HTTP ${e}: ${t}`)}if(!a.body)throw new Error("Response body is null");n("SSE connection established successfully"),r?.();const i=a.body.getReader();try{for await(const t of async function*(e){const t=new TextDecoder("utf-8");let s="";for(;;){const{value:n,done:o}=await e.read();if(o)break;s+=t.decode(n,{stream:!0});const r=s.split("\n");s=r.pop()||"";let a="";for(const e of r)e.startsWith("data: ")?a=e.slice(6):""===e&&a&&(yield{data:a},a="")}if(s.startsWith("data: ")){const e=s.slice(6);e&&(yield{data:e})}}(i))try{if(!t.data){n("Received empty SSE event");continue}const e=JSON.parse(t.data);n(`SSE message received: ${JSON.stringify(e)}`),o(e)}catch(c){e(c),n(`Failed to parse SSE event data: ${t.data}`)}}finally{i.releaseLock()}},wt=class{async execute(){n("Deploy flow started");const o=await r();if(!o)return;if(await b(o,{checkApplication:!0}),!(await lt(o.token,{ciMode:!0})))return;const i=`${a()}/business/v1/applications/deploy`;n(`GET ${i} (SSE)`);let c=0,l="",d=!1,p=0,u=!1;const m=ae();let g=0,f=0,w=null;const y=(e,t)=>{const s=Math.round(e);m.text(`[${s}%] ${t}`)},$=()=>{v();const e=c,t=Date.now(),s=ht*((mt-e)/mt);w=setInterval(()=>{const n=Date.now()-t;let o;if(f>t){const e=Date.now()-f,t=mt-g,s=3030.3030303030305*t;o=Math.min(g+e/s*t,mt)}else o=Math.min(e+n/s*(mt-e),mt);o>c&&(c=o,y(c,l))},200)},v=()=>{w&&(clearInterval(w),w=null)};let k=!1;const x=e=>{if(u=!0,"string"==typeof e.error)return v(),m.stop(),console.error(T.red(e.error)),d=!0,void(k=!0);"string"==typeof e.message&&(l=e.message),"number"==typeof e.progress&&(g=e.progress,f=Date.now(),e.progress>c&&(c=e.progress)),"string"!=typeof e.message&&"number"!=typeof e.progress||(c>=100?(v(),m.stop(),d=!0):y(c,l))};for(;p<=5;)try{return u?y(c,l):m.start("Connecting..."),$(),await ft(i,o.token,x,()=>{u||(m.text(`[${Math.round(c)}%] Deployment in progress...`),l="Deployment in progress...")}),v(),m.stop(),n("SSE connection closed"),k&&t(1),void(d&&await this.showDeployedUrl(o.token))}catch(S){if(v(),S instanceof Error&&S.message.startsWith("EXIT:"))throw S;if(e(S),n(`SSE connection error: ${S}`),d)return m.stop(),void(k&&t(1));if(h(S))return m.stop(),void console.log(T.yellow(s));S instanceof Error&&S.message.startsWith("HTTP")&&(m.stop(),console.error(T.red("Could not complete deployment. Check your connection and try again.")),t(1)),p++,p>5&&(m.stop(),console.error(T.red("Deployment failed. Maximum reconnection attempts exceeded.")),t(1));const o=Math.min(1e3*Math.pow(2,p-1),3e4);n(`Connection error. Reconnecting in ${o/1e3}s... (attempt ${p}/5)`),await gt(o)}}async showDeployedUrl(t){try{const e=`${o()}/business/v1/applications`;n(`GET ${e} (for deployed URL)`);const s=await fetch(e,{headers:{Authorization:`Bearer ${t}`}});if(!s.ok)return;const r=await s.json().catch(()=>({})),a=ut({subdomain:r.subdomain,appFullCustomDomain:r.appFullCustomDomain});a&&console.log(`Application is deployed to ${T.cyan(a)}`)}catch(s){e(s)}}},yt=104857600,$t=class extends Error{},vt=class extends Error{},bt=class extends Error{},kt="/business/v1/cli/tail-token";async function xt(e,t={}){const s=`${o()}${kt}${t.debug?"?debug=1":""}`;try{await fetch(s,{method:"DELETE",headers:{Authorization:`Bearer ${e}`}})}catch{}}var St=["log","info","warn","error","debug"],Et={error:4,warn:3,log:2,info:2,debug:1};function Dt(e){return"string"==typeof e&&St.includes(e)}function Tt(e){return e.map(e=>"string"==typeof e?e:function(e){try{return JSON.stringify(e)}catch{return String(e)}}(e)).join(" ")}function jt(e){return e<10?`0${e}`:String(e)}function Ct(e){const t=new Date(e);return`${jt(t.getHours())}:${jt(t.getMinutes())}:${jt(t.getSeconds())}.${s=t.getMilliseconds(),s<10?`00${s}`:s<100?`0${s}`:String(s)}`;var s}function Nt(e,t){if("string"==typeof e)return e;if("object"==typeof(s=e)&&null!==s&&"string"==typeof s.name&&"string"==typeof s.message&&Object.keys(s).every(e=>"name"===e||"message"===e||"stack"===e)){const t=`${e.name}: ${e.message}`;return e.stack?`${t}\n${e.stack}`:t}var s;return G(e,{colors:t,depth:2,breakLength:120})}var Pt={error:e=>T.red(e),warn:e=>T.yellow(e),debug:e=>T.dim(e),log:e=>e,info:e=>e};function It(e,t){const s=t.color?T.dim(Ct(e.ts)):Ct(e.ts),n=e.level.toUpperCase().padEnd(5),o=[s,t.color?Pt[e.level](n):n];o.push(e.args.map(e=>Nt(e,t.color)).join(" "));let r=o.join(" ");if(e.truncated){const e=" …(truncated)";r+=t.color?T.dim(e):e}return r}function At(e,t,s){let n;try{n=JSON.parse(e)}catch{return{kind:"malformed",raw:e}}if("object"!=typeof n||null===n||!Dt(n.level))return{kind:"malformed",raw:e};const o=n;return Array.isArray(o.args)||(o.args=void 0===o.args?[]:[o.args]),r=o.level,a=t.minLevel,Et[r]>=Et[a]&&function(e,t){return!t||t.test(Tt(e.args))}(o,t.filter)?{kind:"line",line:It(o,s)}:{kind:"filtered"};var r,a}var Ft=new Set([4401,4403]);var Rt=[{label:"node_modules",segments:["node_modules"],description:"installed dependencies"},{label:"node_modules/.vite",segments:["node_modules",".vite"],description:"vite cache"},{label:"node_modules/.vite-temp",segments:["node_modules",".vite-temp"],description:"vite cache"},{label:"src/dashnex",segments:["src","dashnex"],description:"generated module registry"},{label:"dist",segments:["dist"],description:"build output"}],Ot={label:".wrangler",segments:[".wrangler"],description:"local database"},Bt=async e=>{await q(e,{recursive:!0,force:!0,maxRetries:5,retryDelay:100})},_t={name:"app",description:"Manage DashNex applications (should be executed in application folder)",handler:{execute:async()=>{}},options:[],subcommands:[{name:"create",description:"Create a new DashNex application",handler:new class{async pollApplicationCreated(t,s,o=5e3,r=6e4){const a=Date.now();let i=0;for(;Date.now()-a<r;){i++,n(`Polling application status (attempt ${i})...`);try{const e=await fetch(t,{headers:{Authorization:`Bearer ${s}`}});if(n(`Poll response: ${e.status}`),200===e.status){const t=await e.json();if(n(`Poll response body status: ${t.status}`),"creating"!==t.status)return n(`Application status is "${t.status}", creation complete`),{ready:!0,status:t.status};n('Application status is still "creating", continuing to poll...')}}catch(c){e(c),n(`Poll attempt ${i} failed with network error`)}await pt(o)}return{ready:!1}}async execute(a={}){n("Create flow started");const i=await r();if(!i)return;n("Auth valid, checking if business already has application");const c=ae(),l=`${o()}/business/v1/applications`;n(`GET ${l}`),c.start("Checking application status...");try{const e=await fetch(l,{headers:{Authorization:`Bearer ${i.token}`}});c.stop(),n(`Applications response: ${e.status}`),n(`Applications response body: ${await e.text()}`),404!==e.status&&(console.error(T.red("Business already has an application. Run 'dashnex app pull' to pull the existing one.")),t(1))}catch(y){if(c.stop(),e(y),y instanceof Error&&y.message.startsWith("EXIT:"))throw y;if(h(y))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}n("No existing application, checking agreements");const d=void 0!==a.subdomain;if(!(await lt(i.token,{ciMode:d})))return;let p;n("Agreements checked, proceeding to subdomain prompt");const u=e=>`${o()}/business/v1/applications/check-subdomain?subdomain=${encodeURIComponent(e)}`;let m=a.subdomain?.trim();for(;;){if(m)p=m,m=void 0,n(`Subdomain provided via flag: ${p}`);else{if(d)return console.error(T.red("Subdomain is not available.")),void t(1);{const{sub:e}=await R.prompt([{type:"input",name:"sub",message:"Pick your subdomain:",validate:e=>!!e.trim()||"Subdomain is required"}]);p=e.trim()}}n(`Checking subdomain: ${p}`);const o=u(p);n(`GET ${o}`),c.start("Checking subdomain...");try{const e=await fetch(o,{headers:{Authorization:`Bearer ${i.token}`}});c.stop(),n(`Check subdomain response: ${e.status}`);const s=await e.text();if(n(`Check subdomain response body: ${s}`),!e.ok){const o=e.headers.get("content-type")??"";let r="Failed to check subdomain availability.";if(401!==e.status&&403!==e.status||(r="Please run 'dashnex login' to authenticate.",console.error(T.red(r)),t(1)),o.includes("application/json")){const e=JSON.parse(s);n(`Check subdomain error body: ${JSON.stringify(e)}`);const t=dt(e);t&&(r=t)}console.error(T.red(r));continue}const r=JSON.parse(s);if(n(`Check subdomain parsed body: ${JSON.stringify(r)}`),r.available){n(`Subdomain ${p} is available`);break}console.error(T.red("Subdomain is not available. Please choose a different one."))}catch(y){if(c.stop(),e(y),y instanceof Error&&y.message.startsWith("EXIT:"))throw y;if(h(y))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}}const g=`${o()}/business/v1/cli/create`;n(`POST ${g}`);const f={subdomain:p};a.template&&(f.template=a.template,n(`Template: ${a.template}`));const w=JSON.stringify(f);n(`Create request body: ${w}`),c.start("Creating application...");try{const r=await fetch(g,{method:"POST",headers:{Authorization:`Bearer ${i.token}`,"Content-Type":"application/json"},body:w});c.stop(),n(`Create response: ${r.status}`);const l=await r.text();if(n(`Create response body: ${l}`),503===r.status){n("Create returned 503, polling for application creation..."),c.start("Waiting for application to be ready...");const e=`${o()}/business/v1/applications`,s=await this.pollApplicationCreated(e,i.token);c.stop(),s.ready||(console.error(T.red("Application creation is taking longer than expected. Try again later or run 'dashnex app pull' to check.")),t(1)),"failed"===s.status&&(console.error(T.red("Application creation failed. Please try again.")),t(1)),n("Application found after polling")}else if(!r.ok){const e=r.headers.get("content-type")??"";let s="Failed to create application.";if(401===r.status||403===r.status)s="Please run 'dashnex login' to authenticate.";else if(e.includes("application/json")){const e=JSON.parse(l);n(`Create error body parsed: ${JSON.stringify(e)}`);const t=dt(e);t&&(s=t)}console.error(T.red(s)),t(1)}let p;if(n("Application created successfully"),!0===a.pull?(p=!0,n("--pull flag set, pulling without prompting")):!1===a.pull?(p=!1,n("--no-pull flag set, skipping pull")):d?(p=!1,n("CI mode (--subdomain), skipping pull")):(p=(await R.prompt([{type:"confirm",name:"pull",message:"Pull the application locally?",default:!0}])).pull,n(`User chose to pull: ${p}`)),p){n("Running pull flow");try{await(new it).execute()}catch(y){if(e(y),y instanceof Error&&y.message.startsWith("EXIT:"))throw y;if(h(y))return void console.log(T.yellow(s));console.error(T.red("Failed to pull application."))}}else console.log(T.green("Application is created successfully. Run 'dashnex app pull' to pull it to your computer or 'dashnex app deploy' to deploy it to the cloud."))}catch(y){if(c.stop(),e(y),y instanceof Error&&y.message.startsWith("EXIT:"))throw y;if(h(y))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}}},options:[{flags:"-t, --template <template>",description:"Template (name@branch-or-tag)",defaultValue:"webapp-base@live"},{flags:"--subdomain <subdomain>",description:"Subdomain (CI mode, skip prompts)"},{flags:"--pull",description:"Auto-pull after creation (skip prompt)"},{flags:"--no-pull",description:"Skip the pull prompt and show the success message"}]},{name:"pull",description:"Pull existing application to specified folder",handler:new it,arguments:[{name:"folder",description:"Target folder (defaults to interactive prompt)"}],options:[{flags:"-y, --yes",description:"Skip prompts and child commands (CI mode)"}]},{name:"push",description:"Push local application to DashNex",handler:new class{async execute(o={}){n("Push flow started"),await j.pathExists(C.join(process.cwd(),"dashnex.json"))||(console.error(T.red("Not a DashNex application folder (dashnex.json not found).")),console.error(T.red("Run this command from your application root.")),t(1)),!1!==o.check?(n("Running pre-push checks"),await(new Oe).run(!1)||(console.error(T.red("Push aborted: checks failed. Re-run with --no-check to skip.")),t(1))):n("Skipping pre-push checks (--no-check)");const a=await r();if(!a)return;await b(a,{checkApplication:!0});const i=process.cwd(),c=ae();try{c.start("Analyzing local files...");const e=await Ve(i),t=await Ye(i);n(`Local manifest has ${Object.keys(t).length} files`),c.text("Comparing with remote...");const s=await this.requestPlan(a.token,t,e?.commit);c.stop(),"legacy"===s?(n("v2 push endpoints unavailable, falling back to legacy zip push"),await this.pushLegacy(a.token,i,c)):await this.pushDiff(a.token,i,t,s,o,c),await this.promptAndDeploy(o)}catch(l){if(c.stop(),e(l),l instanceof Error&&l.message.startsWith("EXIT:"))throw l;if(h(l))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}}async requestPlan(e,t,s){const r=`${o()}/business/v1/cli/v2/push/plan`;n(`POST ${r}`);const a=await fetch(r,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({manifest:t,lastSyncCommit:s})});if(n(`Plan response: ${a.status}`),a.ok)return await a.json();const i=await a.json().catch(()=>({}));if(((e,t)=>404===e&&"string"==typeof t.message&&t.message.startsWith("Cannot "))(a.status,i))return"legacy";this.failFromResponse(a.status,i,"Failed to push application.")}async pushDiff(e,s,o,r,a,i){let c=r;if(c.remoteChanged.length>0&&!a.force){console.error(T.red("Remote application has changes you don't have locally:"));for(const e of c.remoteChanged.slice(0,20))console.error(T.red(` ${e}`));c.remoteChanged.length>20&&console.error(T.red(` … and ${c.remoteChanged.length-20} more`)),console.error(T.red("Run 'dashnex app pull' first, or push anyway with --force.")),t(1)}const l=c.upload.filter(e=>e in o),d=await Je(s);let p=c.delete.filter(e=>!d.ignores(e));if(0!==l.length||0!==p.length){if(p.length>0){const e=await Ve(s),t=a.yes||"1"===process.env.DASHNEX_CI;console.log(T.yellow(`${p.length} file(s) will be deleted from the application repository:`));for(const s of p.slice(0,20))console.log(T.yellow(` ${s}`));if(p.length>20&&console.log(T.yellow(` … and ${p.length-20} more`)),!t){const{confirmDelete:t}=await R.prompt([{type:"confirm",name:"confirmDelete",message:"Delete these files from the remote repository?",default:null!==e}]);t||(n("User declined remote deletions, pushing without them"),p=[])}}if(0!==l.length||0!==p.length)for(let r=1;;r+=1){i.start(1===r?"Uploading changes...":"Remote changed, retrying...");const d=await this.applyPush(e,s,l,p,c.baseCommit);if(i.stop(),"ok"===d.status){await Ke(s,d.commit,o);const e=[`${l.length} file(s) updated`];return p.length>0&&e.push(`${p.length} deleted`),console.log(T.cyan(`Pushed: ${e.join(", ")}`)),void console.log(T.green("Application pushed successfully."))}r>=2&&(console.error(T.red("Remote repository keeps changing, please retry the push.")),t(1)),n("Push apply conflicted, re-planning");const u=await Ve(s),h=await this.requestPlan(e,o,u?.commit);"legacy"===h&&(console.error(T.red("Failed to push application.")),t(1)),h.remoteChanged.length>0&&!a.force&&(console.error(T.red("Remote application changed during the push. Run 'dashnex app pull' first, or push with --force.")),t(1)),c=h}else console.log(T.green("Everything up to date."))}else console.log(T.green("Everything up to date."))}async applyPush(e,t,s,r,a){const i=new M;for(const n of s){const e=C.join(t,...n.split("/")),s=C.posix.dirname(n);i.addLocalFile(e,"."===s?"":s,C.posix.basename(n))}const c=C.join(L.tmpdir(),`dashnex-push-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`);try{i.writeZip(c),n(`Diff zip written to ${c} (${s.length} files, ${r.length} deletions)`),await this.ensureZipWithinLimit(c);const t=`${o()}/business/v1/cli/v2/push/apply`;n(`POST ${t}`);const l=await j.readFile(c),d=new FormData;d.append("file",new Blob([l],{type:"application/zip"}),"diff.zip"),d.append("baseCommit",a),d.append("deletions",JSON.stringify(r));const p=await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${e}`},body:d});if(n(`Apply response: ${p.status}`),409===p.status)return{status:"conflict"};if(!p.ok){const e=await p.json().catch(()=>({}));this.failFromResponse(p.status,e,"Failed to push application.")}return{status:"ok",commit:(await p.json().catch(()=>({}))).commit??""}}finally{await j.remove(c).catch(()=>{})}}async pushLegacy(e,t,s){const r=await Ye(t),a=Object.keys(r);n(`Legacy push including ${a.length} files`);const i=new M;for(const n of a){const e=C.join(t,...n.split("/")),s=C.posix.dirname(n);i.addLocalFile(e,"."===s?"":s,C.posix.basename(n))}const c=C.join(L.tmpdir(),`dashnex-push-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`);try{s.start("Packaging application..."),i.writeZip(c),n(`Zip written to ${c}`),await this.ensureZipWithinLimit(c);const t=`${o()}/business/v1/cli/push`;n(`POST ${t}`);const r=await j.readFile(c),a=new FormData;a.append("file",new Blob([r],{type:"application/zip"}),"archive.zip"),s.text("Uploading...");const l=await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${e}`},body:a});if(s.stop(),n(`Response: ${l.status}`),!l.ok){const e=await l.json().catch(()=>({}));this.failFromResponse(l.status,e,"Failed to push application.")}console.log(T.green("Application pushed successfully."))}finally{await j.remove(c).catch(()=>{}),n("Temp zip removed")}}async ensureZipWithinLimit(e){const{size:s}=await j.stat(e);if(s>yt){const e=e=>(e/1048576).toFixed(1);console.error(T.red(`Push package is ${e(s)} MB — the maximum is ${e(yt)} MB.`)),console.error(T.red("Remove or .gitignore large files and try again.")),t(1)}}failFromResponse(e,s,n){let o=n;if(401===e||403===e)o="Please run 'dashnex login' to authenticate.";else{const e=(e=>"string"==typeof e.error?e.error:"string"==typeof e.message?e.message:void 0)(s);e&&(o=e)}console.error(T.red(o)),t(1)}async promptAndDeploy(e){let t;void 0!==e.deploy?(t=e.deploy,n(`Deploy decided via flag: ${t}`)):"1"===process.env.DASHNEX_CI?(t=!1,n("CI mode: skipping deploy prompt (use --deploy to deploy)")):(t=(await R.prompt([{type:"confirm",name:"deploy",message:"Deploy the application?",default:!0}])).deploy,n(`User chose to deploy: ${t}`)),t&&(n("Running deploy flow"),await(new wt).execute())}},options:[{flags:"--deploy",description:"Auto-deploy after push (skip prompt)"},{flags:"--no-deploy",description:"Skip deploy prompt"},{flags:"--no-check",description:"Skip pre-push checks"},{flags:"-f, --force",description:"Push even when the remote has changes you don't have locally"},{flags:"-y, --yes",description:"Skip prompts (CI mode)"}]},{name:"delete",description:"Delete application from DashNex",handler:new class{async execute(a={}){if(n("Delete flow started"),!a.yes){const{confirm:e}=await R.prompt([{type:"confirm",name:"confirm",message:"Are you sure you want to delete this application? This action cannot be undone.",default:!1}]);if(!e)return n("User cancelled deletion"),void console.log(T.yellow("Deletion cancelled."))}const i=await r();if(!i)return;await b(i,{checkApplication:!0});const c=`${o()}/business/v1/applications`;n(`DELETE ${c}`);const l=ae();l.start("Deleting application...");try{const e=await fetch(c,{method:"DELETE",headers:{Authorization:`Bearer ${i.token}`}});if(l.stop(),n(`Response: ${e.status}`),!e.ok){const s=e.headers.get("content-type")??"";let n="Failed to delete application.";if(401===e.status||403===e.status)n="Please run 'dashnex login' to authenticate.";else if(s.includes("application/json")){const t=(e=>"string"==typeof e.error?e.error:"string"==typeof e.message?e.message:void 0)(await e.json().catch(()=>({})));t&&(n=t)}console.error(T.red(n)),t(1)}const s=await e.json().catch(()=>({})),o="string"==typeof s.message?s.message:"Application deleted successfully.";n("Application deleted successfully"),console.log(T.green(o))}catch(d){if(l.stop(),e(d),d instanceof Error&&d.message.startsWith("EXIT:"))throw d;if(h(d))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}}},options:[{flags:"-y, --yes",description:"Skip confirmation prompt"}]},{name:"deploy",description:"Deploy application to DashNex",handler:new wt,options:[]},{name:"status",description:"Show current application status",handler:new class{async execute(){n("Status flow started");const a=await r();if(!a)return;const i=`${o()}/business/v1/applications`;n(`GET ${i}`);const c=ae();c.start("Fetching application status...");try{const e=await fetch(i,{headers:{Authorization:`Bearer ${a.token}`}});if(c.stop(),n(`Response: ${e.status}`),404===e.status)return void console.log(T.yellow("Application not created"));if(!e.ok){const s=e.headers.get("content-type")??"";let n="Failed to fetch application status.";if(401===e.status||403===e.status)n="Please run 'dashnex login' to authenticate.";else if(s.includes("application/json")){const t=await e.json().catch(()=>({}));"string"==typeof t.error?n=t.error:"string"==typeof t.message&&(n=t.message)}console.error(T.red(n)),t(1)}const s=await e.json().catch(()=>({}));n(`Application body: ${JSON.stringify(s)}`);const o=a.userName?`${a.userName} (${a.businessId})`:a.businessId;console.log(`Business: ${T.cyan(o||"unknown")}`);const r=void 0!==s.id?String(s.id):void 0;r&&console.log(`Application ID: ${T.cyan(r)}`);const l=s.status??"unknown";console.log(`Status: ${T.cyan(l)}`);const d=ut(s);d&&console.log(`URL: ${T.cyan(d)}`),console.log(`Session: ${T.cyan(a.settingsPath??"DASHNEX_TOKEN environment variable")}`),await f(a,r)}catch(l){if(c.stop(),e(l),l instanceof Error&&l.message.startsWith("EXIT:"))throw l;if(h(l))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}}},options:[]},{name:"clear",description:"Delete local dependencies, caches and build output",handler:new class{async execute(t={}){n("Clear flow started");const o=process.cwd();j.pathExistsSync(C.join(o,"dashnex.json"))||(console.error(T.red("Run this command in a DashNex Web application folder.")),process.exit(1));const r=t.yes||"1"===process.env.DASHNEX_CI;n(`Non-interactive: ${r}`),r||process.stdin.isTTY||(console.error(T.red("'dashnex app clear' needs confirmation. Run it in a terminal, or pass -y to clear without prompts (the local database is kept).")),process.exit(1));try{const t=await this.collectTargets(o),s=C.join(o,...Ot.segments),i=await j.pathExists(s)&&!r;if(0===t.length&&!i)return void console.log(T.yellow("Nothing to clear."));const c=[];let l=0;if(t.length>0){console.log("The following will be deleted:");for(const e of t)console.log(` ${T.cyan(e.label)} ${T.dim(`(${e.description})`)}`);if(!r){const{confirm:e}=await R.prompt([{type:"confirm",name:"confirm",message:"Delete these files?",default:!1}]);if(!e)return n("User cancelled clear"),void console.log(T.yellow("Cancelled."))}const s=[],i=ae();for(const r of t){const t=C.join(o,...r.segments);n(`Removing ${t}`),i.start(`Removing ${r.label}...`);try{await Bt(t),s.push(r.label)}catch(a){e(a),c.push(r.label)}}i.stop();for(const e of s)console.log(T.green(`Removed ${e}`));l+=s.length}if(i){const{confirm:t}=await R.prompt([{type:"confirm",name:"confirm",message:"Delete the local database as well?",default:!1}]);if(t){n(`Removing ${s}`);try{await Bt(s),console.log(T.green(`Removed ${Ot.label}`)),l++}catch(a){e(a),c.push(Ot.label)}}}if(c.length>0){for(const e of c)console.error(T.red(`Could not delete ${e} — it may be in use. Stop 'dashnex dev' and try again.`));process.exit(1)}if(0===l)return void console.log(T.yellow("Nothing was deleted."));console.log(T.green("Clear complete. Run 'dashnex install' to set the application up again."))}catch(a){if(a instanceof Error&&a.message.startsWith("EXIT:"))throw a;if(e(a),h(a))return void console.log(T.yellow(s));throw a}}async collectTargets(e){const t=[];for(const s of Rt)await j.pathExists(C.join(e,...s.segments))&&t.push(s);return t.filter(s=>!t.some(t=>t!==s&&((e,t)=>{const s=C.relative(t,e);return""!==s&&!s.startsWith("..")&&!C.isAbsolute(s)})(C.join(e,...s.segments),C.join(e,...t.segments))))}},options:[{flags:"-y, --yes",description:"Skip prompts and keep the local database (CI mode)"}]},{name:"tail",description:"Stream live console/debug logs from the deployed worker (Ctrl+C to stop)",handler:new class{async execute(t={}){const s=t.level??"log";Dt(s)||(console.error(T.red(`Invalid --level '${t.level}'. Use one of: log, info, warn, error, debug.`)),process.exit(1));let a=null;if(t.filter)try{a=new RegExp(t.filter)}catch{console.error(T.red(`Invalid --filter regular expression: ${t.filter}`)),process.exit(1)}const i=await r();if(!i)return;await b(i);const c=!!t.debug;let l;try{l=await async function(e,t={}){const s=`${o()}${kt}`;n(`POST ${s}${t.debug?" (debug)":""}`);const r=await fetch(s,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({debug:!!t.debug})});if(n(`Response: ${r.status}`),401===r.status||403===r.status)throw new $t("Not authorised to tail this application.");if(404===r.status)throw new vt("Business has no application, or the tail endpoint is unavailable.");if(!r.ok)throw new bt(`Failed to start tail (status ${r.status}).`);const a=await r.json().catch(()=>({}));if(!a.token||!a.workerUrl)throw new bt("Malformed tail-token response from the platform.");return{token:a.token,workerUrl:a.workerUrl,expiresAt:"number"==typeof a.expiresAt?a.expiresAt:0}}(i.token,{debug:c})}catch(v){e(v),v instanceof $t&&(console.error(T.red(v.message)),process.exit(1)),v instanceof vt&&(console.error(T.red(v.message)),process.exit(2)),console.error(T.red(v instanceof Error?v.message:"Could not start tail. Check your connection.")),process.exit(3)}const d={color:!!process.stdout.isTTY},p={minLevel:s,filter:a},u=(h=l.workerUrl,`${/^(localhost|127\.0\.0\.1)/.test(h)?"ws":"wss"}://${h}/api/realtime/tail`);var h;const m=`dashnex.tail.token.${l.token}`;console.log(T.dim(`Tailing ${l.workerUrl} — Ctrl+C to stop.`)),c&&console.log(T.dim("Debug logging enabled on the worker (DEBUG=1); may take a minute to take effect."));let g=!1,f=Date.now();const w=setInterval(()=>{g||Date.now()-f>=3e4&&(console.log(T.dim("(no logs in 30s)")),f=Date.now())},5e3),y=new H(u,m,{WebSocket:J,connectionTimeout:1e4,maxReconnectionDelay:16e3}),$=async e=>{if(!g){g=!0,clearInterval(w);try{y.close()}catch{}await Promise.race([xt(i.token,{debug:c}),new Promise(e=>setTimeout(e,3e3))]),process.exit(e)}};process.on("SIGINT",()=>{console.log(),$(0)}),y.binaryType="arraybuffer",y.addEventListener("message",e=>{f=Date.now();const t=At("string"==typeof e.data?e.data:"",p,d);"line"===t.kind?console.log(t.line):"malformed"===t.kind&&console.error(T.dim(`[malformed frame] ${t.raw}`))}),y.addEventListener("close",e=>{if(!g)return Ft.has(e.code)?(console.error(T.red("Tail token was rejected (auth). Exiting.")),void $(1)):void n(`socket closed (code ${e.code}); reconnecting…`)}),y.addEventListener("error",e=>{n(`socket error: ${e.message??"unknown"}`)}),await new Promise(()=>{})}},options:[{flags:"--level <level>",description:"Minimum level: log, info, warn, error, debug"},{flags:"--filter <regex>",description:"Only show records whose args match this regex"},{flags:"--debug",description:"Also enable DEBUG=1 on the worker so debug-gated logs are shown"}]}]},zt={name:"secrets",description:"Manage DashNex secrets",handler:{execute:async()=>{}},options:[],subcommands:[{name:"list",description:"List all secret names bound to the worker",handler:new class{async execute(){n("Secrets list flow started");const a=await r();if(!a)return;await b(a);const i=`${o()}/business/v1/cli/secrets`;n(`GET ${i}`);const c=ae();c.start("Fetching secrets...");try{const e=await fetch(i,{headers:{Authorization:`Bearer ${a.token}`}});if(c.stop(),n(`Response: ${e.status}`),!e.ok){let s="Failed to fetch secrets.";401===e.status||403===e.status?s="Please run 'dashnex login' to authenticate.":404===e.status?s="Business has no application.":502===e.status&&(s="Error fetching secrets. Make sure your application is deployed."),console.error(T.red(s)),t(1)}const s=(await e.json()).secrets??[];if(0===s.length)return void console.log(T.yellow("No secrets set."));console.log(T.bold(`\nSecrets (${s.length}):\n`)),s.forEach(({name:e})=>{console.log(` ${T.cyan(e)}`)}),console.log("")}catch(l){if(c.stop(),e(l),l instanceof Error&&l.message.startsWith("EXIT:"))throw l;if(h(l))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}}},options:[]},{name:"set",description:"Create or update a secret (prompts for missing KEY/VALUE)",handler:new class{async execute(a={}){n("Secrets set flow started");let{name:i,value:c}=a;const l="1"===process.env.DASHNEX_CI;i||(l&&(console.error(T.red("--name is required in CI mode (--ci).")),t(1)),i=(await R.prompt([{type:"input",name:"name",message:"Secret name:",validate:e=>""!==e.trim()||"Name is required"}])).name.trim()),c||(l&&(console.error(T.red("--value is required in CI mode (--ci).")),t(1)),c=(await R.prompt([{type:"password",name:"value",message:`Enter value for ${i}:`,mask:"*",validate:e=>""!==e||"Value is required"}])).value);const d=await r();if(!d)return;await b(d);const p=`${o()}/business/v1/cli/secrets`;n(`PUT ${p}`);const u=ae();u.start(`Setting secret '${i}'...`);try{const e=await fetch(p,{method:"PUT",headers:{Authorization:`Bearer ${d.token}`,"Content-Type":"application/json"},body:JSON.stringify({name:i,value:c})});if(u.stop(),n(`Response: ${e.status}`),!e.ok){let s="Failed to set secret.";401===e.status||403===e.status?s="Please run 'dashnex login' to authenticate.":400===e.status?s="Missing or empty name / value.":404===e.status?s="Business has no application.":502===e.status&&(s="Error setting secret. Make sure your application is deployed."),console.error(T.red(s)),t(1)}n(`Secret '${i}' set successfully`),console.log(T.green(`Secret '${i}' set successfully.`))}catch(m){if(u.stop(),e(m),m instanceof Error&&m.message.startsWith("EXIT:"))throw m;if(h(m))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}}},arguments:[{name:"name",description:"Secret name (KEY)"},{name:"value",description:"Secret value"}],options:[{flags:"--name <name>",description:"Secret name (alternative to positional KEY)"},{flags:"--value <value>",description:"Secret value (alternative to positional VALUE)"}]},{name:"delete",description:"Delete a secret (prompts for missing KEY)",handler:new class{async execute(a={}){n("Secrets delete flow started");let{name:i}=a;const c="1"===process.env.DASHNEX_CI;i||(c&&(console.error(T.red("--name is required in CI mode (--ci).")),t(1)),i=(await R.prompt([{type:"input",name:"name",message:"Secret name:",validate:e=>""!==e.trim()||"Name is required"}])).name.trim());const l=await r();if(!l)return;await b(l);const d=`${o()}/business/v1/cli/secrets/${encodeURIComponent(i)}`;n(`DELETE ${d}`);const p=ae();p.start(`Deleting secret '${i}'...`);try{const e=await fetch(d,{method:"DELETE",headers:{Authorization:`Bearer ${l.token}`}});if(p.stop(),n(`Response: ${e.status}`),!e.ok){let s="Failed to delete secret.";401===e.status||403===e.status?s="Please run 'dashnex login' to authenticate.":404===e.status?s=`Secret '${i}' not found.`:502===e.status&&(s="Error deleting secret. Make sure your application is deployed."),console.error(T.red(s)),t(1)}n(`Secret '${i}' deleted successfully`),console.log(T.green(`Secret '${i}' deleted.`))}catch(u){if(p.stop(),e(u),u instanceof Error&&u.message.startsWith("EXIT:"))throw u;if(h(u))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}}},arguments:[{name:"name",description:"Secret name (KEY)"}],options:[{flags:"--name <name>",description:"Secret name (alternative to positional KEY)"}]}]},Wt=async(e,t)=>{const s=C.join(e,".npmrc");return!!(await j.pathExists(s))&&(await j.copy(s,C.join(t,".npmrc"),{overwrite:!0}),await(async e=>{const t=C.join(e,".gitignore"),s=await j.pathExists(t)?await j.readFile(t,"utf8"):"";if(s.split(/\r?\n/).some(e=>".npmrc"===e.trim()))return;const n=""===s||s.endsWith("\n")?s:`${s}\n`;await j.writeFile(t,`${n}.npmrc\n`)})(t),!0)},Lt=["node_modules","dist",".git","coverage"],Mt=e=>e.replace(/\\/g,"/").split("/").filter(e=>""!==e),Ut=async(e,t)=>{const s=await(async e=>{const t=/* @__PURE__ */new Set;return await Y.t({file:e,onReadEntry:e=>{const[s]=Mt(e.path);t.add(s??"")}}),1===t.size?1:0})(e);await j.ensureDir(t),await Y.x({file:e,cwd:t,strip:s,filter:e=>{const t=Mt(e),n=s>0?t.slice(s):t.filter(e=>"."!==e);return!(void 0!==n[0]&&Lt.includes(n[0]))}})},Xt=async(e,t,s)=>{const n=C.join(e,"package.json"),o=await j.readJson(n);o.name=`@dashnex/${t}`,o.description=s,o.version="0.1.0",await j.writeJson(n,o,{spaces:2})},Ht=async(e,t)=>{const s=C.join(e,"package.json"),n=await j.readJson(s),o={...n.dependencies??{},[`@dashnex/${t}`]:`link:packages/${t}`};n.dependencies=Object.fromEntries(Object.entries(o).sort(([e],[t])=>e<t?-1:e>t?1:0)),await j.writeJson(s,n,{spaces:2})},Jt="Run this command in a DashNex Web application folder.",Gt="Could not reach DashNex API. Check your connection and try again.",qt=class extends Error{},Yt=e=>(console.error(T.red(e)),process.exit(1)),Vt=e=>e instanceof Error&&e.message.startsWith("EXIT:"),Kt=async t=>{try{return await t()}catch(s){if(Vt(s)||h(s))throw s;throw e(s),new qt(s instanceof Error?s.message:String(s))}},Zt="@dashnex/",Qt=e=>{const t=e.trim();return(t.startsWith(Zt)?t.slice(9):t).toLowerCase()},es=e=>`${Zt}${e}`,ts=()=>x().replace(/^https?:\/\//,""),ss=()=>`Join the Developers program first: ${ts()} → Developers.`,ns=e=>`${es(e)} isn't reserved by this business.`,os=e=>`Bump the version (run pnpm version patch in packages/${e}) and submit again.`,rs=(e,t)=>`${es(e)}${t?` v${t}`:""} is already in review. Wait for the decision before submitting again.`,as=e=>(console.error(T.red(e)),process.exit(1)),is=e=>({Authorization:`Bearer ${e}`}),cs=(e,t)=>as(401===e||403===e?"Please run 'dashnex login' to authenticate.":t),ls=async e=>{const t=`${o()}/business/v1/cli/developers/access`;n(`GET ${t}`);const s=await fetch(t,{headers:is(e)});if(n(`Developer access response: ${s.status}`),!s.ok)return{ok:!1,status:s.status};const r=await s.json().catch(()=>null);if("object"!=typeof r||null===r||Array.isArray(r))return{ok:!1,status:s.status};const a=r;return{ok:!0,access:{agreementAccepted:!0===a.agreementAccepted,reservations:Array.isArray(a.reservations)?a.reservations:[]}}},ds=e=>e.ok?e.access:cs(e.status,"Failed to check developer access."),ps=(e,t)=>{if(!e.agreementAccepted)return as(ss());if(0===e.reservations.length)return as(`Reserve a module name first: ${ts()} → Developers.`);if(void 0===t)return;const s=Qt(t),n=e.reservations.find(e=>Qt(e.address)===s);return n||as(ns(s))},us=async(e,t)=>{const s=await(async e=>ds(await ls(e)))(e),n=ps(s,t);return n?{access:s,reservation:n}:{access:s}},hs=["version_not_higher","not_reserved","in_review","agreement_required"],ms=e=>[`Created packages/${e} (${es(e)}).`,"Next steps:",` 1. cd packages/${e} && pnpm install && pnpm build`," 2. dashnex install && dashnex dev (links the module into your app)",` 3. dashnex module submit ${e} (when it is ready for review)`].join("\n"),gs=/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/,fs=(e,t)=>{const s=gs.exec(e),n=gs.exec(t);if(!s||!n)return null;for(let a=1;a<=3;a++){const e=Number(s[a])-Number(n[a]);if(0!==e)return e}const o=s[4],r=n[4];return o||r?o?r?((e,t)=>{const s=e.split("."),n=t.split("."),o=Math.min(s.length,n.length);for(let r=0;r<o;r++){const e=s[r],t=n[r],o=/^\d+$/.test(e),a=/^\d+$/.test(t);if(o&&a){const s=Number(e)-Number(t);if(0!==s)return s;continue}if(o!==a)return o?-1:1;if(e!==t)return e<t?-1:1}return s.length-n.length})(o,r):-1:1:0},ws=t=>new Promise(s=>{const n=t();n.on("error",t=>{e(t),s(1)}),n.on("close",e=>{s(e??1)})}),ys=(e,t,s)=>{switch(e.status){case"accepted":return void console.log(T.green(((e,t)=>`Submitted ${es(e)} v${t} for review. Status: In review.`)(t,s)));case"version_not_higher":return Yt(os(t));case"not_reserved":return Yt(ns(t));case"in_review":return Yt(rs(t));case"agreement_required":return Yt(ss());case"error":return Yt(e.message)}},$s={name:"module",description:"Develop marketplace modules (run in DashNex application folder)",handler:{execute:async()=>{}},options:[],subcommands:[{name:"init",description:"Create reserved modules in packages/ from the module template",handler:new class{async execute(t={}){n("Module init flow started");const a=!0===t.ci||"1"===process.env.DASHNEX_CI,i=process.cwd();if(!(await j.pathExists(C.join(i,"dashnex.json"))))return Yt(Jt);const c=C.join(L.tmpdir(),`dashnex-template-${z(6).toString("hex")}.tgz`),l=ae(),d=()=>j.remove(c).catch(()=>{});try{const s=await Kt(()=>r());if(!s)return;const{access:m,reservation:g}=await Kt(()=>us(s.token,t.address)),f=C.join(i,"packages");let w;if(await j.ensureDir(f),g){if(await j.pathExists(C.join(f,g.address)))return Yt(`packages/${g.address} already exists.`);w=[g]}else{const e=[];for(const t of m.reservations)await j.pathExists(C.join(f,t.address))||e.push(t);if(0===e.length)return void console.log(T.yellow("All reserved modules already exist in packages/."));if(w=await(async(e,t,s)=>{if(t.all||1===e.length)return e;if(s)return Yt("Pass an address or --all in CI mode.");const{addresses:n}=await R.prompt([{type:"checkbox",name:"addresses",message:"Which modules do you want to create?",choices:e.map(e=>({name:`${e.moduleName} (${es(e.address)})`,value:e.address,checked:!0}))}]);return e.filter(e=>n.includes(e.address))})(e,t,a),0===w.length)return void console.log(T.yellow("No modules selected."))}l.start("Downloading the module template...");const y=await Kt(()=>(async e=>{const t=`${o()}/business/v1/cli/developers/template`;n(`GET ${t}`);const s=await fetch(t,{headers:is(e)});return n(`Module template response: ${s.status}`),s.ok?{ok:!0,bytes:Buffer.from(await s.arrayBuffer())}:{ok:!1,status:s.status}})(s.token));l.stop(),await j.writeFile(c,(p=y,p.ok?p.bytes:cs(p.status,"Failed to download the module template.")));let $=!1;for(const{address:t,moduleName:n}of w){const s=C.join(f,t);try{await j.ensureDir(s),await Ut(c,s),await Xt(s,t,n),await Wt(i,s)||($=!0),await Ht(i,t)}catch(u){if(await d(),await j.remove(s).catch(()=>{}),Vt(u)||h(u))throw u;return e(u),Yt(`Failed to create packages/${t}: ${u instanceof Error?u.message:String(u)}`)}console.log(T.green(ms(t)))}$&&console.log(T.yellow("No DashNex registry token found (.npmrc). Deploy your app once, then run 'dashnex app pull' before building or submitting."))}catch(u){if(l.stop(),await d(),e(u),Vt(u))throw u;if(h(u))return void console.log(T.yellow(s));u instanceof qt?console.error(T.red(Gt)):console.error(T.red(`Module init failed: ${u instanceof Error?u.message:String(u)}`)),process.exit(1)}finally{await d(),n("Temp template tarball removed")}var p}},arguments:[{name:"address",description:"Module address to create (prompts when several are missing)"}],options:[{flags:"--all",description:"Create every reserved module that is not in packages/ yet"}]},{name:"submit",description:"Build, publish and submit a module for review",handler:new class{async execute(t){n("Module submit flow started");const a=process.cwd(),i=Qt(t.address),c=es(i);if(!(await j.pathExists(C.join(a,"dashnex.json"))))return Yt(Jt);const l=ae();try{const t=await Kt(()=>r());if(!t)return;l.start("Checking developer access...");const s=await Kt(()=>ls(t.token));l.stop();const d=ps(ds(s),i),p=(e=>{const t=e?.inReview;if("object"!=typeof t||null===t)return;const{version:s}=t;return"string"==typeof s&&""!==s?s:void 0})(d);if(p)return Yt(rs(i,p));const u=C.join(a,"packages",i);if(!(await j.pathExists(C.join(u,"package.json"))))return Yt(`packages/${i} not found. Run 'dashnex module init ${i}' first.`);const h=await(async(e,t)=>{const s=C.join(e,"package.json"),n=await j.readJson(s).catch(()=>null);return n?.name!==es(t)?Yt(`packages/${t}/package.json must be named ${es(t)}.`):"string"!=typeof n.version||""===n.version?Yt(`packages/${t}/package.json must have a version.`):n.version})(u,i),m=d?.lastSubmittedVersion;if(m&&!((e,t)=>{const s=fs(e,t);return null!==s&&s>0})(h,m))return Yt(os(i));const g=await(async e=>{const t=C.join(e,".npmrc");if(!(await j.pathExists(t)))return null;const s=(e=>{const t={};for(const s of e.split(/\r?\n/)){const e=s.trim();if(!e||e.startsWith("#"))continue;const n=e.indexOf("=");-1!==n&&(t[e.slice(0,n).trim()]=e.slice(n+1).trim())}return t})(await j.readFile(t,"utf8")),n=s["@dashnex:registry"]||"https://npm.dashnex.com/";let o;try{o=new URL(n).host}catch{return null}const r=s[`//${o}/:_authToken`];return r?{registry:n,host:o,token:r}:null})(a);if(!g)return Yt("No DashNex registry token found (.npmrc). Deploy your app once, then run 'dashnex app pull' and submit again.");await Wt(a,u),l.start(`Installing dependencies for ${c}...`);const f=await Ee(u);if(l.stop(),0!==f)return Yt("Build failed.");if(console.log(`Building ${c}...`),0!==await(async(e,t)=>{const s=`${await Se("pnpm")?"pnpm":"npm"} run ${t}`;return n(`Executing: ${s} in ${e}`),ws(()=>I(s,{cwd:e,shell:!0,stdio:"inherit"}))})(u,"build"))return Yt("Build failed.");if(await(async(t,s,o,r)=>{const a=`${t.replace(/\/+$/,"")}/@dashnex%2F${o}`;n(`GET ${a}`);try{const e=await fetch(a,{headers:{Authorization:`Bearer ${s}`}});if(n(`Registry responded ${e.status}`),200!==e.status)return!1;const t=await e.json();return Boolean(t.versions?.[r])}catch(i){return e(i),!1}})(g.registry,g.token,i,h))console.log(T.yellow(`v${h} is already in the registry, skipping publish.`));else if(0!==await(async(e,t)=>{const s=["publish","--registry",t,"--tag","review"],o={cwd:e,stdio:"inherit",shell:"win32"===process.platform};return n(`Executing: npm ${s.join(" ")} in ${e}`),ws(()=>I("npm",s,o))})(u,g.registry))return Yt(`Publishing ${c} v${h} failed.`);l.start("Submitting for review...");const w=await Kt(()=>(async(e,t,s)=>{const r=`${o()}/business/v1/cli/developers/submissions`;n(`POST ${r}`);const a=await fetch(r,{method:"POST",headers:{...is(e),"Content-Type":"application/json"},body:JSON.stringify({address:t,version:s})});n(`Module submission response: ${a.status}`);const i=await a.json().catch(()=>({}));if(202===a.status)return{status:"accepted",submissionId:String(i.submissionId??"")};if(409===a.status)return{status:"agreement_required"};if(400===a.status){const e=hs.find(e=>e===i.code);if(e)return{status:e}}const c=i.message??i.error??"Failed to submit the module.";return{status:"error",message:String(c)}})(t.token,i,h));l.stop(),ys(w,i,h)}catch(d){if(l.stop(),e(d),Vt(d))throw d;if(h(d))return void console.log(T.yellow(s));d instanceof qt?console.error(T.red(Gt)):console.error(T.red(`Module submit failed: ${d instanceof Error?d.message:String(d)}`)),process.exit(1)}}},arguments:[{name:"address",description:"Module address (the part after @dashnex/)",required:!0}],options:[]}]},vs=[{name:"version",description:"Display the version of the CLI",handler:new class{async execute(e){const t=C.dirname(N(import.meta.url)),s=JSON.parse(F.readFileSync(C.resolve(t,"..","..","package.json"),"utf8"));console.log(s.version)}},options:[]},{name:"login",description:"Log in to your DashNex account",handler:new class{async execute(o={}){n("Login flow started");const a=process.cwd(),c=await w(a)??g(a);n(`Settings file: ${c}`);const l=await $(a),d=o.business??l?.businessId,p=await r({exitOnFailure:!1,dashnexPath:c});if(p){if(!d||u(d,p.businessId))return console.log(T.green(`Already logged in as ${p.userName??"user"} (business ${p.businessId})`)),void console.log(T.dim(`Session: ${p.settingsPath??"DASHNEX_TOKEN environment variable"}`));!o.business&&l?.businessId&&console.log(T.yellow(`Signed in to business ${p.businessId}, but this folder belongs to business ${l.businessId}.`))}!o.business&&l?.businessId&&console.log(T.dim(`Using business ${l.businessId} from ${l.envPath} (--business picks another).`)),E()||(console.error(T.red("No DashNex ID OAuth client is configured for this environment.")),console.error(T.yellow("Set DASHNEX_OAUTH_CLIENT_ID, or use APPLICATION_ENV=qa.")),t(1));try{const e=await j.pathExists(c)?await i(c):null;if(e){n("Resuming with saved DashNex ID session");try{return void(await this.selectBusiness(e,c,d))}catch(f){if(!(f instanceof te))throw f;n("Saved DashNex ID session rejected, starting a new sign-in")}}const t=await this.authorize(!1!==o.browser);await de(c,{id:{token:t.token,expiresAt:m(t.expiresIn),...t.refreshToken&&{refreshToken:t.refreshToken}}}),await this.selectBusiness(t.token,c,d)}catch(f){if(e(f),h(f))return void console.log(T.yellow(s));if((f instanceof Q||f instanceof ee)&&(console.error(T.red(f.message)),t(1)),f instanceof Error&&f.message.startsWith("EXIT:"))throw f;console.error(T.red("Could not reach DashNex. Check your connection and try again.")),t(1)}}async authorize(e){const{verifier:t,challenge:s}=(()=>{const e=V(z(32));return{verifier:e,challenge:V(B("sha256").update(e).digest())}})(),o=(e=>{const t=new URL(`${D()}/api/oauth/authorize`);return t.searchParams.set("client_id",E()),t.searchParams.set("redirect_uri",Z),t.searchParams.set("response_type","code"),t.searchParams.set("code_challenge",e),t.searchParams.set("code_challenge_method","S256"),t.toString()})(s);n(`Authorize URL: ${o}`);const r=!!e&&await K(o);console.log(""),r?(console.log(T.green("Opened your browser to finish signing in.")),console.log(T.dim("If nothing opened, use this URL:"))):console.log(T.bold("Open this URL to sign in:")),console.log(T.cyan(o)),console.log(""),console.log(T.dim("Approve the request, then copy the code shown and paste it below.")),console.log("");const{code:a}=await R.prompt([{type:"input",name:"code",message:"Paste the code:",validate:e=>!!e.trim()||"Code is required"}]),i=ae();i.start("Verifying code...");try{return await(async(e,t)=>{const s=`${D()}/api/oauth/token`;n(`POST ${s}`);const{response:o,body:r}=await p(s,{method:"POST",body:JSON.stringify({grant_type:"authorization_code",client_id:E(),redirect_uri:Z,code:e.trim(),code_verifier:t})});if(n(`Response: ${o.status}`),S("Token response",r),!o.ok){const e="string"==typeof r.error?r.error:void 0,t="string"==typeof r.error_description?r.error_description:void 0;if("invalid_grant"===e)throw new Q("That code is not valid anymore. Codes expire 10 minutes after they are issued and can only be used once.",e);throw new Q(t??"Could not exchange the code for a token.",e)}const a=r;if(!a.access_token)throw new Q("The authorization server did not return an access token.");return{token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in}})(a,t)}finally{i.stop()}}async selectBusiness(e,t,s){const n=await ie(e,t,{businessId:s});console.log(T.green(`Logged in as ${n.name} (business ${n.id})`)),console.log(T.dim(`Session: ${t}`))}},options:[{flags:"--business <id>",description:"Business ID to select (skip prompt)"},{flags:"--no-browser",description:"Print the sign-in URL instead of opening a browser"}]},{name:"logout",description:"Log out and remove local credentials",handler:new class{async execute(){const e=await w(process.cwd());e?(await l(e,e=>{delete e.business,delete e.id}),console.log(T.green("Logged out.")),console.log(T.dim(`Session removed from ${e}`))):console.log(T.green("Logged out."))}},options:[]},{name:"switch",description:"Switch to another business (prompts when no ID is given)",handler:new class{async execute(o={}){n("Switch flow started");const r=await w(process.cwd()),a=r?await c(r):null;r&&a?.business?.token||(console.error(T.red("Please run 'dashnex login' to authenticate.")),t(1));const l=a.id;l?.token||(console.error(T.red("Switching businesses requires signing in with DashNex ID.")),console.error(T.yellow("Run 'dashnex logout' and then 'dashnex login' to sign in again.")),t(1));const d=e=>ie(e,r,{businessId:o.businessId,currentBusinessId:a.business?.id});try{const e=await i(r)??l.token;let t;try{t=await d(e)}catch(p){if(!(p instanceof te))throw p;n("DashNex ID session rejected, refreshing");const e=await k(r);if(!e)throw p;t=await d(e)}console.log(T.green(`Switched to ${t.name} (business ${t.id})`)),console.log(T.dim(`Session: ${r}`));const s=await $();s?.businessId&&!u(s.businessId,t.id)&&console.warn(T.yellow(`Warning: this folder belongs to business ${s.businessId} (BUSINESS_ID in ${s.envPath}).`))}catch(p){if(e(p),h(p))return void console.log(T.yellow(s));if(p instanceof te&&(console.error(T.red(p.message)),console.error(T.yellow("Run 'dashnex logout' and then 'dashnex login' to sign in again.")),t(1)),p instanceof ee&&(console.error(T.red(p.message)),t(1)),p instanceof Error&&p.message.startsWith("EXIT:"))throw p;console.error(T.red("Could not reach DashNex. Check your connection and try again.")),t(1)}}},arguments:[{name:"businessId",description:"Business ID to switch to (skip prompt)"}],options:[]},{name:"whoami",description:"Show current logged-in business and user",handler:new class{async execute(){n("Whoami flow started");const a=await r();if(!a)return;const i=await v()?`${o()}/business/v1/`:`${d()}/users/v1/`;n(`GET ${i}`);const c=ae();c.start("Fetching user info...");try{const e=await fetch(i,{headers:{Authorization:`Bearer ${a.token}`}});if(c.stop(),n(`Response: ${e.status}`),!e.ok){const s=e.headers.get("content-type")??"";let n="Failed to fetch user info.";if(401===e.status||403===e.status)n="Please run 'dashnex login' to authenticate.";else if(s.includes("application/json")){const t="string"==typeof(l=await e.json().catch(()=>({}))).error?l.error:"string"==typeof l.message?l.message:void 0;t&&(n=t)}console.error(T.red(n)),t(1)}const s=await e.json().catch(()=>({})),o="string"==typeof s.name?s.name:"string"==typeof s.companyName?s.companyName:"Unknown",r=s.memberUser,d=r&&"string"==typeof r.fullName?r.fullName:r&&"string"==typeof r.name?r.name:"Unknown";n(`Whoami: ${o} (${d})`),console.log(T.green(`You are logged in as ${o} (${d})`)),console.log(`Business ID: ${T.cyan(a.businessId||"unknown")}`),console.log(`Session: ${T.cyan(a.settingsPath??"DASHNEX_TOKEN environment variable")}`),await f(a)}catch(p){if(c.stop(),e(p),p instanceof Error&&p.message.startsWith("EXIT:"))throw p;if(h(p))return void console.log(T.yellow(s));console.error(T.red("Could not reach DashNex API. Check your connection and try again.")),t(1)}var l}},options:[]},{name:"dev",description:"Run the development server (run in DashNex application folder)",handler:new class{async execute(t={}){n("Dev flow started");try{const e=await xe(process.cwd(),t);process.exit(e)}catch(s){if(s instanceof Error&&s.message.startsWith("EXIT:"))throw s;e(s),s instanceof Error?console.error(T.red(s.message)):console.error(T.red("Failed to run npm run dev")),process.exit(1)}}},options:[{flags:"-p, --port <port>",description:"Port for the dev server (default: PORT env or 3000)"},{flags:"-H, --host <host>",description:"Host to bind, e.g. 0.0.0.0 inside Docker (default: HOST env or localhost)"},{flags:"--https",description:"Serve over HTTPS with a trusted local certificate (mkcert)"}]},{name:"check",description:"Run build-time validations (run in DashNex application folder)",handler:new Oe,options:[]},{name:"install",description:"Install dependencies using pnpm (preferred) or npm (run in DashNex application folder)",handler:new class{async execute(){n("Install flow started");const t=C.join(process.cwd(),"dashnex.json");await j.pathExists(t)||(console.error(T.red("Run this command in a DashNex Web application folder.")),process.exit(1));const s=process.cwd(),o=ae();o.start("Installing dependencies...");try{const e=await Ee(s);o.stop(),0!==e&&process.exit(e),console.log(T.green("Dependencies installed.")),process.exit(0)}catch(r){o.stop(),e(r),r instanceof Error?console.error(T.red(r.message)):console.error(T.red("Failed to run install")),process.exit(1)}}},options:[]},_t,zt,$s];export{vs as default};
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as s,t as e}from"./chunks/dashnex-
|
|
1
|
+
import{n as s,t as e}from"./chunks/dashnex-BKNuCWid.js";var n={name:s.name,version:s.version,description:s.description,...e};export{n as default};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CliCommand } from '@dashnex/types';
|
|
2
|
+
export interface ModuleInitOptions {
|
|
3
|
+
address?: string;
|
|
4
|
+
all?: boolean;
|
|
5
|
+
ci?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class ModuleInitCommand implements CliCommand {
|
|
8
|
+
execute(options?: ModuleInitOptions): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const APP_ROOT_REQUIRED = "Run this command in a DashNex Web application folder.";
|
|
2
|
+
export declare const CONNECTION_MESSAGE = "Could not reach DashNex API. Check your connection and try again.";
|
|
3
|
+
export declare class ApiUnreachableError extends Error {
|
|
4
|
+
}
|
|
5
|
+
export declare const fail: (message: string) => never;
|
|
6
|
+
export declare const isExitError: (error: unknown) => boolean;
|
|
7
|
+
/** Runs a network step; any failure other than a deliberate exit or user interrupt means the API was unreachable. */
|
|
8
|
+
export declare const viaApi: <T>(step: () => Promise<T>) => Promise<T>;
|
package/dist/src/lib/api.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
declare const getApiBase: () => string;
|
|
2
2
|
declare const getBusinessApiBase: () => string;
|
|
3
|
+
declare const getBusinessPortalBase: () => string;
|
|
3
4
|
declare const getDashnexIdBase: () => string;
|
|
4
5
|
declare const getOAuthClientId: () => string;
|
|
5
6
|
declare const getDeployerBase: () => string;
|
|
@@ -7,4 +8,4 @@ declare const apiFetch: (url: string, options?: RequestInit) => Promise<{
|
|
|
7
8
|
response: Response;
|
|
8
9
|
body: Record<string, unknown>;
|
|
9
10
|
}>;
|
|
10
|
-
export { getApiBase, getBusinessApiBase, getDashnexIdBase, getOAuthClientId, getDeployerBase, apiFetch, };
|
|
11
|
+
export { getApiBase, getBusinessApiBase, getBusinessPortalBase, getDashnexIdBase, getOAuthClientId, getDeployerBase, apiFetch, };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ends the running command with an exit code without calling process.exit().
|
|
3
|
+
*
|
|
4
|
+
* process.exit() tears the process down while libuv may still be closing
|
|
5
|
+
* handles (fetch keep-alive sockets, child-process pipes). On Windows that
|
|
6
|
+
* aborts Node with "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING),
|
|
7
|
+
* file src\win\async.c". Throwing instead unwinds to the command runner in
|
|
8
|
+
* cli.ts, which sets process.exitCode and lets the event loop drain.
|
|
9
|
+
*
|
|
10
|
+
* The message keeps the "EXIT:<code>" shape that catch blocks already rethrow.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ExitSignal extends Error {
|
|
13
|
+
readonly code: number;
|
|
14
|
+
constructor(code: number);
|
|
15
|
+
}
|
|
16
|
+
export declare function exit(code?: number): never;
|
|
17
|
+
/**
|
|
18
|
+
* Sets the exit code and lets Node exit once nothing is pending. A handle that
|
|
19
|
+
* never closes would otherwise keep the process alive, so an unref'd timer
|
|
20
|
+
* forces the exit after a short grace period.
|
|
21
|
+
*/
|
|
22
|
+
export declare const finishWithExitCode: (code: number) => void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a package.json script in the given directory, preferring pnpm over npm.
|
|
3
|
+
*
|
|
4
|
+
* @returns The script's exit code (a failure to spawn counts as 1)
|
|
5
|
+
*/
|
|
6
|
+
export declare const runScriptInDir: (cwd: string, script: string) => Promise<number>;
|
|
7
|
+
/**
|
|
8
|
+
* Install dependencies and build the module package.
|
|
9
|
+
*
|
|
10
|
+
* @returns 0 on success, otherwise the failing step's exit code
|
|
11
|
+
*/
|
|
12
|
+
export declare const buildModule: (moduleDir: string) => Promise<number>;
|
|
13
|
+
/**
|
|
14
|
+
* Publish the module package to the given registry. The auth token is not
|
|
15
|
+
* passed on the command line; npm picks it up from the project's .npmrc.
|
|
16
|
+
* Submissions go out under the `review` dist-tag: the registry moves `latest`
|
|
17
|
+
* only when a version is approved.
|
|
18
|
+
*
|
|
19
|
+
* @returns The npm exit code (a failure to spawn counts as 1)
|
|
20
|
+
*/
|
|
21
|
+
export declare const runNpmPublish: (moduleDir: string, registry: string) => Promise<number>;
|
|
22
|
+
/**
|
|
23
|
+
* Check whether the registry already has the given version of a module.
|
|
24
|
+
* Any failure (non-200 status, network error) reports false; publishing will
|
|
25
|
+
* surface the real problem.
|
|
26
|
+
*/
|
|
27
|
+
export declare const registryHasVersion: (registry: string, token: string, address: string, version: string) => Promise<boolean>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const TEMPLATE_EXCLUDED_DIRS: string[];
|
|
2
|
+
export declare const extractTemplateTarball: (tarballPath: string, targetDir: string) => Promise<void>;
|
|
3
|
+
export declare const personalizeModulePackage: (moduleDir: string, address: string, moduleName: string) => Promise<void>;
|
|
4
|
+
export declare const linkModuleIntoApp: (appDir: string, address: string) => Promise<void>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const DEFAULT_DASHNEX_REGISTRY = "https://npm.dashnex.com/";
|
|
2
|
+
export interface RegistryConfig {
|
|
3
|
+
registry: string;
|
|
4
|
+
host: string;
|
|
5
|
+
token: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const readRegistryConfig: (appDir: string) => Promise<RegistryConfig | null>;
|
|
8
|
+
export declare const syncModuleNpmrc: (appDir: string, moduleDir: string) => Promise<boolean>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ProjectIdentity {
|
|
2
|
+
/** Application root: the nearest folder holding dashnex.json. */
|
|
3
|
+
root: string;
|
|
4
|
+
/** File BUSINESS_ID came from (the application's .env when none names it). */
|
|
5
|
+
envPath: string;
|
|
6
|
+
/** File APPLICATION_ID came from (the application's .env when none names it). */
|
|
7
|
+
applicationEnvPath: string;
|
|
8
|
+
businessId?: string;
|
|
9
|
+
applicationId?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Read in this order; the first file that names an ID wins for that ID. */
|
|
12
|
+
export declare const PROJECT_ENV_FILES: string[];
|
|
13
|
+
/**
|
|
14
|
+
* The business and application a folder belongs to, read from
|
|
15
|
+
* .env.production, .env.development and the .env that DashNex generates for
|
|
16
|
+
* every application, whichever of them exist. Returns null outside an
|
|
17
|
+
* application, and leaves the IDs undefined when no file names them.
|
|
18
|
+
*/
|
|
19
|
+
export declare const readProjectIdentity: (startDir?: string) => Promise<ProjectIdentity | null>;
|
|
20
|
+
export declare const sameId: (a: string | number | undefined, b: string | number | undefined) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isHigherVersion: (candidate: string, baseline: string) => boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Token for one business, exchanged from the DashNex ID session. */
|
|
2
|
+
export interface BusinessSession {
|
|
3
|
+
token?: string;
|
|
4
|
+
refreshToken?: string;
|
|
5
|
+
id?: string | number;
|
|
6
|
+
}
|
|
7
|
+
/** DashNex ID session; lets `dashnex switch` change business without signing in again. */
|
|
8
|
+
export interface IdSession {
|
|
9
|
+
token?: string;
|
|
10
|
+
refreshToken?: string;
|
|
11
|
+
/** Epoch milliseconds. Missing on sessions saved by CLI 0.7.1 and older. */
|
|
12
|
+
expiresAt?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `.dashnex/settings.json`. The CLI owns `business` and `id`; everything else
|
|
16
|
+
* (server CLI settings such as `ai`, other tools' `code`, `genx`…) belongs to
|
|
17
|
+
* someone else and is kept as-is on every write.
|
|
18
|
+
*/
|
|
19
|
+
export interface Settings {
|
|
20
|
+
business?: BusinessSession;
|
|
21
|
+
id?: IdSession;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Moves the flat keys older CLIs wrote into `business` / `id`. This CLI never
|
|
26
|
+
* writes them, so when they are present an older CLI signed in after it did:
|
|
27
|
+
* they are the newer session and replace both groups. A CLI from before
|
|
28
|
+
* DashNex ID sign-in writes no id keys, and its business token must not be
|
|
29
|
+
* paired with an id session it did not come from.
|
|
30
|
+
*/
|
|
31
|
+
export declare const normalizeSettings: (raw: unknown) => Settings;
|
|
32
|
+
/** The settings file, normalized; null when it is missing or unreadable. */
|
|
33
|
+
export declare const readSettings: (settingsPath: string) => Promise<Settings | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Re-reads the file, applies `change` and writes it back, so keys written
|
|
36
|
+
* since the caller last read it (another process, another tool) survive. An
|
|
37
|
+
* unreadable file is replaced: it held nothing usable, and refusing would
|
|
38
|
+
* leave login stuck.
|
|
39
|
+
*/
|
|
40
|
+
export declare const updateSettings: (settingsPath: string, change: (settings: Settings) => Settings | void) => Promise<Settings>;
|
|
@@ -2,6 +2,8 @@ export interface EnsureLoggedInResult {
|
|
|
2
2
|
token: string;
|
|
3
3
|
businessId: string;
|
|
4
4
|
userName?: string;
|
|
5
|
+
/** Settings file the session was read from; absent for DASHNEX_TOKEN. */
|
|
6
|
+
settingsPath?: string;
|
|
5
7
|
}
|
|
6
8
|
export interface EnsureLoggedInOptions {
|
|
7
9
|
exitOnFailure?: boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Business } from './businesses.js';
|
|
2
|
+
import { Settings } from '../lib/settings.js';
|
|
2
3
|
export interface SelectBusinessOptions {
|
|
3
4
|
/** Business ID to switch to without prompting. */
|
|
4
5
|
businessId?: string | number;
|
|
@@ -12,8 +13,8 @@ export interface SelectBusinessOptions {
|
|
|
12
13
|
*/
|
|
13
14
|
export declare const selectBusiness: (idToken: string, dashnexPath: string, options?: SelectBusinessOptions) => Promise<Business>;
|
|
14
15
|
/**
|
|
15
|
-
* Merges into settings.json rather than replacing it. The file
|
|
16
|
-
* business CLI settings and anything an app or module put there, so
|
|
17
|
-
* must only ever add or update its own keys.
|
|
16
|
+
* Merges top-level keys into settings.json rather than replacing it. The file
|
|
17
|
+
* also holds business CLI settings and anything an app or module put there, so
|
|
18
|
+
* the CLI must only ever add or update its own keys.
|
|
18
19
|
*/
|
|
19
|
-
export declare const writeSettings: (dashnexPath: string, config:
|
|
20
|
+
export declare const writeSettings: (dashnexPath: string, config: Settings) => Promise<void>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
type ReservationStatus = string;
|
|
2
|
+
interface Reservation {
|
|
3
|
+
address: string;
|
|
4
|
+
moduleName: string;
|
|
5
|
+
status: ReservationStatus;
|
|
6
|
+
lastSubmittedVersion: string | null;
|
|
7
|
+
/** The version currently waiting for a decision. Absent on older business-ms (the submission POST still says `in_review`). */
|
|
8
|
+
inReview?: {
|
|
9
|
+
version: string;
|
|
10
|
+
} | null;
|
|
11
|
+
}
|
|
12
|
+
interface DeveloperAccess {
|
|
13
|
+
agreementAccepted: boolean;
|
|
14
|
+
reservations: Reservation[];
|
|
15
|
+
}
|
|
16
|
+
type SubmissionResult = {
|
|
17
|
+
status: "accepted";
|
|
18
|
+
submissionId: string;
|
|
19
|
+
} | {
|
|
20
|
+
status: "version_not_higher" | "not_reserved" | "in_review" | "agreement_required";
|
|
21
|
+
} | {
|
|
22
|
+
status: "error";
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
declare const normalizeAddress: (input: string) => string;
|
|
26
|
+
declare const moduleName: (address: string) => string;
|
|
27
|
+
declare const joinMessage: () => string;
|
|
28
|
+
declare const reserveMessage: () => string;
|
|
29
|
+
declare const notReservedMessage: (address: string) => string;
|
|
30
|
+
declare const bumpMessage: (address: string) => string;
|
|
31
|
+
declare const submittedMessage: (address: string, version: string) => string;
|
|
32
|
+
declare const inReviewMessage: (address: string, version?: string) => string;
|
|
33
|
+
/** The version the reservation has in review, or undefined when none is (or the server doesn't report it). */
|
|
34
|
+
declare const versionInReview: (reservation: Reservation | undefined) => string | undefined;
|
|
35
|
+
type AccessResponse = {
|
|
36
|
+
ok: true;
|
|
37
|
+
access: DeveloperAccess;
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
status: number;
|
|
41
|
+
};
|
|
42
|
+
/** Network only: never exits, so callers can stop their spinner before reporting a failure. */
|
|
43
|
+
declare const requestDeveloperAccess: (token: string) => Promise<AccessResponse>;
|
|
44
|
+
/** Maps a failed access response to its exit message; returns the access on success. */
|
|
45
|
+
declare const accessOrExit: (response: AccessResponse) => DeveloperAccess;
|
|
46
|
+
declare const fetchDeveloperAccess: (token: string) => Promise<DeveloperAccess>;
|
|
47
|
+
/** Applies the Developers-program rules; exits with the matching message when one fails. */
|
|
48
|
+
declare const assertDeveloperAccess: (access: DeveloperAccess, address?: string) => Reservation | undefined;
|
|
49
|
+
declare const ensureDeveloperAccess: (token: string, address?: string) => Promise<{
|
|
50
|
+
access: DeveloperAccess;
|
|
51
|
+
reservation?: Reservation;
|
|
52
|
+
}>;
|
|
53
|
+
type TemplateResponse = {
|
|
54
|
+
ok: true;
|
|
55
|
+
bytes: Buffer;
|
|
56
|
+
} | {
|
|
57
|
+
ok: false;
|
|
58
|
+
status: number;
|
|
59
|
+
};
|
|
60
|
+
/** Network only: never exits, so callers can stop their spinner before reporting a failure. */
|
|
61
|
+
declare const requestTemplate: (token: string) => Promise<TemplateResponse>;
|
|
62
|
+
/** Maps a failed template response to its exit message; returns the tarball bytes on success. */
|
|
63
|
+
declare const templateOrExit: (response: TemplateResponse) => Buffer;
|
|
64
|
+
declare const downloadTemplate: (token: string, destPath: string) => Promise<void>;
|
|
65
|
+
declare const createSubmission: (token: string, address: string, version: string) => Promise<SubmissionResult>;
|
|
66
|
+
export type { ReservationStatus, Reservation, DeveloperAccess, SubmissionResult };
|
|
67
|
+
export { normalizeAddress, moduleName, joinMessage, reserveMessage, notReservedMessage, bumpMessage, submittedMessage, inReviewMessage, versionInReview, requestDeveloperAccess, accessOrExit, fetchDeveloperAccess, assertDeveloperAccess, ensureDeveloperAccess, requestTemplate, templateOrExit, downloadTemplate, createSubmission, };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { EnsureLoggedInResult } from './auth.js';
|
|
2
|
+
export interface AssertProjectOptions {
|
|
3
|
+
/** Also require .env APPLICATION_ID to match the business's application. */
|
|
4
|
+
checkApplication?: boolean;
|
|
5
|
+
/** Folder the command acts on; defaults to the current directory. */
|
|
6
|
+
projectDir?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Stops the command when the session is not for this folder's business (and application). */
|
|
9
|
+
export declare const assertSessionMatchesProject: (session: EnsureLoggedInResult, options?: AssertProjectOptions) => Promise<void>;
|
|
10
|
+
/** Same comparison as assertSessionMatchesProject, reported as a warning. */
|
|
11
|
+
export declare const warnIfSessionMismatchesProject: (session: EnsureLoggedInResult, applicationId?: string) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "dashnex",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"dashnex": {
|
|
6
6
|
"minAppVersion": "1.1.0"
|
|
7
7
|
},
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"inquirer": "^13.4.3",
|
|
47
47
|
"jiti": "^2.7.0",
|
|
48
48
|
"partysocket": "^1.3.0",
|
|
49
|
+
"tar": "^7.5.22",
|
|
49
50
|
"ws": "^8.21.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import e from"chalk";import s from"fs-extra";import n from"path";var r=e=>e instanceof Error&&("ExitPromptError"===e.name||("AbortError"===e.name||("User force closed the prompt"===e.message||!!e.message.includes("prompt was closed")))),t="Command interrupted.",o=e=>{const s=e.toLowerCase();return s.includes("token")||s.includes("password")||"auth_code"===s},a=e=>{if(null===e||"object"!=typeof e)return e;if(Array.isArray(e))return e.map(a);const s={};for(const[n,r]of Object.entries(e))o(n)?s[n]="[REDACTED]":s[n]=a(r);return s},i=s=>{process.env.DEBUG&&console.log(e.dim(s))},c=(s,n)=>{if(process.env.DEBUG){const r=a(n);console.log(e.dim(`${s}: ${JSON.stringify(r,null,2)}`))}},p=s=>{process.env.DEBUG&&s instanceof Error&&(console.log(e.dim(`Error: ${s.message}`)),s.stack&&console.log(e.dim(`Stack: ${s.stack}`)))},l=()=>"qa"===process.env.APPLICATION_ENV?"https://api.qa.dashnex.com":"https://api.dashnex.com",u=()=>process.env.DASHNEX_BUSINESS_API_URL?process.env.DASHNEX_BUSINESS_API_URL.replace(/\/+$/,""):"qa"===process.env.APPLICATION_ENV?"https://api.business.qa.dashnex.com":"https://api.business.dashnex.com",d={qa:"https://app.qa.dashnex.com",live:"https://app.dashnex.com"},h={qa:"hacmrrpg1wnjv12oq22p8lfm",live:"paijq1vhr6kxnbm22wjoftep"},f=()=>process.env.DASHNEX_ID_URL?process.env.DASHNEX_ID_URL.replace(/\/+$/,""):d[process.env.APPLICATION_ENV||"live"]||d.live,k=()=>process.env.DASHNEX_OAUTH_CLIENT_ID?process.env.DASHNEX_OAUTH_CLIENT_ID:h[process.env.APPLICATION_ENV||"live"]??"",T={qa:"https://3atfynmu2w.us-east-1.awsapprunner.com",live:"https://rkwbzvhcwh.us-east-1.awsapprunner.com"},m=()=>T[process.env.APPLICATION_ENV||"live"]||T.live,w=async(e,s={})=>{const n=await fetch(e,{...s,headers:{"Content-Type":"application/json",...s.headers}});return{response:n,body:await n.json().catch(()=>({}))}},v=process.env.DASHNEX_DIR||".dashnex",x=e=>n.join(e,v,"settings.json"),y=e=>Date.now()+1e3*(e??86400),N=async e=>{try{return await s.readJson(e)}catch{return null}},E=async e=>{const n=await N(e);if(!n?.idRefreshToken)return i("No DashNex ID refresh token saved"),null;const r=`${f()}/api/auth/refresh`;i(`POST ${r}`);let t=!1;try{const{response:o,body:a}=await w(r,{method:"POST",body:JSON.stringify({refreshToken:n.idRefreshToken})});i(`Response: ${o.status}`);const c=a;if(o.ok&&c.token&&c.refreshToken){const n=await N(e)??{};return await s.writeJson(e,{...n,idToken:c.token,idRefreshToken:c.refreshToken,idExpiresAt:y()},{spaces:2}),i("Refreshed DashNex ID session"),c.token}i(`DashNex ID session refresh rejected: ${c.error??"no tokens in response"}`),t=401===o.status}catch(a){p(a)}const o=await N(e);return o?.idToken&&o.idRefreshToken!==n.idRefreshToken?(i("DashNex ID session was refreshed by another process"),o.idToken):(t&&o&&(delete o.idRefreshToken,await s.writeJson(e,o,{spaces:2})),null)},g=async e=>{const s=await N(e);if(!s?.idToken)return null;const n=s.idExpiresAt;return"number"==typeof n&&n-3e5>Date.now()?s.idToken:(i(n?"DashNex ID session expired or expiring soon":"DashNex ID session expiry unknown"),await E(e)??s.idToken)},D=()=>{console.error(e.red("Please run 'dashnex login' to authenticate.")),console.error(e.green(`If you don't have an account yet, create one at ${f()}`))},A=e=>Boolean(e.idToken),I=async e=>{let r=n.resolve(e);for(;;){const e=x(r);if(await s.pathExists(e))return e;const t=n.dirname(r);if(t===r)return null;r=t}},S=e=>{try{const s=e.split(".");if(3!==s.length)return null;const n=JSON.parse(Buffer.from(s[1],"base64url").toString());return void 0!==n.id?String(n.id):null}catch{return null}},_=(e,s=30)=>{try{const n=e.split(".");if(3!==n.length)return!1;const r=JSON.parse(Buffer.from(n[1],"base64url").toString());if("number"!=typeof r.exp)return!1;const t=Math.floor(Date.now()/1e3),o=r.exp-s<=t;return o&&i(`Token expired or expiring soon (exp: ${r.exp}, now: ${t})`),o}catch{return!1}},b=async(e,n,r)=>{const t=A(r)?`${u()}/business/v1/token/refresh`:`${l()}/auth/v1/token/refresh`;i(`POST ${t}`);const{response:o,body:a}=await w(t,{method:"POST",body:JSON.stringify({refreshToken:e})});if(i(`Response: ${o.status}`),c("Refresh response",a),!o.ok)return i("Token refresh failed"),null;const p=a;if(!p.token||!p.refreshToken)return i("Refresh response missing tokens"),null;const d=await s.readJson(n);return d.token=p.token,d.refreshToken=p.refreshToken,await s.writeJson(n,d,{spaces:2}),i("Updated settings.json with new tokens"),$(p.token,p.refreshToken,n,{...d,token:p.token,refreshToken:p.refreshToken})},$=async(e,s,n,r)=>{if(_(e)){if(!s)return i("Token expired, no refreshToken available"),null;i("Token expired, refreshing proactively");try{return await b(s,n,r)}catch(o){return p(o),null}}const t=A(r)?`${u()}/business/v1/`:`${l()}/users/v1/`;i(`GET ${t}`);try{const{response:o,body:a}=await w(t,{headers:{Authorization:`Bearer ${e}`}});if(i(`Response: ${o.status}`),c("Validation response",a),o.ok){const s=a,n=s.name??s.companyName,t=r.businessId??(void 0!==s.id?String(s.id):void 0);if("string"==typeof n&&void 0!==t)return i(`Token valid, Already logged in as ${n}`),{token:e,businessId:String(t),userName:n}}return s?(i("Validation endpoint rejected token, refreshing"),await b(s,n,r)):(i("Token invalid, no refreshToken available"),null)}catch(o){return p(o),null}},O=async(e=process.cwd())=>{const n=await I(e);if(!n)return!1;try{return A(await s.readJson(n))}catch{return!1}},P=async(n={})=>{const{exitOnFailure:r=!0,dashnexPath:t}=n,o=process.env.DASHNEX_TOKEN;if(o)return _(o)?(i("DASHNEX_TOKEN is expired"),console.error(e.red("DASHNEX_TOKEN is expired. Provide a freshly issued token.")),r&&process.exit(1),null):(i("Using DASHNEX_TOKEN from environment"),{token:o,businessId:S(o)??""});const a=t??await I(process.cwd());if(!a||!(await s.pathExists(a)))return i(".dashnex/settings.json not found"),r&&(D(),process.exit(1)),null;let c;i(`Found settings at ${a}`);try{c=await s.readJson(a)}catch{return i("settings.json parse failed"),r&&(D(),process.exit(1)),null}const{token:p,refreshToken:l,businessId:u}=c;if(!p)return i("settings.json missing token"),r&&(D(),process.exit(1)),null;const d=await $(p,l,a,c);return d?(A(c)&&await g(a),d):(r&&(D(),process.exit(1)),null)};export{c as _,y as a,x as c,u as d,f,p as g,i as h,g as i,w as l,k as m,I as n,E as o,m as p,O as r,v as s,P as t,l as u,t as v,r as y};
|