@deploid/studio 2.0.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 ADDED
@@ -0,0 +1,20 @@
1
+ # Deploid Studio
2
+
3
+ Desktop GUI for running Deploid commands in a selected project folder.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @deploid/studio
9
+ ```
10
+
11
+ ## Run
12
+
13
+ ```bash
14
+ deploid-studio
15
+ ```
16
+
17
+ ## Notes
18
+
19
+ - Deploid Studio executes `@deploid/cli` commands from the selected working directory.
20
+ - Packaging in Deploid 2.0 is `capacitor` only.
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ const { existsSync } = require('node:fs');
3
+ const { join } = require('node:path');
4
+ const { spawn } = require('node:child_process');
5
+
6
+ const electronPath = require('electron');
7
+ const appMain = join(__dirname, '..', 'dist', 'main.js');
8
+
9
+ if (!existsSync(appMain)) {
10
+ console.error('Deploid Studio is not built yet. Run: pnpm --filter @deploid/studio build');
11
+ process.exit(1);
12
+ }
13
+
14
+ const child = spawn(electronPath, [appMain, ...process.argv.slice(2)], {
15
+ stdio: 'inherit',
16
+ env: process.env
17
+ });
18
+
19
+ child.on('exit', (code) => process.exit(code ?? 0));
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
package/dist/main.js ADDED
@@ -0,0 +1,110 @@
1
+ import { app, BrowserWindow, dialog, ipcMain } from 'electron';
2
+ import { spawn } from 'node:child_process';
3
+ import { createRequire } from 'node:module';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ const require = createRequire(import.meta.url);
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ let mainWindow = null;
10
+ let activeProcess = null;
11
+ const ALLOWED_COMMANDS = new Set([
12
+ 'init',
13
+ 'assets',
14
+ 'package',
15
+ 'build',
16
+ 'deploy',
17
+ 'devices',
18
+ 'logs',
19
+ 'uninstall',
20
+ 'debug',
21
+ 'ios',
22
+ 'ios:handbook',
23
+ 'firebase',
24
+ 'plugin'
25
+ ]);
26
+ function createWindow() {
27
+ mainWindow = new BrowserWindow({
28
+ width: 1000,
29
+ height: 720,
30
+ webPreferences: {
31
+ preload: join(__dirname, 'preload.js'),
32
+ contextIsolation: true,
33
+ nodeIntegration: false
34
+ }
35
+ });
36
+ mainWindow.loadFile(join(__dirname, 'renderer', 'index.html'));
37
+ }
38
+ function sendLog(kind, message) {
39
+ if (!mainWindow)
40
+ return;
41
+ mainWindow.webContents.send('studio:log', { kind, message });
42
+ }
43
+ function resolveCliEntrypoint() {
44
+ return require.resolve('@deploid/cli/dist/index.js');
45
+ }
46
+ function parseCommand(input) {
47
+ return input.trim().split(/\s+/).filter(Boolean);
48
+ }
49
+ ipcMain.handle('studio:choose-project', async () => {
50
+ const result = await dialog.showOpenDialog({
51
+ properties: ['openDirectory']
52
+ });
53
+ if (result.canceled || result.filePaths.length === 0) {
54
+ return null;
55
+ }
56
+ return result.filePaths[0];
57
+ });
58
+ ipcMain.handle('studio:run-command', async (_event, payload) => {
59
+ if (activeProcess) {
60
+ throw new Error('A command is already running');
61
+ }
62
+ if (!payload.cwd) {
63
+ throw new Error('Project folder is required');
64
+ }
65
+ const args = parseCommand(payload.command);
66
+ if (args.length === 0) {
67
+ throw new Error('Command is required');
68
+ }
69
+ if (!ALLOWED_COMMANDS.has(args[0])) {
70
+ throw new Error(`Unsupported command: ${args[0]}`);
71
+ }
72
+ const cliEntrypoint = resolveCliEntrypoint();
73
+ sendLog('system', `$ deploid ${args.join(' ')}\n`);
74
+ activeProcess = spawn(process.execPath, [cliEntrypoint, ...args], {
75
+ cwd: payload.cwd,
76
+ env: process.env
77
+ });
78
+ mainWindow?.webContents.send('studio:state', { running: true });
79
+ activeProcess.stdout.on('data', (chunk) => {
80
+ sendLog('stdout', String(chunk));
81
+ });
82
+ activeProcess.stderr.on('data', (chunk) => {
83
+ sendLog('stderr', String(chunk));
84
+ });
85
+ return await new Promise((resolve) => {
86
+ activeProcess?.on('close', (code) => {
87
+ sendLog('system', `\nProcess exited with code ${String(code)}\n`);
88
+ activeProcess = null;
89
+ mainWindow?.webContents.send('studio:state', { running: false });
90
+ resolve({ code });
91
+ });
92
+ });
93
+ });
94
+ ipcMain.handle('studio:stop-command', async () => {
95
+ if (!activeProcess) {
96
+ return { stopped: false };
97
+ }
98
+ activeProcess.kill('SIGINT');
99
+ return { stopped: true };
100
+ });
101
+ app.whenReady().then(createWindow);
102
+ app.on('window-all-closed', () => {
103
+ if (process.platform !== 'darwin')
104
+ app.quit();
105
+ });
106
+ app.on('activate', () => {
107
+ if (BrowserWindow.getAllWindows().length === 0)
108
+ createWindow();
109
+ });
110
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAuC,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,IAAI,UAAU,GAAyB,IAAI,CAAC;AAC5C,IAAI,aAAa,GAA0C,IAAI,CAAC;AAEhE,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAC/B,MAAM;IACN,QAAQ;IACR,SAAS;IACT,OAAO;IACP,QAAQ;IACR,SAAS;IACT,MAAM;IACN,WAAW;IACX,OAAO;IACP,KAAK;IACL,cAAc;IACd,UAAU;IACV,QAAQ;CACT,CAAC,CAAC;AAEH,SAAS,YAAY;IACnB,UAAU,GAAG,IAAI,aAAa,CAAC;QAC7B,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,GAAG;QACX,cAAc,EAAE;YACd,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC;YACtC,gBAAgB,EAAE,IAAI;YACtB,eAAe,EAAE,KAAK;SACvB;KACF,CAAC,CAAC;IAEH,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,OAAO,CAAC,IAAoC,EAAE,OAAe;IACpE,IAAI,CAAC,UAAU;QAAE,OAAO;IACxB,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,oBAAoB;IAC3B,OAAO,OAAO,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,OAAO,CAAC,MAAM,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;IACjD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC;QACzC,UAAU,EAAE,CAAC,eAAe,CAAC;KAC9B,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,KAAK,EAAE,MAAM,EAAE,OAAyC,EAAE,EAAE;IAC/F,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,aAAa,GAAG,oBAAoB,EAAE,CAAC;IAC7C,OAAO,CAAC,QAAQ,EAAE,aAAa,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEnD,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,EAAE;QAChE,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,EAAE,OAAO,CAAC,GAAG;KACjB,CAAC,CAAC;IAEH,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAEhE,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;QACxC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IACH,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;QACxC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,IAAI,OAAO,CAA0B,CAAC,OAAO,EAAE,EAAE;QAC5D,aAAa,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAClC,OAAO,CAAC,QAAQ,EAAE,8BAA8B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClE,aAAa,GAAG,IAAI,CAAC;YACrB,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YACjE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,MAAM,CAAC,qBAAqB,EAAE,KAAK,IAAI,EAAE;IAC/C,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;IACD,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AAEnC,GAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,GAAG,CAAC,IAAI,EAAE,CAAC;AAChD,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE;IACtB,IAAI,aAAa,CAAC,aAAa,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,YAAY,EAAE,CAAC;AACjE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=preload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../src/preload.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import { contextBridge, ipcRenderer } from 'electron';
2
+ contextBridge.exposeInMainWorld('deploidStudio', {
3
+ chooseProject: () => ipcRenderer.invoke('studio:choose-project'),
4
+ runCommand: (cwd, command) => ipcRenderer.invoke('studio:run-command', { cwd, command }),
5
+ stopCommand: () => ipcRenderer.invoke('studio:stop-command'),
6
+ onLog: (cb) => ipcRenderer.on('studio:log', (_event, payload) => cb(payload)),
7
+ onState: (cb) => ipcRenderer.on('studio:state', (_event, payload) => cb(payload))
8
+ });
9
+ //# sourceMappingURL=preload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preload.js","sourceRoot":"","sources":["../src/preload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEtD,aAAa,CAAC,iBAAiB,CAAC,eAAe,EAAE;IAC/C,aAAa,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,uBAAuB,CAA2B;IAC1F,UAAU,EAAE,CAAC,GAAW,EAAE,OAAe,EAAE,EAAE,CAC3C,WAAW,CAAC,MAAM,CAAC,oBAAoB,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,CAAqC;IAChG,WAAW,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,qBAAqB,CAAkC;IAC7F,KAAK,EAAE,CAAC,EAA8E,EAAE,EAAE,CACxF,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;IAChE,OAAO,EAAE,CAAC,EAAyC,EAAE,EAAE,CACrD,WAAW,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;CACnE,CAAC,CAAC"}
@@ -0,0 +1,114 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Deploid Studio</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0b1210;
10
+ --card: #15241f;
11
+ --line: #254037;
12
+ --text: #e7fff3;
13
+ --muted: #9ec8b6;
14
+ --accent: #56d9a1;
15
+ }
16
+ body {
17
+ margin: 0;
18
+ font-family: "Segoe UI", Arial, sans-serif;
19
+ background: radial-gradient(circle at top right, #133226, var(--bg) 50%);
20
+ color: var(--text);
21
+ }
22
+ .wrap {
23
+ max-width: 980px;
24
+ margin: 24px auto;
25
+ padding: 0 16px;
26
+ }
27
+ .card {
28
+ background: linear-gradient(180deg, #1a2f29, var(--card));
29
+ border: 1px solid var(--line);
30
+ border-radius: 10px;
31
+ padding: 16px;
32
+ }
33
+ .row {
34
+ display: flex;
35
+ gap: 12px;
36
+ align-items: center;
37
+ flex-wrap: wrap;
38
+ }
39
+ input, select, button {
40
+ border-radius: 8px;
41
+ border: 1px solid var(--line);
42
+ background: #0f1d19;
43
+ color: var(--text);
44
+ padding: 10px 12px;
45
+ }
46
+ input {
47
+ flex: 1;
48
+ min-width: 320px;
49
+ }
50
+ button {
51
+ cursor: pointer;
52
+ }
53
+ button.primary {
54
+ background: var(--accent);
55
+ color: #062517;
56
+ border: none;
57
+ font-weight: 700;
58
+ }
59
+ #logs {
60
+ margin-top: 14px;
61
+ background: #07110e;
62
+ border: 1px solid var(--line);
63
+ border-radius: 8px;
64
+ height: 430px;
65
+ overflow: auto;
66
+ padding: 12px;
67
+ white-space: pre-wrap;
68
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
69
+ font-size: 12px;
70
+ }
71
+ .muted {
72
+ color: var(--muted);
73
+ font-size: 12px;
74
+ }
75
+ @media (max-width: 640px) {
76
+ input {
77
+ min-width: 100%;
78
+ }
79
+ }
80
+ </style>
81
+ </head>
82
+ <body>
83
+ <div class="wrap">
84
+ <div class="card">
85
+ <h2>Deploid Studio</h2>
86
+ <p class="muted">Run Deploid commands in a selected project folder.</p>
87
+ <div class="row">
88
+ <input id="cwd" type="text" placeholder="Project folder..." />
89
+ <button id="pick">Choose Folder</button>
90
+ </div>
91
+ <div class="row" style="margin-top: 10px;">
92
+ <select id="cmd">
93
+ <option value="init">init</option>
94
+ <option value="assets">assets</option>
95
+ <option value="package">package</option>
96
+ <option value="build">build</option>
97
+ <option value="deploy">deploy</option>
98
+ <option value="devices">devices</option>
99
+ <option value="logs">logs</option>
100
+ <option value="uninstall">uninstall</option>
101
+ <option value="debug">debug</option>
102
+ <option value="ios">ios</option>
103
+ <option value="ios:handbook">ios:handbook</option>
104
+ </select>
105
+ <button id="run" class="primary">Run Command</button>
106
+ <button id="stop">Stop</button>
107
+ </div>
108
+ <div id="status" class="muted" style="margin-top: 10px;">Idle</div>
109
+ <div id="logs"></div>
110
+ </div>
111
+ </div>
112
+ <script src="./renderer.js"></script>
113
+ </body>
114
+ </html>
@@ -0,0 +1,44 @@
1
+ const cwdInput = document.getElementById('cwd');
2
+ const pickButton = document.getElementById('pick');
3
+ const runButton = document.getElementById('run');
4
+ const stopButton = document.getElementById('stop');
5
+ const cmdSelect = document.getElementById('cmd');
6
+ const logs = document.getElementById('logs');
7
+ const status = document.getElementById('status');
8
+
9
+ function appendLog(text) {
10
+ logs.textContent += text;
11
+ logs.scrollTop = logs.scrollHeight;
12
+ }
13
+
14
+ pickButton.addEventListener('click', async () => {
15
+ const folder = await window.deploidStudio.chooseProject();
16
+ if (folder) cwdInput.value = folder;
17
+ });
18
+
19
+ runButton.addEventListener('click', async () => {
20
+ const cwd = cwdInput.value.trim();
21
+ if (!cwd) {
22
+ appendLog('Select a project folder first.\n');
23
+ return;
24
+ }
25
+ const command = cmdSelect.value;
26
+ try {
27
+ await window.deploidStudio.runCommand(cwd, command);
28
+ } catch (error) {
29
+ appendLog(`Error: ${error.message}\n`);
30
+ }
31
+ });
32
+
33
+ stopButton.addEventListener('click', async () => {
34
+ await window.deploidStudio.stopCommand();
35
+ });
36
+
37
+ window.deploidStudio.onLog((entry) => {
38
+ appendLog(entry.message);
39
+ });
40
+
41
+ window.deploidStudio.onState((state) => {
42
+ status.textContent = state.running ? 'Running...' : 'Idle';
43
+ runButton.disabled = state.running;
44
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@deploid/studio",
3
+ "version": "2.0.0",
4
+ "description": "Desktop GUI for running Deploid workflows in a project folder",
5
+ "private": false,
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "main": "dist/main.js",
11
+ "bin": {
12
+ "deploid-studio": "bin/deploid-studio.cjs"
13
+ },
14
+ "files": [
15
+ "dist/",
16
+ "bin/",
17
+ "renderer/",
18
+ "README.md"
19
+ ],
20
+ "dependencies": {
21
+ "electron": "^31.7.7",
22
+ "@deploid/cli": "2.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^24.9.1"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc -b && node ./scripts/copy-renderer.mjs",
29
+ "dev": "pnpm build && node ./bin/deploid-studio.cjs"
30
+ }
31
+ }
@@ -0,0 +1,114 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Deploid Studio</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0b1210;
10
+ --card: #15241f;
11
+ --line: #254037;
12
+ --text: #e7fff3;
13
+ --muted: #9ec8b6;
14
+ --accent: #56d9a1;
15
+ }
16
+ body {
17
+ margin: 0;
18
+ font-family: "Segoe UI", Arial, sans-serif;
19
+ background: radial-gradient(circle at top right, #133226, var(--bg) 50%);
20
+ color: var(--text);
21
+ }
22
+ .wrap {
23
+ max-width: 980px;
24
+ margin: 24px auto;
25
+ padding: 0 16px;
26
+ }
27
+ .card {
28
+ background: linear-gradient(180deg, #1a2f29, var(--card));
29
+ border: 1px solid var(--line);
30
+ border-radius: 10px;
31
+ padding: 16px;
32
+ }
33
+ .row {
34
+ display: flex;
35
+ gap: 12px;
36
+ align-items: center;
37
+ flex-wrap: wrap;
38
+ }
39
+ input, select, button {
40
+ border-radius: 8px;
41
+ border: 1px solid var(--line);
42
+ background: #0f1d19;
43
+ color: var(--text);
44
+ padding: 10px 12px;
45
+ }
46
+ input {
47
+ flex: 1;
48
+ min-width: 320px;
49
+ }
50
+ button {
51
+ cursor: pointer;
52
+ }
53
+ button.primary {
54
+ background: var(--accent);
55
+ color: #062517;
56
+ border: none;
57
+ font-weight: 700;
58
+ }
59
+ #logs {
60
+ margin-top: 14px;
61
+ background: #07110e;
62
+ border: 1px solid var(--line);
63
+ border-radius: 8px;
64
+ height: 430px;
65
+ overflow: auto;
66
+ padding: 12px;
67
+ white-space: pre-wrap;
68
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
69
+ font-size: 12px;
70
+ }
71
+ .muted {
72
+ color: var(--muted);
73
+ font-size: 12px;
74
+ }
75
+ @media (max-width: 640px) {
76
+ input {
77
+ min-width: 100%;
78
+ }
79
+ }
80
+ </style>
81
+ </head>
82
+ <body>
83
+ <div class="wrap">
84
+ <div class="card">
85
+ <h2>Deploid Studio</h2>
86
+ <p class="muted">Run Deploid commands in a selected project folder.</p>
87
+ <div class="row">
88
+ <input id="cwd" type="text" placeholder="Project folder..." />
89
+ <button id="pick">Choose Folder</button>
90
+ </div>
91
+ <div class="row" style="margin-top: 10px;">
92
+ <select id="cmd">
93
+ <option value="init">init</option>
94
+ <option value="assets">assets</option>
95
+ <option value="package">package</option>
96
+ <option value="build">build</option>
97
+ <option value="deploy">deploy</option>
98
+ <option value="devices">devices</option>
99
+ <option value="logs">logs</option>
100
+ <option value="uninstall">uninstall</option>
101
+ <option value="debug">debug</option>
102
+ <option value="ios">ios</option>
103
+ <option value="ios:handbook">ios:handbook</option>
104
+ </select>
105
+ <button id="run" class="primary">Run Command</button>
106
+ <button id="stop">Stop</button>
107
+ </div>
108
+ <div id="status" class="muted" style="margin-top: 10px;">Idle</div>
109
+ <div id="logs"></div>
110
+ </div>
111
+ </div>
112
+ <script src="./renderer.js"></script>
113
+ </body>
114
+ </html>
@@ -0,0 +1,44 @@
1
+ const cwdInput = document.getElementById('cwd');
2
+ const pickButton = document.getElementById('pick');
3
+ const runButton = document.getElementById('run');
4
+ const stopButton = document.getElementById('stop');
5
+ const cmdSelect = document.getElementById('cmd');
6
+ const logs = document.getElementById('logs');
7
+ const status = document.getElementById('status');
8
+
9
+ function appendLog(text) {
10
+ logs.textContent += text;
11
+ logs.scrollTop = logs.scrollHeight;
12
+ }
13
+
14
+ pickButton.addEventListener('click', async () => {
15
+ const folder = await window.deploidStudio.chooseProject();
16
+ if (folder) cwdInput.value = folder;
17
+ });
18
+
19
+ runButton.addEventListener('click', async () => {
20
+ const cwd = cwdInput.value.trim();
21
+ if (!cwd) {
22
+ appendLog('Select a project folder first.\n');
23
+ return;
24
+ }
25
+ const command = cmdSelect.value;
26
+ try {
27
+ await window.deploidStudio.runCommand(cwd, command);
28
+ } catch (error) {
29
+ appendLog(`Error: ${error.message}\n`);
30
+ }
31
+ });
32
+
33
+ stopButton.addEventListener('click', async () => {
34
+ await window.deploidStudio.stopCommand();
35
+ });
36
+
37
+ window.deploidStudio.onLog((entry) => {
38
+ appendLog(entry.message);
39
+ });
40
+
41
+ window.deploidStudio.onState((state) => {
42
+ status.textContent = state.running ? 'Running...' : 'Idle';
43
+ runButton.disabled = state.running;
44
+ });