@frp-bridge/core 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 imba97 <https://github.com/imba97>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,317 @@
1
+ import { ClientConfig, ServerConfig, ProxyConfig } from '@frp-bridge/types';
2
+
3
+ type Awaitable<T> = T | Promise<T>;
4
+ type RuntimeMode = 'client' | 'server';
5
+ type RuntimeStatus = 'idle' | 'starting' | 'running' | 'stopping' | 'error';
6
+ interface RuntimeContext {
7
+ id: string;
8
+ mode: RuntimeMode;
9
+ workDir: string;
10
+ platform: string;
11
+ clock?: () => number;
12
+ logger?: RuntimeLogger;
13
+ }
14
+ interface RuntimeLogger {
15
+ debug: (message: string, context?: Record<string, unknown>) => void;
16
+ info: (message: string, context?: Record<string, unknown>) => void;
17
+ warn: (message: string, context?: Record<string, unknown>) => void;
18
+ error: (message: string, context?: Record<string, unknown>) => void;
19
+ }
20
+ interface RuntimeState {
21
+ status: RuntimeStatus;
22
+ version: number;
23
+ lastAppliedAt?: number;
24
+ lastError?: RuntimeError;
25
+ }
26
+ interface CommandMetadata {
27
+ requestId?: string;
28
+ correlationId?: string;
29
+ author?: string;
30
+ issuedAt?: number;
31
+ }
32
+ interface RuntimeCommand<TPayload = unknown> {
33
+ name: string;
34
+ payload: TPayload;
35
+ metadata?: CommandMetadata;
36
+ }
37
+ interface CommandResult<TResult = unknown> {
38
+ status: CommandStatus;
39
+ version?: number;
40
+ events?: RuntimeEvent[];
41
+ result?: TResult;
42
+ error?: RuntimeError;
43
+ snapshot?: ConfigSnapshot;
44
+ }
45
+ type CommandStatus = 'success' | 'failed' | 'pending';
46
+ interface RuntimeQuery<TPayload = unknown> {
47
+ name: string;
48
+ payload?: TPayload;
49
+ }
50
+ interface QueryResult<TResult = unknown> {
51
+ result: TResult;
52
+ version: number;
53
+ }
54
+ interface RuntimeEvent<TPayload = unknown> {
55
+ type: string;
56
+ timestamp: number;
57
+ version?: number;
58
+ payload?: TPayload;
59
+ }
60
+ interface RuntimeError {
61
+ code: RuntimeErrorCode;
62
+ message: string;
63
+ details?: Record<string, unknown>;
64
+ }
65
+ type RuntimeErrorCode = 'VALIDATION_ERROR' | 'RUNTIME_ERROR' | 'SYSTEM_ERROR';
66
+ interface ConfigSnapshot {
67
+ version: number;
68
+ checksum: string;
69
+ appliedAt: number;
70
+ author?: string;
71
+ summary?: string;
72
+ }
73
+ interface SnapshotStorage {
74
+ save: (snapshot: ConfigSnapshot) => Awaitable<void>;
75
+ load: (version: number) => Awaitable<ConfigSnapshot | undefined>;
76
+ list: () => Awaitable<ConfigSnapshot[]>;
77
+ }
78
+ interface CommandHandlerContext {
79
+ context: RuntimeContext;
80
+ state: RuntimeState;
81
+ emit: (events: RuntimeEvent[]) => void;
82
+ requestVersionBump: () => number;
83
+ }
84
+ type CommandHandler<TPayload = unknown, TResult = unknown> = (command: RuntimeCommand<TPayload>, ctx: CommandHandlerContext) => Awaitable<CommandResult<TResult>>;
85
+ type QueryHandler<TPayload = unknown, TResult = unknown> = (query: RuntimeQuery<TPayload>, ctx: RuntimeContext) => Awaitable<QueryResult<TResult>>;
86
+ interface RuntimeAdapters {
87
+ storage?: SnapshotStorage;
88
+ commands?: Record<string, CommandHandler>;
89
+ queries?: Record<string, QueryHandler>;
90
+ }
91
+
92
+ declare class FrpRuntime {
93
+ private readonly context;
94
+ private readonly storage?;
95
+ private readonly commandHandlers;
96
+ private readonly queryHandlers;
97
+ private eventBuffer;
98
+ private commandQueue;
99
+ private state;
100
+ constructor(context: RuntimeContext, adapters?: RuntimeAdapters);
101
+ registerCommand(name: string, handler: CommandHandler): void;
102
+ registerQuery(name: string, handler: QueryHandler): void;
103
+ execute<TPayload, TResult = unknown>(command: RuntimeCommand<TPayload>): Promise<CommandResult<TResult>>;
104
+ query<TPayload, TResult = unknown>(query: RuntimeQuery<TPayload>): Promise<QueryResult<TResult>>;
105
+ snapshot(): RuntimeState;
106
+ drainEvents(): RuntimeEvent[];
107
+ private runCommand;
108
+ private pushEvents;
109
+ private bumpVersion;
110
+ private persistSnapshot;
111
+ private normalizeError;
112
+ private buildError;
113
+ private now;
114
+ }
115
+
116
+ /**
117
+ * FRP process management utilities
118
+ */
119
+
120
+ interface FrpProcessManagerOptions {
121
+ /** Working directory for FRP files */
122
+ workDir?: string;
123
+ /** FRP version (defaults to latest) */
124
+ version?: string;
125
+ /** Mode: client or server */
126
+ mode: 'client' | 'server';
127
+ /** Optional logger */
128
+ logger?: RuntimeLogger;
129
+ }
130
+ interface NodeInfo {
131
+ /** Node ID */
132
+ id: string;
133
+ /** Node name */
134
+ name: string;
135
+ /** Server address */
136
+ serverAddr: string;
137
+ /** Server port */
138
+ serverPort?: number;
139
+ /** Authentication token */
140
+ token?: string;
141
+ /** Additional config */
142
+ config?: Partial<ClientConfig | ServerConfig>;
143
+ }
144
+ /**
145
+ * Manages FRP client/server lifecycle, config, and tunnels
146
+ */
147
+ declare class FrpProcessManager {
148
+ private readonly workDir;
149
+ private version;
150
+ private readonly mode;
151
+ private readonly specifiedVersion?;
152
+ private readonly logger;
153
+ private process;
154
+ private configPath;
155
+ private binaryPath;
156
+ constructor(options: FrpProcessManagerOptions);
157
+ /** Ensure version is fetched and binary path is set */
158
+ private ensureVersion;
159
+ /** Download FRP binary for current platform */
160
+ downloadFrpBinary(): Promise<void>;
161
+ /** Update FRP binary to latest version */
162
+ updateFrpBinary(newVersion?: string): Promise<void>;
163
+ /** Check if binary exists */
164
+ hasBinary(): boolean;
165
+ /** Get current configuration */
166
+ getConfig(): ClientConfig | ServerConfig | null;
167
+ /** Update configuration */
168
+ updateConfig(config: Partial<ClientConfig | ServerConfig>): void;
169
+ /** Backup configuration */
170
+ backupConfig(): Promise<string>;
171
+ /** Return the absolute config file path */
172
+ getConfigPath(): string;
173
+ /** Read raw config file contents */
174
+ readConfigFile(): string | null;
175
+ /** Overwrite config file with provided content */
176
+ writeConfigFile(content: string): void;
177
+ /** Start FRP process */
178
+ start(): Promise<void>;
179
+ /** Stop FRP process */
180
+ stop(): Promise<void>;
181
+ /** Check if process is running */
182
+ isRunning(): boolean;
183
+ /** Add node (for client mode) */
184
+ addNode(node: NodeInfo): void;
185
+ /** Get node info */
186
+ getNode(): NodeInfo | null;
187
+ /** Update node info */
188
+ updateNode(node: Partial<NodeInfo>): void;
189
+ /** Remove node */
190
+ removeNode(): void;
191
+ /** Add tunnel (proxy) */
192
+ addTunnel(proxy: ProxyConfig): void;
193
+ /** Get tunnel by name */
194
+ getTunnel(name: string): ProxyConfig | null;
195
+ /** Update tunnel */
196
+ updateTunnel(name: string, proxy: Partial<ProxyConfig>): void;
197
+ /** Remove tunnel */
198
+ removeTunnel(name: string): void;
199
+ /** List all tunnels */
200
+ listTunnels(): ProxyConfig[];
201
+ }
202
+
203
+ interface FrpBridgeRuntimeOptions {
204
+ id?: string;
205
+ mode?: RuntimeMode;
206
+ logger?: RuntimeLogger;
207
+ clock?: () => number;
208
+ platform?: string;
209
+ workDir?: string;
210
+ }
211
+ interface FrpBridgeProcessOptions extends Partial<Omit<FrpProcessManagerOptions, 'mode'>> {
212
+ mode?: 'client' | 'server';
213
+ workDir?: string;
214
+ }
215
+ interface FrpBridgeOptions {
216
+ mode: 'client' | 'server';
217
+ workDir?: string;
218
+ runtime?: FrpBridgeRuntimeOptions;
219
+ process?: FrpBridgeProcessOptions;
220
+ storage?: SnapshotStorage;
221
+ commands?: Record<string, CommandHandler>;
222
+ queries?: Record<string, QueryHandler>;
223
+ eventSink?: (event: RuntimeEvent) => void;
224
+ }
225
+ declare class FrpBridge {
226
+ private readonly options;
227
+ private readonly runtime;
228
+ private readonly process;
229
+ private readonly eventSink?;
230
+ constructor(options: FrpBridgeOptions);
231
+ execute<TPayload, TResult = unknown>(command: RuntimeCommand<TPayload>): Promise<CommandResult<TResult>>;
232
+ query<TPayload, TResult = unknown>(query: RuntimeQuery<TPayload>): Promise<QueryResult<TResult>>;
233
+ snapshot(): RuntimeState;
234
+ drainEvents(): RuntimeEvent[];
235
+ getProcessManager(): FrpProcessManager;
236
+ getRuntime(): FrpRuntime;
237
+ private createDefaultCommands;
238
+ private createDefaultQueries;
239
+ private forwardEvents;
240
+ private runConfigMutation;
241
+ }
242
+
243
+ /**
244
+ * Core constants
245
+ */
246
+ /** GitHub repository owner */
247
+ declare const GITHUB_OWNER = "fatedier";
248
+ /** GitHub repository name */
249
+ declare const GITHUB_REPO = "frp";
250
+ /** Platform-specific binary names */
251
+ declare const BINARY_NAMES: {
252
+ readonly client: "frpc.exe" | "frpc";
253
+ readonly server: "frps.exe" | "frps";
254
+ };
255
+ /** Platform architecture mapping */
256
+ declare const ARCH_MAP: Record<string, string>;
257
+ /** Platform OS mapping */
258
+ declare const OS_MAP: Record<string, string>;
259
+
260
+ /** Custom error for FRP Bridge operations */
261
+ declare class FrpBridgeError extends Error {
262
+ readonly code: string;
263
+ readonly details?: unknown | undefined;
264
+ constructor(message: string, code: string, details?: unknown | undefined);
265
+ }
266
+ /** Error codes */
267
+ declare enum ErrorCode {
268
+ BINARY_NOT_FOUND = "BINARY_NOT_FOUND",
269
+ DOWNLOAD_FAILED = "DOWNLOAD_FAILED",
270
+ EXTRACTION_FAILED = "EXTRACTION_FAILED",
271
+ CONFIG_NOT_FOUND = "CONFIG_NOT_FOUND",
272
+ CONFIG_INVALID = "CONFIG_INVALID",
273
+ PROCESS_ALREADY_RUNNING = "PROCESS_ALREADY_RUNNING",
274
+ PROCESS_NOT_RUNNING = "PROCESS_NOT_RUNNING",
275
+ PROCESS_START_FAILED = "PROCESS_START_FAILED",
276
+ UNSUPPORTED_PLATFORM = "UNSUPPORTED_PLATFORM",
277
+ VERSION_FETCH_FAILED = "VERSION_FETCH_FAILED",
278
+ MODE_ERROR = "MODE_ERROR",
279
+ NOT_FOUND = "NOT_FOUND"
280
+ }
281
+
282
+ declare class FileSnapshotStorage implements SnapshotStorage {
283
+ private readonly directory;
284
+ constructor(directory: string);
285
+ save(snapshot: ConfigSnapshot): Promise<void>;
286
+ load(version: number): Promise<ConfigSnapshot | undefined>;
287
+ list(): Promise<ConfigSnapshot[]>;
288
+ private buildPath;
289
+ }
290
+
291
+ /**
292
+ * Utility functions
293
+ */
294
+ /** Get latest FRP version from GitHub releases */
295
+ declare function getLatestVersion(): Promise<string>;
296
+ /** Get platform identifier for FRP release */
297
+ declare function getPlatform(): string;
298
+ /** Get GitHub release download URL */
299
+ declare function getDownloadUrl(version: string, platform: string): string;
300
+ /** Download file from URL */
301
+ declare function downloadFile(url: string, dest: string): Promise<void>;
302
+ /** Execute command */
303
+ declare function executeCommand(command: string): Promise<{
304
+ stdout: string;
305
+ stderr: string;
306
+ }>;
307
+ /** Check if command exists */
308
+ declare function commandExists(command: string): Promise<boolean>;
309
+ /** Ensure directory exists */
310
+ declare function ensureDir(dirPath: string): void;
311
+ /** Parse TOML-like config to JSON */
312
+ declare function parseToml(content: string): Record<string, any>;
313
+ /** Convert JSON to TOML-like config */
314
+ declare function toToml(obj: Record<string, any>): string;
315
+
316
+ export { ARCH_MAP, BINARY_NAMES, ErrorCode, FileSnapshotStorage, FrpBridge, FrpBridgeError, FrpProcessManager, FrpRuntime, GITHUB_OWNER, GITHUB_REPO, OS_MAP, commandExists, downloadFile, ensureDir, executeCommand, getDownloadUrl, getLatestVersion, getPlatform, parseToml, toToml };
317
+ export type { Awaitable, CommandHandler, CommandHandlerContext, CommandMetadata, CommandResult, CommandStatus, ConfigSnapshot, FrpBridgeOptions, FrpProcessManagerOptions, NodeInfo, QueryHandler, QueryResult, RuntimeAdapters, RuntimeCommand, RuntimeContext, RuntimeError, RuntimeErrorCode, RuntimeEvent, RuntimeLogger, RuntimeMode, RuntimeQuery, RuntimeState, RuntimeStatus, SnapshotStorage };
@@ -0,0 +1,317 @@
1
+ import { ClientConfig, ServerConfig, ProxyConfig } from '@frp-bridge/types';
2
+
3
+ type Awaitable<T> = T | Promise<T>;
4
+ type RuntimeMode = 'client' | 'server';
5
+ type RuntimeStatus = 'idle' | 'starting' | 'running' | 'stopping' | 'error';
6
+ interface RuntimeContext {
7
+ id: string;
8
+ mode: RuntimeMode;
9
+ workDir: string;
10
+ platform: string;
11
+ clock?: () => number;
12
+ logger?: RuntimeLogger;
13
+ }
14
+ interface RuntimeLogger {
15
+ debug: (message: string, context?: Record<string, unknown>) => void;
16
+ info: (message: string, context?: Record<string, unknown>) => void;
17
+ warn: (message: string, context?: Record<string, unknown>) => void;
18
+ error: (message: string, context?: Record<string, unknown>) => void;
19
+ }
20
+ interface RuntimeState {
21
+ status: RuntimeStatus;
22
+ version: number;
23
+ lastAppliedAt?: number;
24
+ lastError?: RuntimeError;
25
+ }
26
+ interface CommandMetadata {
27
+ requestId?: string;
28
+ correlationId?: string;
29
+ author?: string;
30
+ issuedAt?: number;
31
+ }
32
+ interface RuntimeCommand<TPayload = unknown> {
33
+ name: string;
34
+ payload: TPayload;
35
+ metadata?: CommandMetadata;
36
+ }
37
+ interface CommandResult<TResult = unknown> {
38
+ status: CommandStatus;
39
+ version?: number;
40
+ events?: RuntimeEvent[];
41
+ result?: TResult;
42
+ error?: RuntimeError;
43
+ snapshot?: ConfigSnapshot;
44
+ }
45
+ type CommandStatus = 'success' | 'failed' | 'pending';
46
+ interface RuntimeQuery<TPayload = unknown> {
47
+ name: string;
48
+ payload?: TPayload;
49
+ }
50
+ interface QueryResult<TResult = unknown> {
51
+ result: TResult;
52
+ version: number;
53
+ }
54
+ interface RuntimeEvent<TPayload = unknown> {
55
+ type: string;
56
+ timestamp: number;
57
+ version?: number;
58
+ payload?: TPayload;
59
+ }
60
+ interface RuntimeError {
61
+ code: RuntimeErrorCode;
62
+ message: string;
63
+ details?: Record<string, unknown>;
64
+ }
65
+ type RuntimeErrorCode = 'VALIDATION_ERROR' | 'RUNTIME_ERROR' | 'SYSTEM_ERROR';
66
+ interface ConfigSnapshot {
67
+ version: number;
68
+ checksum: string;
69
+ appliedAt: number;
70
+ author?: string;
71
+ summary?: string;
72
+ }
73
+ interface SnapshotStorage {
74
+ save: (snapshot: ConfigSnapshot) => Awaitable<void>;
75
+ load: (version: number) => Awaitable<ConfigSnapshot | undefined>;
76
+ list: () => Awaitable<ConfigSnapshot[]>;
77
+ }
78
+ interface CommandHandlerContext {
79
+ context: RuntimeContext;
80
+ state: RuntimeState;
81
+ emit: (events: RuntimeEvent[]) => void;
82
+ requestVersionBump: () => number;
83
+ }
84
+ type CommandHandler<TPayload = unknown, TResult = unknown> = (command: RuntimeCommand<TPayload>, ctx: CommandHandlerContext) => Awaitable<CommandResult<TResult>>;
85
+ type QueryHandler<TPayload = unknown, TResult = unknown> = (query: RuntimeQuery<TPayload>, ctx: RuntimeContext) => Awaitable<QueryResult<TResult>>;
86
+ interface RuntimeAdapters {
87
+ storage?: SnapshotStorage;
88
+ commands?: Record<string, CommandHandler>;
89
+ queries?: Record<string, QueryHandler>;
90
+ }
91
+
92
+ declare class FrpRuntime {
93
+ private readonly context;
94
+ private readonly storage?;
95
+ private readonly commandHandlers;
96
+ private readonly queryHandlers;
97
+ private eventBuffer;
98
+ private commandQueue;
99
+ private state;
100
+ constructor(context: RuntimeContext, adapters?: RuntimeAdapters);
101
+ registerCommand(name: string, handler: CommandHandler): void;
102
+ registerQuery(name: string, handler: QueryHandler): void;
103
+ execute<TPayload, TResult = unknown>(command: RuntimeCommand<TPayload>): Promise<CommandResult<TResult>>;
104
+ query<TPayload, TResult = unknown>(query: RuntimeQuery<TPayload>): Promise<QueryResult<TResult>>;
105
+ snapshot(): RuntimeState;
106
+ drainEvents(): RuntimeEvent[];
107
+ private runCommand;
108
+ private pushEvents;
109
+ private bumpVersion;
110
+ private persistSnapshot;
111
+ private normalizeError;
112
+ private buildError;
113
+ private now;
114
+ }
115
+
116
+ /**
117
+ * FRP process management utilities
118
+ */
119
+
120
+ interface FrpProcessManagerOptions {
121
+ /** Working directory for FRP files */
122
+ workDir?: string;
123
+ /** FRP version (defaults to latest) */
124
+ version?: string;
125
+ /** Mode: client or server */
126
+ mode: 'client' | 'server';
127
+ /** Optional logger */
128
+ logger?: RuntimeLogger;
129
+ }
130
+ interface NodeInfo {
131
+ /** Node ID */
132
+ id: string;
133
+ /** Node name */
134
+ name: string;
135
+ /** Server address */
136
+ serverAddr: string;
137
+ /** Server port */
138
+ serverPort?: number;
139
+ /** Authentication token */
140
+ token?: string;
141
+ /** Additional config */
142
+ config?: Partial<ClientConfig | ServerConfig>;
143
+ }
144
+ /**
145
+ * Manages FRP client/server lifecycle, config, and tunnels
146
+ */
147
+ declare class FrpProcessManager {
148
+ private readonly workDir;
149
+ private version;
150
+ private readonly mode;
151
+ private readonly specifiedVersion?;
152
+ private readonly logger;
153
+ private process;
154
+ private configPath;
155
+ private binaryPath;
156
+ constructor(options: FrpProcessManagerOptions);
157
+ /** Ensure version is fetched and binary path is set */
158
+ private ensureVersion;
159
+ /** Download FRP binary for current platform */
160
+ downloadFrpBinary(): Promise<void>;
161
+ /** Update FRP binary to latest version */
162
+ updateFrpBinary(newVersion?: string): Promise<void>;
163
+ /** Check if binary exists */
164
+ hasBinary(): boolean;
165
+ /** Get current configuration */
166
+ getConfig(): ClientConfig | ServerConfig | null;
167
+ /** Update configuration */
168
+ updateConfig(config: Partial<ClientConfig | ServerConfig>): void;
169
+ /** Backup configuration */
170
+ backupConfig(): Promise<string>;
171
+ /** Return the absolute config file path */
172
+ getConfigPath(): string;
173
+ /** Read raw config file contents */
174
+ readConfigFile(): string | null;
175
+ /** Overwrite config file with provided content */
176
+ writeConfigFile(content: string): void;
177
+ /** Start FRP process */
178
+ start(): Promise<void>;
179
+ /** Stop FRP process */
180
+ stop(): Promise<void>;
181
+ /** Check if process is running */
182
+ isRunning(): boolean;
183
+ /** Add node (for client mode) */
184
+ addNode(node: NodeInfo): void;
185
+ /** Get node info */
186
+ getNode(): NodeInfo | null;
187
+ /** Update node info */
188
+ updateNode(node: Partial<NodeInfo>): void;
189
+ /** Remove node */
190
+ removeNode(): void;
191
+ /** Add tunnel (proxy) */
192
+ addTunnel(proxy: ProxyConfig): void;
193
+ /** Get tunnel by name */
194
+ getTunnel(name: string): ProxyConfig | null;
195
+ /** Update tunnel */
196
+ updateTunnel(name: string, proxy: Partial<ProxyConfig>): void;
197
+ /** Remove tunnel */
198
+ removeTunnel(name: string): void;
199
+ /** List all tunnels */
200
+ listTunnels(): ProxyConfig[];
201
+ }
202
+
203
+ interface FrpBridgeRuntimeOptions {
204
+ id?: string;
205
+ mode?: RuntimeMode;
206
+ logger?: RuntimeLogger;
207
+ clock?: () => number;
208
+ platform?: string;
209
+ workDir?: string;
210
+ }
211
+ interface FrpBridgeProcessOptions extends Partial<Omit<FrpProcessManagerOptions, 'mode'>> {
212
+ mode?: 'client' | 'server';
213
+ workDir?: string;
214
+ }
215
+ interface FrpBridgeOptions {
216
+ mode: 'client' | 'server';
217
+ workDir?: string;
218
+ runtime?: FrpBridgeRuntimeOptions;
219
+ process?: FrpBridgeProcessOptions;
220
+ storage?: SnapshotStorage;
221
+ commands?: Record<string, CommandHandler>;
222
+ queries?: Record<string, QueryHandler>;
223
+ eventSink?: (event: RuntimeEvent) => void;
224
+ }
225
+ declare class FrpBridge {
226
+ private readonly options;
227
+ private readonly runtime;
228
+ private readonly process;
229
+ private readonly eventSink?;
230
+ constructor(options: FrpBridgeOptions);
231
+ execute<TPayload, TResult = unknown>(command: RuntimeCommand<TPayload>): Promise<CommandResult<TResult>>;
232
+ query<TPayload, TResult = unknown>(query: RuntimeQuery<TPayload>): Promise<QueryResult<TResult>>;
233
+ snapshot(): RuntimeState;
234
+ drainEvents(): RuntimeEvent[];
235
+ getProcessManager(): FrpProcessManager;
236
+ getRuntime(): FrpRuntime;
237
+ private createDefaultCommands;
238
+ private createDefaultQueries;
239
+ private forwardEvents;
240
+ private runConfigMutation;
241
+ }
242
+
243
+ /**
244
+ * Core constants
245
+ */
246
+ /** GitHub repository owner */
247
+ declare const GITHUB_OWNER = "fatedier";
248
+ /** GitHub repository name */
249
+ declare const GITHUB_REPO = "frp";
250
+ /** Platform-specific binary names */
251
+ declare const BINARY_NAMES: {
252
+ readonly client: "frpc.exe" | "frpc";
253
+ readonly server: "frps.exe" | "frps";
254
+ };
255
+ /** Platform architecture mapping */
256
+ declare const ARCH_MAP: Record<string, string>;
257
+ /** Platform OS mapping */
258
+ declare const OS_MAP: Record<string, string>;
259
+
260
+ /** Custom error for FRP Bridge operations */
261
+ declare class FrpBridgeError extends Error {
262
+ readonly code: string;
263
+ readonly details?: unknown | undefined;
264
+ constructor(message: string, code: string, details?: unknown | undefined);
265
+ }
266
+ /** Error codes */
267
+ declare enum ErrorCode {
268
+ BINARY_NOT_FOUND = "BINARY_NOT_FOUND",
269
+ DOWNLOAD_FAILED = "DOWNLOAD_FAILED",
270
+ EXTRACTION_FAILED = "EXTRACTION_FAILED",
271
+ CONFIG_NOT_FOUND = "CONFIG_NOT_FOUND",
272
+ CONFIG_INVALID = "CONFIG_INVALID",
273
+ PROCESS_ALREADY_RUNNING = "PROCESS_ALREADY_RUNNING",
274
+ PROCESS_NOT_RUNNING = "PROCESS_NOT_RUNNING",
275
+ PROCESS_START_FAILED = "PROCESS_START_FAILED",
276
+ UNSUPPORTED_PLATFORM = "UNSUPPORTED_PLATFORM",
277
+ VERSION_FETCH_FAILED = "VERSION_FETCH_FAILED",
278
+ MODE_ERROR = "MODE_ERROR",
279
+ NOT_FOUND = "NOT_FOUND"
280
+ }
281
+
282
+ declare class FileSnapshotStorage implements SnapshotStorage {
283
+ private readonly directory;
284
+ constructor(directory: string);
285
+ save(snapshot: ConfigSnapshot): Promise<void>;
286
+ load(version: number): Promise<ConfigSnapshot | undefined>;
287
+ list(): Promise<ConfigSnapshot[]>;
288
+ private buildPath;
289
+ }
290
+
291
+ /**
292
+ * Utility functions
293
+ */
294
+ /** Get latest FRP version from GitHub releases */
295
+ declare function getLatestVersion(): Promise<string>;
296
+ /** Get platform identifier for FRP release */
297
+ declare function getPlatform(): string;
298
+ /** Get GitHub release download URL */
299
+ declare function getDownloadUrl(version: string, platform: string): string;
300
+ /** Download file from URL */
301
+ declare function downloadFile(url: string, dest: string): Promise<void>;
302
+ /** Execute command */
303
+ declare function executeCommand(command: string): Promise<{
304
+ stdout: string;
305
+ stderr: string;
306
+ }>;
307
+ /** Check if command exists */
308
+ declare function commandExists(command: string): Promise<boolean>;
309
+ /** Ensure directory exists */
310
+ declare function ensureDir(dirPath: string): void;
311
+ /** Parse TOML-like config to JSON */
312
+ declare function parseToml(content: string): Record<string, any>;
313
+ /** Convert JSON to TOML-like config */
314
+ declare function toToml(obj: Record<string, any>): string;
315
+
316
+ export { ARCH_MAP, BINARY_NAMES, ErrorCode, FileSnapshotStorage, FrpBridge, FrpBridgeError, FrpProcessManager, FrpRuntime, GITHUB_OWNER, GITHUB_REPO, OS_MAP, commandExists, downloadFile, ensureDir, executeCommand, getDownloadUrl, getLatestVersion, getPlatform, parseToml, toToml };
317
+ export type { Awaitable, CommandHandler, CommandHandlerContext, CommandMetadata, CommandResult, CommandStatus, ConfigSnapshot, FrpBridgeOptions, FrpProcessManagerOptions, NodeInfo, QueryHandler, QueryResult, RuntimeAdapters, RuntimeCommand, RuntimeContext, RuntimeError, RuntimeErrorCode, RuntimeEvent, RuntimeLogger, RuntimeMode, RuntimeQuery, RuntimeState, RuntimeStatus, SnapshotStorage };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import{homedir as F}from"node:os";import p from"node:process";import{consola as _}from"consola";import{join as d}from"pathe";import{exec as W,spawn as G}from"node:child_process";import{createWriteStream as H,existsSync as f,mkdirSync as z,chmodSync as Y,readFileSync as m,writeFileSync as O,unlinkSync as Q}from"node:fs";import{get as J}from"node:http";import{get as C}from"node:https";import{promisify as X}from"node:util";import{writeFile as K,readFile as S,readdir as Z}from"node:fs/promises";const P="fatedier",D="frp",g={client:p.platform==="win32"?"frpc.exe":"frpc",server:p.platform==="win32"?"frps.exe":"frps"},I={x64:"amd64",arm64:"arm64",arm:"arm",ia32:"386"},$={win32:"windows",darwin:"darwin",linux:"linux",freebsd:"freebsd"};class u extends Error{constructor(t,e,s){super(t),this.code=e,this.details=s,this.name="FrpBridgeError"}}var c=(r=>(r.BINARY_NOT_FOUND="BINARY_NOT_FOUND",r.DOWNLOAD_FAILED="DOWNLOAD_FAILED",r.EXTRACTION_FAILED="EXTRACTION_FAILED",r.CONFIG_NOT_FOUND="CONFIG_NOT_FOUND",r.CONFIG_INVALID="CONFIG_INVALID",r.PROCESS_ALREADY_RUNNING="PROCESS_ALREADY_RUNNING",r.PROCESS_NOT_RUNNING="PROCESS_NOT_RUNNING",r.PROCESS_START_FAILED="PROCESS_START_FAILED",r.UNSUPPORTED_PLATFORM="UNSUPPORTED_PLATFORM",r.VERSION_FETCH_FAILED="VERSION_FETCH_FAILED",r.MODE_ERROR="MODE_ERROR",r.NOT_FOUND="NOT_FOUND",r))(c||{});const N=X(W);async function A(){const r=`https://api.github.com/repos/${P}/${D}/releases/latest`;return new Promise((t,e)=>{C(r,{headers:{"User-Agent":"frp-bridge"}},s=>{if(s.statusCode!==200){e(new Error(`Failed to fetch latest version: ${s.statusCode}`));return}let n="";s.on("data",i=>n+=i),s.on("end",()=>{try{const i=JSON.parse(n).tag_name?.replace(/^v/,"")||"0.65.0";t(i)}catch(i){e(i)}})}).on("error",e)})}function k(){const r=$[p.platform],t=I[p.arch];if(!r||!t)throw new Error(`Unsupported platform: ${p.platform}-${p.arch}`);return`${r}_${t}`}function U(r,t){const e=t.startsWith("windows_")?"zip":"tar.gz";return`https://github.com/${P}/${D}/releases/download/v${r}/frp_${r}_${t}.${e}`}async function T(r,t){return new Promise((e,s)=>{const n=H(t);(r.startsWith("https")?C:J)(r,i=>{if(i.statusCode===302||i.statusCode===301){const o=i.headers.location;if(o){n.close(),T(o,t).then(e).catch(s);return}}if(i.statusCode!==200){s(new Error(`Failed to download: ${i.statusCode}`));return}i.pipe(n),n.on("finish",()=>{n.close(),e()})}).on("error",i=>{n.close(),s(i)})})}async function b(r){return N(r)}async function R(r){try{return p.platform==="win32"?await N(`where ${r}`):await N(`which ${r}`),!0}catch{return!1}}function l(r){f(r)||z(r,{recursive:!0})}function w(r){const t=r.split(`
2
+ `),e={};let s="";for(const n of t){const i=n.trim();if(!i||i.startsWith("#"))continue;if(i.startsWith("[")&&i.endsWith("]")){s=i.slice(1,-1),e[s]||(e[s]={});continue}const o=i.indexOf("=");if(o>0){const h=i.slice(0,o).trim();let a=i.slice(o+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),a==="true"?a=!0:a==="false"?a=!1:Number.isNaN(Number(a))||(a=Number(a)),s?e[s][h]=a:e[h]=a}}return e}function E(r){const t=[];for(const[e,s]of Object.entries(r))(typeof s!="object"||s===null||Array.isArray(s))&&t.push(L(e,s));for(const[e,s]of Object.entries(r))if(typeof s=="object"&&s!==null&&!Array.isArray(s)){t.push(""),t.push(`[${e}]`);for(const[n,i]of Object.entries(s))t.push(L(n,i))}return t.join(`
3
+ `)}function L(r,t){return typeof t=="string"?`${r} = "${t}"`:typeof t=="boolean"||typeof t=="number"?`${r} = ${t}`:Array.isArray(t)?`${r} = [${t.map(e=>typeof e=="string"?`"${e}"`:e).join(", ")}]`:`${r} = "${String(t)}"`}class M{workDir;version=null;mode;specifiedVersion;logger;process=null;configPath;binaryPath;constructor(t){this.mode=t.mode,this.specifiedVersion=t.version,this.workDir=t.workDir||d(F(),".frp-bridge"),this.logger=t.logger??_.withTag("FrpProcessManager"),l(this.workDir),this.configPath=d(this.workDir,`frp${this.mode==="client"?"c":"s"}.toml`),this.binaryPath=""}async ensureVersion(){if(!this.version){this.version=this.specifiedVersion||await A();const t=this.mode==="client"?g.client:g.server;this.binaryPath=d(this.workDir,"bin",this.version,t)}}async downloadFrpBinary(){await this.ensureVersion();const t=k(),e=U(this.version,t),s=t.startsWith("windows_"),n=s?"zip":"tar.gz",i=d(this.workDir,`frp_${this.version}.${n}`),o=d(this.workDir,"bin",this.version);l(o),await T(e,i);const h=d(this.workDir,"temp");if(l(h),s){if(!await R("unzip"))throw new u("unzip is required for extraction on Windows",c.EXTRACTION_FAILED);await b(`unzip -o "${i}" -d "${h}"`)}else{const j=await R("gzip"),q=await R("tar");if(!j||!q)throw new u("gzip and tar are required for extraction",c.EXTRACTION_FAILED);await b(`tar -xzf "${i}" -C "${h}"`)}const a=d(h,`frp_${this.version}_${t}`),y=d(a,this.mode==="client"?g.client:g.server);if(!f(y))throw new u(`Binary not found: ${y}`,c.BINARY_NOT_FOUND);const v=await import("fs-extra");await v.copy(y,this.binaryPath),s||Y(this.binaryPath,493),await v.remove(i),await v.remove(h)}async updateFrpBinary(t){await this.ensureVersion();const e=t||await A();if(e===this.version)return;if(f(this.binaryPath)){const n=`${this.binaryPath}.bak`;await(await import("fs-extra")).copy(this.binaryPath,n)}this.version=e;const s=this.mode==="client"?g.client:g.server;this.binaryPath=d(this.workDir,"bin",this.version,s),await this.downloadFrpBinary()}hasBinary(){return f(this.binaryPath)}getConfig(){if(!f(this.configPath))return null;const t=m(this.configPath,"utf-8");return w(t)}updateConfig(t){const e={...this.getConfig()||{},...t},s=E(e);O(this.configPath,s,"utf-8")}async backupConfig(){if(!f(this.configPath))throw new u("Config file does not exist",c.CONFIG_NOT_FOUND);const t=Date.now(),e=`${this.configPath}.${t}.bak`;return await(await import("fs-extra")).copy(this.configPath,e),e}getConfigPath(){return this.configPath}readConfigFile(){return f(this.configPath)?m(this.configPath,"utf-8"):null}writeConfigFile(t){l(this.workDir),O(this.configPath,t,"utf-8")}async start(){if(await this.ensureVersion(),this.process)throw new u("Process already running",c.PROCESS_ALREADY_RUNNING);if(this.hasBinary()||await this.downloadFrpBinary(),!f(this.configPath))throw new u("Config file does not exist",c.CONFIG_NOT_FOUND);this.process=G(this.binaryPath,["-c",this.configPath],{stdio:"inherit"}),this.process.on("error",t=>{this.logger.error("FRP process error",{error:t}),this.process=null}),this.process.on("exit",t=>{t!==0&&this.logger.error("FRP process exited with non-zero code",{code:t}),this.process=null})}async stop(){if(this.process)return new Promise(t=>{this.process.on("exit",()=>{this.process=null,t()}),this.process.kill("SIGTERM"),setTimeout(()=>{this.process&&this.process.kill("SIGKILL")},5e3)})}isRunning(){return this.process!==null&&!this.process.killed}addNode(t){if(this.mode!=="client")throw new u("Nodes can only be added in client mode",c.MODE_ERROR);const e=this.getConfig()||{};e.serverAddr=t.serverAddr,e.serverPort=t.serverPort||7e3,t.token&&(e.auth={...e.auth,token:t.token}),t.config&&Object.assign(e,t.config),this.updateConfig(e)}getNode(){if(this.mode!=="client")throw new u("Nodes are only available in client mode",c.MODE_ERROR);const t=this.getConfig();return!t||!t.serverAddr?null:{id:"default",name:"default",serverAddr:t.serverAddr,serverPort:t.serverPort,token:t.auth?.token}}updateNode(t){if(this.mode!=="client")throw new u("Nodes can only be updated in client mode",c.MODE_ERROR);const e=this.getConfig()||{};t.serverAddr&&(e.serverAddr=t.serverAddr),t.serverPort&&(e.serverPort=t.serverPort),t.token&&(e.auth={...e.auth,token:t.token}),t.config&&Object.assign(e,t.config),this.updateConfig(e)}removeNode(){if(this.mode!=="client")throw new u("Nodes can only be removed in client mode",c.MODE_ERROR);f(this.configPath)&&Q(this.configPath)}addTunnel(t){if(this.mode!=="client")throw new Error("Tunnels can only be added in client mode");const e=f(this.configPath)?m(this.configPath,"utf-8"):"",s=E({[t.name]:t}),n=e?`${e}
4
+
5
+ ${s}`:s;O(this.configPath,n,"utf-8")}getTunnel(t){if(this.mode!=="client")throw new Error("Tunnels are only available in client mode");if(!f(this.configPath))return null;const e=m(this.configPath,"utf-8");return w(e)[t]||null}updateTunnel(t,e){if(this.mode!=="client")throw new u("Tunnels can only be updated in client mode",c.MODE_ERROR);if(!f(this.configPath))throw new u("Config file does not exist",c.CONFIG_NOT_FOUND);const s=m(this.configPath,"utf-8"),n=w(s);if(!n[t])throw new u(`Tunnel ${t} not found`,c.NOT_FOUND);n[t]={...n[t],...e};const i=E(n);O(this.configPath,i,"utf-8")}removeTunnel(t){if(this.mode!=="client")throw new u("Tunnels can only be removed in client mode",c.MODE_ERROR);if(!f(this.configPath))return;const e=m(this.configPath,"utf-8"),s=w(e);delete s[t];const n=E(s);O(this.configPath,n,"utf-8")}listTunnels(){if(this.mode!=="client")throw new u("Tunnels are only available in client mode",c.MODE_ERROR);if(!f(this.configPath))return[];const t=m(this.configPath,"utf-8"),e=w(t),s=[];for(const[n,i]of Object.entries(e))typeof i=="object"&&i!==null&&"type"in i&&s.push(i);return s}}const tt="Unknown command",et="Unknown query";class x{constructor(t,e={}){this.context=t,this.storage=e.storage,Object.entries(e.commands??{}).forEach(([s,n])=>{this.commandHandlers.set(s,n)}),Object.entries(e.queries??{}).forEach(([s,n])=>{this.queryHandlers.set(s,n)})}storage;commandHandlers=new Map;queryHandlers=new Map;eventBuffer=[];commandQueue=Promise.resolve();state={status:"idle",version:0};registerCommand(t,e){this.commandHandlers.set(t,e)}registerQuery(t,e){this.queryHandlers.set(t,e)}execute(t){const e=this.commandQueue.then(()=>this.runCommand(t));return this.commandQueue=e.then(()=>{},()=>{}),e}async query(t){const e=this.queryHandlers.get(t.name);if(!e)throw this.buildError("VALIDATION_ERROR",`${et}: ${t.name}`);return e(t,this.context)}snapshot(){return{...this.state}}drainEvents(){const t=this.eventBuffer;return this.eventBuffer=[],t}async runCommand(t){const e=this.commandHandlers.get(t.name);if(!e)return{status:"failed",error:this.buildError("VALIDATION_ERROR",`${tt}: ${t.name}`)};const s={...this.state},n={bumped:!1},i={context:this.context,state:s,emit:o=>this.pushEvents(o),requestVersionBump:()=>this.bumpVersion(t.metadata?.author,n)};this.state.status="running";try{const o=await e(t,i);return o.events&&this.pushEvents(o.events),o.snapshot&&await this.persistSnapshot(o.snapshot,t.metadata?.author),o.error?(this.state.lastError=o.error,this.state.status="error"):o.status==="success"&&(this.state.lastError=void 0,this.state.status="running"),{...o,version:o.version??this.state.version}}catch(o){const h=this.normalizeError(o);return this.state.lastError=h,this.state.status="error",{status:"failed",error:h,version:this.state.version}}}pushEvents(t){const e=this.now();t.forEach(s=>{this.eventBuffer.push({...s,timestamp:s.timestamp??e,version:s.version??this.state.version})})}bumpVersion(t,e){return e.bumped?this.state.version:(e.bumped=!0,this.state.version+=1,this.state.lastAppliedAt=this.now(),t&&this.pushEvents([{type:"config:version-bumped",timestamp:this.now(),version:this.state.version,payload:{author:t}}]),this.state.version)}async persistSnapshot(t,e){this.storage&&t&&await this.storage.save({...t,version:t.version??this.state.version,appliedAt:t.appliedAt??this.now(),author:t.author??e})}normalizeError(t){return t&&typeof t=="object"&&"code"in t&&"message"in t?t:{code:"SYSTEM_ERROR",message:t instanceof Error?t.message:"Unknown error"}}buildError(t,e,s){return{code:t,message:e,details:s}}now(){return this.context.clock?this.context.clock():Date.now()}}function B(r){return!!r&&typeof r=="object"&&"version"in r}class V{constructor(t){this.directory=t,l(t)}async save(t){if(typeof t.version!="number")throw new TypeError("Snapshot version must be a number when using FileSnapshotStorage");l(this.directory);const e=JSON.stringify(t,null,2);await K(this.buildPath(t.version),e,"utf-8")}async load(t){const e=this.buildPath(t);if(!f(e))return;const s=await S(e,"utf-8"),n=JSON.parse(s);if(!B(n))throw new TypeError(`Invalid snapshot schema at version ${t}`);return n}async list(){l(this.directory);const t=await Z(this.directory),e=[];for(const s of t){if(!s.endsWith(".json"))continue;const n=await S(d(this.directory,s),"utf-8"),i=JSON.parse(n);B(i)&&e.push(i)}return e.sort((s,n)=>s.version-n.version)}buildPath(t){return d(this.directory,`${t}.json`)}}const st="config.apply",rt="config.applyRaw",nt="process.stop",it="process.status",ot="runtime.snapshot";class at{constructor(t){this.options=t;const e=t.workDir??d(F(),".frp-bridge"),s=t.runtime?.workDir??d(e,"runtime"),n=t.process?.workDir??d(e,"process");l(e),l(s),l(n);const i=t.runtime?.logger??_.withTag("FrpRuntime"),o=t.process?.logger??_.withTag("FrpProcessManager");this.process=new M({mode:t.process?.mode??t.mode,version:t.process?.version,workDir:n,logger:o});const h=t.storage??new V(d(s,"snapshots")),a={id:t.runtime?.id??"default",mode:t.runtime?.mode??t.mode,workDir:s,platform:t.runtime?.platform??p.platform,clock:t.runtime?.clock,logger:i},y={...this.createDefaultCommands(),...t.commands??{}},v={...this.createDefaultQueries(),...t.queries??{}};this.runtime=new x(a,{storage:h,commands:y,queries:v}),this.eventSink=t.eventSink}runtime;process;eventSink;execute(t){return this.runtime.execute(t).finally(()=>{this.forwardEvents()})}query(t){return this.runtime.query(t).finally(()=>{this.forwardEvents()})}snapshot(){return this.runtime.snapshot()}drainEvents(){return this.runtime.drainEvents()}getProcessManager(){return this.process}getRuntime(){return this.runtime}createDefaultCommands(){const t=async(n,i)=>n.payload?.config?this.runConfigMutation(async()=>{this.process.updateConfig(n.payload.config)},n.payload.restart,i):{status:"failed",error:{code:"VALIDATION_ERROR",message:"config.apply requires payload.config"}},e=async(n,i)=>{const o=n.payload?.content;if(!o?.trim())return{status:"failed",error:{code:"VALIDATION_ERROR",message:"config.applyRaw requires payload.content"}};try{w(o)}catch(h){return{status:"failed",error:{code:"VALIDATION_ERROR",message:"config.applyRaw received invalid TOML content",details:h instanceof Error?{message:h.message}:void 0}}}return this.runConfigMutation(async()=>{this.process.writeConfigFile(o)},n.payload?.restart,i)},s=async()=>this.process.isRunning()?(await this.process.stop(),{status:"success",events:[{type:"process:stopped",timestamp:Date.now()}]}):{status:"success"};return{[st]:t,[rt]:e,[nt]:s}}createDefaultQueries(){const t=async()=>{const s=this.runtime.snapshot();return{result:{running:this.process.isRunning(),config:this.process.getConfig()},version:s.version}},e=async()=>{const s=this.runtime.snapshot();return{result:s,version:s.version}};return{[it]:t,[ot]:e}}forwardEvents(){this.eventSink&&this.runtime.drainEvents().forEach(t=>this.eventSink?.(t))}async runConfigMutation(t,e,s){await t();const n=e??!0;let i;return n&&(this.process.isRunning()&&await this.process.stop(),await this.process.start(),i=[{type:"process:started",timestamp:Date.now()}]),s.requestVersionBump(),{status:"success",events:i}}}export{I as ARCH_MAP,g as BINARY_NAMES,c as ErrorCode,V as FileSnapshotStorage,at as FrpBridge,u as FrpBridgeError,M as FrpProcessManager,x as FrpRuntime,P as GITHUB_OWNER,D as GITHUB_REPO,$ as OS_MAP,R as commandExists,T as downloadFile,l as ensureDir,b as executeCommand,U as getDownloadUrl,A as getLatestVersion,k as getPlatform,w as parseToml,E as toToml};
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@frp-bridge/core",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "description": "Frp bridge core",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/frp-web/bridge#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git@github.com:frp-web/bridge.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/frp-web/bridge/issues"
14
+ },
15
+ "keywords": [
16
+ "frp",
17
+ "bridge",
18
+ "cli"
19
+ ],
20
+ "main": "dist/index.mjs",
21
+ "module": "dist/index.mjs",
22
+ "types": "dist/index.d.ts",
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "dependencies": {
27
+ "axios": "^1.12.2",
28
+ "consola": "^3.2.3",
29
+ "execa": "^9.6.0",
30
+ "fs-extra": "^11.3.2",
31
+ "ini": "^5.0.0",
32
+ "pathe": "^2.0.3",
33
+ "@frp-bridge/types": "0.0.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/fs-extra": "^11.0.4",
37
+ "@types/node": "^24.7.0"
38
+ },
39
+ "scripts": {
40
+ "stub": "unbuild --stub",
41
+ "build": "unbuild"
42
+ }
43
+ }