nstantpage-agent 0.5.34 → 0.6.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/dist/cli.bundle.js +210 -0
- package/dist/cli.js +9 -1
- package/dist/commands/service.js +5 -1
- package/dist/commands/start.js +9 -2
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +73 -0
- package/dist/fileManager.d.ts +11 -0
- package/dist/fileManager.js +44 -0
- package/dist/localServer.d.ts +3 -0
- package/dist/localServer.js +49 -1
- package/dist/packageInstaller.js +14 -1
- package/dist/statusServer.js +643 -2
- package/dist/tunnel.js +2 -1
- package/dist/version.d.ts +5 -0
- package/dist/version.js +34 -0
- package/package.json +3 -1
- package/scripts/postinstall.mjs +190 -0
package/dist/cli.bundle.js
CHANGED
|
@@ -24426,6 +24426,48 @@ var FileManager = class {
|
|
|
24426
24426
|
getProjectDir() {
|
|
24427
24427
|
return this.projectDir;
|
|
24428
24428
|
}
|
|
24429
|
+
/**
|
|
24430
|
+
* Build a recursive file tree of the project directory.
|
|
24431
|
+
* Returns a structure matching the backend's /api/projects/{id}/tree format.
|
|
24432
|
+
*/
|
|
24433
|
+
async getFileTree() {
|
|
24434
|
+
const root = {
|
|
24435
|
+
name: path8.basename(this.projectDir),
|
|
24436
|
+
path: "",
|
|
24437
|
+
isDirectory: true,
|
|
24438
|
+
children: []
|
|
24439
|
+
};
|
|
24440
|
+
if (existsSync(this.projectDir)) {
|
|
24441
|
+
root.children = await this.buildTree(this.projectDir, "");
|
|
24442
|
+
}
|
|
24443
|
+
return root;
|
|
24444
|
+
}
|
|
24445
|
+
async buildTree(dir, relativePath) {
|
|
24446
|
+
const SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".vite-cache", ".next", "__pycache__", ".turbo", ".cache"]);
|
|
24447
|
+
const entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
24448
|
+
const result = [];
|
|
24449
|
+
const sorted = entries.sort((a, b) => {
|
|
24450
|
+
if (a.isDirectory() && !b.isDirectory())
|
|
24451
|
+
return -1;
|
|
24452
|
+
if (!a.isDirectory() && b.isDirectory())
|
|
24453
|
+
return 1;
|
|
24454
|
+
return a.name.localeCompare(b.name);
|
|
24455
|
+
});
|
|
24456
|
+
for (const entry of sorted) {
|
|
24457
|
+
if (entry.name.startsWith(".") && entry.name !== ".env")
|
|
24458
|
+
continue;
|
|
24459
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
24460
|
+
if (entry.isDirectory()) {
|
|
24461
|
+
if (SKIP_DIRS3.has(entry.name))
|
|
24462
|
+
continue;
|
|
24463
|
+
const children = await this.buildTree(path8.join(dir, entry.name), entryRelPath);
|
|
24464
|
+
result.push({ name: entry.name, path: entryRelPath, isDirectory: true, children });
|
|
24465
|
+
} else {
|
|
24466
|
+
result.push({ name: entry.name, path: entryRelPath, isDirectory: false, children: [] });
|
|
24467
|
+
}
|
|
24468
|
+
}
|
|
24469
|
+
return result;
|
|
24470
|
+
}
|
|
24429
24471
|
/**
|
|
24430
24472
|
* Check if a path exists.
|
|
24431
24473
|
*/
|
|
@@ -26033,6 +26075,9 @@ Access-Control-Allow-Origin: *\r
|
|
|
26033
26075
|
"/live/push-to-backend": this.handlePushToBackend,
|
|
26034
26076
|
"/live/pull-from-backend": this.handlePullFromBackend,
|
|
26035
26077
|
"/live/disk-file": this.handleDiskFile,
|
|
26078
|
+
"/live/tree": this.handleTree,
|
|
26079
|
+
"/live/file-content": this.handleFileContent,
|
|
26080
|
+
"/live/save-file": this.handleSaveFile,
|
|
26036
26081
|
"/health": this.handleHealth
|
|
26037
26082
|
};
|
|
26038
26083
|
if (handlers[path17])
|
|
@@ -26834,6 +26879,47 @@ Access-Control-Allow-Origin: *\r
|
|
|
26834
26879
|
this.json(res, { success: false, error: error.message }, 500);
|
|
26835
26880
|
}
|
|
26836
26881
|
}
|
|
26882
|
+
// ─── /live/tree ──────────────────────────────────────────────
|
|
26883
|
+
async handleTree(_req, res) {
|
|
26884
|
+
try {
|
|
26885
|
+
const tree = await this.fileManager.getFileTree();
|
|
26886
|
+
this.json(res, tree);
|
|
26887
|
+
} catch (error) {
|
|
26888
|
+
this.json(res, { error: error.message }, 500);
|
|
26889
|
+
}
|
|
26890
|
+
}
|
|
26891
|
+
// ─── /live/file-content ──────────────────────────────────────
|
|
26892
|
+
async handleFileContent(_req, res, _body, url) {
|
|
26893
|
+
const filePath = url.searchParams.get("path");
|
|
26894
|
+
if (!filePath) {
|
|
26895
|
+
this.json(res, { error: "Missing path parameter" }, 400);
|
|
26896
|
+
return;
|
|
26897
|
+
}
|
|
26898
|
+
try {
|
|
26899
|
+
const content = await this.fileManager.readFile(filePath);
|
|
26900
|
+
if (content === null) {
|
|
26901
|
+
this.json(res, { error: "File not found" }, 404);
|
|
26902
|
+
return;
|
|
26903
|
+
}
|
|
26904
|
+
this.json(res, { content });
|
|
26905
|
+
} catch (error) {
|
|
26906
|
+
this.json(res, { error: error.message }, 500);
|
|
26907
|
+
}
|
|
26908
|
+
}
|
|
26909
|
+
// ─── /live/save-file ────────────────────────────────────────
|
|
26910
|
+
async handleSaveFile(_req, res, body) {
|
|
26911
|
+
try {
|
|
26912
|
+
const { filePath, content } = JSON.parse(body);
|
|
26913
|
+
if (!filePath || content === void 0) {
|
|
26914
|
+
this.json(res, { error: "filePath and content required" }, 400);
|
|
26915
|
+
return;
|
|
26916
|
+
}
|
|
26917
|
+
await this.fileManager.writeFiles({ [filePath]: content });
|
|
26918
|
+
this.json(res, { success: true, message: "File saved" });
|
|
26919
|
+
} catch (error) {
|
|
26920
|
+
this.json(res, { error: error.message }, 500);
|
|
26921
|
+
}
|
|
26922
|
+
}
|
|
26837
26923
|
// ─── /health ─────────────────────────────────────────────────
|
|
26838
26924
|
async handleHealth(_req, res) {
|
|
26839
26925
|
this.json(res, {
|
|
@@ -27583,6 +27669,7 @@ var StatusServer = class {
|
|
|
27583
27669
|
this.server = http5.createServer((req, res) => {
|
|
27584
27670
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
27585
27671
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
27672
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
27586
27673
|
res.setHeader("Content-Type", "application/json");
|
|
27587
27674
|
if (req.method === "OPTIONS") {
|
|
27588
27675
|
res.writeHead(204);
|
|
@@ -27642,6 +27729,85 @@ var StatusServer = class {
|
|
|
27642
27729
|
});
|
|
27643
27730
|
return;
|
|
27644
27731
|
}
|
|
27732
|
+
const parsedUrl = new URL(req.url || "/", `http://localhost:${STATUS_PORT}`);
|
|
27733
|
+
if (req.method === "GET" && url === "/browse/tree") {
|
|
27734
|
+
const dir = parsedUrl.searchParams.get("dir");
|
|
27735
|
+
if (!dir || !fs15.existsSync(dir)) {
|
|
27736
|
+
res.writeHead(400);
|
|
27737
|
+
res.end(JSON.stringify({ error: "Missing or invalid dir parameter" }));
|
|
27738
|
+
return;
|
|
27739
|
+
}
|
|
27740
|
+
try {
|
|
27741
|
+
const tree = buildFileTree(dir, path14.basename(dir));
|
|
27742
|
+
res.writeHead(200);
|
|
27743
|
+
res.end(JSON.stringify(tree));
|
|
27744
|
+
} catch (err) {
|
|
27745
|
+
res.writeHead(500);
|
|
27746
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
27747
|
+
}
|
|
27748
|
+
return;
|
|
27749
|
+
}
|
|
27750
|
+
if (req.method === "GET" && url === "/browse/file") {
|
|
27751
|
+
const dir = parsedUrl.searchParams.get("dir");
|
|
27752
|
+
const filePath = parsedUrl.searchParams.get("path");
|
|
27753
|
+
if (!dir || !filePath) {
|
|
27754
|
+
res.writeHead(400);
|
|
27755
|
+
res.end(JSON.stringify({ error: "dir and path parameters required" }));
|
|
27756
|
+
return;
|
|
27757
|
+
}
|
|
27758
|
+
const fullPath = path14.resolve(dir, filePath);
|
|
27759
|
+
if (!fullPath.startsWith(path14.resolve(dir))) {
|
|
27760
|
+
res.writeHead(403);
|
|
27761
|
+
res.end(JSON.stringify({ error: "Path traversal not allowed" }));
|
|
27762
|
+
return;
|
|
27763
|
+
}
|
|
27764
|
+
try {
|
|
27765
|
+
if (!fs15.existsSync(fullPath)) {
|
|
27766
|
+
res.writeHead(404);
|
|
27767
|
+
res.end(JSON.stringify({ error: "File not found" }));
|
|
27768
|
+
return;
|
|
27769
|
+
}
|
|
27770
|
+
const content = fs15.readFileSync(fullPath, "utf-8");
|
|
27771
|
+
res.writeHead(200);
|
|
27772
|
+
res.end(JSON.stringify({ content }));
|
|
27773
|
+
} catch (err) {
|
|
27774
|
+
res.writeHead(500);
|
|
27775
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
27776
|
+
}
|
|
27777
|
+
return;
|
|
27778
|
+
}
|
|
27779
|
+
if (req.method === "POST" && url === "/browse/save") {
|
|
27780
|
+
let body = "";
|
|
27781
|
+
req.on("data", (chunk) => {
|
|
27782
|
+
body += chunk.toString();
|
|
27783
|
+
});
|
|
27784
|
+
req.on("end", () => {
|
|
27785
|
+
try {
|
|
27786
|
+
const { dir, filePath, content } = JSON.parse(body);
|
|
27787
|
+
if (!dir || !filePath || content === void 0) {
|
|
27788
|
+
res.writeHead(400);
|
|
27789
|
+
res.end(JSON.stringify({ error: "dir, filePath, and content required" }));
|
|
27790
|
+
return;
|
|
27791
|
+
}
|
|
27792
|
+
const fullPath = path14.resolve(dir, filePath);
|
|
27793
|
+
if (!fullPath.startsWith(path14.resolve(dir))) {
|
|
27794
|
+
res.writeHead(403);
|
|
27795
|
+
res.end(JSON.stringify({ error: "Path traversal not allowed" }));
|
|
27796
|
+
return;
|
|
27797
|
+
}
|
|
27798
|
+
const parentDir = path14.dirname(fullPath);
|
|
27799
|
+
if (!fs15.existsSync(parentDir))
|
|
27800
|
+
fs15.mkdirSync(parentDir, { recursive: true });
|
|
27801
|
+
fs15.writeFileSync(fullPath, content, "utf-8");
|
|
27802
|
+
res.writeHead(200);
|
|
27803
|
+
res.end(JSON.stringify({ success: true }));
|
|
27804
|
+
} catch (err) {
|
|
27805
|
+
res.writeHead(500);
|
|
27806
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
27807
|
+
}
|
|
27808
|
+
});
|
|
27809
|
+
return;
|
|
27810
|
+
}
|
|
27645
27811
|
res.writeHead(404);
|
|
27646
27812
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
27647
27813
|
});
|
|
@@ -27681,6 +27847,45 @@ var StatusServer = class {
|
|
|
27681
27847
|
}, null, 2), "utf-8");
|
|
27682
27848
|
}
|
|
27683
27849
|
};
|
|
27850
|
+
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
27851
|
+
"node_modules",
|
|
27852
|
+
"dist",
|
|
27853
|
+
".git",
|
|
27854
|
+
".vite-cache",
|
|
27855
|
+
".next",
|
|
27856
|
+
"__pycache__",
|
|
27857
|
+
".turbo",
|
|
27858
|
+
".cache",
|
|
27859
|
+
"build",
|
|
27860
|
+
".svelte-kit"
|
|
27861
|
+
]);
|
|
27862
|
+
function buildFileTree(dirPath, name, relativePath = "") {
|
|
27863
|
+
const node = { name, path: relativePath, isDirectory: true, children: [] };
|
|
27864
|
+
let entries;
|
|
27865
|
+
try {
|
|
27866
|
+
entries = fs15.readdirSync(dirPath, { withFileTypes: true });
|
|
27867
|
+
} catch {
|
|
27868
|
+
return node;
|
|
27869
|
+
}
|
|
27870
|
+
const dirs = [];
|
|
27871
|
+
const files = [];
|
|
27872
|
+
for (const entry of entries) {
|
|
27873
|
+
if (entry.name.startsWith(".") && entry.name !== ".env")
|
|
27874
|
+
continue;
|
|
27875
|
+
const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
27876
|
+
if (entry.isDirectory()) {
|
|
27877
|
+
if (SKIP_DIRS2.has(entry.name))
|
|
27878
|
+
continue;
|
|
27879
|
+
dirs.push(buildFileTree(path14.join(dirPath, entry.name), entry.name, childRelative));
|
|
27880
|
+
} else {
|
|
27881
|
+
files.push({ name: entry.name, path: childRelative, isDirectory: false, children: [] });
|
|
27882
|
+
}
|
|
27883
|
+
}
|
|
27884
|
+
dirs.sort((a, b) => a.name.localeCompare(b.name));
|
|
27885
|
+
files.sort((a, b) => a.name.localeCompare(b.name));
|
|
27886
|
+
node.children = [...dirs, ...files];
|
|
27887
|
+
return node;
|
|
27888
|
+
}
|
|
27684
27889
|
|
|
27685
27890
|
// dist/commands/start.js
|
|
27686
27891
|
var VERSION = "0.5.29";
|
|
@@ -27786,6 +27991,11 @@ function cleanupPreviousAgent(projectId, apiPort, devPort) {
|
|
|
27786
27991
|
killPort(apiPort);
|
|
27787
27992
|
if (devPort !== oldDevPort)
|
|
27788
27993
|
killPort(devPort);
|
|
27994
|
+
const backendPort = devPort + 1001;
|
|
27995
|
+
killPort(backendPort);
|
|
27996
|
+
const oldBackendPort = oldDevPort ? oldDevPort + 1001 : 0;
|
|
27997
|
+
if (oldBackendPort && oldBackendPort !== backendPort)
|
|
27998
|
+
killPort(oldBackendPort);
|
|
27789
27999
|
clearProjectConfig(projectId);
|
|
27790
28000
|
}
|
|
27791
28001
|
async function startCommand(directory, options) {
|
package/dist/cli.js
CHANGED
|
@@ -21,11 +21,13 @@ import { logoutCommand } from './commands/logout.js';
|
|
|
21
21
|
import { startCommand } from './commands/start.js';
|
|
22
22
|
import { statusCommand } from './commands/status.js';
|
|
23
23
|
import { serviceInstallCommand, serviceUninstallCommand, serviceStatusCommand, serviceStartCommand, serviceStopCommand } from './commands/service.js';
|
|
24
|
+
import { updateCommand } from './commands/update.js';
|
|
25
|
+
import { getPackageVersion } from './version.js';
|
|
24
26
|
const program = new Command();
|
|
25
27
|
program
|
|
26
28
|
.name('nstantpage')
|
|
27
29
|
.description('Local development agent for nstantpage.com — run projects on your machine, preview in the cloud')
|
|
28
|
-
.version(
|
|
30
|
+
.version(getPackageVersion());
|
|
29
31
|
program
|
|
30
32
|
.command('login')
|
|
31
33
|
.description('Authenticate with nstantpage.com')
|
|
@@ -102,5 +104,11 @@ service
|
|
|
102
104
|
.command('status')
|
|
103
105
|
.description('Check if the background service is running')
|
|
104
106
|
.action(serviceStatusCommand);
|
|
107
|
+
program
|
|
108
|
+
.command('update')
|
|
109
|
+
.description('Update CLI and desktop app to the latest version')
|
|
110
|
+
.option('--cli', 'Update only the CLI package')
|
|
111
|
+
.option('--desktop', 'Update only the desktop app')
|
|
112
|
+
.action(updateCommand);
|
|
105
113
|
program.parse();
|
|
106
114
|
//# sourceMappingURL=cli.js.map
|
package/dist/commands/service.js
CHANGED
|
@@ -130,7 +130,7 @@ export async function serviceInstallCommand(options = {}) {
|
|
|
130
130
|
}
|
|
131
131
|
// Fallback: headless service (no tray icon)
|
|
132
132
|
if (electronAppPath === null) {
|
|
133
|
-
console.log(chalk.gray(' Tip:
|
|
133
|
+
console.log(chalk.gray(' Tip: Run "nstantpage update --desktop" to download the desktop app with tray icon.'));
|
|
134
134
|
}
|
|
135
135
|
if (platform === 'darwin') {
|
|
136
136
|
await installLaunchd(gateway, token);
|
|
@@ -234,6 +234,8 @@ function findElectronApp() {
|
|
|
234
234
|
if (platform === 'darwin') {
|
|
235
235
|
// Check common macOS install paths
|
|
236
236
|
const candidates = [
|
|
237
|
+
// Downloaded by postinstall (npm i -g nstantpage-agent)
|
|
238
|
+
path.join(os.homedir(), '.nstantpage', 'desktop', 'nstantpage.app'),
|
|
237
239
|
'/Applications/nstantpage.app',
|
|
238
240
|
path.join(os.homedir(), 'Applications', 'nstantpage.app'),
|
|
239
241
|
// Dev build location (from electron-builder --dir)
|
|
@@ -247,6 +249,8 @@ function findElectronApp() {
|
|
|
247
249
|
}
|
|
248
250
|
else if (platform === 'win32') {
|
|
249
251
|
const candidates = [
|
|
252
|
+
// Downloaded by postinstall (npm i -g nstantpage-agent)
|
|
253
|
+
path.join(os.homedir(), '.nstantpage', 'desktop', 'nstantpage.exe'),
|
|
250
254
|
path.join(os.homedir(), 'AppData', 'Local', 'Programs', 'nstantpage', 'nstantpage.exe'),
|
|
251
255
|
path.join('C:\\Program Files', 'nstantpage', 'nstantpage.exe'),
|
|
252
256
|
];
|
package/dist/commands/start.js
CHANGED
|
@@ -26,7 +26,8 @@ import { LocalServer } from '../localServer.js';
|
|
|
26
26
|
import { PackageInstaller } from '../packageInstaller.js';
|
|
27
27
|
import { probeLocalPostgres, ensureLocalProjectDb, closeAdminPool, writeDatabaseUrlToEnv } from '../projectDb.js';
|
|
28
28
|
import { StatusServer } from '../statusServer.js';
|
|
29
|
-
|
|
29
|
+
import { getPackageVersion } from '../version.js';
|
|
30
|
+
const VERSION = getPackageVersion();
|
|
30
31
|
/**
|
|
31
32
|
* Resolve the backend API base URL.
|
|
32
33
|
* - If --backend is passed, use it
|
|
@@ -170,7 +171,13 @@ function cleanupPreviousAgent(projectId, apiPort, devPort) {
|
|
|
170
171
|
killPort(apiPort);
|
|
171
172
|
if (devPort !== oldDevPort)
|
|
172
173
|
killPort(devPort);
|
|
173
|
-
// 4.
|
|
174
|
+
// 4. Also free the backend port (devPort + 1001) that DevServer uses for Express
|
|
175
|
+
const backendPort = devPort + 1001;
|
|
176
|
+
killPort(backendPort);
|
|
177
|
+
const oldBackendPort = oldDevPort ? oldDevPort + 1001 : 0;
|
|
178
|
+
if (oldBackendPort && oldBackendPort !== backendPort)
|
|
179
|
+
killPort(oldBackendPort);
|
|
180
|
+
// 5. Clear stale project config
|
|
174
181
|
clearProjectConfig(projectId);
|
|
175
182
|
}
|
|
176
183
|
export async function startCommand(directory, options) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update command — update both CLI (npm) and desktop app (GitHub releases).
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* nstantpage update — Update both CLI and desktop
|
|
6
|
+
* nstantpage update --cli — Update CLI only (npm)
|
|
7
|
+
* nstantpage update --desktop — Update desktop only (GitHub releases)
|
|
8
|
+
*/
|
|
9
|
+
interface UpdateOptions {
|
|
10
|
+
cli?: boolean;
|
|
11
|
+
desktop?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function updateCommand(options?: UpdateOptions): Promise<void>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update command — update both CLI (npm) and desktop app (GitHub releases).
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* nstantpage update — Update both CLI and desktop
|
|
6
|
+
* nstantpage update --cli — Update CLI only (npm)
|
|
7
|
+
* nstantpage update --desktop — Update desktop only (GitHub releases)
|
|
8
|
+
*/
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
import { getPackageVersion } from '../version.js';
|
|
15
|
+
const DESKTOP_DIR = path.join(os.homedir(), '.nstantpage', 'desktop');
|
|
16
|
+
const VERSION_FILE = path.join(DESKTOP_DIR, '.version');
|
|
17
|
+
export async function updateCommand(options = {}) {
|
|
18
|
+
const updateBoth = !options.cli && !options.desktop;
|
|
19
|
+
const currentVersion = getPackageVersion();
|
|
20
|
+
console.log(chalk.blue(`\n nstantpage v${currentVersion}\n`));
|
|
21
|
+
if (updateBoth || options.cli) {
|
|
22
|
+
await updateCli(currentVersion);
|
|
23
|
+
}
|
|
24
|
+
if (updateBoth || options.desktop) {
|
|
25
|
+
await updateDesktop();
|
|
26
|
+
}
|
|
27
|
+
console.log('');
|
|
28
|
+
}
|
|
29
|
+
async function updateCli(currentVersion) {
|
|
30
|
+
console.log(chalk.gray(' Checking npm for CLI updates...'));
|
|
31
|
+
try {
|
|
32
|
+
const latest = execSync('npm view nstantpage-agent version 2>/dev/null', {
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
}).trim();
|
|
35
|
+
if (latest === currentVersion) {
|
|
36
|
+
console.log(chalk.green(` ✓ CLI is up to date (${currentVersion})`));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.log(chalk.yellow(` Updating CLI: ${currentVersion} → ${latest}`));
|
|
40
|
+
execSync('npm install -g nstantpage-agent@latest', {
|
|
41
|
+
stdio: 'inherit',
|
|
42
|
+
});
|
|
43
|
+
console.log(chalk.green(` ✓ CLI updated to ${latest}`));
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.log(chalk.red(` ✗ CLI update failed: ${err.message}`));
|
|
47
|
+
console.log(chalk.gray(' Try manually: npm install -g nstantpage-agent@latest'));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function updateDesktop() {
|
|
51
|
+
console.log(chalk.gray(' Checking GitHub for desktop updates...'));
|
|
52
|
+
try {
|
|
53
|
+
// Re-run the postinstall script which handles download + version check
|
|
54
|
+
const scriptPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', 'scripts', 'postinstall.mjs');
|
|
55
|
+
if (fs.existsSync(scriptPath)) {
|
|
56
|
+
execSync(`node "${scriptPath}"`, { stdio: 'inherit' });
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// If installed globally, the script is in the package root
|
|
60
|
+
const altPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'scripts', 'postinstall.mjs');
|
|
61
|
+
if (fs.existsSync(altPath)) {
|
|
62
|
+
execSync(`node "${altPath}"`, { stdio: 'inherit' });
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(chalk.yellow(' ⚠ Desktop updater script not found. Reinstall to fix: npm i -g nstantpage-agent'));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
console.log(chalk.red(` ✗ Desktop update failed: ${err.message}`));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=update.js.map
|
package/dist/fileManager.d.ts
CHANGED
|
@@ -36,6 +36,17 @@ export declare class FileManager {
|
|
|
36
36
|
* Get the project directory path.
|
|
37
37
|
*/
|
|
38
38
|
getProjectDir(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Build a recursive file tree of the project directory.
|
|
41
|
+
* Returns a structure matching the backend's /api/projects/{id}/tree format.
|
|
42
|
+
*/
|
|
43
|
+
getFileTree(): Promise<{
|
|
44
|
+
name: string;
|
|
45
|
+
path: string;
|
|
46
|
+
isDirectory: boolean;
|
|
47
|
+
children: any[];
|
|
48
|
+
}>;
|
|
49
|
+
private buildTree;
|
|
39
50
|
/**
|
|
40
51
|
* Check if a path exists.
|
|
41
52
|
*/
|
package/dist/fileManager.js
CHANGED
|
@@ -101,6 +101,50 @@ export class FileManager {
|
|
|
101
101
|
getProjectDir() {
|
|
102
102
|
return this.projectDir;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Build a recursive file tree of the project directory.
|
|
106
|
+
* Returns a structure matching the backend's /api/projects/{id}/tree format.
|
|
107
|
+
*/
|
|
108
|
+
async getFileTree() {
|
|
109
|
+
const root = {
|
|
110
|
+
name: path.basename(this.projectDir),
|
|
111
|
+
path: '',
|
|
112
|
+
isDirectory: true,
|
|
113
|
+
children: [],
|
|
114
|
+
};
|
|
115
|
+
if (existsSync(this.projectDir)) {
|
|
116
|
+
root.children = await this.buildTree(this.projectDir, '');
|
|
117
|
+
}
|
|
118
|
+
return root;
|
|
119
|
+
}
|
|
120
|
+
async buildTree(dir, relativePath) {
|
|
121
|
+
const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', '.vite-cache', '.next', '__pycache__', '.turbo', '.cache']);
|
|
122
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
123
|
+
const result = [];
|
|
124
|
+
// Sort: directories first, then alphabetically
|
|
125
|
+
const sorted = entries.sort((a, b) => {
|
|
126
|
+
if (a.isDirectory() && !b.isDirectory())
|
|
127
|
+
return -1;
|
|
128
|
+
if (!a.isDirectory() && b.isDirectory())
|
|
129
|
+
return 1;
|
|
130
|
+
return a.name.localeCompare(b.name);
|
|
131
|
+
});
|
|
132
|
+
for (const entry of sorted) {
|
|
133
|
+
if (entry.name.startsWith('.') && entry.name !== '.env')
|
|
134
|
+
continue;
|
|
135
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
136
|
+
if (entry.isDirectory()) {
|
|
137
|
+
if (SKIP_DIRS.has(entry.name))
|
|
138
|
+
continue;
|
|
139
|
+
const children = await this.buildTree(path.join(dir, entry.name), entryRelPath);
|
|
140
|
+
result.push({ name: entry.name, path: entryRelPath, isDirectory: true, children });
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
result.push({ name: entry.name, path: entryRelPath, isDirectory: false, children: [] });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
104
148
|
/**
|
|
105
149
|
* Check if a path exists.
|
|
106
150
|
*/
|
package/dist/localServer.d.ts
CHANGED
|
@@ -129,6 +129,9 @@ export declare class LocalServer {
|
|
|
129
129
|
private handlePushToBackend;
|
|
130
130
|
private handlePullFromBackend;
|
|
131
131
|
private handleDiskFile;
|
|
132
|
+
private handleTree;
|
|
133
|
+
private handleFileContent;
|
|
134
|
+
private handleSaveFile;
|
|
132
135
|
private handleHealth;
|
|
133
136
|
/**
|
|
134
137
|
* Get a pg Pool connected to this project's local database.
|
package/dist/localServer.js
CHANGED
|
@@ -25,6 +25,7 @@ import { ErrorStore, structuredErrorToString } from './errorStore.js';
|
|
|
25
25
|
import { PackageInstaller } from './packageInstaller.js';
|
|
26
26
|
import { probeLocalPostgres, ensureLocalProjectDb, isLocalPgAvailable, writeDatabaseUrlToEnv } from './projectDb.js';
|
|
27
27
|
import { AgentSync } from './agentSync.js';
|
|
28
|
+
import { getPackageVersion } from './version.js';
|
|
28
29
|
// ─── Try to load node-pty for real PTY support ─────────────────
|
|
29
30
|
let ptyModule = null;
|
|
30
31
|
try {
|
|
@@ -405,6 +406,9 @@ export class LocalServer {
|
|
|
405
406
|
'/live/push-to-backend': this.handlePushToBackend,
|
|
406
407
|
'/live/pull-from-backend': this.handlePullFromBackend,
|
|
407
408
|
'/live/disk-file': this.handleDiskFile,
|
|
409
|
+
'/live/tree': this.handleTree,
|
|
410
|
+
'/live/file-content': this.handleFileContent,
|
|
411
|
+
'/live/save-file': this.handleSaveFile,
|
|
408
412
|
'/health': this.handleHealth,
|
|
409
413
|
};
|
|
410
414
|
if (handlers[path])
|
|
@@ -828,7 +832,7 @@ export class LocalServer {
|
|
|
828
832
|
connected: true,
|
|
829
833
|
projectId: this.options.projectId,
|
|
830
834
|
agent: {
|
|
831
|
-
version:
|
|
835
|
+
version: getPackageVersion(),
|
|
832
836
|
hostname: os.hostname(),
|
|
833
837
|
platform: `${os.platform()} ${os.arch()}`,
|
|
834
838
|
},
|
|
@@ -1272,6 +1276,50 @@ export class LocalServer {
|
|
|
1272
1276
|
this.json(res, { success: false, error: error.message }, 500);
|
|
1273
1277
|
}
|
|
1274
1278
|
}
|
|
1279
|
+
// ─── /live/tree ──────────────────────────────────────────────
|
|
1280
|
+
async handleTree(_req, res) {
|
|
1281
|
+
try {
|
|
1282
|
+
const tree = await this.fileManager.getFileTree();
|
|
1283
|
+
this.json(res, tree);
|
|
1284
|
+
}
|
|
1285
|
+
catch (error) {
|
|
1286
|
+
this.json(res, { error: error.message }, 500);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
// ─── /live/file-content ──────────────────────────────────────
|
|
1290
|
+
async handleFileContent(_req, res, _body, url) {
|
|
1291
|
+
const filePath = url.searchParams.get('path');
|
|
1292
|
+
if (!filePath) {
|
|
1293
|
+
this.json(res, { error: 'Missing path parameter' }, 400);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
try {
|
|
1297
|
+
const content = await this.fileManager.readFile(filePath);
|
|
1298
|
+
if (content === null) {
|
|
1299
|
+
this.json(res, { error: 'File not found' }, 404);
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
this.json(res, { content });
|
|
1303
|
+
}
|
|
1304
|
+
catch (error) {
|
|
1305
|
+
this.json(res, { error: error.message }, 500);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
// ─── /live/save-file ────────────────────────────────────────
|
|
1309
|
+
async handleSaveFile(_req, res, body) {
|
|
1310
|
+
try {
|
|
1311
|
+
const { filePath, content } = JSON.parse(body);
|
|
1312
|
+
if (!filePath || content === undefined) {
|
|
1313
|
+
this.json(res, { error: 'filePath and content required' }, 400);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
await this.fileManager.writeFiles({ [filePath]: content });
|
|
1317
|
+
this.json(res, { success: true, message: 'File saved' });
|
|
1318
|
+
}
|
|
1319
|
+
catch (error) {
|
|
1320
|
+
this.json(res, { error: error.message }, 500);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1275
1323
|
// ─── /health ─────────────────────────────────────────────────
|
|
1276
1324
|
async handleHealth(_req, res) {
|
|
1277
1325
|
this.json(res, {
|
package/dist/packageInstaller.js
CHANGED
|
@@ -62,7 +62,20 @@ export class PackageInstaller {
|
|
|
62
62
|
console.log(` [Installer] Installing project dependencies...`);
|
|
63
63
|
const pm = this.detectPackageManager();
|
|
64
64
|
const args = this.buildFastInstallArgs(pm);
|
|
65
|
-
|
|
65
|
+
try {
|
|
66
|
+
await this.runCommand(pm, args, 300_000);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
// Frozen-lockfile failed (lockfile out-of-date) — retry without it
|
|
70
|
+
if (String(err.message || err).includes('ERR_PNPM_OUTDATED_LOCKFILE') ||
|
|
71
|
+
String(err.message || err).includes('frozen-lockfile')) {
|
|
72
|
+
console.log(` [Installer] Lockfile outdated — retrying without frozen-lockfile...`);
|
|
73
|
+
await this.runCommand(pm, ['install', '--no-frozen-lockfile'], 300_000);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
66
79
|
this.writeDepsHash();
|
|
67
80
|
console.log(` [Installer] Dependencies installed`);
|
|
68
81
|
}
|