hop2 0.8.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/.hop-tmux.conf +18 -0
- package/INTEGRATION.md +92 -0
- package/README.md +260 -0
- package/hay/CHANGELOG.md +46 -0
- package/hay/DEV_NOTES.md +39 -0
- package/hay/README.md +290 -0
- package/hay/apps/cli/package.json +25 -0
- package/hay/apps/cli/src/index.ts +1250 -0
- package/hay/apps/cli/tsconfig.json +8 -0
- package/hay/apps/server/package.json +37 -0
- package/hay/apps/server/src/index.ts +103 -0
- package/hay/apps/server/src/lib.ts +161 -0
- package/hay/apps/server/src/pty.ts +68 -0
- package/hay/apps/server/src/rooms.ts +421 -0
- package/hay/apps/server/src/utils.ts +20 -0
- package/hay/apps/server/test/rooms.test.ts +146 -0
- package/hay/apps/server/tsconfig.json +9 -0
- package/hay/apps/web/index.html +12 -0
- package/hay/apps/web/package.json +32 -0
- package/hay/apps/web/scripts/run-e2e.mjs +130 -0
- package/hay/apps/web/src/App.tsx +1302 -0
- package/hay/apps/web/src/components/MobileKeyboard.tsx +701 -0
- package/hay/apps/web/src/main.tsx +13 -0
- package/hay/apps/web/src/setupTests.ts +1 -0
- package/hay/apps/web/src/styles.css +1198 -0
- package/hay/apps/web/src/utils/optimisticEcho.ts +115 -0
- package/hay/apps/web/src/utils/presence.ts +24 -0
- package/hay/apps/web/src/vite-env.d.ts +10 -0
- package/hay/apps/web/test/optimisticEcho.test.ts +39 -0
- package/hay/apps/web/test/presence.test.ts +18 -0
- package/hay/apps/web/tsconfig.json +8 -0
- package/hay/apps/web/vite.config.ts +13 -0
- package/hay/apps/web/vitest.config.ts +12 -0
- package/hay/package-lock.json +5988 -0
- package/hay/package.json +18 -0
- package/hay/packages/shared/package.json +27 -0
- package/hay/packages/shared/src/colors.ts +16 -0
- package/hay/packages/shared/src/index.ts +2 -0
- package/hay/packages/shared/src/protocol.ts +90 -0
- package/hay/packages/shared/test/protocol.test.ts +33 -0
- package/hay/packages/shared/tsconfig.json +7 -0
- package/hay/tsconfig.base.json +15 -0
- package/hay-web/assets/index-CTTzApRp.css +32 -0
- package/hay-web/assets/index-FDfdolCs.js +60 -0
- package/hay-web/assets/index-FDfdolCs.js.map +1 -0
- package/hay-web/index.html +13 -0
- package/hop +3717 -0
- package/package.json +52 -0
- package/sessions.html +243 -0
- package/tunnel-shell.js +254 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import http from "node:http";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { chromium } from "playwright";
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const root = path.resolve(__dirname, "../../..");
|
|
10
|
+
const port = "4170";
|
|
11
|
+
const baseUrl = `http://localhost:${port}`;
|
|
12
|
+
const serverEntry = path.join(root, "apps/server/dist/index.js");
|
|
13
|
+
const webDist = path.join(root, "apps/web/dist");
|
|
14
|
+
|
|
15
|
+
const run = (command, envOverrides = {}) => {
|
|
16
|
+
execSync(command, {
|
|
17
|
+
cwd: root,
|
|
18
|
+
stdio: "inherit",
|
|
19
|
+
env: {
|
|
20
|
+
...process.env,
|
|
21
|
+
...envOverrides
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const waitForServer = (url, timeoutMs = 15000) => {
|
|
27
|
+
const startedAt = Date.now();
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const attempt = () => {
|
|
30
|
+
const request = http.get(url, (res) => {
|
|
31
|
+
res.resume();
|
|
32
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
33
|
+
resolve(true);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
retry();
|
|
37
|
+
});
|
|
38
|
+
request.on("error", retry);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const retry = () => {
|
|
42
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
43
|
+
reject(new Error("Server did not start in time"));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
setTimeout(attempt, 500);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
attempt();
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const joinRoom = async (page, name, room) => {
|
|
54
|
+
await page.goto(baseUrl);
|
|
55
|
+
await page.getByLabel("Display name").fill(name);
|
|
56
|
+
await page.getByLabel("Room").fill(room);
|
|
57
|
+
await page.getByRole("button", { name: "Start session" }).click();
|
|
58
|
+
await page.locator(".terminal-frame").waitFor({ state: "visible" });
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const runE2E = async () => {
|
|
62
|
+
const browser = await chromium.launch();
|
|
63
|
+
try {
|
|
64
|
+
const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
|
|
65
|
+
const room = `e2e-${Date.now()}`;
|
|
66
|
+
const page1 = await context.newPage();
|
|
67
|
+
const page2 = await context.newPage();
|
|
68
|
+
|
|
69
|
+
await joinRoom(page1, "Alex", room);
|
|
70
|
+
await joinRoom(page2, "Blake", room);
|
|
71
|
+
|
|
72
|
+
await page1.locator(".terminal-frame").click();
|
|
73
|
+
await page1.keyboard.type("echo shared");
|
|
74
|
+
await page1.keyboard.press("Enter");
|
|
75
|
+
|
|
76
|
+
await page2.waitForFunction(() => {
|
|
77
|
+
return window.__termshare?.getBufferText().includes("shared");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const roomLock = `e2e-${Date.now()}-lock`;
|
|
81
|
+
const context2 = await browser.newContext({ viewport: { width: 1280, height: 720 } });
|
|
82
|
+
const page3 = await context2.newPage();
|
|
83
|
+
const page4 = await context2.newPage();
|
|
84
|
+
|
|
85
|
+
await joinRoom(page3, "Casey", roomLock);
|
|
86
|
+
await joinRoom(page4, "Drew", roomLock);
|
|
87
|
+
|
|
88
|
+
await page3.getByRole("button", { name: "Lock control" }).click();
|
|
89
|
+
await page3.waitForFunction(() => {
|
|
90
|
+
return document.querySelector(".status-main")?.textContent?.includes("Locked");
|
|
91
|
+
});
|
|
92
|
+
await page4.locator(".terminal-frame").click();
|
|
93
|
+
await page4.keyboard.type("whoami");
|
|
94
|
+
await page4.keyboard.press("Enter");
|
|
95
|
+
|
|
96
|
+
await page4.waitForFunction(() => {
|
|
97
|
+
return document.querySelector(".notice")?.textContent?.includes("Control is locked");
|
|
98
|
+
});
|
|
99
|
+
const notice = await page4.locator(".notice").textContent();
|
|
100
|
+
assert.ok(notice?.includes("Control is locked"));
|
|
101
|
+
|
|
102
|
+
await context.close();
|
|
103
|
+
await context2.close();
|
|
104
|
+
} finally {
|
|
105
|
+
await browser.close();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
run("npm -w packages/shared run build");
|
|
110
|
+
run("npm -w apps/server run build");
|
|
111
|
+
run("npm -w apps/web run build", { VITE_E2E: "true" });
|
|
112
|
+
|
|
113
|
+
const server = spawn("node", [serverEntry], {
|
|
114
|
+
cwd: root,
|
|
115
|
+
stdio: "inherit",
|
|
116
|
+
env: {
|
|
117
|
+
...process.env,
|
|
118
|
+
PORT: port,
|
|
119
|
+
SERVE_WEB: "true",
|
|
120
|
+
WEB_DIST_PATH: webDist,
|
|
121
|
+
PTY_MODE: "mock"
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
await waitForServer(`${baseUrl}/health`);
|
|
127
|
+
await runE2E();
|
|
128
|
+
} finally {
|
|
129
|
+
server.kill("SIGTERM");
|
|
130
|
+
}
|