nstantpage-agent 0.2.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.
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Error Store — tracks build, type, lint, and runtime errors for a project.
3
+ * This replaces the container-based error tracking from the gateway.
4
+ */
5
+ export class ErrorStore {
6
+ buildErrors = [];
7
+ typeErrors = [];
8
+ lintErrors = [];
9
+ runtimeErrors = [];
10
+ setBuildErrors(errors) {
11
+ this.buildErrors = errors;
12
+ }
13
+ setTypeErrors(errors) {
14
+ this.typeErrors = errors;
15
+ }
16
+ setLintErrors(errors) {
17
+ this.lintErrors = errors;
18
+ }
19
+ addRuntimeError(error) {
20
+ // Dedup by signature
21
+ if (!this.runtimeErrors.some(e => e.signature === error.signature)) {
22
+ this.runtimeErrors.push(error);
23
+ // Keep max 50 runtime errors
24
+ if (this.runtimeErrors.length > 50) {
25
+ this.runtimeErrors = this.runtimeErrors.slice(-50);
26
+ }
27
+ }
28
+ }
29
+ clearRuntimeErrors() {
30
+ this.runtimeErrors = [];
31
+ }
32
+ clearAll() {
33
+ this.buildErrors = [];
34
+ this.typeErrors = [];
35
+ this.lintErrors = [];
36
+ this.runtimeErrors = [];
37
+ }
38
+ getAllErrors() {
39
+ const all = [
40
+ ...this.buildErrors,
41
+ ...this.typeErrors,
42
+ ...this.lintErrors,
43
+ ...this.runtimeErrors,
44
+ ];
45
+ // Deduplicate by signature
46
+ const seen = new Set();
47
+ return all.filter(e => {
48
+ if (seen.has(e.signature))
49
+ return false;
50
+ seen.add(e.signature);
51
+ return true;
52
+ });
53
+ }
54
+ getErrorsByType() {
55
+ return {
56
+ buildErrors: this.buildErrors.map(structuredErrorToString),
57
+ typeErrors: this.typeErrors.map(structuredErrorToString),
58
+ lintErrors: this.lintErrors.map(structuredErrorToString),
59
+ runtimeErrors: this.runtimeErrors.map(structuredErrorToString),
60
+ };
61
+ }
62
+ getStructuredByType() {
63
+ return {
64
+ buildErrors: [...this.buildErrors],
65
+ typeErrors: [...this.typeErrors],
66
+ lintErrors: [...this.lintErrors],
67
+ runtimeErrors: [...this.runtimeErrors],
68
+ };
69
+ }
70
+ }
71
+ /**
72
+ * Convert structured error to display string.
73
+ */
74
+ export function structuredErrorToString(error) {
75
+ if (error.file && error.line) {
76
+ const prefix = error.type === 'type' ? '[TS]' :
77
+ error.type === 'lint' ? '[Lint]' :
78
+ error.type === 'hard' ? '[HARD]' :
79
+ error.type === 'runtime' ? '[Runtime]' : '';
80
+ const codeStr = error.code ? ` ${error.code}:` : '';
81
+ return `${prefix ? prefix + ' ' : ''}${error.file} (Line ${error.line}):${codeStr} ${error.message}`;
82
+ }
83
+ return error.message;
84
+ }
85
+ /**
86
+ * Parse a raw error string into a structured error.
87
+ */
88
+ export function parseErrorString(errorStr, defaultType = 'build') {
89
+ let file;
90
+ let line;
91
+ let col;
92
+ let message = errorStr;
93
+ let type = defaultType;
94
+ let code;
95
+ // Strip ANSI codes
96
+ // eslint-disable-next-line no-control-regex
97
+ const cleanStr = errorStr.replace(/\u001b\[[0-9;]*m/g, '').trim();
98
+ // Pattern 1: [TYPE] file (Line N): message
99
+ const prefixMatch = cleanStr.match(/^\[(TS|Lint|HARD|Build|Runtime)\]\s*(.+?)\s*\(Line\s*(\d+)\):\s*(.+)$/i);
100
+ if (prefixMatch) {
101
+ const typeStr = prefixMatch[1].toLowerCase();
102
+ if (typeStr === 'ts')
103
+ type = 'type';
104
+ else if (typeStr === 'lint')
105
+ type = 'lint';
106
+ else if (typeStr === 'hard')
107
+ type = 'hard';
108
+ else if (typeStr === 'runtime')
109
+ type = 'runtime';
110
+ else
111
+ type = 'build';
112
+ file = prefixMatch[2];
113
+ line = parseInt(prefixMatch[3], 10);
114
+ message = prefixMatch[4];
115
+ const codeMatch = message.match(/\s*\[([^\]]+)\]$/);
116
+ if (codeMatch) {
117
+ code = codeMatch[1];
118
+ message = message.replace(/\s*\[[^\]]+\]$/, '');
119
+ }
120
+ }
121
+ // Pattern 2: file(line,col): error CODE: message
122
+ else {
123
+ const tscMatch = cleanStr.match(/^(.+?)\((\d+),(\d+)\):\s*(?:error|warning)\s+(TS\d+):\s*(.+)$/);
124
+ if (tscMatch) {
125
+ file = tscMatch[1];
126
+ line = parseInt(tscMatch[2], 10);
127
+ col = parseInt(tscMatch[3], 10);
128
+ code = tscMatch[4];
129
+ message = tscMatch[5];
130
+ type = 'type';
131
+ }
132
+ // Pattern 3: file:line:col: message
133
+ else {
134
+ const colonMatch = cleanStr.match(/^(.+?):(\d+):(\d+):\s*(.+)$/);
135
+ if (colonMatch) {
136
+ file = colonMatch[1];
137
+ line = parseInt(colonMatch[2], 10);
138
+ col = parseInt(colonMatch[3], 10);
139
+ message = colonMatch[4];
140
+ }
141
+ }
142
+ }
143
+ // Normalize file path to project-relative
144
+ if (file) {
145
+ file = file.replace(/^\.\//, '').replace(/^\//, '');
146
+ const srcIdx = file.indexOf('src/');
147
+ if (srcIdx > 0)
148
+ file = file.substring(srcIdx);
149
+ }
150
+ const signature = `${type}:${file || 'unknown'}:${line || 0}:${message.substring(0, 100)}`;
151
+ return { file, line, col, message: message.trim(), type, code, signature, raw: errorStr };
152
+ }
153
+ /**
154
+ * Filter non-error messages (Babel notes, CSS at-rule warnings, etc.)
155
+ */
156
+ export function filterNonErrors(errors) {
157
+ return errors.filter(error => {
158
+ if (/\[BABEL\]\s*Note:/i.test(error))
159
+ return false;
160
+ if (error.includes('node_modules/@babel/parser/src/parser/statement.ts'))
161
+ return false;
162
+ if (error.includes('at-rule-no-unknown'))
163
+ return false;
164
+ return true;
165
+ });
166
+ }
167
+ //# sourceMappingURL=errorStore.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * File Manager — handles file I/O for the local project.
3
+ * Replaces the gateway's projectManager.writeFiles + docker cp workflow.
4
+ */
5
+ export interface FileManagerOptions {
6
+ projectDir: string;
7
+ }
8
+ export declare class FileManager {
9
+ private projectDir;
10
+ constructor(options: FileManagerOptions);
11
+ /**
12
+ * Write multiple files to the project directory.
13
+ * files: { "src/App.tsx": "content...", "src/utils.ts": "content..." }
14
+ */
15
+ writeFiles(files: Record<string, string>): Promise<{
16
+ filesWritten: number;
17
+ paths: string[];
18
+ }>;
19
+ /**
20
+ * Delete files from the project directory.
21
+ */
22
+ deleteFiles(filePaths: string[]): Promise<number>;
23
+ /**
24
+ * Read a file from the project directory.
25
+ */
26
+ readFile(filePath: string): Promise<string | null>;
27
+ /**
28
+ * List all source files in the project (src/ directory).
29
+ */
30
+ listSourceFiles(): Promise<string[]>;
31
+ /**
32
+ * Check if the project has a backend (server/ directory).
33
+ */
34
+ hasBackend(): boolean;
35
+ /**
36
+ * Get the project directory path.
37
+ */
38
+ getProjectDir(): string;
39
+ /**
40
+ * Check if a path exists.
41
+ */
42
+ exists(relativePath: string): boolean;
43
+ private walkDir;
44
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * File Manager — handles file I/O for the local project.
3
+ * Replaces the gateway's projectManager.writeFiles + docker cp workflow.
4
+ */
5
+ import path from 'path';
6
+ import fs from 'fs/promises';
7
+ import { existsSync } from 'fs';
8
+ export class FileManager {
9
+ projectDir;
10
+ constructor(options) {
11
+ this.projectDir = options.projectDir;
12
+ }
13
+ /**
14
+ * Write multiple files to the project directory.
15
+ * files: { "src/App.tsx": "content...", "src/utils.ts": "content..." }
16
+ */
17
+ async writeFiles(files) {
18
+ let filesWritten = 0;
19
+ const paths = [];
20
+ for (const [filePath, content] of Object.entries(files)) {
21
+ try {
22
+ // Sanitize path — prevent directory traversal
23
+ const normalized = path.normalize(filePath).replace(/\.\.\//g, '');
24
+ const fullPath = path.join(this.projectDir, normalized);
25
+ // Security check
26
+ if (!fullPath.startsWith(this.projectDir)) {
27
+ console.warn(` [FileManager] Blocked path traversal: ${filePath}`);
28
+ continue;
29
+ }
30
+ // Ensure directory exists
31
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
32
+ // Write file
33
+ await fs.writeFile(fullPath, content, 'utf-8');
34
+ filesWritten++;
35
+ paths.push(normalized);
36
+ }
37
+ catch (err) {
38
+ console.error(` [FileManager] Failed to write ${filePath}: ${err.message}`);
39
+ }
40
+ }
41
+ return { filesWritten, paths };
42
+ }
43
+ /**
44
+ * Delete files from the project directory.
45
+ */
46
+ async deleteFiles(filePaths) {
47
+ let filesDeleted = 0;
48
+ for (const filePath of filePaths) {
49
+ try {
50
+ const normalized = path.normalize(filePath).replace(/\.\.\//g, '');
51
+ const fullPath = path.join(this.projectDir, normalized);
52
+ if (!fullPath.startsWith(this.projectDir))
53
+ continue;
54
+ await fs.unlink(fullPath);
55
+ filesDeleted++;
56
+ }
57
+ catch (err) {
58
+ if (err.code !== 'ENOENT') {
59
+ console.error(` [FileManager] Failed to delete ${filePath}: ${err.message}`);
60
+ }
61
+ }
62
+ }
63
+ return filesDeleted;
64
+ }
65
+ /**
66
+ * Read a file from the project directory.
67
+ */
68
+ async readFile(filePath) {
69
+ try {
70
+ const normalized = path.normalize(filePath).replace(/\.\.\//g, '');
71
+ const fullPath = path.join(this.projectDir, normalized);
72
+ if (!fullPath.startsWith(this.projectDir))
73
+ return null;
74
+ return await fs.readFile(fullPath, 'utf-8');
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ /**
81
+ * List all source files in the project (src/ directory).
82
+ */
83
+ async listSourceFiles() {
84
+ const srcDir = path.join(this.projectDir, 'src');
85
+ if (!existsSync(srcDir))
86
+ return [];
87
+ const files = [];
88
+ await this.walkDir(srcDir, files);
89
+ return files.map(f => path.relative(this.projectDir, f));
90
+ }
91
+ /**
92
+ * Check if the project has a backend (server/ directory).
93
+ */
94
+ hasBackend() {
95
+ return existsSync(path.join(this.projectDir, 'server', 'index.ts')) ||
96
+ existsSync(path.join(this.projectDir, 'server', 'index.js'));
97
+ }
98
+ /**
99
+ * Get the project directory path.
100
+ */
101
+ getProjectDir() {
102
+ return this.projectDir;
103
+ }
104
+ /**
105
+ * Check if a path exists.
106
+ */
107
+ exists(relativePath) {
108
+ return existsSync(path.join(this.projectDir, relativePath));
109
+ }
110
+ async walkDir(dir, files) {
111
+ const entries = await fs.readdir(dir, { withFileTypes: true });
112
+ for (const entry of entries) {
113
+ const fullPath = path.join(dir, entry.name);
114
+ if (entry.isDirectory()) {
115
+ // Skip node_modules, dist, .git
116
+ if (['node_modules', 'dist', '.git', '.vite-cache'].includes(entry.name))
117
+ continue;
118
+ await this.walkDir(fullPath, files);
119
+ }
120
+ else {
121
+ const ext = path.extname(entry.name).toLowerCase();
122
+ if (['.ts', '.tsx', '.js', '.jsx', '.css', '.scss', '.json', '.html'].includes(ext)) {
123
+ files.push(fullPath);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ //# sourceMappingURL=fileManager.js.map
@@ -0,0 +1,8 @@
1
+ export { TunnelClient } from './tunnel.js';
2
+ export { LocalServer } from './localServer.js';
3
+ export { DevServer } from './devServer.js';
4
+ export { FileManager } from './fileManager.js';
5
+ export { Checker } from './checker.js';
6
+ export { ErrorStore } from './errorStore.js';
7
+ export { PackageInstaller } from './packageInstaller.js';
8
+ export { getConfig } from './config.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { TunnelClient } from './tunnel.js';
2
+ export { LocalServer } from './localServer.js';
3
+ export { DevServer } from './devServer.js';
4
+ export { FileManager } from './fileManager.js';
5
+ export { Checker } from './checker.js';
6
+ export { ErrorStore } from './errorStore.js';
7
+ export { PackageInstaller } from './packageInstaller.js';
8
+ export { getConfig } from './config.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Local Server — handles /live/* API requests on the user's machine.
3
+ *
4
+ * This replaces the PreviewGateway + Docker container workflow:
5
+ * - File sync (write files, track changes)
6
+ * - Type checking (run tsc locally)
7
+ * - Error tracking (build, type, runtime)
8
+ * - Package installation (npm/pnpm install)
9
+ * - Dev server management (start, stop, restart)
10
+ * - Terminal execution (run commands in project dir)
11
+ * - Logs and stats
12
+ *
13
+ * Requests arrive through the tunnel from the gateway.
14
+ */
15
+ import { DevServer } from './devServer.js';
16
+ export interface LocalServerOptions {
17
+ projectDir: string;
18
+ projectId: string;
19
+ apiPort: number;
20
+ devPort: number;
21
+ env?: Record<string, string>;
22
+ }
23
+ export declare class LocalServer {
24
+ private server;
25
+ private options;
26
+ private devServer;
27
+ private fileManager;
28
+ private checker;
29
+ private errorStore;
30
+ private packageInstaller;
31
+ private lastHeartbeat;
32
+ constructor(options: LocalServerOptions);
33
+ getDevServer(): DevServer;
34
+ getApiPort(): number;
35
+ getDevPort(): number;
36
+ start(): Promise<void>;
37
+ stop(): Promise<void>;
38
+ private handleRequest;
39
+ private getHandler;
40
+ private handleSync;
41
+ private handleFiles;
42
+ private handleCheck;
43
+ private handleErrors;
44
+ private handleReload;
45
+ private handleRuntimeError;
46
+ private handleInstall;
47
+ private handleExec;
48
+ private handleTerminal;
49
+ private handleContainerStatus;
50
+ private handleContainerStats;
51
+ private handleLogs;
52
+ private handleDev;
53
+ private handleDevRestart;
54
+ private handleDevStop;
55
+ private handleOpen;
56
+ private handleClose;
57
+ private handleHeartbeat;
58
+ private handleHardPatterns;
59
+ private handleHardPatternsBatch;
60
+ private handleNormalize;
61
+ private handleNormalizeBatch;
62
+ private handleInvalidate;
63
+ private handleRefetch;
64
+ private handleGracePeriod;
65
+ private handleStats;
66
+ private handleUsage;
67
+ private handleHealth;
68
+ private json;
69
+ private collectBody;
70
+ }