@miosa/cli 0.2.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 +327 -0
- package/dist/bin/miosa.d.ts +3 -0
- package/dist/bin/miosa.d.ts.map +1 -0
- package/dist/bin/miosa.js +139 -0
- package/dist/bin/miosa.js.map +1 -0
- package/dist/client.d.ts +74 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +523 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/agent.d.ts +18 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +468 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/alerts.d.ts +3 -0
- package/dist/commands/alerts.d.ts.map +1 -0
- package/dist/commands/alerts.js +41 -0
- package/dist/commands/alerts.js.map +1 -0
- package/dist/commands/api-keys.d.ts +3 -0
- package/dist/commands/api-keys.d.ts.map +1 -0
- package/dist/commands/api-keys.js +119 -0
- package/dist/commands/api-keys.js.map +1 -0
- package/dist/commands/api-resource.d.ts +20 -0
- package/dist/commands/api-resource.d.ts.map +1 -0
- package/dist/commands/api-resource.js +120 -0
- package/dist/commands/api-resource.js.map +1 -0
- package/dist/commands/apps.d.ts +3 -0
- package/dist/commands/apps.d.ts.map +1 -0
- package/dist/commands/apps.js +218 -0
- package/dist/commands/apps.js.map +1 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +25 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +363 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/backups.d.ts +3 -0
- package/dist/commands/backups.d.ts.map +1 -0
- package/dist/commands/backups.js +23 -0
- package/dist/commands/backups.js.map +1 -0
- package/dist/commands/checkpoints.d.ts +3 -0
- package/dist/commands/checkpoints.d.ts.map +1 -0
- package/dist/commands/checkpoints.js +33 -0
- package/dist/commands/checkpoints.js.map +1 -0
- package/dist/commands/computers.d.ts +3 -0
- package/dist/commands/computers.d.ts.map +1 -0
- package/dist/commands/computers.js +118 -0
- package/dist/commands/computers.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +114 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/connect.d.ts +3 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +96 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/containers.d.ts +3 -0
- package/dist/commands/containers.d.ts.map +1 -0
- package/dist/commands/containers.js +20 -0
- package/dist/commands/containers.js.map +1 -0
- package/dist/commands/cp.d.ts +3 -0
- package/dist/commands/cp.d.ts.map +1 -0
- package/dist/commands/cp.js +102 -0
- package/dist/commands/cp.js.map +1 -0
- package/dist/commands/cron.d.ts +3 -0
- package/dist/commands/cron.d.ts.map +1 -0
- package/dist/commands/cron.js +65 -0
- package/dist/commands/cron.js.map +1 -0
- package/dist/commands/databases.d.ts +3 -0
- package/dist/commands/databases.d.ts.map +1 -0
- package/dist/commands/databases.js +222 -0
- package/dist/commands/databases.js.map +1 -0
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +174 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/deploy.d.ts +3 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +579 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/desktop.d.ts +3 -0
- package/dist/commands/desktop.d.ts.map +1 -0
- package/dist/commands/desktop.js +276 -0
- package/dist/commands/desktop.js.map +1 -0
- package/dist/commands/dev.d.ts +3 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +246 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +241 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/domains.d.ts +3 -0
- package/dist/commands/domains.d.ts.map +1 -0
- package/dist/commands/domains.js +31 -0
- package/dist/commands/domains.js.map +1 -0
- package/dist/commands/enterprise-util.d.ts +37 -0
- package/dist/commands/enterprise-util.d.ts.map +1 -0
- package/dist/commands/enterprise-util.js +185 -0
- package/dist/commands/enterprise-util.js.map +1 -0
- package/dist/commands/exec.d.ts +3 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/exec.js +68 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/functions.d.ts +3 -0
- package/dist/commands/functions.d.ts.map +1 -0
- package/dist/commands/functions.js +47 -0
- package/dist/commands/functions.js.map +1 -0
- package/dist/commands/gha-runners.d.ts +3 -0
- package/dist/commands/gha-runners.d.ts.map +1 -0
- package/dist/commands/gha-runners.js +33 -0
- package/dist/commands/gha-runners.js.map +1 -0
- package/dist/commands/groups.d.ts +3 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +38 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/host.d.ts +3 -0
- package/dist/commands/host.d.ts.map +1 -0
- package/dist/commands/host.js +74 -0
- package/dist/commands/host.js.map +1 -0
- package/dist/commands/hosts.d.ts +3 -0
- package/dist/commands/hosts.d.ts.map +1 -0
- package/dist/commands/hosts.js +90 -0
- package/dist/commands/hosts.js.map +1 -0
- package/dist/commands/link.d.ts +8 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +124 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +172 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/logs.d.ts +3 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +94 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/ls.d.ts +3 -0
- package/dist/commands/ls.d.ts.map +1 -0
- package/dist/commands/ls.js +67 -0
- package/dist/commands/ls.js.map +1 -0
- package/dist/commands/machines.d.ts +3 -0
- package/dist/commands/machines.d.ts.map +1 -0
- package/dist/commands/machines.js +29 -0
- package/dist/commands/machines.js.map +1 -0
- package/dist/commands/mcp.d.ts +21 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +1021 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/meshes.d.ts +3 -0
- package/dist/commands/meshes.d.ts.map +1 -0
- package/dist/commands/meshes.js +27 -0
- package/dist/commands/meshes.js.map +1 -0
- package/dist/commands/network-policy.d.ts +3 -0
- package/dist/commands/network-policy.d.ts.map +1 -0
- package/dist/commands/network-policy.js +40 -0
- package/dist/commands/network-policy.js.map +1 -0
- package/dist/commands/project.d.ts +4 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +25 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/pull.d.ts +3 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +155 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/regions.d.ts +3 -0
- package/dist/commands/regions.d.ts.map +1 -0
- package/dist/commands/regions.js +67 -0
- package/dist/commands/regions.js.map +1 -0
- package/dist/commands/releases.d.ts +3 -0
- package/dist/commands/releases.d.ts.map +1 -0
- package/dist/commands/releases.js +176 -0
- package/dist/commands/releases.js.map +1 -0
- package/dist/commands/rm.d.ts +3 -0
- package/dist/commands/rm.d.ts.map +1 -0
- package/dist/commands/rm.js +42 -0
- package/dist/commands/rm.js.map +1 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +131 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/sandbox.d.ts +3 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +352 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/schedules.d.ts +3 -0
- package/dist/commands/schedules.d.ts.map +1 -0
- package/dist/commands/schedules.js +37 -0
- package/dist/commands/schedules.js.map +1 -0
- package/dist/commands/secrets.d.ts +3 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +194 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/services.d.ts +3 -0
- package/dist/commands/services.d.ts.map +1 -0
- package/dist/commands/services.js +70 -0
- package/dist/commands/services.js.map +1 -0
- package/dist/commands/shell.d.ts +16 -0
- package/dist/commands/shell.d.ts.map +1 -0
- package/dist/commands/shell.js +527 -0
- package/dist/commands/shell.js.map +1 -0
- package/dist/commands/snapshot.d.ts +10 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +181 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/ssh.d.ts +3 -0
- package/dist/commands/ssh.d.ts.map +1 -0
- package/dist/commands/ssh.js +37 -0
- package/dist/commands/ssh.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +300 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/storage.d.ts +3 -0
- package/dist/commands/storage.d.ts.map +1 -0
- package/dist/commands/storage.js +180 -0
- package/dist/commands/storage.js.map +1 -0
- package/dist/commands/tenant.d.ts +3 -0
- package/dist/commands/tenant.d.ts.map +1 -0
- package/dist/commands/tenant.js +87 -0
- package/dist/commands/tenant.js.map +1 -0
- package/dist/commands/tunnel.d.ts +3 -0
- package/dist/commands/tunnel.d.ts.map +1 -0
- package/dist/commands/tunnel.js +418 -0
- package/dist/commands/tunnel.js.map +1 -0
- package/dist/commands/up.d.ts +14 -0
- package/dist/commands/up.d.ts.map +1 -0
- package/dist/commands/up.js +703 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/commands/util.d.ts +19 -0
- package/dist/commands/util.d.ts.map +1 -0
- package/dist/commands/util.js +116 -0
- package/dist/commands/util.js.map +1 -0
- package/dist/commands/volumes.d.ts +3 -0
- package/dist/commands/volumes.d.ts.map +1 -0
- package/dist/commands/volumes.js +196 -0
- package/dist/commands/volumes.js.map +1 -0
- package/dist/commands/watch.d.ts +3 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +398 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/commands/webhooks.d.ts +3 -0
- package/dist/commands/webhooks.d.ts.map +1 -0
- package/dist/commands/webhooks.js +23 -0
- package/dist/commands/webhooks.js.map +1 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +84 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/commands/workspaces.d.ts +3 -0
- package/dist/commands/workspaces.d.ts.map +1 -0
- package/dist/commands/workspaces.js +87 -0
- package/dist/commands/workspaces.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +129 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +62 -0
- package/dist/errors.js.map +1 -0
- package/dist/framework-detector.d.ts +22 -0
- package/dist/framework-detector.d.ts.map +1 -0
- package/dist/framework-detector.js +373 -0
- package/dist/framework-detector.js.map +1 -0
- package/dist/pty/raw-mode.d.ts +7 -0
- package/dist/pty/raw-mode.d.ts.map +1 -0
- package/dist/pty/raw-mode.js +22 -0
- package/dist/pty/raw-mode.js.map +1 -0
- package/dist/pty/ws-pty-client.d.ts +12 -0
- package/dist/pty/ws-pty-client.d.ts.map +1 -0
- package/dist/pty/ws-pty-client.js +69 -0
- package/dist/pty/ws-pty-client.js.map +1 -0
- package/dist/types.d.ts +326 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/progress.d.ts +10 -0
- package/dist/ui/progress.d.ts.map +1 -0
- package/dist/ui/progress.js +36 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/spinner.d.ts +4 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +7 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/table.d.ts +8 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +46 -0
- package/dist/ui/table.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `miosa up` — smart context-aware launch command.
|
|
3
|
+
*
|
|
4
|
+
* Detects what you're working with and does the right thing:
|
|
5
|
+
* .miosa.json or miosa.json → redeploy existing project
|
|
6
|
+
* Dockerfile → build and deploy as container (not yet live — falls through to deploy)
|
|
7
|
+
* package.json / mix.exs / requirements.txt → detect framework, deploy
|
|
8
|
+
* --computer flag → create a desktop computer
|
|
9
|
+
* --sandbox flag → create a sandbox
|
|
10
|
+
* nothing → interactive mode to pick an action
|
|
11
|
+
*/
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { execSync } from "node:child_process";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { loadConfig } from "../config.js";
|
|
17
|
+
import { MiosaClient, parseSse } from "../client.js";
|
|
18
|
+
import { handleError } from "./util.js";
|
|
19
|
+
import { detectFramework, FRAMEWORK_LABELS, } from "../framework-detector.js";
|
|
20
|
+
import { UserError } from "../errors.js";
|
|
21
|
+
import { toDeploymentId } from "../types.js";
|
|
22
|
+
// ── .miosa.json helpers ───────────────────────────────────────────────────────
|
|
23
|
+
const PROJECT_CONFIG_FILES = [".miosa.json", "miosa.json"];
|
|
24
|
+
function loadProjectConfig(dir) {
|
|
25
|
+
for (const filename of PROJECT_CONFIG_FILES) {
|
|
26
|
+
const p = path.join(dir, filename);
|
|
27
|
+
if (!fs.existsSync(p))
|
|
28
|
+
continue;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function saveProjectConfig(dir, cfg) {
|
|
39
|
+
const p = path.join(dir, ".miosa.json");
|
|
40
|
+
fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n");
|
|
41
|
+
}
|
|
42
|
+
// ── Signals ───────────────────────────────────────────────────────────────────
|
|
43
|
+
/**
|
|
44
|
+
* Write a single status line. In JSON mode output nothing (caller handles JSON).
|
|
45
|
+
* Uses \r to overwrite the same line for streaming progress feel.
|
|
46
|
+
*/
|
|
47
|
+
function line(msg, opts) {
|
|
48
|
+
if (opts.json)
|
|
49
|
+
return;
|
|
50
|
+
process.stdout.write(msg + "\n");
|
|
51
|
+
}
|
|
52
|
+
function lineProgress(msg, opts) {
|
|
53
|
+
if (opts.json)
|
|
54
|
+
return;
|
|
55
|
+
process.stdout.write(` ${msg}\n`);
|
|
56
|
+
}
|
|
57
|
+
function getGitInfo(dir) {
|
|
58
|
+
try {
|
|
59
|
+
execSync("git rev-parse --git-dir", { cwd: dir, stdio: "ignore" });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new UserError("Not a git repository.", "Run `git init && git remote add origin <url>` first.");
|
|
63
|
+
}
|
|
64
|
+
let repoUrl;
|
|
65
|
+
try {
|
|
66
|
+
repoUrl = execSync("git remote get-url origin", {
|
|
67
|
+
cwd: dir,
|
|
68
|
+
encoding: "utf8",
|
|
69
|
+
}).trim();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw new UserError("No git remote named 'origin' found.", "Add one with: git remote add origin https://github.com/you/repo");
|
|
73
|
+
}
|
|
74
|
+
// Normalize SSH → HTTPS
|
|
75
|
+
if (repoUrl.startsWith("git@github.com:")) {
|
|
76
|
+
repoUrl = repoUrl
|
|
77
|
+
.replace("git@github.com:", "https://github.com/")
|
|
78
|
+
.replace(/\.git$/, "");
|
|
79
|
+
}
|
|
80
|
+
repoUrl = repoUrl.replace(/\.git$/, "");
|
|
81
|
+
let currentBranch = "main";
|
|
82
|
+
try {
|
|
83
|
+
currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
84
|
+
cwd: dir,
|
|
85
|
+
encoding: "utf8",
|
|
86
|
+
}).trim();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// default
|
|
90
|
+
}
|
|
91
|
+
return { repoUrl, currentBranch };
|
|
92
|
+
}
|
|
93
|
+
// ── Deploy log streaming ──────────────────────────────────────────────────────
|
|
94
|
+
async function streamDeployLogs(client, deploymentId, opts) {
|
|
95
|
+
const res = await client.streamDeploymentLogs(deploymentId);
|
|
96
|
+
let lastState = "success";
|
|
97
|
+
for await (const event of parseSse(res.body)) {
|
|
98
|
+
switch (event.type) {
|
|
99
|
+
case "stdout":
|
|
100
|
+
if (!opts.json)
|
|
101
|
+
process.stdout.write(chalk.dim(" ") + event.data);
|
|
102
|
+
break;
|
|
103
|
+
case "stderr":
|
|
104
|
+
if (!opts.json)
|
|
105
|
+
process.stderr.write(chalk.red(" ") + event.data);
|
|
106
|
+
break;
|
|
107
|
+
case "error":
|
|
108
|
+
if (!opts.json)
|
|
109
|
+
console.error(chalk.red(` [error] ${event.message}`));
|
|
110
|
+
lastState = "failure";
|
|
111
|
+
break;
|
|
112
|
+
case "done": {
|
|
113
|
+
if (event.result && typeof event.result === "object") {
|
|
114
|
+
const r = event.result;
|
|
115
|
+
if (r["state"] === "failed")
|
|
116
|
+
lastState = "failure";
|
|
117
|
+
}
|
|
118
|
+
return lastState;
|
|
119
|
+
}
|
|
120
|
+
case "unknown": {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(event.raw);
|
|
123
|
+
if (typeof parsed["line"] === "string" && !opts.json) {
|
|
124
|
+
const stream = parsed["stream"] ?? "stdout";
|
|
125
|
+
const logLine = parsed["line"];
|
|
126
|
+
if (stream === "stderr") {
|
|
127
|
+
process.stderr.write(chalk.red(" ") + logLine + "\n");
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
process.stdout.write(chalk.dim(" ") + logLine + "\n");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// ignore unparseable frames
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
default:
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return lastState;
|
|
144
|
+
}
|
|
145
|
+
// ── Mode: redeploy existing .miosa.json ──────────────────────────────────────
|
|
146
|
+
async function runRedeploy(client, projectCfg, opts) {
|
|
147
|
+
if (!opts.json) {
|
|
148
|
+
console.log();
|
|
149
|
+
line(` Detected: ${chalk.cyan(FRAMEWORK_LABELS[projectCfg.framework] ?? projectCfg.framework)}`, opts);
|
|
150
|
+
line(` Found .miosa.json → redeploying ${chalk.bold(projectCfg.name)}`, opts);
|
|
151
|
+
console.log();
|
|
152
|
+
}
|
|
153
|
+
lineProgress("Queuing build...", opts);
|
|
154
|
+
try {
|
|
155
|
+
await client.redeployDeployment(projectCfg.deploymentId);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
handleError(err);
|
|
159
|
+
}
|
|
160
|
+
lineProgress("Build queued", opts);
|
|
161
|
+
if (!opts.json) {
|
|
162
|
+
console.log();
|
|
163
|
+
line(` ${chalk.bold("Build log:")}`, opts);
|
|
164
|
+
line(` ${"─".repeat(60)}`, opts);
|
|
165
|
+
}
|
|
166
|
+
const buildResult = await streamDeployLogs(client, projectCfg.deploymentId, opts);
|
|
167
|
+
if (!opts.json) {
|
|
168
|
+
line(` ${"─".repeat(60)}`, opts);
|
|
169
|
+
console.log();
|
|
170
|
+
}
|
|
171
|
+
if (buildResult === "success") {
|
|
172
|
+
try {
|
|
173
|
+
const dep = await client.getDeployment(projectCfg.deploymentId);
|
|
174
|
+
const tenant = await client.getTenant();
|
|
175
|
+
const url = `https://${dep.slug}.${tenant.slug}.miosa.app`;
|
|
176
|
+
if (opts.json) {
|
|
177
|
+
console.log(JSON.stringify({ id: dep.id, url, name: dep.name, state: dep.state }));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.log(chalk.green(" Deployed"));
|
|
181
|
+
console.log();
|
|
182
|
+
console.log(` ${chalk.bold("URL:")} ${chalk.cyan(url)}`);
|
|
183
|
+
console.log();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
if (opts.json) {
|
|
188
|
+
console.log(JSON.stringify({ id: projectCfg.deploymentId, state: "running" }));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log(chalk.green(" Deployed"));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
if (opts.json) {
|
|
197
|
+
console.log(JSON.stringify({ id: projectCfg.deploymentId, state: "failed" }));
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
console.log(chalk.red(" Build failed."));
|
|
201
|
+
console.log();
|
|
202
|
+
console.log(chalk.dim(" View full logs: miosa deploy logs"));
|
|
203
|
+
}
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// ── Mode: first deploy ────────────────────────────────────────────────────────
|
|
208
|
+
async function runFirstDeploy(cwd, client, opts) {
|
|
209
|
+
const { repoUrl, currentBranch } = getGitInfo(cwd);
|
|
210
|
+
const detection = detectFramework(cwd);
|
|
211
|
+
const hasDockerfile = fs.existsSync(path.join(cwd, "Dockerfile"));
|
|
212
|
+
let framework = "unknown";
|
|
213
|
+
let buildCommand = "npm run build";
|
|
214
|
+
let runCommand = "npm start";
|
|
215
|
+
let frameworkLabel = "application";
|
|
216
|
+
if (detection) {
|
|
217
|
+
framework = detection.framework;
|
|
218
|
+
buildCommand = detection.buildCommand;
|
|
219
|
+
runCommand = detection.runCommand;
|
|
220
|
+
frameworkLabel =
|
|
221
|
+
FRAMEWORK_LABELS[detection.framework] ?? detection.framework;
|
|
222
|
+
}
|
|
223
|
+
else if (hasDockerfile) {
|
|
224
|
+
frameworkLabel = "container (Dockerfile)";
|
|
225
|
+
buildCommand = "";
|
|
226
|
+
runCommand = "";
|
|
227
|
+
}
|
|
228
|
+
if (!opts.json) {
|
|
229
|
+
console.log();
|
|
230
|
+
if (detection) {
|
|
231
|
+
line(` Detected: ${chalk.cyan(frameworkLabel)} (confidence ${detection.confidence}%)`, opts);
|
|
232
|
+
}
|
|
233
|
+
else if (hasDockerfile) {
|
|
234
|
+
line(` Detected: ${chalk.cyan("Dockerfile")}`, opts);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
line(chalk.yellow(" No framework detected — deploying as generic app"), opts);
|
|
238
|
+
}
|
|
239
|
+
line(` Repo: ${chalk.dim(repoUrl)}`, opts);
|
|
240
|
+
line(` Branch: ${chalk.dim(currentBranch)}`, opts);
|
|
241
|
+
console.log();
|
|
242
|
+
}
|
|
243
|
+
// ── Determine deploy parameters ──────────────────────────────────────────
|
|
244
|
+
let deployName;
|
|
245
|
+
let deployBranch;
|
|
246
|
+
let deployBuild;
|
|
247
|
+
let deployRun;
|
|
248
|
+
const defaultName = path.basename(cwd).replace(/[^a-z0-9-]/gi, "-");
|
|
249
|
+
if (opts.yes || opts.json) {
|
|
250
|
+
// Non-interactive: use flags or defaults
|
|
251
|
+
deployName = opts.name ?? defaultName;
|
|
252
|
+
deployBranch = currentBranch;
|
|
253
|
+
deployBuild = buildCommand;
|
|
254
|
+
deployRun = runCommand;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
// Interactive
|
|
258
|
+
const { default: inquirer } = await import("inquirer");
|
|
259
|
+
const answers = await inquirer.prompt([
|
|
260
|
+
{
|
|
261
|
+
type: "input",
|
|
262
|
+
name: "name",
|
|
263
|
+
message: "Name:",
|
|
264
|
+
default: opts.name ?? defaultName,
|
|
265
|
+
validate: (v) => (v.length > 0 ? true : "Name is required"),
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
type: "input",
|
|
269
|
+
name: "branch",
|
|
270
|
+
message: "Branch:",
|
|
271
|
+
default: currentBranch,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
type: "input",
|
|
275
|
+
name: "buildCommand",
|
|
276
|
+
message: "Build command:",
|
|
277
|
+
default: buildCommand,
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
type: "input",
|
|
281
|
+
name: "runCommand",
|
|
282
|
+
message: "Run command:",
|
|
283
|
+
default: runCommand,
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: "confirm",
|
|
287
|
+
name: "confirm",
|
|
288
|
+
message: "Deploy?",
|
|
289
|
+
default: true,
|
|
290
|
+
},
|
|
291
|
+
]);
|
|
292
|
+
if (!answers.confirm) {
|
|
293
|
+
line(chalk.dim(" Cancelled."), opts);
|
|
294
|
+
process.exit(0);
|
|
295
|
+
}
|
|
296
|
+
deployName = answers.name;
|
|
297
|
+
deployBranch = answers.branch;
|
|
298
|
+
deployBuild = answers.buildCommand;
|
|
299
|
+
deployRun = answers.runCommand;
|
|
300
|
+
}
|
|
301
|
+
// ── Create deployment ─────────────────────────────────────────────────────
|
|
302
|
+
lineProgress(`Creating deployment "${deployName}"...`, opts);
|
|
303
|
+
let deployment;
|
|
304
|
+
let webhookSecret;
|
|
305
|
+
try {
|
|
306
|
+
const result = await client.createDeployment({
|
|
307
|
+
name: deployName,
|
|
308
|
+
repo_url: repoUrl,
|
|
309
|
+
branch: deployBranch,
|
|
310
|
+
build_command: deployBuild || undefined,
|
|
311
|
+
run_command: deployRun || undefined,
|
|
312
|
+
auto_deploy: true,
|
|
313
|
+
});
|
|
314
|
+
deployment = result.data;
|
|
315
|
+
webhookSecret = result.webhook_secret;
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
handleError(err);
|
|
319
|
+
}
|
|
320
|
+
lineProgress(`Deployment "${deployment.name}" created`, opts);
|
|
321
|
+
// ── Save .miosa.json ──────────────────────────────────────────────────────
|
|
322
|
+
const projectCfg = {
|
|
323
|
+
version: 1,
|
|
324
|
+
deploymentId: toDeploymentId(deployment.id),
|
|
325
|
+
name: deployment.name,
|
|
326
|
+
framework,
|
|
327
|
+
buildCommand: deployBuild,
|
|
328
|
+
runCommand: deployRun,
|
|
329
|
+
branch: deployBranch,
|
|
330
|
+
};
|
|
331
|
+
saveProjectConfig(cwd, projectCfg);
|
|
332
|
+
if (!opts.json)
|
|
333
|
+
lineProgress("Saved .miosa.json", opts);
|
|
334
|
+
// ── Webhook notice (human-only — agents don't need this) ──────────────────
|
|
335
|
+
if (!opts.json && !opts.yes) {
|
|
336
|
+
console.log();
|
|
337
|
+
console.log(chalk.bold.yellow(" ACTION REQUIRED — GitHub Webhook"));
|
|
338
|
+
console.log(chalk.dim(" The webhook secret below is shown ONCE. Store it now."));
|
|
339
|
+
console.log();
|
|
340
|
+
console.log(` ${chalk.bold("Webhook URL:")} https://api.miosa.ai/api/v1/integrations/github/webhook`);
|
|
341
|
+
console.log(` ${chalk.bold("Content type:")} application/json`);
|
|
342
|
+
console.log(` ${chalk.bold("Secret:")} ${chalk.green(webhookSecret)}`);
|
|
343
|
+
console.log(` ${chalk.bold("Events:")} push`);
|
|
344
|
+
console.log();
|
|
345
|
+
console.log(chalk.dim(" Add this at: " + repoUrl + "/settings/hooks/new"));
|
|
346
|
+
console.log();
|
|
347
|
+
}
|
|
348
|
+
// ── Trigger initial build ─────────────────────────────────────────────────
|
|
349
|
+
lineProgress("Queuing initial build...", opts);
|
|
350
|
+
try {
|
|
351
|
+
await client.redeployDeployment(projectCfg.deploymentId);
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
handleError(err);
|
|
355
|
+
}
|
|
356
|
+
lineProgress("Build queued", opts);
|
|
357
|
+
// ── Stream logs ───────────────────────────────────────────────────────────
|
|
358
|
+
if (!opts.json) {
|
|
359
|
+
console.log();
|
|
360
|
+
line(` ${chalk.bold("Build log:")}`, opts);
|
|
361
|
+
line(` ${"─".repeat(60)}`, opts);
|
|
362
|
+
}
|
|
363
|
+
const buildResult = await streamDeployLogs(client, projectCfg.deploymentId, opts);
|
|
364
|
+
if (!opts.json) {
|
|
365
|
+
line(` ${"─".repeat(60)}`, opts);
|
|
366
|
+
console.log();
|
|
367
|
+
}
|
|
368
|
+
if (buildResult === "success") {
|
|
369
|
+
try {
|
|
370
|
+
const dep = await client.getDeployment(projectCfg.deploymentId);
|
|
371
|
+
const tenant = await client.getTenant();
|
|
372
|
+
const url = `https://${dep.slug}.${tenant.slug}.miosa.app`;
|
|
373
|
+
if (opts.json) {
|
|
374
|
+
console.log(JSON.stringify({ id: dep.id, url, name: dep.name, state: dep.state }));
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
console.log(chalk.green(" Deployed"));
|
|
378
|
+
console.log();
|
|
379
|
+
console.log(` ${chalk.bold("URL:")} ${chalk.cyan(url)}`);
|
|
380
|
+
console.log();
|
|
381
|
+
console.log(chalk.dim(" Next steps:"));
|
|
382
|
+
console.log(chalk.dim(" miosa deploy logs — tail logs"));
|
|
383
|
+
console.log(chalk.dim(" miosa deploy domain add example.com — add custom domain"));
|
|
384
|
+
console.log(chalk.dim(" miosa deploy env set KEY=VALUE — set env var"));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
if (opts.json) {
|
|
389
|
+
console.log(JSON.stringify({ id: deployment.id, state: "running" }));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
console.log(chalk.green(" Deployed"));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
if (opts.json) {
|
|
398
|
+
console.log(JSON.stringify({ id: deployment.id, state: "failed" }));
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
console.log(chalk.red(" Build failed."));
|
|
402
|
+
console.log();
|
|
403
|
+
console.log(chalk.dim(" View full logs: miosa deploy logs"));
|
|
404
|
+
}
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function runComputerMode(client, opts) {
|
|
409
|
+
let computerName;
|
|
410
|
+
let computerOs;
|
|
411
|
+
let computerSize;
|
|
412
|
+
if (opts.yes || opts.json) {
|
|
413
|
+
computerName = opts.name ?? `my-computer-${Date.now().toString(36)}`;
|
|
414
|
+
computerOs = opts.os;
|
|
415
|
+
computerSize = opts.size;
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
const { default: inquirer } = await import("inquirer");
|
|
419
|
+
const answers = await inquirer.prompt([
|
|
420
|
+
{
|
|
421
|
+
type: "input",
|
|
422
|
+
name: "name",
|
|
423
|
+
message: "Computer name:",
|
|
424
|
+
default: opts.name ?? "my-computer",
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
type: "list",
|
|
428
|
+
name: "os",
|
|
429
|
+
message: "Operating system:",
|
|
430
|
+
choices: [
|
|
431
|
+
{ name: "Ubuntu 22.04", value: "ubuntu" },
|
|
432
|
+
{ name: "Debian 12", value: "debian" },
|
|
433
|
+
{ name: "macOS (coming soon)", value: "macos", disabled: true },
|
|
434
|
+
],
|
|
435
|
+
default: opts.os,
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
type: "list",
|
|
439
|
+
name: "size",
|
|
440
|
+
message: "Size:",
|
|
441
|
+
choices: [
|
|
442
|
+
{ name: "small (2 vCPU, 4 GB RAM)", value: "small" },
|
|
443
|
+
{ name: "medium (4 vCPU, 8 GB RAM)", value: "medium" },
|
|
444
|
+
{ name: "large (8 vCPU, 16 GB RAM)", value: "large" },
|
|
445
|
+
],
|
|
446
|
+
default: opts.size,
|
|
447
|
+
},
|
|
448
|
+
]);
|
|
449
|
+
computerName = answers.name;
|
|
450
|
+
computerOs = answers.os;
|
|
451
|
+
computerSize = answers.size;
|
|
452
|
+
}
|
|
453
|
+
if (!opts.json) {
|
|
454
|
+
console.log();
|
|
455
|
+
lineProgress(`Creating computer "${computerName}"...`, opts);
|
|
456
|
+
}
|
|
457
|
+
let computer;
|
|
458
|
+
try {
|
|
459
|
+
const result = await client.apiPost("/api/v1/computers", {
|
|
460
|
+
name: computerName,
|
|
461
|
+
os: computerOs,
|
|
462
|
+
size: computerSize,
|
|
463
|
+
desktop: true,
|
|
464
|
+
});
|
|
465
|
+
computer = result.data;
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
handleError(err);
|
|
469
|
+
}
|
|
470
|
+
// Poll until running or timeout (30s)
|
|
471
|
+
lineProgress("Booting...", opts);
|
|
472
|
+
const deadline = Date.now() + 30_000;
|
|
473
|
+
let finalComputer = computer;
|
|
474
|
+
while (Date.now() < deadline) {
|
|
475
|
+
try {
|
|
476
|
+
const polled = await client.apiGet(`/api/v1/computers/${encodeURIComponent(computer.id)}`);
|
|
477
|
+
finalComputer = polled.data;
|
|
478
|
+
if (finalComputer.state === "running")
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
// poll errors are transient — keep trying
|
|
483
|
+
}
|
|
484
|
+
await sleep(1_000);
|
|
485
|
+
lineProgress(` state: ${finalComputer.state}`, opts);
|
|
486
|
+
}
|
|
487
|
+
const desktopUrl = finalComputer.desktop_url ??
|
|
488
|
+
`https://app.miosa.ai/computers/${finalComputer.id}`;
|
|
489
|
+
if (opts.json) {
|
|
490
|
+
console.log(JSON.stringify({
|
|
491
|
+
id: finalComputer.id,
|
|
492
|
+
name: finalComputer.name,
|
|
493
|
+
state: finalComputer.state,
|
|
494
|
+
url: desktopUrl,
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
console.log();
|
|
499
|
+
console.log(chalk.green(" Ready"));
|
|
500
|
+
console.log();
|
|
501
|
+
console.log(` ${chalk.bold("Desktop:")} ${chalk.cyan(desktopUrl)}`);
|
|
502
|
+
console.log(` ${chalk.bold("SSH:")} ${chalk.dim(`miosa ssh ${finalComputer.id}`)}`);
|
|
503
|
+
console.log();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async function runSandboxMode(client, opts) {
|
|
507
|
+
let sandboxName;
|
|
508
|
+
let sandboxImage;
|
|
509
|
+
if (opts.yes || opts.json) {
|
|
510
|
+
sandboxName = opts.name ?? `sandbox-${Date.now().toString(36)}`;
|
|
511
|
+
sandboxImage = opts.image;
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
const { default: inquirer } = await import("inquirer");
|
|
515
|
+
const answers = await inquirer.prompt([
|
|
516
|
+
{
|
|
517
|
+
type: "input",
|
|
518
|
+
name: "name",
|
|
519
|
+
message: "Sandbox name:",
|
|
520
|
+
default: opts.name ?? "my-sandbox",
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
type: "list",
|
|
524
|
+
name: "image",
|
|
525
|
+
message: "Image:",
|
|
526
|
+
choices: [
|
|
527
|
+
{ name: "miosa-sandbox (default)", value: "miosa-sandbox" },
|
|
528
|
+
{ name: "python-3.12", value: "python-3.12" },
|
|
529
|
+
{ name: "node-20", value: "node-20" },
|
|
530
|
+
{ name: "ubuntu-22.04", value: "ubuntu-22.04" },
|
|
531
|
+
],
|
|
532
|
+
default: opts.image,
|
|
533
|
+
},
|
|
534
|
+
]);
|
|
535
|
+
sandboxName = answers.name;
|
|
536
|
+
sandboxImage = answers.image;
|
|
537
|
+
}
|
|
538
|
+
if (!opts.json) {
|
|
539
|
+
console.log();
|
|
540
|
+
lineProgress(`Creating sandbox "${sandboxName}"...`, opts);
|
|
541
|
+
}
|
|
542
|
+
const start = Date.now();
|
|
543
|
+
let sandbox;
|
|
544
|
+
try {
|
|
545
|
+
const result = await client.apiPost("/api/v1/sandboxes", {
|
|
546
|
+
name: sandboxName,
|
|
547
|
+
template_id: sandboxImage,
|
|
548
|
+
});
|
|
549
|
+
sandbox = result.data;
|
|
550
|
+
}
|
|
551
|
+
catch (err) {
|
|
552
|
+
handleError(err);
|
|
553
|
+
}
|
|
554
|
+
const elapsed = Date.now() - start;
|
|
555
|
+
if (opts.json) {
|
|
556
|
+
console.log(JSON.stringify({
|
|
557
|
+
id: sandbox.id,
|
|
558
|
+
name: sandbox.name,
|
|
559
|
+
state: sandbox.state,
|
|
560
|
+
}));
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
console.log(chalk.green(` Ready (${elapsed}ms)`));
|
|
564
|
+
console.log();
|
|
565
|
+
console.log(` ${chalk.bold("Exec:")} ${chalk.dim(`miosa sandbox exec ${sandbox.id} "python app.py"`)}`);
|
|
566
|
+
console.log(` ${chalk.bold("SSH:")} ${chalk.dim(`miosa sandbox ssh ${sandbox.id}`)}`);
|
|
567
|
+
console.log();
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// ── Mode: interactive disambiguation ─────────────────────────────────────────
|
|
571
|
+
async function runInteractiveDisambiguate(cwd, client, opts) {
|
|
572
|
+
const { default: inquirer } = await import("inquirer");
|
|
573
|
+
console.log();
|
|
574
|
+
console.log(` ${chalk.dim("No project detected in current directory. What would you like to create?")}`);
|
|
575
|
+
console.log();
|
|
576
|
+
const { action } = await inquirer.prompt([
|
|
577
|
+
{
|
|
578
|
+
type: "list",
|
|
579
|
+
name: "action",
|
|
580
|
+
message: "Action:",
|
|
581
|
+
choices: [
|
|
582
|
+
{ name: "Deploy a GitHub repository", value: "deploy" },
|
|
583
|
+
{ name: "Create a desktop computer", value: "computer" },
|
|
584
|
+
{ name: "Create a sandbox", value: "sandbox" },
|
|
585
|
+
],
|
|
586
|
+
},
|
|
587
|
+
]);
|
|
588
|
+
switch (action) {
|
|
589
|
+
case "deploy":
|
|
590
|
+
await runFirstDeploy(cwd, client, opts);
|
|
591
|
+
break;
|
|
592
|
+
case "computer":
|
|
593
|
+
await runComputerMode(client, opts);
|
|
594
|
+
break;
|
|
595
|
+
case "sandbox":
|
|
596
|
+
await runSandboxMode(client, opts);
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// ── Context detection ─────────────────────────────────────────────────────────
|
|
601
|
+
function detectMode(cwd, opts) {
|
|
602
|
+
// Explicit flags override everything
|
|
603
|
+
if (opts.computer)
|
|
604
|
+
return "computer";
|
|
605
|
+
if (opts.sandbox)
|
|
606
|
+
return "sandbox";
|
|
607
|
+
// Existing project config
|
|
608
|
+
if (loadProjectConfig(cwd))
|
|
609
|
+
return "redeploy";
|
|
610
|
+
// Deployable source code present
|
|
611
|
+
const hasPackageJson = fs.existsSync(path.join(cwd, "package.json"));
|
|
612
|
+
const hasMixExs = fs.existsSync(path.join(cwd, "mix.exs"));
|
|
613
|
+
const hasRequirements = fs.existsSync(path.join(cwd, "requirements.txt"));
|
|
614
|
+
const hasDockerfile = fs.existsSync(path.join(cwd, "Dockerfile"));
|
|
615
|
+
const hasGoMod = fs.existsSync(path.join(cwd, "go.mod"));
|
|
616
|
+
const hasCargo = fs.existsSync(path.join(cwd, "Cargo.toml"));
|
|
617
|
+
const hasGemfile = fs.existsSync(path.join(cwd, "Gemfile"));
|
|
618
|
+
const hasIndexHtml = fs.existsSync(path.join(cwd, "index.html"));
|
|
619
|
+
if (hasPackageJson ||
|
|
620
|
+
hasMixExs ||
|
|
621
|
+
hasRequirements ||
|
|
622
|
+
hasDockerfile ||
|
|
623
|
+
hasGoMod ||
|
|
624
|
+
hasCargo ||
|
|
625
|
+
hasGemfile ||
|
|
626
|
+
hasIndexHtml) {
|
|
627
|
+
return "deploy-new";
|
|
628
|
+
}
|
|
629
|
+
return "interactive";
|
|
630
|
+
}
|
|
631
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
632
|
+
function sleep(ms) {
|
|
633
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
634
|
+
}
|
|
635
|
+
// ── register ──────────────────────────────────────────────────────────────────
|
|
636
|
+
export function register(program) {
|
|
637
|
+
program
|
|
638
|
+
.command("up")
|
|
639
|
+
.description("Smart launch: deploy app, create computer, or start sandbox — auto-detected from context")
|
|
640
|
+
.addHelpText("after", `
|
|
641
|
+
Context detection (evaluated in order):
|
|
642
|
+
.miosa.json / miosa.json found → redeploy existing deployment
|
|
643
|
+
--computer flag → create a desktop computer
|
|
644
|
+
--sandbox flag → create a sandbox
|
|
645
|
+
package.json / mix.exs / etc. → detect framework, deploy
|
|
646
|
+
empty directory → interactive mode
|
|
647
|
+
|
|
648
|
+
Examples:
|
|
649
|
+
miosa up Auto-detect and deploy
|
|
650
|
+
miosa up --yes --json --name my-app Scriptable deploy (no prompts)
|
|
651
|
+
miosa up --computer --os ubuntu --size medium Create a desktop computer
|
|
652
|
+
miosa up --sandbox --image python-3.12 Create a Python sandbox
|
|
653
|
+
miosa up --computer --yes --json Scriptable computer creation
|
|
654
|
+
`)
|
|
655
|
+
.option("-y, --yes", "Skip all interactive prompts, use defaults", false)
|
|
656
|
+
.option("--json", "Output machine-readable JSON (implies --yes)", false)
|
|
657
|
+
.option("--name <name>", "Resource name (app, computer, or sandbox)")
|
|
658
|
+
// computer options
|
|
659
|
+
.option("--computer", "Create a desktop computer", false)
|
|
660
|
+
.option("--os <os>", "Operating system for computer mode", "ubuntu")
|
|
661
|
+
.option("--size <size>", "Size: small | medium | large", "small")
|
|
662
|
+
// sandbox options
|
|
663
|
+
.option("--sandbox", "Create a sandbox", false)
|
|
664
|
+
.option("--image <image>", "Sandbox image/template", "miosa-sandbox")
|
|
665
|
+
.action(async (rawOpts) => {
|
|
666
|
+
// --json implies --yes
|
|
667
|
+
const opts = { ...rawOpts, yes: rawOpts.yes || rawOpts.json };
|
|
668
|
+
try {
|
|
669
|
+
const cwd = process.cwd();
|
|
670
|
+
const config = loadConfig();
|
|
671
|
+
const client = new MiosaClient(config);
|
|
672
|
+
const mode = detectMode(cwd, opts);
|
|
673
|
+
switch (mode) {
|
|
674
|
+
case "redeploy": {
|
|
675
|
+
const projectCfg = loadProjectConfig(cwd);
|
|
676
|
+
// projectCfg is guaranteed non-null when mode === "redeploy"
|
|
677
|
+
await runRedeploy(client, projectCfg, opts);
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
case "deploy-new":
|
|
681
|
+
await runFirstDeploy(cwd, client, opts);
|
|
682
|
+
break;
|
|
683
|
+
case "computer":
|
|
684
|
+
await runComputerMode(client, opts);
|
|
685
|
+
break;
|
|
686
|
+
case "sandbox":
|
|
687
|
+
await runSandboxMode(client, opts);
|
|
688
|
+
break;
|
|
689
|
+
case "interactive":
|
|
690
|
+
if (opts.yes || opts.json) {
|
|
691
|
+
// Non-interactive but no context — require explicit flag
|
|
692
|
+
throw new UserError("Cannot determine what to create. Specify --computer, --sandbox, or run from a project directory.", "Examples:\n miosa up --computer\n miosa up --sandbox\n cd my-project && miosa up");
|
|
693
|
+
}
|
|
694
|
+
await runInteractiveDisambiguate(cwd, client, opts);
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
catch (err) {
|
|
699
|
+
handleError(err);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
//# sourceMappingURL=up.js.map
|