nstantpage-agent 0.7.0 → 0.8.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.js +2 -9
- package/dist/commands/service.js +0 -132
- package/dist/commands/start.js +1 -9
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +6 -0
- 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 -249
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,6 @@ 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
24
|
import { updateCommand } from './commands/update.js';
|
|
25
|
-
import { runCommand } from './commands/run.js';
|
|
26
25
|
import { syncCommand } from './commands/sync.js';
|
|
27
26
|
import { getPackageVersion } from './version.js';
|
|
28
27
|
const program = new Command();
|
|
@@ -53,17 +52,13 @@ program
|
|
|
53
52
|
.option('--dir <path>', 'Project directory override')
|
|
54
53
|
.option('--no-dev', 'Skip starting the dev server (start manually later)')
|
|
55
54
|
.action(startCommand);
|
|
56
|
-
program
|
|
57
|
-
.command('run')
|
|
58
|
-
.description('Open the nstantpage desktop app')
|
|
59
|
-
.option('--local [port]', 'Connect to local backend (default: 5001)')
|
|
60
|
-
.action(runCommand);
|
|
61
55
|
program
|
|
62
56
|
.command('sync')
|
|
63
57
|
.description('Sync local directory to nstantpage and start agent')
|
|
64
58
|
.argument('[directory]', 'Project directory to sync (defaults to current directory)', '.')
|
|
65
59
|
.option('-p, --port <port>', 'Local dev server port', '3000')
|
|
66
60
|
.option('-a, --api-port <port>', 'Local API server port', '18924')
|
|
61
|
+
.option('--local [port]', 'Sync to local backend (default: localhost:5001)')
|
|
67
62
|
.option('--gateway <url>', 'Gateway URL (default: from login)')
|
|
68
63
|
.option('--backend <url>', 'Backend API URL (auto-detected from gateway)')
|
|
69
64
|
.option('--token <token>', 'Auth token (skip login flow)')
|
|
@@ -124,9 +119,7 @@ service
|
|
|
124
119
|
.action(serviceStatusCommand);
|
|
125
120
|
program
|
|
126
121
|
.command('update')
|
|
127
|
-
.description('Update CLI
|
|
128
|
-
.option('--cli', 'Update only the CLI package')
|
|
129
|
-
.option('--desktop', 'Update only the desktop app')
|
|
122
|
+
.description('Update CLI to the latest version')
|
|
130
123
|
.action(updateCommand);
|
|
131
124
|
program.parse();
|
|
132
125
|
//# sourceMappingURL=cli.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.js
CHANGED
|
@@ -24,7 +24,6 @@ import { getConfig, getProjectConfig, setProjectConfig, clearProjectConfig, getD
|
|
|
24
24
|
import { TunnelClient } from '../tunnel.js';
|
|
25
25
|
import { LocalServer } from '../localServer.js';
|
|
26
26
|
import { PackageInstaller } from '../packageInstaller.js';
|
|
27
|
-
import { ensureGit } from '../gitSetup.js';
|
|
28
27
|
import { probeLocalPostgres, ensureLocalProjectDb, closeAdminPool, writeDatabaseUrlToEnv } from '../projectDb.js';
|
|
29
28
|
import { StatusServer } from '../statusServer.js';
|
|
30
29
|
import { getPackageVersion } from '../version.js';
|
|
@@ -255,14 +254,6 @@ export async function startCommand(directory, options) {
|
|
|
255
254
|
cleanupPreviousAgent(projectId, apiPort, devPort);
|
|
256
255
|
// Brief pause for OS to release ports (50ms is sufficient on macOS/Linux)
|
|
257
256
|
await new Promise(r => setTimeout(r, 50));
|
|
258
|
-
// Ensure git is installed (needed for undo/version control)
|
|
259
|
-
const gitAvailable = await ensureGit();
|
|
260
|
-
if (gitAvailable) {
|
|
261
|
-
console.log(chalk.green(` ✓ Git available`));
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
console.log(chalk.yellow(` ⚠ Git not available — undo feature will be limited`));
|
|
265
|
-
}
|
|
266
257
|
console.log(chalk.blue(`\n🚀 nstantpage agent v${VERSION}\n`));
|
|
267
258
|
console.log(chalk.gray(` Project ID: ${projectId}`));
|
|
268
259
|
console.log(chalk.gray(` Device ID: ${deviceId.slice(0, 12)}...`));
|
|
@@ -632,6 +623,7 @@ async function startStandbyMode(token, options, backendUrl, deviceId) {
|
|
|
632
623
|
devPort: allocatePortsForProject(pid).devPort,
|
|
633
624
|
apiPort: allocatePortsForProject(pid).apiPort,
|
|
634
625
|
tunnelStatus: proj.tunnel.status,
|
|
626
|
+
projectDir: resolveProjectDir('.', pid),
|
|
635
627
|
})),
|
|
636
628
|
requestsForwarded: standbyTunnel.stats.requestsForwarded +
|
|
637
629
|
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);
|
|
@@ -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.0",
|
|
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,249 +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
|
-
/**
|
|
115
|
-
* Ensure git is installed. Try to auto-install if missing.
|
|
116
|
-
*/
|
|
117
|
-
function ensureGit() {
|
|
118
|
-
try {
|
|
119
|
-
execSync('git --version', { stdio: 'pipe' });
|
|
120
|
-
return; // already installed
|
|
121
|
-
} catch {
|
|
122
|
-
// git not found — try to install
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log(' Installing git (required for undo/version control)...');
|
|
126
|
-
const platform = os.platform();
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
if (platform === 'darwin') {
|
|
130
|
-
// Try Homebrew first (non-interactive)
|
|
131
|
-
try {
|
|
132
|
-
execSync('brew --version', { stdio: 'pipe' });
|
|
133
|
-
execSync('brew install git', { stdio: 'inherit' });
|
|
134
|
-
console.log(' ✓ Git installed via Homebrew');
|
|
135
|
-
return;
|
|
136
|
-
} catch {}
|
|
137
|
-
// Trigger Xcode CLT install
|
|
138
|
-
console.log(' Run this to install git: xcode-select --install');
|
|
139
|
-
} else if (platform === 'win32') {
|
|
140
|
-
try {
|
|
141
|
-
execSync('winget --version', { stdio: 'pipe' });
|
|
142
|
-
execSync('winget install --id Git.Git -e --source winget --accept-package-agreements --accept-source-agreements', { stdio: 'inherit' });
|
|
143
|
-
console.log(' ✓ Git installed via winget');
|
|
144
|
-
return;
|
|
145
|
-
} catch {}
|
|
146
|
-
console.log(' Install Git for Windows: https://git-scm.com/download/win');
|
|
147
|
-
} else {
|
|
148
|
-
// Linux: try apt, dnf, etc.
|
|
149
|
-
const managers = [
|
|
150
|
-
{ check: 'apt-get --version', install: 'sudo apt-get install -y git' },
|
|
151
|
-
{ check: 'dnf --version', install: 'sudo dnf install -y git' },
|
|
152
|
-
{ check: 'yum --version', install: 'sudo yum install -y git' },
|
|
153
|
-
{ check: 'pacman --version', install: 'sudo pacman -S --noconfirm git' },
|
|
154
|
-
];
|
|
155
|
-
for (const { check, install } of managers) {
|
|
156
|
-
try {
|
|
157
|
-
execSync(check, { stdio: 'pipe' });
|
|
158
|
-
execSync(install, { stdio: 'inherit' });
|
|
159
|
-
console.log(' ✓ Git installed');
|
|
160
|
-
return;
|
|
161
|
-
} catch { continue; }
|
|
162
|
-
}
|
|
163
|
-
console.log(' Please install git: sudo apt-get install git');
|
|
164
|
-
}
|
|
165
|
-
} catch (err) {
|
|
166
|
-
console.log(` Note: Could not auto-install git (${err.message}). Install it manually for undo support.`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function main() {
|
|
171
|
-
// Ensure git is available (non-fatal)
|
|
172
|
-
try { ensureGit(); } catch {}
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const assetPattern = getAssetPattern();
|
|
176
|
-
if (!assetPattern) {
|
|
177
|
-
// No desktop build for this platform — skip silently
|
|
178
|
-
process.exit(0);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Check latest release
|
|
182
|
-
const release = await fetchJson(
|
|
183
|
-
`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest`
|
|
184
|
-
);
|
|
185
|
-
const version = release.tag_name; // e.g., "v0.5.44"
|
|
186
|
-
|
|
187
|
-
// Check if already downloaded and up-to-date
|
|
188
|
-
if (fs.existsSync(VERSION_FILE)) {
|
|
189
|
-
const installed = fs.readFileSync(VERSION_FILE, 'utf-8').trim();
|
|
190
|
-
if (installed === version) {
|
|
191
|
-
console.log(` nstantpage desktop ${version} already installed.`);
|
|
192
|
-
process.exit(0);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Find the matching asset
|
|
197
|
-
const asset = release.assets.find((a) => assetPattern.test(a.name));
|
|
198
|
-
if (!asset) {
|
|
199
|
-
// No matching asset — skip silently
|
|
200
|
-
process.exit(0);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
console.log(` Installing nstantpage desktop ${version}...`);
|
|
204
|
-
|
|
205
|
-
// Prepare directories
|
|
206
|
-
fs.mkdirSync(DESKTOP_DIR, { recursive: true });
|
|
207
|
-
|
|
208
|
-
const platform = os.platform();
|
|
209
|
-
|
|
210
|
-
if (platform === 'darwin') {
|
|
211
|
-
// Download zip, extract .app
|
|
212
|
-
const zipPath = path.join(DESKTOP_DIR, 'download.zip');
|
|
213
|
-
await downloadFile(asset.browser_download_url, zipPath);
|
|
214
|
-
|
|
215
|
-
// Remove old app if exists
|
|
216
|
-
const appPath = path.join(DESKTOP_DIR, 'nstantpage.app');
|
|
217
|
-
if (fs.existsSync(appPath)) {
|
|
218
|
-
fs.rmSync(appPath, { recursive: true, force: true });
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Extract using ditto (macOS native, preserves permissions and code signing)
|
|
222
|
-
execSync(`ditto -xk "${zipPath}" "${DESKTOP_DIR}"`, { stdio: 'pipe' });
|
|
223
|
-
fs.unlinkSync(zipPath);
|
|
224
|
-
|
|
225
|
-
if (fs.existsSync(appPath)) {
|
|
226
|
-
// Remove quarantine attribute so macOS doesn't block it
|
|
227
|
-
try { execSync(`xattr -rd com.apple.quarantine "${appPath}" 2>/dev/null`); } catch {}
|
|
228
|
-
console.log(` ✓ nstantpage desktop ${version} installed to ${appPath}`);
|
|
229
|
-
} else {
|
|
230
|
-
console.log(' ⚠ Extraction succeeded but app not found at expected path.');
|
|
231
|
-
}
|
|
232
|
-
} else if (platform === 'win32') {
|
|
233
|
-
// Download exe
|
|
234
|
-
const exePath = path.join(DESKTOP_DIR, 'nstantpage.exe');
|
|
235
|
-
await downloadFile(asset.browser_download_url, exePath);
|
|
236
|
-
console.log(` ✓ nstantpage desktop ${version} installed to ${exePath}`);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Record version
|
|
240
|
-
fs.writeFileSync(VERSION_FILE, version, 'utf-8');
|
|
241
|
-
} catch (err) {
|
|
242
|
-
// Non-fatal: network errors, rate limits, etc. User can still use CLI-only mode.
|
|
243
|
-
// Only show a brief note, don't fail the npm install.
|
|
244
|
-
console.log(` Note: Could not download desktop app (${err.message}). CLI mode will work fine.`);
|
|
245
|
-
console.log(' Run "nstantpage update" later to retry.');
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
main();
|