ports-manager 1.0.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,133 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.activate = activate;
37
+ exports.deactivate = deactivate;
38
+ const vscode = __importStar(require("vscode"));
39
+ const crypto = __importStar(require("node:crypto"));
40
+ const fs = __importStar(require("node:fs"));
41
+ const os = __importStar(require("node:os"));
42
+ const path = __importStar(require("node:path"));
43
+ const server_1 = require("./server");
44
+ let bridge;
45
+ let descriptorFile;
46
+ let activeToken;
47
+ function writeDescriptor(port, token) {
48
+ const directory = path.join(os.homedir(), '.ports-manager');
49
+ const filename = path.join(directory, 'bridge.json');
50
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
51
+ try {
52
+ fs.chmodSync(directory, 0o700);
53
+ }
54
+ catch {
55
+ // Windows ACLs do not map directly to POSIX modes.
56
+ }
57
+ const temporary = path.join(directory, `.bridge-${process.pid}-${crypto.randomBytes(6).toString('hex')}.tmp`);
58
+ const payload = JSON.stringify({
59
+ endpoint: `http://127.0.0.1:${port}/`,
60
+ token,
61
+ pid: process.pid
62
+ });
63
+ fs.writeFileSync(temporary, payload, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
64
+ try {
65
+ fs.renameSync(temporary, filename);
66
+ }
67
+ catch (error) {
68
+ if (process.platform !== 'win32') {
69
+ fs.rmSync(temporary, { force: true });
70
+ throw error;
71
+ }
72
+ fs.rmSync(filename, { force: true });
73
+ fs.renameSync(temporary, filename);
74
+ }
75
+ try {
76
+ fs.chmodSync(filename, 0o600);
77
+ }
78
+ catch {
79
+ // Best effort on Windows.
80
+ }
81
+ return filename;
82
+ }
83
+ function removeOwnDescriptor() {
84
+ if (!descriptorFile || !activeToken)
85
+ return;
86
+ try {
87
+ const current = JSON.parse(fs.readFileSync(descriptorFile, 'utf8'));
88
+ if (current.token === activeToken)
89
+ fs.rmSync(descriptorFile, { force: true });
90
+ }
91
+ catch {
92
+ // Already removed or replaced.
93
+ }
94
+ }
95
+ async function activate(context) {
96
+ const token = crypto.randomBytes(32).toString('hex');
97
+ activeToken = token;
98
+ const host = {
99
+ create(spec) {
100
+ return vscode.window.createTerminal({
101
+ name: spec.name,
102
+ cwd: spec.cwd,
103
+ env: spec.env,
104
+ isTransient: true
105
+ });
106
+ }
107
+ };
108
+ bridge = new server_1.BridgeServer(token, host);
109
+ try {
110
+ const port = await bridge.listen();
111
+ descriptorFile = writeDescriptor(port, token);
112
+ }
113
+ catch (error) {
114
+ await bridge.close();
115
+ bridge = undefined;
116
+ activeToken = undefined;
117
+ vscode.window.showErrorMessage(`Ports Manager bridge failed to start: ${error instanceof Error ? error.message : error}`);
118
+ return;
119
+ }
120
+ context.subscriptions.push(vscode.window.onDidCloseTerminal((terminal) => bridge?.forget(terminal)), vscode.commands.registerCommand('ports-manager.stopAll', () => {
121
+ const count = bridge?.stopAll() || 0;
122
+ vscode.window.showInformationMessage(`Stopped ${count} Ports Manager terminal${count === 1 ? '' : 's'}.`);
123
+ }));
124
+ }
125
+ async function deactivate() {
126
+ removeOwnDescriptor();
127
+ if (bridge)
128
+ await bridge.close();
129
+ bridge = undefined;
130
+ activeToken = undefined;
131
+ descriptorFile = undefined;
132
+ }
133
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.js","sourceRoot":"","sources":["../../../bridge/src/extension.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.BridgeServer = void 0;
37
+ exports.validateLaunchBody = validateLaunchBody;
38
+ const http = __importStar(require("node:http"));
39
+ const node_crypto_1 = require("node:crypto");
40
+ const MAX_BODY = 64 * 1024;
41
+ function plainObject(value) {
42
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
43
+ }
44
+ function shortString(value, maximum = 4096) {
45
+ return typeof value === 'string' && value.length > 0 && value.length <= maximum;
46
+ }
47
+ function validateLaunchBody(value) {
48
+ if (!plainObject(value) || !shortString(value.tag, 1024) ||
49
+ !Array.isArray(value.terminals) || value.terminals.length < 1 || value.terminals.length > 8) {
50
+ throw new Error('invalid terminal launch body');
51
+ }
52
+ const terminals = value.terminals.map((candidate) => {
53
+ if (!plainObject(candidate) || !shortString(candidate.name, 200) ||
54
+ !shortString(candidate.cwd, 4096) || !shortString(candidate.command, 16384) ||
55
+ !plainObject(candidate.env)) {
56
+ throw new Error('invalid terminal specification');
57
+ }
58
+ const env = {};
59
+ for (const [key, item] of Object.entries(candidate.env)) {
60
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof item !== 'string' || item.length > 16384) {
61
+ throw new Error('invalid terminal environment');
62
+ }
63
+ env[key] = item;
64
+ }
65
+ return { name: candidate.name, cwd: candidate.cwd, command: candidate.command, env };
66
+ });
67
+ return { tag: value.tag, terminals };
68
+ }
69
+ function validateStopBody(value) {
70
+ if (!plainObject(value) || !shortString(value.tag, 1024))
71
+ throw new Error('invalid stop body');
72
+ return { tag: value.tag };
73
+ }
74
+ function isLoopback(address) {
75
+ return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
76
+ }
77
+ function authorized(header, token) {
78
+ if (!header?.startsWith('Bearer '))
79
+ return false;
80
+ const supplied = Buffer.from(header.slice(7));
81
+ const expected = Buffer.from(token);
82
+ return supplied.length === expected.length && (0, node_crypto_1.timingSafeEqual)(supplied, expected);
83
+ }
84
+ async function readJson(request) {
85
+ if (!String(request.headers['content-type'] || '').toLowerCase().startsWith('application/json')) {
86
+ throw new Error('content-type must be application/json');
87
+ }
88
+ const chunks = [];
89
+ let size = 0;
90
+ for await (const chunk of request) {
91
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
92
+ size += buffer.length;
93
+ if (size > MAX_BODY)
94
+ throw new Error('request body too large');
95
+ chunks.push(buffer);
96
+ }
97
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
98
+ }
99
+ function send(response, status, body) {
100
+ const payload = JSON.stringify(body);
101
+ response.writeHead(status, {
102
+ 'Content-Type': 'application/json',
103
+ 'Content-Length': Buffer.byteLength(payload),
104
+ 'Cache-Control': 'no-store'
105
+ });
106
+ response.end(payload);
107
+ }
108
+ class BridgeServer {
109
+ token;
110
+ host;
111
+ server;
112
+ tracked = new Map();
113
+ constructor(token, host) {
114
+ this.token = token;
115
+ this.host = host;
116
+ }
117
+ async listen() {
118
+ if (this.server)
119
+ throw new Error('bridge already listening');
120
+ this.server = http.createServer((request, response) => void this.handle(request, response));
121
+ await new Promise((resolve, reject) => {
122
+ this.server.once('error', reject);
123
+ this.server.listen(0, '127.0.0.1', resolve);
124
+ });
125
+ const address = this.server.address();
126
+ if (!address || typeof address === 'string')
127
+ throw new Error('bridge failed to bind');
128
+ return address.port;
129
+ }
130
+ async handle(request, response) {
131
+ try {
132
+ if (!isLoopback(request.socket.remoteAddress))
133
+ return send(response, 403, { error: 'loopback only' });
134
+ if (!authorized(request.headers.authorization, this.token))
135
+ return send(response, 401, { error: 'unauthorized' });
136
+ if (request.method !== 'POST')
137
+ return send(response, 405, { error: 'POST required' });
138
+ const body = await readJson(request);
139
+ if (request.url === '/terminals') {
140
+ const launch = validateLaunchBody(body);
141
+ this.stopTag(launch.tag);
142
+ const terminals = new Set();
143
+ try {
144
+ for (const spec of launch.terminals) {
145
+ const terminal = this.host.create(spec);
146
+ terminals.add(terminal);
147
+ terminal.show(true);
148
+ terminal.sendText(spec.command, true);
149
+ }
150
+ }
151
+ catch (error) {
152
+ for (const terminal of terminals)
153
+ terminal.dispose();
154
+ throw error;
155
+ }
156
+ this.tracked.set(launch.tag, terminals);
157
+ return send(response, 201, { created: terminals.size });
158
+ }
159
+ if (request.url === '/stop') {
160
+ const stop = validateStopBody(body);
161
+ const stopped = this.stopTag(stop.tag);
162
+ return send(response, 200, { stopped });
163
+ }
164
+ return send(response, 404, { error: 'not found' });
165
+ }
166
+ catch (error) {
167
+ return send(response, 400, { error: error instanceof Error ? error.message : 'bad request' });
168
+ }
169
+ }
170
+ forget(terminal) {
171
+ for (const [tag, terminals] of this.tracked) {
172
+ terminals.delete(terminal);
173
+ if (!terminals.size)
174
+ this.tracked.delete(tag);
175
+ }
176
+ }
177
+ stopTag(tag) {
178
+ const terminals = this.tracked.get(tag);
179
+ if (!terminals)
180
+ return 0;
181
+ this.tracked.delete(tag);
182
+ for (const terminal of terminals)
183
+ terminal.dispose();
184
+ return terminals.size;
185
+ }
186
+ stopAll() {
187
+ let count = 0;
188
+ for (const tag of [...this.tracked.keys()])
189
+ count += this.stopTag(tag);
190
+ return count;
191
+ }
192
+ async close() {
193
+ this.stopAll();
194
+ if (!this.server)
195
+ return;
196
+ const server = this.server;
197
+ this.server = undefined;
198
+ await new Promise((resolve) => server.close(() => resolve()));
199
+ }
200
+ }
201
+ exports.BridgeServer = BridgeServer;
202
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../bridge/src/server.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "ports-manager",
3
+ "version": "1.0.0",
4
+ "displayName": "Ports Manager",
5
+ "publisher": "SENODROOM",
6
+ "author": "Muhammad Saad Amin",
7
+ "description": "Launch paired frontend/backend projects on free ports with CORS handling and optional VS Code/Cursor terminals.",
8
+ "main": "./dist/bridge/src/extension.js",
9
+ "exports": {
10
+ ".": "./src/index.js"
11
+ },
12
+ "bin": {
13
+ "ports-manager": "bin/ports-manager.js"
14
+ },
15
+ "type": "commonjs",
16
+ "engines": {
17
+ "node": ">=18",
18
+ "vscode": "^1.85.0"
19
+ },
20
+ "categories": [
21
+ "Other"
22
+ ],
23
+ "activationEvents": [
24
+ "onStartupFinished"
25
+ ],
26
+ "contributes": {
27
+ "commands": [
28
+ {
29
+ "command": "ports-manager.stopAll",
30
+ "title": "Ports Manager: Stop All Managed Terminals"
31
+ }
32
+ ]
33
+ },
34
+ "scripts": {
35
+ "compile:bridge": "tsc -p tsconfig.bridge.json",
36
+ "test": "npm run test:cli && npm run test:bridge",
37
+ "test:cli": "node --test test/core.test.js test/cors.integration.test.js test/process.integration.test.js",
38
+ "test:bridge": "npm run compile:bridge && node --test dist/bridge/test/server.test.js",
39
+ "package:vsix": "npm run compile:bridge && vsce package --allow-missing-repository",
40
+ "package:ovsx": "npm run package:vsix",
41
+ "publish:ovsx": "ovsx publish",
42
+ "prepack": "npm run compile:bridge",
43
+ "start": "node bin/ports-manager.js"
44
+ },
45
+ "dependencies": {
46
+ "chalk": "^4.1.2",
47
+ "cosmiconfig": "^9.0.0",
48
+ "jsonc-parser": "^3.3.1",
49
+ "tree-kill": "^1.2.2"
50
+ },
51
+ "optionalDependencies": {
52
+ "http-proxy-middleware": "^3.0.5"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^22.10.0",
56
+ "@types/vscode": "^1.85.0",
57
+ "@vscode/vsce": "^3.2.2",
58
+ "ovsx": "^0.10.1",
59
+ "typescript": "^5.7.2"
60
+ },
61
+ "keywords": [
62
+ "ports",
63
+ "mern",
64
+ "cli",
65
+ "vscode",
66
+ "cursor",
67
+ "cors",
68
+ "dev-server"
69
+ ],
70
+ "license": "MIT",
71
+ "directories": {
72
+ "test": "test"
73
+ },
74
+ "repository": {
75
+ "type": "git",
76
+ "url": "git+https://github.com/SENODROOM/ports-manager.git"
77
+ },
78
+ "bugs": {
79
+ "url": "https://github.com/SENODROOM/ports-manager/issues"
80
+ },
81
+ "homepage": "https://github.com/SENODROOM/ports-manager#readme"
82
+ }
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+ const http = require('node:http');
7
+ const { terminalCommand } = require('./ide');
8
+
9
+ function descriptorPath() {
10
+ return path.join(os.homedir(), '.ports-manager', 'bridge.json');
11
+ }
12
+
13
+ function readDescriptor(filename = descriptorPath()) {
14
+ let descriptor;
15
+ try {
16
+ descriptor = JSON.parse(fs.readFileSync(filename, 'utf8'));
17
+ } catch (error) {
18
+ throw new Error(`bridge descriptor unavailable at ${filename}: ${error.message}`);
19
+ }
20
+ const endpoint = new URL(descriptor.endpoint);
21
+ if (endpoint.protocol !== 'http:' ||
22
+ !['127.0.0.1', 'localhost', '[::1]'].includes(endpoint.hostname)) {
23
+ throw new Error('bridge endpoint must be loopback HTTP');
24
+ }
25
+ if (typeof descriptor.token !== 'string' || descriptor.token.length < 32) {
26
+ throw new Error('bridge descriptor has an invalid token');
27
+ }
28
+ return { endpoint, token: descriptor.token };
29
+ }
30
+
31
+ function requestBridge(route, body, descriptor = readDescriptor()) {
32
+ const payload = Buffer.from(JSON.stringify(body));
33
+ return new Promise((resolve, reject) => {
34
+ const request = http.request(new URL(route, descriptor.endpoint), {
35
+ method: 'POST',
36
+ headers: {
37
+ Authorization: `Bearer ${descriptor.token}`,
38
+ 'Content-Type': 'application/json',
39
+ 'Content-Length': payload.length
40
+ },
41
+ timeout: 2500
42
+ }, (response) => {
43
+ let output = '';
44
+ response.setEncoding('utf8');
45
+ response.on('data', (chunk) => { output += chunk; });
46
+ response.on('end', () => {
47
+ if (response.statusCode < 200 || response.statusCode >= 300) {
48
+ reject(new Error(`bridge returned ${response.statusCode}: ${output}`));
49
+ } else {
50
+ resolve(output ? JSON.parse(output) : {});
51
+ }
52
+ });
53
+ });
54
+ request.on('timeout', () => request.destroy(new Error('bridge request timed out')));
55
+ request.on('error', (error) => reject(new Error(`bridge unavailable: ${error.message}`)));
56
+ request.end(payload);
57
+ });
58
+ }
59
+
60
+ function toTerminal(spec) {
61
+ return {
62
+ name: `Ports Manager ${spec.name}`,
63
+ cwd: spec.cwd,
64
+ env: spec.env,
65
+ command: terminalCommand(spec)
66
+ };
67
+ }
68
+
69
+ async function launchInBridge(specs, proxySpec, tag) {
70
+ const terminals = [toTerminal(specs.backend), toTerminal(specs.frontend)];
71
+ if (proxySpec) terminals.push(toTerminal(proxySpec));
72
+ return requestBridge('/terminals', { tag, terminals });
73
+ }
74
+
75
+ async function stopBridge(tag) {
76
+ return requestBridge('/stop', { tag });
77
+ }
78
+
79
+ module.exports = {
80
+ descriptorPath, launchInBridge, readDescriptor, requestBridge, stopBridge, toTerminal
81
+ };