portless-studio 0.1.0 → 0.1.2
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/README.md +78 -192
- package/dist/cli/cli-args.js +29 -3
- package/dist/cli/index.js +10 -15
- package/dist/cli/login-service.js +16 -0
- package/dist/cli/mcp/studio-tools.js +20 -7
- package/dist/cli/studio-items.js +5 -2
- package/dist/core/api/contract.js +19 -1
- package/dist/core/config/studio-config.js +6 -0
- package/dist/core/doctor/platform-commands.js +5 -2
- package/dist/core/ports/direct-url.js +7 -0
- package/dist/core/ports/docker-container.js +85 -0
- package/dist/core/routes/route.js +14 -2
- package/dist/core/updates/version.js +62 -0
- package/dist/server/application/auto-namer.js +14 -0
- package/dist/server/application/container-source.js +19 -0
- package/dist/server/application/doctor.js +225 -9
- package/dist/server/application/port-discovery.js +49 -6
- package/dist/server/application/proxy-start-check.js +83 -0
- package/dist/server/application/short-urls.js +136 -0
- package/dist/server/application/studio-runtime.js +46 -7
- package/dist/server/application/terminals.js +142 -15
- package/dist/server/application/update-check.js +66 -0
- package/dist/server/infrastructure/config/json-update-cache.js +35 -0
- package/dist/server/infrastructure/portless/portless-registry.js +31 -4
- package/dist/server/infrastructure/portless/state-files.js +23 -7
- package/dist/server/infrastructure/system/docker-containers.js +64 -0
- package/dist/server/infrastructure/system/doctor-facts.js +44 -1
- package/dist/server/infrastructure/system/npm-registry.js +20 -0
- package/dist/server/infrastructure/system/port-forwarder.js +78 -0
- package/dist/server/infrastructure/system/process-runner.js +2 -1
- package/dist/server/infrastructure/terminal/process-tree.js +71 -0
- package/dist/server/infrastructure/terminal/terminal-spawner.js +44 -3
- package/dist/server/interface/http/http-server.js +43 -2
- package/dist/server/interface/http/json-io.js +1 -0
- package/dist/server/main.js +62 -8
- package/dist/web/assets/index-BDEwEBPS.js +15 -0
- package/dist/web/assets/index-C9wkL1J8.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/web/assets/index-CA7HpYbk.css +0 -1
- package/dist/web/assets/index-CC6BquZf.js +0 -15
package/dist/server/main.js
CHANGED
|
@@ -2,33 +2,48 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { stopContainer } from "./application/container-source.js";
|
|
5
6
|
import { createDoctorService } from "./application/doctor.js";
|
|
6
7
|
import { OpenWith } from "./application/open-with.js";
|
|
7
8
|
import { controlProxy } from "./application/proxy-control.js";
|
|
9
|
+
import { confirmProxyStarts } from "./application/proxy-start-check.js";
|
|
8
10
|
import { createAlias, renameAlias } from "./application/route-use-cases.js";
|
|
11
|
+
import { publicUrls, SHORT_URL_PORT, ShortUrls } from "./application/short-urls.js";
|
|
9
12
|
import { stopProcess } from "./application/stop-process.js";
|
|
10
13
|
import { StudioRuntime } from "./application/studio-runtime.js";
|
|
11
14
|
import { TerminalManager } from "./application/terminals.js";
|
|
15
|
+
import { UpdateChecker, updateChecksAllowed } from "./application/update-check.js";
|
|
12
16
|
import { systemAppCatalog } from "./infrastructure/apps/app-catalog.js";
|
|
13
17
|
import { createDetachedLauncher } from "./infrastructure/apps/detached-launcher.js";
|
|
14
18
|
import { JsonCustomAppStore, JsonRouteProjectStore, openWithPathFor, routeProjectsPathFor, } from "./infrastructure/apps/json-stores.js";
|
|
15
19
|
import { autoNamedPathFor, JsonAutoNamedStore, } from "./infrastructure/config/json-auto-named-store.js";
|
|
16
20
|
import { defaultConfigPath, JsonConfigStore } from "./infrastructure/config/json-config-store.js";
|
|
21
|
+
import { JsonUpdateCache, updateCheckPathFor } from "./infrastructure/config/json-update-cache.js";
|
|
17
22
|
import { proxyStartRecordPathFor, readProxyStartRecord, recordProxyStarts, } from "./infrastructure/config/proxy-start-record.js";
|
|
18
23
|
import { PortlessRegistry } from "./infrastructure/portless/portless-registry.js";
|
|
19
|
-
import { resolveStateDir } from "./infrastructure/portless/state-files.js";
|
|
20
|
-
import {
|
|
24
|
+
import { inspectRoutesFile, resolveStateDir } from "./infrastructure/portless/state-files.js";
|
|
25
|
+
import { DockerContainers } from "./infrastructure/system/docker-containers.js";
|
|
26
|
+
import { collectDoctorFacts, proxyPidsAmong, reachesPortless, testBind, } from "./infrastructure/system/doctor-facts.js";
|
|
21
27
|
import { FaviconFetcher } from "./infrastructure/system/favicon-fetcher.js";
|
|
28
|
+
import { fetchLatestVersion } from "./infrastructure/system/npm-registry.js";
|
|
22
29
|
import { OsNotifier } from "./infrastructure/system/os-notifier.js";
|
|
30
|
+
import { startForwarder } from "./infrastructure/system/port-forwarder.js";
|
|
23
31
|
import { ProjectInfoReader } from "./infrastructure/system/project-info.js";
|
|
24
32
|
import { nodeSystemIo } from "./infrastructure/system/system-io.js";
|
|
25
33
|
import { createPortScanner, listPortsFor, probeHttpResponse, } from "./infrastructure/system/system-port-scanner.js";
|
|
26
34
|
import { SystemProcessControl } from "./infrastructure/system/system-process-control.js";
|
|
27
|
-
import {
|
|
35
|
+
import { systemProcessTree } from "./infrastructure/terminal/process-tree.js";
|
|
36
|
+
import { createTerminalSpawner, detectTerminalMode, interactiveShellName, nodeTerminalFolders, } from "./infrastructure/terminal/terminal-spawner.js";
|
|
28
37
|
import { createHttpServer } from "./interface/http/http-server.js";
|
|
29
38
|
export { defaultConfigPath } from "./infrastructure/config/json-config-store.js";
|
|
30
39
|
/** Only this machine can connect; see docs/architecture.md, Security. */
|
|
31
40
|
const LOOPBACK = "127.0.0.1";
|
|
41
|
+
/** `PORTLESS_STUDIO_SHORT_URL_PORT`, when it is a valid port; port 80 otherwise. */
|
|
42
|
+
export function shortUrlPortFrom(env) {
|
|
43
|
+
const raw = env.PORTLESS_STUDIO_SHORT_URL_PORT;
|
|
44
|
+
const port = raw === undefined ? Number.NaN : Number(raw);
|
|
45
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : SHORT_URL_PORT;
|
|
46
|
+
}
|
|
32
47
|
// Lets startServer reuse the runtime behind services built here, so both share one scan cache.
|
|
33
48
|
const runtimes = new WeakMap();
|
|
34
49
|
export function createStudioServices(options = {}) {
|
|
@@ -39,11 +54,20 @@ export function createStudioServices(options = {}) {
|
|
|
39
54
|
bin: options.portlessBin ?? (process.env.PORTLESS_BIN || "portless"),
|
|
40
55
|
...(options.npmBin === undefined ? {} : { npmBin: options.npmBin }),
|
|
41
56
|
});
|
|
42
|
-
// Every proxy Studio starts is written down, so `uninstall` stops only Studio's own.
|
|
43
|
-
const registry = recordProxyStarts(portless, proxyStartRecordPathFor(configPath));
|
|
44
57
|
const scanner = createPortScanner();
|
|
58
|
+
const proxyPids = (pids) => proxyPidsAmong(process.platform, nodeSystemIo, pids);
|
|
59
|
+
const shortUrls = new ShortUrls({
|
|
60
|
+
forwarding: { listen: (port, target) => startForwarder({ port, targetPort: target }) },
|
|
61
|
+
port: options.shortUrlPort ?? shortUrlPortFrom(process.env),
|
|
62
|
+
log: (line) => console.log(line),
|
|
63
|
+
reachesProxy: (port) => reachesPortless(port),
|
|
64
|
+
});
|
|
65
|
+
// A start counts only once the proxy listens, and every proxy Studio starts is written down,
|
|
66
|
+
// so `uninstall` stops only Studio's own. Route URLs drop the port while Studio forwards it.
|
|
67
|
+
const registry = publicUrls(recordProxyStarts(confirmProxyStarts(portless, { scan: () => scanner.scan(), proxyPids }), proxyStartRecordPathFor(configPath)), () => shortUrls.publicPort());
|
|
45
68
|
const configStore = new JsonConfigStore(configPath);
|
|
46
69
|
const notifier = new OsNotifier({ platform: process.platform });
|
|
70
|
+
const containers = new DockerContainers();
|
|
47
71
|
const runtime = new StudioRuntime({
|
|
48
72
|
registry,
|
|
49
73
|
scanner,
|
|
@@ -51,11 +75,13 @@ export function createStudioServices(options = {}) {
|
|
|
51
75
|
projects: new ProjectInfoReader(),
|
|
52
76
|
studioPort: options.studioPort ?? null,
|
|
53
77
|
probe: (port) => probeHttpResponse(port),
|
|
54
|
-
proxyPids
|
|
78
|
+
proxyPids,
|
|
55
79
|
autoNamedStore: new JsonAutoNamedStore(autoNamedPathFor(configPath)),
|
|
56
80
|
notifier,
|
|
57
81
|
icons: new FaviconFetcher(),
|
|
58
82
|
routeProjectStore: new JsonRouteProjectStore(routeProjectsPathFor(configPath)),
|
|
83
|
+
containers,
|
|
84
|
+
shortUrls,
|
|
59
85
|
});
|
|
60
86
|
const openWith = new OpenWith({
|
|
61
87
|
catalog: systemAppCatalog(),
|
|
@@ -66,6 +92,8 @@ export function createStudioServices(options = {}) {
|
|
|
66
92
|
const terminals = new TerminalManager({
|
|
67
93
|
spawner: lazySpawner(),
|
|
68
94
|
folders: nodeTerminalFolders(),
|
|
95
|
+
processTree: systemProcessTree(),
|
|
96
|
+
homeDir: os.homedir(),
|
|
69
97
|
workspace: {
|
|
70
98
|
openableFolders: () => runtime.openableFolders(),
|
|
71
99
|
snapshot: () => runtime.readSnapshot(),
|
|
@@ -97,10 +125,24 @@ export function createStudioServices(options = {}) {
|
|
|
97
125
|
probe: (port) => probeHttpResponse(port),
|
|
98
126
|
...(studioService ? { loginService: () => studioService.status() } : {}),
|
|
99
127
|
systemNotifier: () => notifier.availability(),
|
|
128
|
+
routesFile: () => inspectRoutesFile(stateDir),
|
|
129
|
+
docker: () => containers.read(),
|
|
130
|
+
lowPort: testBind,
|
|
131
|
+
shortUrls: (config) => shortUrls.inspect(config),
|
|
100
132
|
}),
|
|
101
133
|
controlProxy: proxy,
|
|
102
134
|
updateConfig: (config) => runtime.updateConfig(config),
|
|
135
|
+
syncShortUrls: (config) => shortUrls.sync(config),
|
|
103
136
|
});
|
|
137
|
+
const version = options.version;
|
|
138
|
+
const updates = version === undefined
|
|
139
|
+
? undefined
|
|
140
|
+
: new UpdateChecker({
|
|
141
|
+
current: version,
|
|
142
|
+
enabled: async () => updateChecksAllowed(process.env) && (await runtime.settings()).checkForUpdates,
|
|
143
|
+
fetchLatest: () => fetchLatestVersion(),
|
|
144
|
+
cache: new JsonUpdateCache(updateCheckPathFor(configPath)),
|
|
145
|
+
});
|
|
104
146
|
const services = {
|
|
105
147
|
registry,
|
|
106
148
|
scanner,
|
|
@@ -113,10 +155,13 @@ export function createStudioServices(options = {}) {
|
|
|
113
155
|
stoppablePids: () => runtime.stoppablePids(),
|
|
114
156
|
selfPid: process.pid,
|
|
115
157
|
}),
|
|
158
|
+
stopContainer: (name) => stopContainer(name, containers),
|
|
116
159
|
studioService,
|
|
117
160
|
openWith,
|
|
118
161
|
terminals,
|
|
119
162
|
openUrl: options.openUrl,
|
|
163
|
+
updates,
|
|
164
|
+
shortUrls,
|
|
120
165
|
};
|
|
121
166
|
runtimes.set(services, runtime);
|
|
122
167
|
return services;
|
|
@@ -130,6 +175,7 @@ export function createUninstallServices(configPath = defaultConfigPath()) {
|
|
|
130
175
|
recordPath,
|
|
131
176
|
routeProjectsPathFor(configPath),
|
|
132
177
|
openWithPathFor(configPath),
|
|
178
|
+
updateCheckPathFor(configPath),
|
|
133
179
|
];
|
|
134
180
|
const processes = new SystemProcessControl({
|
|
135
181
|
platform: process.platform,
|
|
@@ -160,6 +206,7 @@ export async function startServer(options) {
|
|
|
160
206
|
scanner: services.scanner,
|
|
161
207
|
configStore: services.configStore,
|
|
162
208
|
projects: new ProjectInfoReader(),
|
|
209
|
+
...(services.shortUrls ? { shortUrls: services.shortUrls } : {}),
|
|
163
210
|
});
|
|
164
211
|
const token = randomBytes(32).toString("base64url");
|
|
165
212
|
const http = createHttpServer({
|
|
@@ -170,10 +217,12 @@ export async function startServer(options) {
|
|
|
170
217
|
doctor: services.doctor,
|
|
171
218
|
controlProxy: services.controlProxy,
|
|
172
219
|
stopProcess: services.stopProcess,
|
|
220
|
+
stopContainer: services.stopContainer,
|
|
173
221
|
...(services.studioService ? { studioService: services.studioService } : {}),
|
|
174
222
|
...(services.openWith ? { openWith: services.openWith } : {}),
|
|
175
223
|
...(services.terminals ? { terminals: services.terminals } : {}),
|
|
176
224
|
...(services.openUrl ? { openUrl: services.openUrl } : {}),
|
|
225
|
+
...(services.updates ? { updates: services.updates } : {}),
|
|
177
226
|
});
|
|
178
227
|
await new Promise((resolve, reject) => {
|
|
179
228
|
http.server.once("error", reject);
|
|
@@ -185,6 +234,8 @@ export async function startServer(options) {
|
|
|
185
234
|
const { port } = http.server.address();
|
|
186
235
|
studio.setStudioPort(port);
|
|
187
236
|
studio.start();
|
|
237
|
+
// The short-URL forwarder settles before anyone prints or registers a URL.
|
|
238
|
+
await studio.ready();
|
|
188
239
|
return {
|
|
189
240
|
url: `http://${LOOPBACK}:${port}`,
|
|
190
241
|
port,
|
|
@@ -195,8 +246,10 @@ export async function startServer(options) {
|
|
|
195
246
|
studio.stop();
|
|
196
247
|
services.terminals?.closeAll();
|
|
197
248
|
http.closeStreams();
|
|
198
|
-
|
|
199
|
-
|
|
249
|
+
void (services.shortUrls?.close() ?? Promise.resolve()).finally(() => {
|
|
250
|
+
http.server.close(() => resolve());
|
|
251
|
+
http.server.closeAllConnections();
|
|
252
|
+
});
|
|
200
253
|
}),
|
|
201
254
|
};
|
|
202
255
|
}
|
|
@@ -211,6 +264,7 @@ function lazySpawner() {
|
|
|
211
264
|
get mode() {
|
|
212
265
|
return mode;
|
|
213
266
|
},
|
|
267
|
+
shellName: interactiveShellName(),
|
|
214
268
|
spawn: async (request) => {
|
|
215
269
|
real ??= detectTerminalMode(process.platform).then((detected) => createTerminalSpawner({ mode: detected }));
|
|
216
270
|
const spawner = await real;
|