@naviyra/cloud-panel 0.0.3-beta2 → 0.0.3-beta3

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.
@@ -1,214 +1,214 @@
1
- import { spawn } from "node:child_process";
2
- import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
- import path from "node:path";
4
- import {
5
- TASK_NAME,
6
- panelOrigin,
7
- removeBootTask,
8
- runPowerShell,
9
- startBootTask,
10
- stopBootTask,
11
- } from "./windows-boot.mjs";
12
-
13
- export const COMMANDS = ["status", "open", "stop", "start", "update", "uninstall"];
14
-
15
- export function isProtectedDir(dir) {
16
- const resolved = path.resolve(dir);
17
- const roots = [
18
- process.env.WINDIR,
19
- process.env.ProgramFiles,
20
- process.env["ProgramFiles(x86)"],
21
- ].filter(Boolean);
22
- return roots.some((root) => {
23
- const base = path.resolve(root);
24
- const prefix = base.toLowerCase() + path.sep;
25
- return resolved.toLowerCase().startsWith(prefix) || resolved.toLowerCase() === base.toLowerCase();
26
- });
27
- }
28
-
29
- export function canRemoveInstall(dir) {
30
- const resolved = path.resolve(dir);
31
- const base = path.basename(resolved).toLowerCase();
32
- if (base === "system32" || base === "windows" || base === "syswow64") return false;
33
- const windir = path.resolve(process.env.WINDIR || "C:\\Windows");
34
- return resolved.toLowerCase() !== windir.toLowerCase();
35
- }
36
-
37
- export function problemFromLog(text) {
38
- const lines = String(text)
39
- .split(/\r?\n/)
40
- .map((line) => line.trim())
41
- .filter(Boolean);
42
- if (lines.some((line) => /permission denied/i.test(line)))
43
- return "PostgreSQL could not create its data directory because this folder is protected. Install into an empty folder you own, such as D:\\naviyra-install.";
44
- return lines.findLast((line) => /could not start|exited 1|error:/i.test(line)) || "";
45
- }
46
-
47
- export function formatStatus({ dir, taskState, origin, ready, problem }) {
48
- if (!dir && (!taskState || taskState === "missing"))
49
- return "Naviyra is not installed. From an empty folder you own, run: npx @naviyra/cloud-panel";
50
- const lines = [
51
- ready ? "Naviyra is running." : "Naviyra is not ready.",
52
- `Panel: ${origin || "http://127.0.0.1:3000"}`,
53
- ];
54
- if (dir) lines.push(`Install folder: ${dir}`);
55
- lines.push(`Startup task: ${taskState || "not registered"}`);
56
- if (!ready && problem) lines.push(problem);
57
- lines.push(
58
- ready
59
- ? "Open the panel with: npx @naviyra/cloud-panel open"
60
- : "After it is ready, open it with: npx @naviyra/cloud-panel open",
61
- );
62
- return lines.join("\n");
63
- }
64
-
65
- async function isPanel(dir) {
66
- try {
67
- const pkg = JSON.parse(await readFile(path.join(dir, "package.json"), "utf8"));
68
- await access(path.join(dir, "apps", "panel", "package.json"));
69
- return pkg.name === "@naviyra/cloud-panel";
70
- } catch {
71
- return false;
72
- }
73
- }
74
-
75
- export async function queryTask(run = runPowerShell) {
76
- const result = await run(`$task = Get-ScheduledTask -TaskName ${"'" + TASK_NAME.replaceAll("'", "''") + "'"} -ErrorAction SilentlyContinue
77
- if (-not $task) { Write-Output 'STATE=missing'; exit 0 }
78
- Write-Output ('STATE=' + $task.State)
79
- Write-Output ('DIR=' + $task.Actions[0].WorkingDirectory)
80
- `);
81
- const state = result.output.match(/STATE=(.*)/)?.[1]?.trim() || "missing";
82
- const dir = result.output.match(/DIR=(.*)/)?.[1]?.trim() || "";
83
- return { state, dir };
84
- }
85
-
86
- export async function panelHealthy(origin) {
87
- try {
88
- const response = await fetch(`${origin}/api/me`, {
89
- redirect: "manual",
90
- signal: AbortSignal.timeout(2000),
91
- });
92
- const body = await response.json();
93
- return response.status === 401 && body.error === "Please sign in";
94
- } catch {
95
- return false;
96
- }
97
- }
98
-
99
- function openBrowser(origin) {
100
- const file = process.platform === "win32" ? "explorer.exe" : "xdg-open";
101
- const browser = spawn(file, [origin], { windowsHide: true, stdio: "ignore" });
102
- browser.once("error", () => console.log(`Open ${origin} in your browser.`));
103
- browser.unref();
104
- }
105
-
106
- async function locate(options) {
107
- const task = options.queryTask ? await options.queryTask() : await queryTask(options.runPowerShell);
108
- if (task.dir && (await isPanel(task.dir))) return { ...task, dir: task.dir };
109
- if (options.cwd && (await isPanel(options.cwd)))
110
- return { state: task.state, dir: options.cwd };
111
- return { state: task.state || "missing", dir: "" };
112
- }
113
-
114
- async function readStatus(dir, taskState) {
115
- let envText = "";
116
- let logText = "";
117
- if (dir) {
118
- try {
119
- envText = await readFile(path.join(dir, ".env"), "utf8");
120
- } catch {
121
- envText = "";
122
- }
123
- try {
124
- logText = await readFile(path.join(dir, ".runtime", "service.log"), "utf8");
125
- } catch {
126
- logText = "";
127
- }
128
- }
129
- const origin = panelOrigin(envText);
130
- const ready = await panelHealthy(origin);
131
- return {
132
- dir,
133
- taskState,
134
- origin,
135
- ready,
136
- problem: ready ? "" : problemFromLog(logText),
137
- };
138
- }
139
-
140
- export async function linkBin(root) {
141
- const binDir = path.join(root, "node_modules", ".bin");
142
- await mkdir(binDir, { recursive: true });
143
- await writeFile(
144
- path.join(binDir, "cloud-panel.cmd"),
145
- '@ECHO off\r\nnode "%~dp0\\..\\..\\bin\\naviyra.mjs" %*\r\n',
146
- );
147
- await writeFile(
148
- path.join(binDir, "cloud-panel.ps1"),
149
- [
150
- "$node = (Get-Command node -ErrorAction Stop).Source",
151
- '& $node (Join-Path $PSScriptRoot "..\\..\\bin\\naviyra.mjs") @args',
152
- "exit $LASTEXITCODE",
153
- "",
154
- ].join("\n"),
155
- );
156
- }
157
-
158
- export async function execute(command, options = {}) {
159
- const located = await locate(options);
160
- if (command === "status") {
161
- console.log(formatStatus(await readStatus(located.dir, located.state)));
162
- return;
163
- }
164
- if (!located.dir)
165
- throw Error("Naviyra is not installed. From an empty folder you own, run: npx @naviyra/cloud-panel");
166
- const root = located.dir;
167
- const run = options.runPowerShell;
168
- if (command === "stop") {
169
- const stopped = await stopBootTask({ root, runPowerShell: run });
170
- if (!stopped.ok) throw Error(stopped.message || "Could not stop Naviyra.");
171
- console.log("Naviyra is stopped.");
172
- return;
173
- }
174
- if (command === "start") {
175
- const started = await startBootTask(run);
176
- if (!started.ok) throw Error(started.message || "Could not start Naviyra.");
177
- console.log("Naviyra is starting. Check it with: npx @naviyra/cloud-panel status");
178
- return;
179
- }
180
- if (command === "open") {
181
- const status = await readStatus(root, located.state);
182
- if (!status.ready) {
183
- console.log(formatStatus(status));
184
- throw Error("The panel is not ready to open yet.");
185
- }
186
- openBrowser(status.origin);
187
- console.log(`Opened ${status.origin}`);
188
- return;
189
- }
190
- if (command === "uninstall") {
191
- const removed = await removeBootTask({ root, runPowerShell: run });
192
- if (!removed.ok) throw Error(removed.message || "Could not remove the startup task.");
193
- if (canRemoveInstall(root) && (await isPanel(root))) {
194
- await rm(root, { recursive: true, force: true });
195
- console.log(`Uninstalled Naviyra and removed ${root}`);
196
- return;
197
- }
198
- console.log(`${removed.message} The install folder was left at ${root}`);
199
- return;
200
- }
201
- if (command === "update") {
202
- if (!options.source) throw Error("Cannot find the new Naviyra package to copy.");
203
- if (path.resolve(options.source) === path.resolve(root))
204
- throw Error("Run npx @naviyra/cloud-panel update so the newest package can replace this install.");
205
- await options.copy(options.source, root);
206
- await options.install(root);
207
- await linkBin(root);
208
- const started = await startBootTask(run);
209
- if (!started.ok) throw Error(started.message || "Update copied, but Naviyra did not start.");
210
- console.log(`Updated Naviyra in ${root}. Check it with: npx @naviyra/cloud-panel status`);
211
- return;
212
- }
213
- throw Error(`Unknown command: ${command}`);
214
- }
1
+ import { spawn } from "node:child_process";
2
+ import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import {
5
+ TASK_NAME,
6
+ panelOrigin,
7
+ removeBootTask,
8
+ runPowerShell,
9
+ startBootTask,
10
+ stopBootTask,
11
+ } from "./windows-boot.mjs";
12
+
13
+ export const COMMANDS = ["status", "open", "stop", "start", "update", "uninstall"];
14
+
15
+ export function isProtectedDir(dir) {
16
+ const resolved = path.resolve(dir);
17
+ const roots = [
18
+ process.env.WINDIR,
19
+ process.env.ProgramFiles,
20
+ process.env["ProgramFiles(x86)"],
21
+ ].filter(Boolean);
22
+ return roots.some((root) => {
23
+ const base = path.resolve(root);
24
+ const prefix = base.toLowerCase() + path.sep;
25
+ return resolved.toLowerCase().startsWith(prefix) || resolved.toLowerCase() === base.toLowerCase();
26
+ });
27
+ }
28
+
29
+ export function canRemoveInstall(dir) {
30
+ const resolved = path.resolve(dir);
31
+ const base = path.basename(resolved).toLowerCase();
32
+ if (base === "system32" || base === "windows" || base === "syswow64") return false;
33
+ const windir = path.resolve(process.env.WINDIR || "C:\\Windows");
34
+ return resolved.toLowerCase() !== windir.toLowerCase();
35
+ }
36
+
37
+ export function problemFromLog(text) {
38
+ const lines = String(text)
39
+ .split(/\r?\n/)
40
+ .map((line) => line.trim())
41
+ .filter(Boolean);
42
+ if (lines.some((line) => /permission denied/i.test(line)))
43
+ return "PostgreSQL could not create its data directory because this folder is protected. Install into an empty folder you own, such as D:\\naviyra-install.";
44
+ return lines.findLast((line) => /could not start|exited 1|error:/i.test(line)) || "";
45
+ }
46
+
47
+ export function formatStatus({ dir, taskState, origin, ready, problem }) {
48
+ if (!dir && (!taskState || taskState === "missing"))
49
+ return "Naviyra is not installed. From an empty folder you own, run: npx @naviyra/cloud-panel";
50
+ const lines = [
51
+ ready ? "Naviyra is running." : "Naviyra is not ready.",
52
+ `Panel: ${origin || "http://127.0.0.1:3000"}`,
53
+ ];
54
+ if (dir) lines.push(`Install folder: ${dir}`);
55
+ lines.push(`Startup task: ${taskState || "not registered"}`);
56
+ if (!ready && problem) lines.push(problem);
57
+ lines.push(
58
+ ready
59
+ ? "Open the panel with: npx @naviyra/cloud-panel open"
60
+ : "After it is ready, open it with: npx @naviyra/cloud-panel open",
61
+ );
62
+ return lines.join("\n");
63
+ }
64
+
65
+ async function isPanel(dir) {
66
+ try {
67
+ const pkg = JSON.parse(await readFile(path.join(dir, "package.json"), "utf8"));
68
+ await access(path.join(dir, "apps", "panel", "package.json"));
69
+ return pkg.name === "@naviyra/cloud-panel";
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ export async function queryTask(run = runPowerShell) {
76
+ const result = await run(`$task = Get-ScheduledTask -TaskName ${"'" + TASK_NAME.replaceAll("'", "''") + "'"} -ErrorAction SilentlyContinue
77
+ if (-not $task) { Write-Output 'STATE=missing'; exit 0 }
78
+ Write-Output ('STATE=' + $task.State)
79
+ Write-Output ('DIR=' + $task.Actions[0].WorkingDirectory)
80
+ `);
81
+ const state = result.output.match(/STATE=(.*)/)?.[1]?.trim() || "missing";
82
+ const dir = result.output.match(/DIR=(.*)/)?.[1]?.trim() || "";
83
+ return { state, dir };
84
+ }
85
+
86
+ export async function panelHealthy(origin) {
87
+ try {
88
+ const response = await fetch(`${origin}/api/me`, {
89
+ redirect: "manual",
90
+ signal: AbortSignal.timeout(2000),
91
+ });
92
+ const body = await response.json();
93
+ return response.status === 401 && body.error === "Please sign in";
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+
99
+ function openBrowser(origin) {
100
+ const file = process.platform === "win32" ? "explorer.exe" : "xdg-open";
101
+ const browser = spawn(file, [origin], { windowsHide: true, stdio: "ignore" });
102
+ browser.once("error", () => console.log(`Open ${origin} in your browser.`));
103
+ browser.unref();
104
+ }
105
+
106
+ async function locate(options) {
107
+ const task = options.queryTask ? await options.queryTask() : await queryTask(options.runPowerShell);
108
+ if (task.dir && (await isPanel(task.dir))) return { ...task, dir: task.dir };
109
+ if (options.cwd && (await isPanel(options.cwd)))
110
+ return { state: task.state, dir: options.cwd };
111
+ return { state: task.state || "missing", dir: "" };
112
+ }
113
+
114
+ async function readStatus(dir, taskState) {
115
+ let envText = "";
116
+ let logText = "";
117
+ if (dir) {
118
+ try {
119
+ envText = await readFile(path.join(dir, ".env"), "utf8");
120
+ } catch {
121
+ envText = "";
122
+ }
123
+ try {
124
+ logText = await readFile(path.join(dir, ".runtime", "service.log"), "utf8");
125
+ } catch {
126
+ logText = "";
127
+ }
128
+ }
129
+ const origin = panelOrigin(envText);
130
+ const ready = await panelHealthy(origin);
131
+ return {
132
+ dir,
133
+ taskState,
134
+ origin,
135
+ ready,
136
+ problem: ready ? "" : problemFromLog(logText),
137
+ };
138
+ }
139
+
140
+ export async function linkBin(root) {
141
+ const binDir = path.join(root, "node_modules", ".bin");
142
+ await mkdir(binDir, { recursive: true });
143
+ await writeFile(
144
+ path.join(binDir, "cloud-panel.cmd"),
145
+ '@ECHO off\r\nnode "%~dp0\\..\\..\\bin\\naviyra.mjs" %*\r\n',
146
+ );
147
+ await writeFile(
148
+ path.join(binDir, "cloud-panel.ps1"),
149
+ [
150
+ "$node = (Get-Command node -ErrorAction Stop).Source",
151
+ '& $node (Join-Path $PSScriptRoot "..\\..\\bin\\naviyra.mjs") @args',
152
+ "exit $LASTEXITCODE",
153
+ "",
154
+ ].join("\n"),
155
+ );
156
+ }
157
+
158
+ export async function execute(command, options = {}) {
159
+ const located = await locate(options);
160
+ if (command === "status") {
161
+ console.log(formatStatus(await readStatus(located.dir, located.state)));
162
+ return;
163
+ }
164
+ if (!located.dir)
165
+ throw Error("Naviyra is not installed. From an empty folder you own, run: npx @naviyra/cloud-panel");
166
+ const root = located.dir;
167
+ const run = options.runPowerShell;
168
+ if (command === "stop") {
169
+ const stopped = await stopBootTask({ root, runPowerShell: run });
170
+ if (!stopped.ok) throw Error(stopped.message || "Could not stop Naviyra.");
171
+ console.log("Naviyra is stopped.");
172
+ return;
173
+ }
174
+ if (command === "start") {
175
+ const started = await startBootTask(run);
176
+ if (!started.ok) throw Error(started.message || "Could not start Naviyra.");
177
+ console.log("Naviyra is starting. Check it with: npx @naviyra/cloud-panel status");
178
+ return;
179
+ }
180
+ if (command === "open") {
181
+ const status = await readStatus(root, located.state);
182
+ if (!status.ready) {
183
+ console.log(formatStatus(status));
184
+ throw Error("The panel is not ready to open yet.");
185
+ }
186
+ openBrowser(status.origin);
187
+ console.log(`Opened ${status.origin}`);
188
+ return;
189
+ }
190
+ if (command === "uninstall") {
191
+ const removed = await removeBootTask({ root, runPowerShell: run });
192
+ if (!removed.ok) throw Error(removed.message || "Could not remove the startup task.");
193
+ if (canRemoveInstall(root) && (await isPanel(root))) {
194
+ await rm(root, { recursive: true, force: true });
195
+ console.log(`Uninstalled Naviyra and removed ${root}`);
196
+ return;
197
+ }
198
+ console.log(`${removed.message} The install folder was left at ${root}`);
199
+ return;
200
+ }
201
+ if (command === "update") {
202
+ if (!options.source) throw Error("Cannot find the new Naviyra package to copy.");
203
+ if (path.resolve(options.source) === path.resolve(root))
204
+ throw Error("Run npx @naviyra/cloud-panel update so the newest package can replace this install.");
205
+ await options.copy(options.source, root);
206
+ await options.install(root);
207
+ await linkBin(root);
208
+ const started = await startBootTask(run);
209
+ if (!started.ok) throw Error(started.message || "Update copied, but Naviyra did not start.");
210
+ console.log(`Updated Naviyra in ${root}. Check it with: npx @naviyra/cloud-panel status`);
211
+ return;
212
+ }
213
+ throw Error(`Unknown command: ${command}`);
214
+ }