nstantpage-agent 0.7.1 → 0.8.1
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.js +5 -4
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +7 -0
- package/dist/commands/run.d.ts +8 -4
- package/dist/commands/run.js +43 -66
- package/dist/commands/service.js +0 -132
- package/dist/commands/start.d.ts +1 -0
- package/dist/commands/start.js +7 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +10 -1
- package/dist/commands/update.d.ts +3 -10
- package/dist/commands/update.js +4 -40
- package/dist/localServer.js +3 -1
- package/dist/statusServer.d.ts +1 -0
- package/package.json +1 -3
- package/dist/gitSetup.d.ts +0 -10
- package/dist/gitSetup.js +0 -162
- package/scripts/postinstall.mjs +0 -190
package/dist/cli.js
CHANGED
|
@@ -33,6 +33,7 @@ program
|
|
|
33
33
|
program
|
|
34
34
|
.command('login')
|
|
35
35
|
.description('Authenticate with nstantpage.com')
|
|
36
|
+
.option('--local [port]', 'Authenticate with local backend (default: 5001)')
|
|
36
37
|
.option('--gateway <url>', 'Gateway URL (auto-detects local vs production)')
|
|
37
38
|
.option('--force', 'Re-authenticate even if already logged in')
|
|
38
39
|
.action(loginCommand);
|
|
@@ -46,6 +47,7 @@ program
|
|
|
46
47
|
.argument('[directory]', 'Project directory (defaults to ~/.nstantpage/projects/<id>)', '.')
|
|
47
48
|
.option('-p, --port <port>', 'Local dev server port', '3000')
|
|
48
49
|
.option('-a, --api-port <port>', 'Local API server port (internal)', '18924')
|
|
50
|
+
.option('--local [port]', 'Connect to local backend (default: 5001)')
|
|
49
51
|
.option('--project-id <id>', 'Link to a specific project ID')
|
|
50
52
|
.option('--gateway <url>', 'Gateway URL (default: from login)')
|
|
51
53
|
.option('--backend <url>', 'Backend API URL (auto-detected from gateway)')
|
|
@@ -55,7 +57,7 @@ program
|
|
|
55
57
|
.action(startCommand);
|
|
56
58
|
program
|
|
57
59
|
.command('run')
|
|
58
|
-
.description('
|
|
60
|
+
.description('Install background service and start the agent')
|
|
59
61
|
.option('--local [port]', 'Connect to local backend (default: 5001)')
|
|
60
62
|
.action(runCommand);
|
|
61
63
|
program
|
|
@@ -64,6 +66,7 @@ program
|
|
|
64
66
|
.argument('[directory]', 'Project directory to sync (defaults to current directory)', '.')
|
|
65
67
|
.option('-p, --port <port>', 'Local dev server port', '3000')
|
|
66
68
|
.option('-a, --api-port <port>', 'Local API server port', '18924')
|
|
69
|
+
.option('--local [port]', 'Sync to local backend (default: localhost:5001)')
|
|
67
70
|
.option('--gateway <url>', 'Gateway URL (default: from login)')
|
|
68
71
|
.option('--backend <url>', 'Backend API URL (auto-detected from gateway)')
|
|
69
72
|
.option('--token <token>', 'Auth token (skip login flow)')
|
|
@@ -124,9 +127,7 @@ service
|
|
|
124
127
|
.action(serviceStatusCommand);
|
|
125
128
|
program
|
|
126
129
|
.command('update')
|
|
127
|
-
.description('Update CLI
|
|
128
|
-
.option('--cli', 'Update only the CLI package')
|
|
129
|
-
.option('--desktop', 'Update only the desktop app')
|
|
130
|
+
.description('Update CLI to the latest version')
|
|
130
131
|
.action(updateCommand);
|
|
131
132
|
program.parse();
|
|
132
133
|
//# sourceMappingURL=cli.js.map
|
package/dist/commands/login.d.ts
CHANGED
package/dist/commands/login.js
CHANGED
|
@@ -46,6 +46,13 @@ function getEmailFromToken(token) {
|
|
|
46
46
|
}
|
|
47
47
|
export async function loginCommand(options = {}) {
|
|
48
48
|
const conf = getConfig();
|
|
49
|
+
// Handle --local flag: set gateway to point at local server
|
|
50
|
+
if (options.local !== undefined && options.local !== false) {
|
|
51
|
+
const port = (typeof options.local === 'string') ? options.local : '5001';
|
|
52
|
+
options.gateway = `ws://localhost:4000`;
|
|
53
|
+
// Also store the backend URL for other commands
|
|
54
|
+
conf.set('localBackendUrl', `http://localhost:${port}`);
|
|
55
|
+
}
|
|
49
56
|
// Check if already logged in (unless --force)
|
|
50
57
|
const existingToken = conf.get('token');
|
|
51
58
|
if (existingToken && !options.force) {
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Run command —
|
|
2
|
+
* Run command — install the background service and start the agent.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* nstantpage run —
|
|
6
|
-
* nstantpage run --local —
|
|
7
|
-
* nstantpage run --local 3000 —
|
|
5
|
+
* nstantpage run — Install service + start agent (connects to nstantpage.com)
|
|
6
|
+
* nstantpage run --local — Same but targeting localhost:5001
|
|
7
|
+
* nstantpage run --local 3000 — Same but targeting localhost:3000
|
|
8
|
+
*
|
|
9
|
+
* This is the main entry point for users. It:
|
|
10
|
+
* 1. Installs the agent as a background service (auto-start on boot)
|
|
11
|
+
* 2. Starts the agent immediately in standby mode
|
|
8
12
|
*/
|
|
9
13
|
interface RunOptions {
|
|
10
14
|
local?: string | boolean;
|
package/dist/commands/run.js
CHANGED
|
@@ -1,82 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Run command —
|
|
2
|
+
* Run command — install the background service and start the agent.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* nstantpage run —
|
|
6
|
-
* nstantpage run --local —
|
|
7
|
-
* nstantpage run --local 3000 —
|
|
5
|
+
* nstantpage run — Install service + start agent (connects to nstantpage.com)
|
|
6
|
+
* nstantpage run --local — Same but targeting localhost:5001
|
|
7
|
+
* nstantpage run --local 3000 — Same but targeting localhost:3000
|
|
8
|
+
*
|
|
9
|
+
* This is the main entry point for users. It:
|
|
10
|
+
* 1. Installs the agent as a background service (auto-start on boot)
|
|
11
|
+
* 2. Starts the agent immediately in standby mode
|
|
8
12
|
*/
|
|
9
13
|
import chalk from 'chalk';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import { execSync } from 'child_process';
|
|
14
|
-
/**
|
|
15
|
-
* Find the Electron desktop app on disk.
|
|
16
|
-
* Same logic as service.ts findElectronApp() — check postinstall location first.
|
|
17
|
-
*/
|
|
18
|
-
function findElectronApp() {
|
|
19
|
-
const platform = os.platform();
|
|
20
|
-
if (platform === 'darwin') {
|
|
21
|
-
const candidates = [
|
|
22
|
-
path.join(os.homedir(), '.nstantpage', 'desktop', 'nstantpage.app'),
|
|
23
|
-
'/Applications/nstantpage.app',
|
|
24
|
-
path.join(os.homedir(), 'Applications', 'nstantpage.app'),
|
|
25
|
-
];
|
|
26
|
-
for (const p of candidates) {
|
|
27
|
-
if (fs.existsSync(p))
|
|
28
|
-
return p;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
else if (platform === 'win32') {
|
|
32
|
-
const candidates = [
|
|
33
|
-
path.join(os.homedir(), '.nstantpage', 'desktop', 'nstantpage.exe'),
|
|
34
|
-
path.join(os.homedir(), 'AppData', 'Local', 'Programs', 'nstantpage', 'nstantpage.exe'),
|
|
35
|
-
path.join('C:\\Program Files', 'nstantpage', 'nstantpage.exe'),
|
|
36
|
-
];
|
|
37
|
-
for (const p of candidates) {
|
|
38
|
-
if (fs.existsSync(p))
|
|
39
|
-
return p;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
14
|
+
import { getConfig } from '../config.js';
|
|
15
|
+
import { serviceInstallCommand } from './service.js';
|
|
16
|
+
import { startCommand } from './start.js';
|
|
44
17
|
export async function runCommand(options = {}) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
console.log(chalk.gray(' Run "nstantpage update --desktop" to download it.'));
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
const platform = os.platform();
|
|
52
|
-
// Build args to pass to the Electron app
|
|
53
|
-
const extraArgs = [];
|
|
18
|
+
const conf = getConfig();
|
|
19
|
+
// Resolve gateway based on --local flag
|
|
20
|
+
let gateway;
|
|
54
21
|
if (options.local !== undefined && options.local !== false) {
|
|
55
22
|
const port = (typeof options.local === 'string') ? options.local : '5001';
|
|
56
|
-
|
|
23
|
+
gateway = `ws://localhost:4000`;
|
|
24
|
+
// Store backend override for the start command
|
|
25
|
+
conf.set('localBackendUrl', `http://localhost:${port}`);
|
|
57
26
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.log(chalk.gray(` Args: ${extraArgs.join(' ')}`));
|
|
27
|
+
else {
|
|
28
|
+
gateway = conf.get('gatewayUrl') || 'wss://webprev.live';
|
|
61
29
|
}
|
|
30
|
+
const token = conf.get('token');
|
|
31
|
+
const isLocal = /^wss?:\/\/(localhost|127\.0\.0\.1)/.test(gateway);
|
|
32
|
+
if (!token && !isLocal) {
|
|
33
|
+
console.log(chalk.red(' ✗ Not authenticated. Run "nstantpage login" first.'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
console.log(chalk.blue(`\n 🚀 nstantpage run\n`));
|
|
37
|
+
console.log(chalk.gray(` Server: ${isLocal ? 'localhost (dev)' : 'nstantpage.com'}`));
|
|
38
|
+
// 1. Install background service (auto-start on boot)
|
|
39
|
+
console.log(chalk.gray(' Installing background service...'));
|
|
62
40
|
try {
|
|
63
|
-
|
|
64
|
-
const argsStr = extraArgs.length ? ` --args ${extraArgs.join(' ')}` : '';
|
|
65
|
-
execSync(`open -a "${appPath}"${argsStr}`, { stdio: 'inherit' });
|
|
66
|
-
}
|
|
67
|
-
else if (platform === 'win32') {
|
|
68
|
-
const argsStr = extraArgs.join(' ');
|
|
69
|
-
execSync(`"${appPath}" ${argsStr}`, { stdio: 'inherit' });
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
console.log(chalk.yellow(' ⚠ Desktop app not supported on this platform yet.'));
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
console.log(chalk.green(' ✓ App launched\n'));
|
|
41
|
+
await serviceInstallCommand({ gateway });
|
|
76
42
|
}
|
|
77
43
|
catch (err) {
|
|
78
|
-
console.log(chalk.
|
|
79
|
-
process.exit(1);
|
|
44
|
+
console.log(chalk.yellow(` ⚠ Service install skipped: ${err.message}`));
|
|
80
45
|
}
|
|
46
|
+
// 2. Start agent in standby mode
|
|
47
|
+
console.log(chalk.gray('\n Starting agent...\n'));
|
|
48
|
+
const backendUrl = isLocal
|
|
49
|
+
? (conf.get('localBackendUrl') || 'http://localhost:5001')
|
|
50
|
+
: undefined;
|
|
51
|
+
await startCommand('.', {
|
|
52
|
+
port: '3000',
|
|
53
|
+
apiPort: '18924',
|
|
54
|
+
gateway,
|
|
55
|
+
token: token || 'local-dev',
|
|
56
|
+
backend: backendUrl,
|
|
57
|
+
});
|
|
81
58
|
}
|
|
82
59
|
//# sourceMappingURL=run.js.map
|
package/dist/commands/service.js
CHANGED
|
@@ -20,11 +20,8 @@ import fs from 'fs';
|
|
|
20
20
|
import path from 'path';
|
|
21
21
|
import os from 'os';
|
|
22
22
|
import { execSync } from 'child_process';
|
|
23
|
-
import { fileURLToPath } from 'url';
|
|
24
23
|
import { getConfig } from '../config.js';
|
|
25
24
|
import { startCommand } from './start.js';
|
|
26
|
-
const __filename_esm = fileURLToPath(import.meta.url);
|
|
27
|
-
const __dirname_esm = path.dirname(__filename_esm);
|
|
28
25
|
const PLIST_LABEL = 'com.nstantpage.agent';
|
|
29
26
|
const SYSTEMD_SERVICE = 'nstantpage-agent';
|
|
30
27
|
const WIN_TASK_NAME = 'NstantpageAgent';
|
|
@@ -115,23 +112,6 @@ export async function serviceInstallCommand(options = {}) {
|
|
|
115
112
|
}
|
|
116
113
|
const gateway = options.gateway || 'wss://webprev.live';
|
|
117
114
|
const platform = os.platform();
|
|
118
|
-
// On desktop platforms, prefer the Electron tray app (agent + tray icon in one)
|
|
119
|
-
const electronAppPath = findElectronApp();
|
|
120
|
-
if (electronAppPath && (platform === 'darwin' || platform === 'win32')) {
|
|
121
|
-
console.log(chalk.blue(`\n Found nstantpage desktop app: ${electronAppPath}`));
|
|
122
|
-
console.log(chalk.blue(' Installing with system tray icon...\n'));
|
|
123
|
-
if (platform === 'darwin') {
|
|
124
|
-
await installElectronLaunchd(electronAppPath);
|
|
125
|
-
}
|
|
126
|
-
else if (platform === 'win32') {
|
|
127
|
-
await installElectronWindowsTask(electronAppPath);
|
|
128
|
-
}
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
// Fallback: headless service (no tray icon)
|
|
132
|
-
if (electronAppPath === null) {
|
|
133
|
-
console.log(chalk.gray(' Tip: Run "nstantpage update --desktop" to download the desktop app with tray icon.'));
|
|
134
|
-
}
|
|
135
115
|
if (platform === 'darwin') {
|
|
136
116
|
await installLaunchd(gateway, token);
|
|
137
117
|
}
|
|
@@ -228,118 +208,6 @@ export async function serviceStatusCommand() {
|
|
|
228
208
|
}
|
|
229
209
|
}
|
|
230
210
|
// ─── Helper Functions ─────────────────────────────────
|
|
231
|
-
// ─── Electron App Detection ──────────────────────────────
|
|
232
|
-
function findElectronApp() {
|
|
233
|
-
const platform = os.platform();
|
|
234
|
-
if (platform === 'darwin') {
|
|
235
|
-
// Check common macOS install paths
|
|
236
|
-
const candidates = [
|
|
237
|
-
// Downloaded by postinstall (npm i -g nstantpage-agent)
|
|
238
|
-
path.join(os.homedir(), '.nstantpage', 'desktop', 'nstantpage.app'),
|
|
239
|
-
'/Applications/nstantpage.app',
|
|
240
|
-
path.join(os.homedir(), 'Applications', 'nstantpage.app'),
|
|
241
|
-
// Dev build location (from electron-builder --dir)
|
|
242
|
-
path.join(__dirname_esm, '..', '..', 'tray', 'dist', 'mac-arm64', 'nstantpage.app'),
|
|
243
|
-
path.join(__dirname_esm, '..', '..', 'tray', 'dist', 'mac', 'nstantpage.app'),
|
|
244
|
-
];
|
|
245
|
-
for (const p of candidates) {
|
|
246
|
-
if (fs.existsSync(p))
|
|
247
|
-
return p;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
else if (platform === 'win32') {
|
|
251
|
-
const candidates = [
|
|
252
|
-
// Downloaded by postinstall (npm i -g nstantpage-agent)
|
|
253
|
-
path.join(os.homedir(), '.nstantpage', 'desktop', 'nstantpage.exe'),
|
|
254
|
-
path.join(os.homedir(), 'AppData', 'Local', 'Programs', 'nstantpage', 'nstantpage.exe'),
|
|
255
|
-
path.join('C:\\Program Files', 'nstantpage', 'nstantpage.exe'),
|
|
256
|
-
];
|
|
257
|
-
for (const p of candidates) {
|
|
258
|
-
if (fs.existsSync(p))
|
|
259
|
-
return p;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
async function installElectronLaunchd(appPath) {
|
|
265
|
-
const plistDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
|
|
266
|
-
const plistPath = path.join(plistDir, `${PLIST_LABEL}.plist`);
|
|
267
|
-
const logPath = path.join(os.homedir(), '.nstantpage', 'agent.log');
|
|
268
|
-
const errPath = path.join(os.homedir(), '.nstantpage', 'agent.err.log');
|
|
269
|
-
fs.mkdirSync(plistDir, { recursive: true });
|
|
270
|
-
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
271
|
-
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
272
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
273
|
-
<plist version="1.0">
|
|
274
|
-
<dict>
|
|
275
|
-
<key>Label</key>
|
|
276
|
-
<string>${PLIST_LABEL}</string>
|
|
277
|
-
<key>ProgramArguments</key>
|
|
278
|
-
<array>
|
|
279
|
-
<string>/usr/bin/open</string>
|
|
280
|
-
<string>-a</string>
|
|
281
|
-
<string>${appPath}</string>
|
|
282
|
-
</array>
|
|
283
|
-
<key>RunAtLoad</key>
|
|
284
|
-
<true/>
|
|
285
|
-
<key>KeepAlive</key>
|
|
286
|
-
<dict>
|
|
287
|
-
<key>Crashed</key>
|
|
288
|
-
<true/>
|
|
289
|
-
</dict>
|
|
290
|
-
<key>ProcessType</key>
|
|
291
|
-
<string>Interactive</string>
|
|
292
|
-
<key>StandardOutPath</key>
|
|
293
|
-
<string>${logPath}</string>
|
|
294
|
-
<key>StandardErrorPath</key>
|
|
295
|
-
<string>${errPath}</string>
|
|
296
|
-
<key>EnvironmentVariables</key>
|
|
297
|
-
<dict>
|
|
298
|
-
<key>NSAppSleepDisabled</key>
|
|
299
|
-
<string>YES</string>
|
|
300
|
-
</dict>
|
|
301
|
-
<key>ThrottleInterval</key>
|
|
302
|
-
<integer>10</integer>
|
|
303
|
-
</dict>
|
|
304
|
-
</plist>`;
|
|
305
|
-
fs.writeFileSync(plistPath, plist, 'utf-8');
|
|
306
|
-
try {
|
|
307
|
-
execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { encoding: 'utf-8' });
|
|
308
|
-
}
|
|
309
|
-
catch { }
|
|
310
|
-
execSync(`launchctl load "${plistPath}"`, { encoding: 'utf-8' });
|
|
311
|
-
console.log(chalk.green('\n ✓ nstantpage installed as desktop app with tray icon\n'));
|
|
312
|
-
console.log(chalk.gray(` App: ${appPath}`));
|
|
313
|
-
console.log(chalk.gray(` Plist: ${plistPath}`));
|
|
314
|
-
console.log(chalk.gray(` Log: ${logPath}`));
|
|
315
|
-
console.log(chalk.gray(` Status: nstantpage service status`));
|
|
316
|
-
console.log(chalk.gray(` Remove: nstantpage service uninstall\n`));
|
|
317
|
-
console.log(chalk.blue(' The app will start on login with a tray icon showing connection status.'));
|
|
318
|
-
console.log(chalk.blue(' Right-click the tray icon for options. Open nstantpage.com to connect projects.\n'));
|
|
319
|
-
}
|
|
320
|
-
async function installElectronWindowsTask(appPath) {
|
|
321
|
-
const logPath = path.join(os.homedir(), '.nstantpage', 'agent.log');
|
|
322
|
-
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
323
|
-
try {
|
|
324
|
-
execSync(`schtasks /delete /tn "${WIN_TASK_NAME}" /f 2>nul`, { encoding: 'utf-8' });
|
|
325
|
-
}
|
|
326
|
-
catch { }
|
|
327
|
-
try {
|
|
328
|
-
execSync(`schtasks /create /tn "${WIN_TASK_NAME}" /tr "\\"${appPath}\\"" /sc onlogon /rl highest /delay 0000:10 /f`, { encoding: 'utf-8' });
|
|
329
|
-
// Start immediately
|
|
330
|
-
execSync(`start "" "${appPath}"`, { encoding: 'utf-8' });
|
|
331
|
-
}
|
|
332
|
-
catch (err) {
|
|
333
|
-
console.log(chalk.yellow(` ⚠ Task Scheduler error: ${err.message}`));
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
console.log(chalk.green('\n ✓ nstantpage installed as desktop app with tray icon\n'));
|
|
337
|
-
console.log(chalk.gray(` App: ${appPath}`));
|
|
338
|
-
console.log(chalk.gray(` Task: ${WIN_TASK_NAME}`));
|
|
339
|
-
console.log(chalk.gray(` Status: nstantpage service status`));
|
|
340
|
-
console.log(chalk.gray(` Remove: nstantpage service uninstall\n`));
|
|
341
|
-
console.log(chalk.blue(' The app will start on login with a tray icon.\n'));
|
|
342
|
-
}
|
|
343
211
|
function getAgentBinPath() {
|
|
344
212
|
try {
|
|
345
213
|
const resolved = execSync('which nstantpage 2>/dev/null || which nstantpage-agent 2>/dev/null', { encoding: 'utf-8' }).trim();
|
package/dist/commands/start.d.ts
CHANGED
package/dist/commands/start.js
CHANGED
|
@@ -182,6 +182,12 @@ function cleanupPreviousAgent(projectId, apiPort, devPort) {
|
|
|
182
182
|
}
|
|
183
183
|
export async function startCommand(directory, options) {
|
|
184
184
|
const conf = getConfig();
|
|
185
|
+
// Handle --local flag: override backend & gateway to point at localhost
|
|
186
|
+
if (options.local !== undefined && options.local !== false) {
|
|
187
|
+
const localPort = (typeof options.local === 'string') ? options.local : '5001';
|
|
188
|
+
options.backend = `http://localhost:${localPort}`;
|
|
189
|
+
options.gateway = `ws://localhost:4000`;
|
|
190
|
+
}
|
|
185
191
|
// If --token was passed, persist it
|
|
186
192
|
if (options.token) {
|
|
187
193
|
conf.set('token', options.token);
|
|
@@ -623,6 +629,7 @@ async function startStandbyMode(token, options, backendUrl, deviceId) {
|
|
|
623
629
|
devPort: allocatePortsForProject(pid).devPort,
|
|
624
630
|
apiPort: allocatePortsForProject(pid).apiPort,
|
|
625
631
|
tunnelStatus: proj.tunnel.status,
|
|
632
|
+
projectDir: resolveProjectDir('.', pid),
|
|
626
633
|
})),
|
|
627
634
|
requestsForwarded: standbyTunnel.stats.requestsForwarded +
|
|
628
635
|
Array.from(activeProjects.values()).reduce((sum, p) => sum + p.tunnel.stats.requestsForwarded, 0),
|
package/dist/commands/sync.d.ts
CHANGED
package/dist/commands/sync.js
CHANGED
|
@@ -98,6 +98,12 @@ export async function syncCommand(directory, options) {
|
|
|
98
98
|
console.log(chalk.red(` ✗ Directory not found: ${projectDir}`));
|
|
99
99
|
process.exit(1);
|
|
100
100
|
}
|
|
101
|
+
// Handle --local flag: override backend & gateway to point at localhost
|
|
102
|
+
if (options.local !== undefined && options.local !== false) {
|
|
103
|
+
const localPort = (typeof options.local === 'string') ? options.local : '5001';
|
|
104
|
+
options.backend = `http://localhost:${localPort}`;
|
|
105
|
+
options.gateway = `ws://localhost:4000`;
|
|
106
|
+
}
|
|
101
107
|
// Check authentication
|
|
102
108
|
const gateway = options.gateway || conf.get('gatewayUrl') || 'wss://webprev.live';
|
|
103
109
|
const isLocalGateway = /^wss?:\/\/(localhost|127\.0\.0\.1)/.test(gateway);
|
|
@@ -130,7 +136,10 @@ export async function syncCommand(directory, options) {
|
|
|
130
136
|
const parts = token.split('.');
|
|
131
137
|
if (parts.length === 3) {
|
|
132
138
|
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));
|
|
133
|
-
|
|
139
|
+
// .NET uses full claim type URIs; also check standard 'sub' claim
|
|
140
|
+
userId = payload['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier']
|
|
141
|
+
|| payload['sub']
|
|
142
|
+
|| payload['nameid'];
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
catch { }
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Update command — update
|
|
2
|
+
* Update command — update the CLI via npm.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* nstantpage update
|
|
6
|
-
* nstantpage update --cli — Update CLI only (npm)
|
|
7
|
-
* nstantpage update --desktop — Update desktop only (GitHub releases)
|
|
5
|
+
* nstantpage update — Update CLI to latest version
|
|
8
6
|
*/
|
|
9
|
-
|
|
10
|
-
cli?: boolean;
|
|
11
|
-
desktop?: boolean;
|
|
12
|
-
}
|
|
13
|
-
export declare function updateCommand(options?: UpdateOptions): Promise<void>;
|
|
14
|
-
export {};
|
|
7
|
+
export declare function updateCommand(): Promise<void>;
|
package/dist/commands/update.js
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Update command — update
|
|
2
|
+
* Update command — update the CLI via npm.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* nstantpage update
|
|
6
|
-
* nstantpage update --cli — Update CLI only (npm)
|
|
7
|
-
* nstantpage update --desktop — Update desktop only (GitHub releases)
|
|
5
|
+
* nstantpage update — Update CLI to latest version
|
|
8
6
|
*/
|
|
9
7
|
import chalk from 'chalk';
|
|
10
|
-
import fs from 'fs';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import os from 'os';
|
|
13
8
|
import { execSync } from 'child_process';
|
|
14
9
|
import { getPackageVersion } from '../version.js';
|
|
15
|
-
|
|
16
|
-
const VERSION_FILE = path.join(DESKTOP_DIR, '.version');
|
|
17
|
-
export async function updateCommand(options = {}) {
|
|
18
|
-
const updateBoth = !options.cli && !options.desktop;
|
|
10
|
+
export async function updateCommand() {
|
|
19
11
|
const currentVersion = getPackageVersion();
|
|
20
12
|
console.log(chalk.blue(`\n nstantpage v${currentVersion}\n`));
|
|
21
|
-
|
|
22
|
-
await updateCli(currentVersion);
|
|
23
|
-
}
|
|
24
|
-
if (updateBoth || options.desktop) {
|
|
25
|
-
await updateDesktop();
|
|
26
|
-
}
|
|
13
|
+
await updateCli(currentVersion);
|
|
27
14
|
console.log('');
|
|
28
15
|
}
|
|
29
16
|
async function updateCli(currentVersion) {
|
|
@@ -47,27 +34,4 @@ async function updateCli(currentVersion) {
|
|
|
47
34
|
console.log(chalk.gray(' Try manually: npm install -g nstantpage-agent@latest'));
|
|
48
35
|
}
|
|
49
36
|
}
|
|
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
37
|
//# sourceMappingURL=update.js.map
|
package/dist/localServer.js
CHANGED
|
@@ -657,7 +657,9 @@ export class LocalServer {
|
|
|
657
657
|
let shell = null;
|
|
658
658
|
let ptyProcess = null;
|
|
659
659
|
// Ensure project directory exists (posix_spawnp fails if cwd is missing)
|
|
660
|
-
|
|
660
|
+
// Use cwd from request if provided (e.g. LocalRepo projects use the actual disk folder path)
|
|
661
|
+
const requestedCwd = parsed.cwd && fs.existsSync(parsed.cwd) ? parsed.cwd : null;
|
|
662
|
+
const spawnCwd = requestedCwd || (fs.existsSync(this.options.projectDir) ? this.options.projectDir : process.cwd());
|
|
661
663
|
// Prefer node-pty for real PTY (interactive shell, echo, prompt, resize)
|
|
662
664
|
if (ptyModule) {
|
|
663
665
|
try {
|
package/dist/statusServer.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nstantpage-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Local development agent for nstantpage.com — run your projects locally, preview in the cloud. Replaces cloud containers for faster builds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,14 +11,12 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist/**/*.js",
|
|
13
13
|
"dist/**/*.d.ts",
|
|
14
|
-
"scripts/postinstall.mjs",
|
|
15
14
|
"README.md"
|
|
16
15
|
],
|
|
17
16
|
"scripts": {
|
|
18
17
|
"build": "tsc",
|
|
19
18
|
"dev": "tsx src/cli.ts",
|
|
20
19
|
"start": "node dist/cli.js",
|
|
21
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
22
20
|
"prepublishOnly": "npm run build"
|
|
23
21
|
},
|
|
24
22
|
"engines": {
|
package/dist/gitSetup.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Git Setup — ensures git is installed on the user's machine.
|
|
3
|
-
* Called during agent startup so GitSnapshotService (backend) can use it for undo.
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Check if git is available, and try to install it if not.
|
|
7
|
-
* Returns true if git is available after this call.
|
|
8
|
-
* Non-blocking: agent continues even if git can't be installed (undo just won't work).
|
|
9
|
-
*/
|
|
10
|
-
export declare function ensureGit(): Promise<boolean>;
|
package/dist/gitSetup.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Git Setup — ensures git is installed on the user's machine.
|
|
3
|
-
* Called during agent startup so GitSnapshotService (backend) can use it for undo.
|
|
4
|
-
*/
|
|
5
|
-
import { execSync } from 'child_process';
|
|
6
|
-
import os from 'os';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
/**
|
|
9
|
-
* Check if git is available, and try to install it if not.
|
|
10
|
-
* Returns true if git is available after this call.
|
|
11
|
-
* Non-blocking: agent continues even if git can't be installed (undo just won't work).
|
|
12
|
-
*/
|
|
13
|
-
export async function ensureGit() {
|
|
14
|
-
if (isGitAvailable()) {
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
console.log(chalk.yellow(' ⚠ Git not found — installing (needed for undo/version control)'));
|
|
18
|
-
const platform = os.platform();
|
|
19
|
-
try {
|
|
20
|
-
if (platform === 'darwin') {
|
|
21
|
-
return await installGitMacOS();
|
|
22
|
-
}
|
|
23
|
-
else if (platform === 'win32') {
|
|
24
|
-
return await installGitWindows();
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
return installGitLinux();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
console.log(chalk.red(` ✗ Could not install git: ${err.message}`));
|
|
32
|
-
printManualInstructions();
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function isGitAvailable() {
|
|
37
|
-
try {
|
|
38
|
-
execSync('git --version', { stdio: 'pipe', encoding: 'utf-8' });
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
async function installGitMacOS() {
|
|
46
|
-
// Check if Xcode CLT is installed (it includes git)
|
|
47
|
-
try {
|
|
48
|
-
execSync('xcode-select -p', { stdio: 'pipe' });
|
|
49
|
-
// CLT installed but git not in PATH — unusual, may need PATH fix
|
|
50
|
-
console.log(chalk.yellow(' Xcode CLT installed but git not in PATH'));
|
|
51
|
-
printManualInstructions();
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
// CLT not installed — trigger installation
|
|
56
|
-
}
|
|
57
|
-
// Try Homebrew first (non-interactive, faster)
|
|
58
|
-
try {
|
|
59
|
-
execSync('brew --version', { stdio: 'pipe' });
|
|
60
|
-
console.log(chalk.gray(' Installing git via Homebrew...'));
|
|
61
|
-
execSync('brew install git', { stdio: 'inherit' });
|
|
62
|
-
if (isGitAvailable()) {
|
|
63
|
-
const ver = execSync('git --version', { encoding: 'utf-8' }).trim();
|
|
64
|
-
console.log(chalk.green(` ✓ ${ver}`));
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
// Homebrew not available — fall through to xcode-select
|
|
70
|
-
}
|
|
71
|
-
// Trigger Xcode CLT install dialog
|
|
72
|
-
console.log(chalk.gray(' Installing Xcode Command Line Tools (includes git)...'));
|
|
73
|
-
console.log(chalk.gray(' Please click "Install" in the dialog if prompted.'));
|
|
74
|
-
try {
|
|
75
|
-
execSync('xcode-select --install 2>/dev/null', { stdio: 'inherit' });
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
// xcode-select --install returns non-zero if dialog was shown
|
|
79
|
-
}
|
|
80
|
-
// Poll for git availability (max 5 minutes — the Xcode dialog is async)
|
|
81
|
-
const maxWaitMs = 300_000;
|
|
82
|
-
const pollMs = 5_000;
|
|
83
|
-
const start = Date.now();
|
|
84
|
-
while (Date.now() - start < maxWaitMs) {
|
|
85
|
-
await new Promise(r => setTimeout(r, pollMs));
|
|
86
|
-
if (isGitAvailable()) {
|
|
87
|
-
const ver = execSync('git --version', { encoding: 'utf-8' }).trim();
|
|
88
|
-
console.log(chalk.green(` ✓ ${ver}`));
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
console.log(chalk.yellow(' ⚠ Timed out waiting for install. Restart after Xcode Tools finishes.'));
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
async function installGitWindows() {
|
|
96
|
-
// Try winget (available on Windows 10 1709+)
|
|
97
|
-
try {
|
|
98
|
-
execSync('winget --version', { stdio: 'pipe' });
|
|
99
|
-
console.log(chalk.gray(' Installing git via winget...'));
|
|
100
|
-
execSync('winget install --id Git.Git -e --source winget --accept-package-agreements --accept-source-agreements', { stdio: 'inherit' });
|
|
101
|
-
if (isGitAvailable()) {
|
|
102
|
-
const ver = execSync('git --version', { encoding: 'utf-8' }).trim();
|
|
103
|
-
console.log(chalk.green(` ✓ ${ver}`));
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
// winget installs may need a new shell for PATH update
|
|
107
|
-
console.log(chalk.yellow(' Git installed — restart your terminal for PATH to update.'));
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
// winget not available
|
|
112
|
-
}
|
|
113
|
-
console.log(chalk.yellow(' Install Git for Windows:'));
|
|
114
|
-
console.log(chalk.gray(' https://git-scm.com/download/win'));
|
|
115
|
-
console.log(chalk.gray(' or: winget install --id Git.Git'));
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
function installGitLinux() {
|
|
119
|
-
// Try common package managers (non-interactive)
|
|
120
|
-
const managers = [
|
|
121
|
-
{ check: 'apt-get --version', install: 'sudo apt-get install -y git' },
|
|
122
|
-
{ check: 'dnf --version', install: 'sudo dnf install -y git' },
|
|
123
|
-
{ check: 'yum --version', install: 'sudo yum install -y git' },
|
|
124
|
-
{ check: 'pacman --version', install: 'sudo pacman -S --noconfirm git' },
|
|
125
|
-
{ check: 'apk --version', install: 'sudo apk add git' },
|
|
126
|
-
];
|
|
127
|
-
for (const { check, install } of managers) {
|
|
128
|
-
try {
|
|
129
|
-
execSync(check, { stdio: 'pipe' });
|
|
130
|
-
console.log(chalk.gray(` Installing git: ${install}`));
|
|
131
|
-
execSync(install, { stdio: 'inherit' });
|
|
132
|
-
if (isGitAvailable()) {
|
|
133
|
-
const ver = execSync('git --version', { encoding: 'utf-8' }).trim();
|
|
134
|
-
console.log(chalk.green(` ✓ ${ver}`));
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
console.log(chalk.yellow(' Please install git:'));
|
|
143
|
-
console.log(chalk.gray(' Ubuntu/Debian: sudo apt-get install git'));
|
|
144
|
-
console.log(chalk.gray(' Fedora: sudo dnf install git'));
|
|
145
|
-
console.log(chalk.gray(' Arch: sudo pacman -S git'));
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
function printManualInstructions() {
|
|
149
|
-
const platform = os.platform();
|
|
150
|
-
console.log(chalk.yellow(' Install git manually:'));
|
|
151
|
-
if (platform === 'darwin') {
|
|
152
|
-
console.log(chalk.gray(' xcode-select --install'));
|
|
153
|
-
console.log(chalk.gray(' or: brew install git'));
|
|
154
|
-
}
|
|
155
|
-
else if (platform === 'win32') {
|
|
156
|
-
console.log(chalk.gray(' https://git-scm.com/download/win'));
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
console.log(chalk.gray(' sudo apt-get install git'));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
//# sourceMappingURL=gitSetup.js.map
|
package/scripts/postinstall.mjs
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* postinstall script for nstantpage-agent npm package.
|
|
4
|
-
* Downloads the platform-specific Electron desktop app from GitHub releases
|
|
5
|
-
* to ~/.nstantpage/desktop/ so that `nstantpage service install` can use it.
|
|
6
|
-
*
|
|
7
|
-
* Runs automatically after `npm i -g nstantpage-agent`.
|
|
8
|
-
* Skips silently if:
|
|
9
|
-
* - Already downloaded and up-to-date
|
|
10
|
-
* - Network is unavailable
|
|
11
|
-
* - Running in CI (CI=true)
|
|
12
|
-
* - Platform has no desktop build (e.g., Linux currently)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import https from 'https';
|
|
16
|
-
import http from 'http';
|
|
17
|
-
import fs from 'fs';
|
|
18
|
-
import path from 'path';
|
|
19
|
-
import os from 'os';
|
|
20
|
-
import { execSync } from 'child_process';
|
|
21
|
-
import { createRequire } from 'module';
|
|
22
|
-
|
|
23
|
-
const require_ = createRequire(import.meta.url);
|
|
24
|
-
|
|
25
|
-
// Skip in CI environments
|
|
26
|
-
if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) {
|
|
27
|
-
process.exit(0);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const GITHUB_OWNER = 'nightwishh';
|
|
31
|
-
const GITHUB_REPO = 'nstantpage-desktop';
|
|
32
|
-
const DESKTOP_DIR = path.join(os.homedir(), '.nstantpage', 'desktop');
|
|
33
|
-
const VERSION_FILE = path.join(DESKTOP_DIR, '.version');
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Fetch JSON from a URL, following redirects.
|
|
37
|
-
*/
|
|
38
|
-
function fetchJson(url) {
|
|
39
|
-
return new Promise((resolve, reject) => {
|
|
40
|
-
const get = url.startsWith('https') ? https.get : http.get;
|
|
41
|
-
get(url, { headers: { 'User-Agent': 'nstantpage-agent-postinstall' } }, (res) => {
|
|
42
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
43
|
-
return fetchJson(res.headers.location).then(resolve, reject);
|
|
44
|
-
}
|
|
45
|
-
if (res.statusCode !== 200) {
|
|
46
|
-
return reject(new Error(`HTTP ${res.statusCode}`));
|
|
47
|
-
}
|
|
48
|
-
let data = '';
|
|
49
|
-
res.on('data', (chunk) => (data += chunk));
|
|
50
|
-
res.on('end', () => {
|
|
51
|
-
try { resolve(JSON.parse(data)); }
|
|
52
|
-
catch (e) { reject(e); }
|
|
53
|
-
});
|
|
54
|
-
}).on('error', reject);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Download a file from URL (following redirects) to dest path.
|
|
60
|
-
* Shows progress on stdout.
|
|
61
|
-
*/
|
|
62
|
-
function downloadFile(url, dest) {
|
|
63
|
-
return new Promise((resolve, reject) => {
|
|
64
|
-
const get = url.startsWith('https') ? https.get : http.get;
|
|
65
|
-
get(url, { headers: { 'User-Agent': 'nstantpage-agent-postinstall' } }, (res) => {
|
|
66
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
67
|
-
return downloadFile(res.headers.location, dest).then(resolve, reject);
|
|
68
|
-
}
|
|
69
|
-
if (res.statusCode !== 200) {
|
|
70
|
-
return reject(new Error(`HTTP ${res.statusCode}`));
|
|
71
|
-
}
|
|
72
|
-
const total = parseInt(res.headers['content-length'] || '0', 10);
|
|
73
|
-
let downloaded = 0;
|
|
74
|
-
const file = fs.createWriteStream(dest);
|
|
75
|
-
res.on('data', (chunk) => {
|
|
76
|
-
downloaded += chunk.length;
|
|
77
|
-
file.write(chunk);
|
|
78
|
-
if (total > 0) {
|
|
79
|
-
const pct = Math.round((downloaded / total) * 100);
|
|
80
|
-
process.stdout.write(`\r Downloading nstantpage desktop... ${pct}%`);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
res.on('end', () => {
|
|
84
|
-
file.end();
|
|
85
|
-
if (total > 0) process.stdout.write('\n');
|
|
86
|
-
resolve(undefined);
|
|
87
|
-
});
|
|
88
|
-
res.on('error', (err) => { file.close(); reject(err); });
|
|
89
|
-
}).on('error', reject);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Determine the release asset name for this platform/arch.
|
|
95
|
-
* Returns null if no desktop build is available.
|
|
96
|
-
*/
|
|
97
|
-
function getAssetPattern() {
|
|
98
|
-
const platform = os.platform();
|
|
99
|
-
const arch = os.arch();
|
|
100
|
-
|
|
101
|
-
if (platform === 'darwin') {
|
|
102
|
-
// macOS: zip contains the .app
|
|
103
|
-
if (arch === 'arm64') return /-arm64-mac\.zip$/;
|
|
104
|
-
return /-mac\.zip$/; // x64
|
|
105
|
-
}
|
|
106
|
-
if (platform === 'win32') {
|
|
107
|
-
// Windows: portable exe or nsis
|
|
108
|
-
return /\.exe$/;
|
|
109
|
-
}
|
|
110
|
-
// Linux: skip for now (AppImage support could be added later)
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function main() {
|
|
115
|
-
try {
|
|
116
|
-
const assetPattern = getAssetPattern();
|
|
117
|
-
if (!assetPattern) {
|
|
118
|
-
// No desktop build for this platform — skip silently
|
|
119
|
-
process.exit(0);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Check latest release
|
|
123
|
-
const release = await fetchJson(
|
|
124
|
-
`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest`
|
|
125
|
-
);
|
|
126
|
-
const version = release.tag_name; // e.g., "v0.5.44"
|
|
127
|
-
|
|
128
|
-
// Check if already downloaded and up-to-date
|
|
129
|
-
if (fs.existsSync(VERSION_FILE)) {
|
|
130
|
-
const installed = fs.readFileSync(VERSION_FILE, 'utf-8').trim();
|
|
131
|
-
if (installed === version) {
|
|
132
|
-
console.log(` nstantpage desktop ${version} already installed.`);
|
|
133
|
-
process.exit(0);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Find the matching asset
|
|
138
|
-
const asset = release.assets.find((a) => assetPattern.test(a.name));
|
|
139
|
-
if (!asset) {
|
|
140
|
-
// No matching asset — skip silently
|
|
141
|
-
process.exit(0);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
console.log(` Installing nstantpage desktop ${version}...`);
|
|
145
|
-
|
|
146
|
-
// Prepare directories
|
|
147
|
-
fs.mkdirSync(DESKTOP_DIR, { recursive: true });
|
|
148
|
-
|
|
149
|
-
const platform = os.platform();
|
|
150
|
-
|
|
151
|
-
if (platform === 'darwin') {
|
|
152
|
-
// Download zip, extract .app
|
|
153
|
-
const zipPath = path.join(DESKTOP_DIR, 'download.zip');
|
|
154
|
-
await downloadFile(asset.browser_download_url, zipPath);
|
|
155
|
-
|
|
156
|
-
// Remove old app if exists
|
|
157
|
-
const appPath = path.join(DESKTOP_DIR, 'nstantpage.app');
|
|
158
|
-
if (fs.existsSync(appPath)) {
|
|
159
|
-
fs.rmSync(appPath, { recursive: true, force: true });
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Extract using ditto (macOS native, preserves permissions and code signing)
|
|
163
|
-
execSync(`ditto -xk "${zipPath}" "${DESKTOP_DIR}"`, { stdio: 'pipe' });
|
|
164
|
-
fs.unlinkSync(zipPath);
|
|
165
|
-
|
|
166
|
-
if (fs.existsSync(appPath)) {
|
|
167
|
-
// Remove quarantine attribute so macOS doesn't block it
|
|
168
|
-
try { execSync(`xattr -rd com.apple.quarantine "${appPath}" 2>/dev/null`); } catch {}
|
|
169
|
-
console.log(` ✓ nstantpage desktop ${version} installed to ${appPath}`);
|
|
170
|
-
} else {
|
|
171
|
-
console.log(' ⚠ Extraction succeeded but app not found at expected path.');
|
|
172
|
-
}
|
|
173
|
-
} else if (platform === 'win32') {
|
|
174
|
-
// Download exe
|
|
175
|
-
const exePath = path.join(DESKTOP_DIR, 'nstantpage.exe');
|
|
176
|
-
await downloadFile(asset.browser_download_url, exePath);
|
|
177
|
-
console.log(` ✓ nstantpage desktop ${version} installed to ${exePath}`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Record version
|
|
181
|
-
fs.writeFileSync(VERSION_FILE, version, 'utf-8');
|
|
182
|
-
} catch (err) {
|
|
183
|
-
// Non-fatal: network errors, rate limits, etc. User can still use CLI-only mode.
|
|
184
|
-
// Only show a brief note, don't fail the npm install.
|
|
185
|
-
console.log(` Note: Could not download desktop app (${err.message}). CLI mode will work fine.`);
|
|
186
|
-
console.log(' Run "nstantpage update" later to retry.');
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
main();
|