nstantpage-agent 0.2.2 → 0.3.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 +4 -2
- package/dist/commands/start.d.ts +8 -3
- package/dist/commands/start.js +135 -31
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ const program = new Command();
|
|
|
20
20
|
program
|
|
21
21
|
.name('nstantpage')
|
|
22
22
|
.description('Local development agent for nstantpage.com — run projects on your machine, preview in the cloud')
|
|
23
|
-
.version('0.
|
|
23
|
+
.version('0.3.0');
|
|
24
24
|
program
|
|
25
25
|
.command('login')
|
|
26
26
|
.description('Authenticate with nstantpage.com')
|
|
@@ -28,12 +28,14 @@ program
|
|
|
28
28
|
program
|
|
29
29
|
.command('start')
|
|
30
30
|
.description('Start the agent for a project (replaces cloud containers)')
|
|
31
|
-
.argument('[directory]', 'Project directory (defaults to
|
|
31
|
+
.argument('[directory]', 'Project directory (defaults to ~/.nstantpage/projects/<id>)', '.')
|
|
32
32
|
.option('-p, --port <port>', 'Local dev server port', '3000')
|
|
33
33
|
.option('-a, --api-port <port>', 'Local API server port (internal)', '18924')
|
|
34
34
|
.option('--project-id <id>', 'Link to a specific project ID')
|
|
35
35
|
.option('--gateway <url>', 'Gateway URL', 'wss://webprev.live')
|
|
36
|
+
.option('--backend <url>', 'Backend API URL (auto-detected from gateway)')
|
|
36
37
|
.option('--token <token>', 'Auth token (skip login flow)')
|
|
38
|
+
.option('--dir <path>', 'Project directory override')
|
|
37
39
|
.option('--no-dev', 'Skip starting the dev server (start manually later)')
|
|
38
40
|
.action(startCommand);
|
|
39
41
|
program
|
package/dist/commands/start.d.ts
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Start command — launch the nstantpage agent
|
|
3
3
|
*
|
|
4
4
|
* Architecture:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
5
|
+
* 1. Resolve or create project directory (~/.nstantpage/projects/{id}/)
|
|
6
|
+
* 2. Fetch project files from the backend API
|
|
7
|
+
* 3. Install dependencies (npm/pnpm)
|
|
8
|
+
* 4. Start local API server (handles /live/* requests for file sync, checks, etc.)
|
|
9
|
+
* 5. Start local dev server (Vite/Next.js — runs project on user's machine)
|
|
10
|
+
* 6. Connect tunnel to gateway (relays requests from cloud to local machine)
|
|
8
11
|
*
|
|
9
12
|
* The agent fully replaces Docker containers:
|
|
10
13
|
* - Files are on the user's disk (no docker cp needed)
|
|
@@ -17,7 +20,9 @@ interface StartOptions {
|
|
|
17
20
|
apiPort: string;
|
|
18
21
|
projectId?: string;
|
|
19
22
|
gateway: string;
|
|
23
|
+
backend?: string;
|
|
20
24
|
token?: string;
|
|
25
|
+
dir?: string;
|
|
21
26
|
noDev?: boolean;
|
|
22
27
|
}
|
|
23
28
|
export declare function startCommand(directory: string, options: StartOptions): Promise<void>;
|
package/dist/commands/start.js
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Start command — launch the nstantpage agent
|
|
3
3
|
*
|
|
4
4
|
* Architecture:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
5
|
+
* 1. Resolve or create project directory (~/.nstantpage/projects/{id}/)
|
|
6
|
+
* 2. Fetch project files from the backend API
|
|
7
|
+
* 3. Install dependencies (npm/pnpm)
|
|
8
|
+
* 4. Start local API server (handles /live/* requests for file sync, checks, etc.)
|
|
9
|
+
* 5. Start local dev server (Vite/Next.js — runs project on user's machine)
|
|
10
|
+
* 6. Connect tunnel to gateway (relays requests from cloud to local machine)
|
|
8
11
|
*
|
|
9
12
|
* The agent fully replaces Docker containers:
|
|
10
13
|
* - Files are on the user's disk (no docker cp needed)
|
|
@@ -15,9 +18,85 @@
|
|
|
15
18
|
import chalk from 'chalk';
|
|
16
19
|
import path from 'path';
|
|
17
20
|
import fs from 'fs';
|
|
21
|
+
import os from 'os';
|
|
18
22
|
import { getConfig } from '../config.js';
|
|
19
23
|
import { TunnelClient } from '../tunnel.js';
|
|
20
24
|
import { LocalServer } from '../localServer.js';
|
|
25
|
+
import { PackageInstaller } from '../packageInstaller.js';
|
|
26
|
+
const VERSION = '0.3.0';
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the backend API base URL.
|
|
29
|
+
* - If --backend is passed, use it
|
|
30
|
+
* - If gateway is local (ws://localhost), assume backend is localhost:5001
|
|
31
|
+
* - Otherwise, default to https://ntstantpage.com
|
|
32
|
+
*/
|
|
33
|
+
function resolveBackendUrl(options) {
|
|
34
|
+
if (options.backend)
|
|
35
|
+
return options.backend.replace(/\/$/, '');
|
|
36
|
+
const isLocal = /^wss?:\/\/(localhost|127\.0\.0\.1)/.test(options.gateway);
|
|
37
|
+
return isLocal ? 'http://localhost:5001' : 'https://ntstantpage.com';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve project directory:
|
|
41
|
+
* - If --dir or [directory] arg given → use that
|
|
42
|
+
* - Otherwise → ~/.nstantpage/projects/{projectId}/
|
|
43
|
+
*/
|
|
44
|
+
function resolveProjectDir(directory, projectId, customDir) {
|
|
45
|
+
// Explicit --dir flag takes priority
|
|
46
|
+
if (customDir)
|
|
47
|
+
return path.resolve(customDir);
|
|
48
|
+
// If user passed a non-default directory argument (not '.')
|
|
49
|
+
if (directory !== '.')
|
|
50
|
+
return path.resolve(directory);
|
|
51
|
+
// Default: create in ~/.nstantpage/projects/{projectId}/
|
|
52
|
+
const defaultBase = path.join(os.homedir(), '.nstantpage', 'projects', projectId);
|
|
53
|
+
return defaultBase;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Fetch project files from the .NET backend and write them to disk.
|
|
57
|
+
*/
|
|
58
|
+
async function fetchProjectFiles(backendUrl, projectId, projectDir, token) {
|
|
59
|
+
const url = `${backendUrl}/api/sandbox/files?projectId=${projectId}`;
|
|
60
|
+
console.log(chalk.gray(` Fetching project files...`));
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
headers: {
|
|
63
|
+
'Authorization': `Bearer ${token}`,
|
|
64
|
+
'Accept': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const text = await response.text().catch(() => '');
|
|
69
|
+
throw new Error(`Failed to fetch project files (${response.status}): ${text}`);
|
|
70
|
+
}
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
if (!data.files || data.files.length === 0) {
|
|
73
|
+
throw new Error('No files returned from backend. Is the project ID correct?');
|
|
74
|
+
}
|
|
75
|
+
// Check if we already have this version (skip re-downloading)
|
|
76
|
+
const versionFile = path.join(projectDir, '.nstantpage-version');
|
|
77
|
+
const existingVersion = fs.existsSync(versionFile)
|
|
78
|
+
? fs.readFileSync(versionFile, 'utf-8').trim()
|
|
79
|
+
: null;
|
|
80
|
+
if (existingVersion === String(data.versionId)) {
|
|
81
|
+
console.log(chalk.gray(` Files up-to-date (version ${data.version})`));
|
|
82
|
+
return { fileCount: data.files.length, isNew: false };
|
|
83
|
+
}
|
|
84
|
+
// Write all files to disk
|
|
85
|
+
let written = 0;
|
|
86
|
+
for (const file of data.files) {
|
|
87
|
+
const filePath = path.join(projectDir, file.path);
|
|
88
|
+
const dir = path.dirname(filePath);
|
|
89
|
+
if (!fs.existsSync(dir)) {
|
|
90
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
fs.writeFileSync(filePath, file.content, 'utf-8');
|
|
93
|
+
written++;
|
|
94
|
+
}
|
|
95
|
+
// Write version marker
|
|
96
|
+
fs.writeFileSync(versionFile, String(data.versionId), 'utf-8');
|
|
97
|
+
console.log(chalk.green(` ✓ ${written} files downloaded (version ${data.version})`));
|
|
98
|
+
return { fileCount: written, isNew: true };
|
|
99
|
+
}
|
|
21
100
|
export async function startCommand(directory, options) {
|
|
22
101
|
const conf = getConfig();
|
|
23
102
|
// If --token was passed, persist it
|
|
@@ -36,53 +115,77 @@ export async function startCommand(directory, options) {
|
|
|
36
115
|
if (!token && isLocalGateway) {
|
|
37
116
|
token = 'local-dev';
|
|
38
117
|
}
|
|
39
|
-
// Resolve project directory
|
|
40
|
-
const projectDir = path.resolve(directory);
|
|
41
|
-
if (!fs.existsSync(projectDir)) {
|
|
42
|
-
console.log(chalk.red(`✗ Directory not found: ${projectDir}`));
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
// Verify it's a project directory
|
|
46
|
-
if (!fs.existsSync(path.join(projectDir, 'package.json'))) {
|
|
47
|
-
console.log(chalk.red(`✗ No package.json found in ${projectDir}`));
|
|
48
|
-
console.log(chalk.gray(' Make sure you\'re in a project directory'));
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
118
|
const devPort = parseInt(options.port, 10);
|
|
52
119
|
const apiPort = parseInt(options.apiPort, 10);
|
|
53
120
|
// Determine project ID
|
|
54
121
|
let projectId = options.projectId || conf.get('projectId');
|
|
55
|
-
// Try .nstantpage.json in project dir
|
|
56
|
-
const localConfig = path.join(projectDir, '.nstantpage.json');
|
|
57
|
-
if (!projectId && fs.existsSync(localConfig)) {
|
|
58
|
-
try {
|
|
59
|
-
const data = JSON.parse(fs.readFileSync(localConfig, 'utf-8'));
|
|
60
|
-
projectId = data.projectId;
|
|
61
|
-
}
|
|
62
|
-
catch { }
|
|
63
|
-
}
|
|
64
122
|
if (!projectId) {
|
|
65
123
|
console.log(chalk.yellow('⚠ No project ID specified.'));
|
|
66
|
-
console.log(chalk.gray(' Use --project-id <id>
|
|
67
|
-
console.log(chalk.gray('
|
|
124
|
+
console.log(chalk.gray(' Use --project-id <id>'));
|
|
125
|
+
console.log(chalk.gray(' Example: npx nstantpage-agent start --project-id 1234'));
|
|
68
126
|
process.exit(1);
|
|
69
127
|
}
|
|
128
|
+
// Resolve project directory
|
|
129
|
+
const projectDir = resolveProjectDir(directory, projectId, options.dir);
|
|
130
|
+
// Create directory if it doesn't exist
|
|
131
|
+
if (!fs.existsSync(projectDir)) {
|
|
132
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
133
|
+
}
|
|
70
134
|
// Save project ID
|
|
71
135
|
conf.set('projectId', projectId);
|
|
72
|
-
|
|
73
|
-
console.log(chalk.
|
|
136
|
+
const backendUrl = resolveBackendUrl(options);
|
|
137
|
+
console.log(chalk.blue(`\n🚀 nstantpage agent v${VERSION}\n`));
|
|
74
138
|
console.log(chalk.gray(` Project ID: ${projectId}`));
|
|
139
|
+
console.log(chalk.gray(` Directory: ${projectDir}`));
|
|
75
140
|
console.log(chalk.gray(` Dev server: port ${devPort}`));
|
|
76
141
|
console.log(chalk.gray(` API server: port ${apiPort}`));
|
|
77
|
-
console.log(chalk.gray(` Gateway: ${options.gateway}
|
|
78
|
-
|
|
142
|
+
console.log(chalk.gray(` Gateway: ${options.gateway}`));
|
|
143
|
+
console.log(chalk.gray(` Backend: ${backendUrl}\n`));
|
|
144
|
+
// 1. Fetch project files from the backend
|
|
145
|
+
try {
|
|
146
|
+
const { fileCount, isNew } = await fetchProjectFiles(backendUrl, projectId, projectDir, token);
|
|
147
|
+
// 2. Install dependencies if this is a new download or node_modules is missing
|
|
148
|
+
const hasNodeModules = fs.existsSync(path.join(projectDir, 'node_modules'));
|
|
149
|
+
if (isNew || !hasNodeModules) {
|
|
150
|
+
if (fs.existsSync(path.join(projectDir, 'package.json'))) {
|
|
151
|
+
console.log(chalk.gray(' Installing dependencies...'));
|
|
152
|
+
const installer = new PackageInstaller({ projectDir });
|
|
153
|
+
const result = await installer.install([], false);
|
|
154
|
+
if (result.success) {
|
|
155
|
+
console.log(chalk.green(` ✓ Dependencies installed`));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log(chalk.yellow(` ⚠ Dependency installation had issues: ${result.output.slice(0, 200)}`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.log(chalk.gray(` Dependencies already installed`));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
console.log(chalk.yellow(` ⚠ Could not fetch project files: ${err.message}`));
|
|
168
|
+
console.log(chalk.gray(' Continuing with existing local files (if any)...'));
|
|
169
|
+
// Don't exit — maybe local files exist from a previous run
|
|
170
|
+
if (!fs.existsSync(path.join(projectDir, 'package.json'))) {
|
|
171
|
+
console.log(chalk.red(`\n✗ No package.json found and cannot fetch files from backend.`));
|
|
172
|
+
console.log(chalk.gray(' Check your project ID and authentication.'));
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Save .nstantpage.json for future runs
|
|
177
|
+
const localConfigPath = path.join(projectDir, '.nstantpage.json');
|
|
178
|
+
if (!fs.existsSync(localConfigPath)) {
|
|
179
|
+
fs.writeFileSync(localConfigPath, JSON.stringify({ projectId }, null, 2), 'utf-8');
|
|
180
|
+
}
|
|
181
|
+
// 3. Start local server (API + dev server)
|
|
79
182
|
const localServer = new LocalServer({
|
|
80
183
|
projectDir,
|
|
81
184
|
projectId,
|
|
82
185
|
apiPort,
|
|
83
186
|
devPort,
|
|
84
187
|
});
|
|
85
|
-
//
|
|
188
|
+
// 4. Create tunnel client
|
|
86
189
|
const tunnel = new TunnelClient({
|
|
87
190
|
gatewayUrl: options.gateway,
|
|
88
191
|
token,
|
|
@@ -133,6 +236,7 @@ export async function startCommand(directory, options) {
|
|
|
133
236
|
console.log(chalk.blue.bold(` ├──────────────────────────────────────────────┤`));
|
|
134
237
|
console.log(chalk.white(` │ Cloud: https://${projectId}.webprev.live`));
|
|
135
238
|
console.log(chalk.white(` │ Local: http://localhost:${devPort}`));
|
|
239
|
+
console.log(chalk.white(` │ Files: ${projectDir}`));
|
|
136
240
|
console.log(chalk.blue.bold(` └──────────────────────────────────────────────┘\n`));
|
|
137
241
|
console.log(chalk.gray(` Mode: ${chalk.green('Agent')} (no containers needed)`));
|
|
138
242
|
console.log(chalk.gray(` All builds, checks, and previews run on this machine.`));
|
package/package.json
CHANGED