hammoc 1.2.3 → 1.4.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/README.md +11 -9
- package/package.json +5 -2
- package/packages/client/dist/assets/{index-B2I3yEWl.js → index-6jREnVYd.js} +1 -1
- package/packages/client/dist/assets/index-BFF0iqyW.css +32 -0
- package/packages/client/dist/assets/index-BcI4y-fU.js +1454 -0
- package/packages/client/dist/index.html +2 -2
- package/packages/client/dist/sw.js +1 -1
- package/packages/server/dist/app.d.ts.map +1 -1
- package/packages/server/dist/app.js +11 -3
- package/packages/server/dist/app.js.map +1 -1
- package/packages/server/dist/controllers/boardController.d.ts.map +1 -1
- package/packages/server/dist/controllers/boardController.js +0 -5
- package/packages/server/dist/controllers/boardController.js.map +1 -1
- package/packages/server/dist/controllers/fileSystemController.d.ts +4 -0
- package/packages/server/dist/controllers/fileSystemController.d.ts.map +1 -1
- package/packages/server/dist/controllers/fileSystemController.js +20 -2
- package/packages/server/dist/controllers/fileSystemController.js.map +1 -1
- package/packages/server/dist/controllers/projectController.js +1 -1
- package/packages/server/dist/controllers/projectController.js.map +1 -1
- package/packages/server/dist/controllers/queueTemplateController.js +2 -2
- package/packages/server/dist/controllers/queueTemplateController.js.map +1 -1
- package/packages/server/dist/controllers/serverController.d.ts.map +1 -1
- package/packages/server/dist/controllers/serverController.js +86 -50
- package/packages/server/dist/controllers/serverController.js.map +1 -1
- package/packages/server/dist/handlers/websocket.d.ts +1 -0
- package/packages/server/dist/handlers/websocket.d.ts.map +1 -1
- package/packages/server/dist/handlers/websocket.js +241 -49
- package/packages/server/dist/handlers/websocket.js.map +1 -1
- package/packages/server/dist/index.js +61 -0
- package/packages/server/dist/index.js.map +1 -1
- package/packages/server/dist/locales/en/server.json +9 -3
- package/packages/server/dist/locales/es/server.json +4 -2
- package/packages/server/dist/locales/ja/server.json +4 -2
- package/packages/server/dist/locales/ko/server.json +9 -3
- package/packages/server/dist/locales/pt/server.json +4 -2
- package/packages/server/dist/locales/zh-CN/server.json +4 -2
- package/packages/server/dist/routes/account.d.ts +7 -0
- package/packages/server/dist/routes/account.d.ts.map +1 -0
- package/packages/server/dist/routes/account.js +35 -0
- package/packages/server/dist/routes/account.js.map +1 -0
- package/packages/server/dist/routes/debug.d.ts +1 -1
- package/packages/server/dist/routes/debug.d.ts.map +1 -1
- package/packages/server/dist/routes/debug.js +60 -1
- package/packages/server/dist/routes/debug.js.map +1 -1
- package/packages/server/dist/routes/preferences.d.ts.map +1 -1
- package/packages/server/dist/routes/preferences.js +11 -2
- package/packages/server/dist/routes/preferences.js.map +1 -1
- package/packages/server/dist/services/accountInfoService.d.ts +38 -0
- package/packages/server/dist/services/accountInfoService.d.ts.map +1 -0
- package/packages/server/dist/services/accountInfoService.js +118 -0
- package/packages/server/dist/services/accountInfoService.js.map +1 -0
- package/packages/server/dist/services/chatService.d.ts.map +1 -1
- package/packages/server/dist/services/chatService.js +27 -2
- package/packages/server/dist/services/chatService.js.map +1 -1
- package/packages/server/dist/services/fileSystemService.d.ts +7 -1
- package/packages/server/dist/services/fileSystemService.d.ts.map +1 -1
- package/packages/server/dist/services/fileSystemService.js +67 -8
- package/packages/server/dist/services/fileSystemService.js.map +1 -1
- package/packages/server/dist/services/fileWatcherService.d.ts +35 -0
- package/packages/server/dist/services/fileWatcherService.d.ts.map +1 -0
- package/packages/server/dist/services/fileWatcherService.js +138 -0
- package/packages/server/dist/services/fileWatcherService.js.map +1 -0
- package/packages/server/dist/services/gitService.d.ts.map +1 -1
- package/packages/server/dist/services/gitService.js +67 -7
- package/packages/server/dist/services/gitService.js.map +1 -1
- package/packages/server/dist/services/historyParser.d.ts +4 -14
- package/packages/server/dist/services/historyParser.d.ts.map +1 -1
- package/packages/server/dist/services/historyParser.js +60 -5
- package/packages/server/dist/services/historyParser.js.map +1 -1
- package/packages/server/dist/services/issueService.d.ts.map +1 -1
- package/packages/server/dist/services/issueService.js +10 -3
- package/packages/server/dist/services/issueService.js.map +1 -1
- package/packages/server/dist/services/notificationService.d.ts.map +1 -1
- package/packages/server/dist/services/notificationService.js +34 -9
- package/packages/server/dist/services/notificationService.js.map +1 -1
- package/packages/server/dist/services/preferencesService.d.ts.map +1 -1
- package/packages/server/dist/services/preferencesService.js +8 -1
- package/packages/server/dist/services/preferencesService.js.map +1 -1
- package/packages/server/dist/services/projectService.d.ts +5 -0
- package/packages/server/dist/services/projectService.d.ts.map +1 -1
- package/packages/server/dist/services/projectService.js +42 -2
- package/packages/server/dist/services/projectService.js.map +1 -1
- package/packages/server/dist/services/ptyService.d.ts +1 -0
- package/packages/server/dist/services/ptyService.d.ts.map +1 -1
- package/packages/server/dist/services/ptyService.js +36 -5
- package/packages/server/dist/services/ptyService.js.map +1 -1
- package/packages/server/dist/services/queueService.d.ts +17 -0
- package/packages/server/dist/services/queueService.d.ts.map +1 -1
- package/packages/server/dist/services/queueService.js +215 -25
- package/packages/server/dist/services/queueService.js.map +1 -1
- package/packages/server/dist/services/sessionBufferManager.d.ts.map +1 -1
- package/packages/server/dist/services/sessionBufferManager.js +26 -0
- package/packages/server/dist/services/sessionBufferManager.js.map +1 -1
- package/packages/server/dist/services/sessionService.d.ts +15 -3
- package/packages/server/dist/services/sessionService.d.ts.map +1 -1
- package/packages/server/dist/services/sessionService.js +64 -6
- package/packages/server/dist/services/sessionService.js.map +1 -1
- package/packages/server/dist/services/streamHandler.d.ts.map +1 -1
- package/packages/server/dist/services/streamHandler.js +2 -1
- package/packages/server/dist/services/streamHandler.js.map +1 -1
- package/packages/server/dist/services/webPushService.d.ts.map +1 -1
- package/packages/server/dist/services/webPushService.js +8 -1
- package/packages/server/dist/services/webPushService.js.map +1 -1
- package/packages/server/dist/snippets/brownfield-create-story +3 -2
- package/packages/server/dist/utils/effortUtils.d.ts +21 -0
- package/packages/server/dist/utils/effortUtils.d.ts.map +1 -0
- package/packages/server/dist/utils/effortUtils.js +36 -0
- package/packages/server/dist/utils/effortUtils.js.map +1 -0
- package/packages/server/dist/utils/errors.d.ts +1 -0
- package/packages/server/dist/utils/errors.d.ts.map +1 -1
- package/packages/server/dist/utils/errors.js +12 -0
- package/packages/server/dist/utils/errors.js.map +1 -1
- package/packages/server/dist/utils/pathUtils.d.ts +3 -2
- package/packages/server/dist/utils/pathUtils.d.ts.map +1 -1
- package/packages/server/dist/utils/pathUtils.js +26 -2
- package/packages/server/dist/utils/pathUtils.js.map +1 -1
- package/packages/server/package.json +2 -1
- package/packages/shared/dist/types/fileSystem.d.ts +19 -0
- package/packages/shared/dist/types/fileSystem.d.ts.map +1 -1
- package/packages/shared/dist/types/fileSystem.js +5 -0
- package/packages/shared/dist/types/fileSystem.js.map +1 -1
- package/packages/shared/dist/types/git.d.ts +6 -1
- package/packages/shared/dist/types/git.d.ts.map +1 -1
- package/packages/shared/dist/types/git.js.map +1 -1
- package/packages/shared/dist/types/history.d.ts +7 -0
- package/packages/shared/dist/types/history.d.ts.map +1 -1
- package/packages/shared/dist/types/preferences.d.ts +2 -1
- package/packages/shared/dist/types/preferences.d.ts.map +1 -1
- package/packages/shared/dist/types/preferences.js +1 -0
- package/packages/shared/dist/types/preferences.js.map +1 -1
- package/packages/shared/dist/types/queue.d.ts +23 -1
- package/packages/shared/dist/types/queue.d.ts.map +1 -1
- package/packages/shared/dist/types/sdk.d.ts +47 -1
- package/packages/shared/dist/types/sdk.d.ts.map +1 -1
- package/packages/shared/dist/types/sdk.js +39 -0
- package/packages/shared/dist/types/sdk.js.map +1 -1
- package/packages/shared/dist/types/websocket.d.ts +14 -0
- package/packages/shared/dist/types/websocket.d.ts.map +1 -1
- package/packages/shared/dist/utils/queueParser.d.ts.map +1 -1
- package/packages/shared/dist/utils/queueParser.js +197 -12
- package/packages/shared/dist/utils/queueParser.js.map +1 -1
- package/scripts/mock-telegram.mjs +172 -0
- package/scripts/run-integration-test.mjs +362 -0
- package/packages/client/dist/assets/index-BghgIdOq.js +0 -1421
- package/packages/client/dist/assets/index-Cc_AX5QV.css +0 -32
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Hammoc integration test launcher.
|
|
4
|
+
*
|
|
5
|
+
* Starts the server with test-specific environment variables, waits until
|
|
6
|
+
* it is ready, then prints connection info for the Playwright MCP operator.
|
|
7
|
+
*
|
|
8
|
+
* Automatically snapshots ~/.hammoc/preferences.json at startup and restores it
|
|
9
|
+
* on exit, so scenarios that mutate language / permission mode / advanced
|
|
10
|
+
* settings don't leak state between runs.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node scripts/run-integration-test.mjs [options]
|
|
14
|
+
*
|
|
15
|
+
* Options:
|
|
16
|
+
* --port=<n> Primary server port (default: 21213)
|
|
17
|
+
* --chat-timeout=<ms> CHAT_TIMEOUT_MS env var (default: 300000)
|
|
18
|
+
* --permission-timeout=<ms> browser init-script snippet to inject
|
|
19
|
+
* --with-notifications Remind operator to grant browser notification permission
|
|
20
|
+
* --with-terminal-disabled Spawn secondary server on <port+1> with TERMINAL_ENABLED=false
|
|
21
|
+
* --bot-api-base=<url> BOT_API_BASE_URL env var (point to mock-telegram.mjs)
|
|
22
|
+
* --mock-telegram Auto-spawn mock-telegram.mjs on <port+17> and wire it up
|
|
23
|
+
* --mock-telegram-port=<n> Override mock-telegram port (default: <primary port>+17)
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { spawn } from 'child_process';
|
|
27
|
+
import { existsSync, copyFileSync, unlinkSync, readdirSync, statSync } from 'fs';
|
|
28
|
+
import os from 'os';
|
|
29
|
+
import path from 'path';
|
|
30
|
+
import { fileURLToPath } from 'url';
|
|
31
|
+
|
|
32
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
34
|
+
|
|
35
|
+
// ── Preferences snapshot/restore ────────────────────────────────────────────
|
|
36
|
+
// Integration scenarios mutate ~/.hammoc/preferences.json (language, permission
|
|
37
|
+
// mode, advanced settings, etc.). Capture the pre-test state at startup and
|
|
38
|
+
// restore it on exit so tests don't leave the user's environment dirty.
|
|
39
|
+
|
|
40
|
+
const PREFS_PATH = path.join(os.homedir(), '.hammoc', 'preferences.json');
|
|
41
|
+
const BACKUP_PATH = path.join(
|
|
42
|
+
os.homedir(),
|
|
43
|
+
'.hammoc',
|
|
44
|
+
`preferences.json.integration-backup-${process.pid}`,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
let snapshotState = { captured: false, originalExisted: false };
|
|
48
|
+
|
|
49
|
+
// Warn about orphan backups from prior runs that force-kill killed before restore.
|
|
50
|
+
// Do not auto-delete — the user may need to inspect them manually. Exclude our own PID.
|
|
51
|
+
function warnOrphanBackups() {
|
|
52
|
+
const dir = path.dirname(PREFS_PATH);
|
|
53
|
+
if (!existsSync(dir)) return;
|
|
54
|
+
const prefix = 'preferences.json.integration-backup-';
|
|
55
|
+
const orphans = readdirSync(dir)
|
|
56
|
+
.filter((n) => n.startsWith(prefix) && n !== path.basename(BACKUP_PATH))
|
|
57
|
+
.map((n) => ({ name: n, mtime: statSync(path.join(dir, n)).mtime }));
|
|
58
|
+
if (orphans.length === 0) return;
|
|
59
|
+
console.log(`⚠ Orphan backups detected in ${dir} — ${orphans.length} file(s):`);
|
|
60
|
+
for (const o of orphans) {
|
|
61
|
+
console.log(` ${o.name} (mtime: ${o.mtime.toISOString()})`);
|
|
62
|
+
}
|
|
63
|
+
console.log(' These are leftovers from prior launchers that did not restore cleanly.');
|
|
64
|
+
console.log(' Inspect and delete manually once you confirm they are not needed.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function snapshotPreferences() {
|
|
68
|
+
warnOrphanBackups();
|
|
69
|
+
if (existsSync(PREFS_PATH)) {
|
|
70
|
+
copyFileSync(PREFS_PATH, BACKUP_PATH);
|
|
71
|
+
snapshotState = { captured: true, originalExisted: true };
|
|
72
|
+
console.log(`✓ Snapshot: preferences.json → ${path.basename(BACKUP_PATH)}`);
|
|
73
|
+
} else {
|
|
74
|
+
snapshotState = { captured: true, originalExisted: false };
|
|
75
|
+
console.log('✓ Snapshot: preferences.json did not exist (will delete on exit if created)');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let restored = false;
|
|
80
|
+
function restorePreferences() {
|
|
81
|
+
if (!snapshotState.captured || restored) return;
|
|
82
|
+
restored = true;
|
|
83
|
+
try {
|
|
84
|
+
if (snapshotState.originalExisted) {
|
|
85
|
+
if (existsSync(BACKUP_PATH)) {
|
|
86
|
+
copyFileSync(BACKUP_PATH, PREFS_PATH);
|
|
87
|
+
unlinkSync(BACKUP_PATH);
|
|
88
|
+
console.log('✓ Preferences restored from snapshot.');
|
|
89
|
+
} else {
|
|
90
|
+
console.error(`✗ Snapshot file missing at ${BACKUP_PATH} — preferences NOT restored.`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
if (existsSync(PREFS_PATH)) {
|
|
94
|
+
unlinkSync(PREFS_PATH);
|
|
95
|
+
console.log('✓ Preferences file removed (did not exist before test).');
|
|
96
|
+
}
|
|
97
|
+
if (existsSync(BACKUP_PATH)) unlinkSync(BACKUP_PATH);
|
|
98
|
+
}
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error(`✗ Failed to restore preferences: ${err.message}`);
|
|
101
|
+
console.error(` Manual recovery: copy ${BACKUP_PATH} → ${PREFS_PATH}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Argument parsing ────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
function parseArgs(argv) {
|
|
108
|
+
const args = {};
|
|
109
|
+
for (const arg of argv.slice(2)) {
|
|
110
|
+
const eqIdx = arg.indexOf('=');
|
|
111
|
+
const key = eqIdx === -1 ? arg : arg.slice(0, eqIdx);
|
|
112
|
+
const value = eqIdx === -1 ? undefined : arg.slice(eqIdx + 1);
|
|
113
|
+
switch (key) {
|
|
114
|
+
case '--port': args.port = parseInt(value, 10); break;
|
|
115
|
+
case '--chat-timeout': args.chatTimeout = parseInt(value, 10); break;
|
|
116
|
+
case '--permission-timeout': args.permissionTimeout = parseInt(value, 10); break;
|
|
117
|
+
case '--with-notifications': args.withNotifications = true; break;
|
|
118
|
+
case '--with-terminal-disabled': args.withTerminalDisabled = true; break;
|
|
119
|
+
case '--trust-proxy': args.trustProxy = true; break;
|
|
120
|
+
case '--bot-api-base': args.botApiBase = value; break;
|
|
121
|
+
case '--mock-telegram': args.mockTelegram = true; break;
|
|
122
|
+
case '--mock-telegram-port': args.mockTelegramPort = parseInt(value, 10); break;
|
|
123
|
+
default:
|
|
124
|
+
if (!key.startsWith('--help')) {
|
|
125
|
+
console.warn(`Unknown option: ${key}`);
|
|
126
|
+
} else {
|
|
127
|
+
printHelp();
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return args;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function printHelp() {
|
|
136
|
+
console.log(`
|
|
137
|
+
Usage: node scripts/run-integration-test.mjs [options]
|
|
138
|
+
|
|
139
|
+
Options:
|
|
140
|
+
--port=<n> Primary server port (default: 21213)
|
|
141
|
+
--chat-timeout=<ms> CHAT_TIMEOUT_MS (default: 300000)
|
|
142
|
+
--permission-timeout=<ms> Permission auto-deny timeout injected via browser_evaluate
|
|
143
|
+
--with-notifications Grants browser notification permission in Playwright context
|
|
144
|
+
--with-terminal-disabled Spawn secondary server on <port+1> with TERMINAL_ENABLED=false
|
|
145
|
+
--trust-proxy Set TRUST_PROXY=true so X-Forwarded-For is honored (L-03-01 requires this)
|
|
146
|
+
--bot-api-base=<url> Telegram API base URL (use with mock-telegram.mjs)
|
|
147
|
+
--mock-telegram Auto-spawn mock-telegram.mjs and inject its URL
|
|
148
|
+
--mock-telegram-port=<n> Override mock-telegram port (default: primary+17)
|
|
149
|
+
--help Show this help message
|
|
150
|
+
`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Server lifecycle ────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
function buildEnv(port, opts) {
|
|
156
|
+
// Use production so the server serves the built client at /. Dev mode expects
|
|
157
|
+
// the Vite dev server to handle the frontend, which we don't start here.
|
|
158
|
+
const env = {
|
|
159
|
+
...process.env,
|
|
160
|
+
NODE_ENV: 'production',
|
|
161
|
+
PORT: String(port),
|
|
162
|
+
HOST: '127.0.0.1',
|
|
163
|
+
CHAT_TIMEOUT_MS: String(opts.chatTimeout ?? 300000),
|
|
164
|
+
// Enable /api/debug/* routes (kill-ws) for integration scenarios like R-01-01
|
|
165
|
+
// without switching to NODE_ENV=development (which would break static file serving).
|
|
166
|
+
ENABLE_TEST_ENDPOINTS: 'true',
|
|
167
|
+
// Honor X-Forwarded-For header — required for L-03-01 (simulate external-IP
|
|
168
|
+
// access via header). Off by default: without this, all requests appear as
|
|
169
|
+
// 127.0.0.1 and the IP-filter cannot be exercised.
|
|
170
|
+
...(opts.trustProxy ? { TRUST_PROXY: 'true' } : {}),
|
|
171
|
+
};
|
|
172
|
+
if (opts.botApiBase) {
|
|
173
|
+
env.BOT_API_BASE_URL = opts.botApiBase;
|
|
174
|
+
}
|
|
175
|
+
return env;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function resolveServerEntry() {
|
|
179
|
+
const distEntry = path.join(ROOT, 'packages', 'server', 'dist', 'index.js');
|
|
180
|
+
if (existsSync(distEntry)) {
|
|
181
|
+
return { cmd: process.execPath, args: [distEntry] };
|
|
182
|
+
}
|
|
183
|
+
// Fall back to tsx for development (no build required)
|
|
184
|
+
const srcEntry = path.join(ROOT, 'packages', 'server', 'src', 'index.ts');
|
|
185
|
+
return {
|
|
186
|
+
cmd: process.execPath,
|
|
187
|
+
args: ['--import', 'tsx/esm', srcEntry],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function spawnServer(port, env, label) {
|
|
192
|
+
const { cmd, args } = resolveServerEntry();
|
|
193
|
+
const proc = spawn(cmd, args, {
|
|
194
|
+
env: { ...env, PORT: String(port) },
|
|
195
|
+
cwd: ROOT,
|
|
196
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Emit PID on its own line so teardown scripts / wrappers can find and kill
|
|
200
|
+
// the server process without having to parse Hammoc's own startup logs.
|
|
201
|
+
console.log(`[${label}] PID ${proc.pid}`);
|
|
202
|
+
|
|
203
|
+
proc.stdout.on('data', (d) => {
|
|
204
|
+
process.stdout.write(`[${label}] ${d}`);
|
|
205
|
+
});
|
|
206
|
+
proc.stderr.on('data', (d) => {
|
|
207
|
+
process.stderr.write(`[${label}] ${d}`);
|
|
208
|
+
});
|
|
209
|
+
proc.on('exit', (code) => {
|
|
210
|
+
console.error(`[${label}] Server exited with code ${code}`);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return proc;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function waitForReady(url, label, timeoutMs = 30_000) {
|
|
217
|
+
const deadline = Date.now() + timeoutMs;
|
|
218
|
+
|
|
219
|
+
while (Date.now() < deadline) {
|
|
220
|
+
try {
|
|
221
|
+
const res = await fetch(url);
|
|
222
|
+
if (res.ok) return;
|
|
223
|
+
} catch {
|
|
224
|
+
// Not ready yet
|
|
225
|
+
}
|
|
226
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
227
|
+
}
|
|
228
|
+
throw new Error(`${label} (${url}) did not become ready within ${timeoutMs}ms`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── Main ────────────────────────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
const opts = parseArgs(process.argv);
|
|
234
|
+
const primaryPort = opts.port ?? 21213;
|
|
235
|
+
const secondaryPort = primaryPort + 1;
|
|
236
|
+
const mockTelegramPort = opts.mockTelegramPort ?? primaryPort + 17;
|
|
237
|
+
|
|
238
|
+
// Spawn mock-telegram first so its URL can be injected into server env.
|
|
239
|
+
let mockTelegramProc = null;
|
|
240
|
+
if (opts.mockTelegram) {
|
|
241
|
+
const mockScript = path.join(ROOT, 'scripts', 'mock-telegram.mjs');
|
|
242
|
+
mockTelegramProc = spawn(
|
|
243
|
+
process.execPath,
|
|
244
|
+
[mockScript, `--port=${mockTelegramPort}`],
|
|
245
|
+
{ cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'] },
|
|
246
|
+
);
|
|
247
|
+
mockTelegramProc.stdout.on('data', (d) => process.stdout.write(`[mock-tg] ${d}`));
|
|
248
|
+
mockTelegramProc.stderr.on('data', (d) => process.stderr.write(`[mock-tg] ${d}`));
|
|
249
|
+
mockTelegramProc.on('exit', (code) => {
|
|
250
|
+
console.error(`[mock-tg] Exited with code ${code}`);
|
|
251
|
+
});
|
|
252
|
+
// When auto-spawning, override --bot-api-base to our local mock.
|
|
253
|
+
opts.botApiBase = `http://127.0.0.1:${mockTelegramPort}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const primaryEnv = buildEnv(primaryPort, opts);
|
|
257
|
+
|
|
258
|
+
console.log('\n═══════════════════════════════════════════════════════');
|
|
259
|
+
console.log(' Hammoc Integration Test Launcher');
|
|
260
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
261
|
+
|
|
262
|
+
// Ensure dists exist or advise build
|
|
263
|
+
const serverDist = path.join(ROOT, 'packages', 'server', 'dist', 'index.js');
|
|
264
|
+
const clientDist = path.join(ROOT, 'packages', 'client', 'dist', 'index.html');
|
|
265
|
+
if (!existsSync(serverDist)) {
|
|
266
|
+
console.warn('⚠ packages/server/dist not found — falling back to tsx (slower startup).');
|
|
267
|
+
console.warn(' Run "npm run build" first for faster tests.\n');
|
|
268
|
+
}
|
|
269
|
+
if (!existsSync(clientDist)) {
|
|
270
|
+
console.warn('⚠ packages/client/dist not found — Playwright will get a blank page.');
|
|
271
|
+
console.warn(' Run "npm run build" first.\n');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Snapshot preferences before touching anything
|
|
275
|
+
snapshotPreferences();
|
|
276
|
+
|
|
277
|
+
// Spawn primary server
|
|
278
|
+
const primaryProc = spawnServer(primaryPort, primaryEnv, 'primary');
|
|
279
|
+
|
|
280
|
+
// Spawn secondary (TERMINAL_ENABLED=false) if requested
|
|
281
|
+
let secondaryProc = null;
|
|
282
|
+
if (opts.withTerminalDisabled) {
|
|
283
|
+
const secondaryEnv = { ...primaryEnv, TERMINAL_ENABLED: 'false' };
|
|
284
|
+
secondaryProc = spawnServer(secondaryPort, secondaryEnv, 'terminal-off');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Cleanup on exit — kill processes first, then restore preferences.
|
|
288
|
+
// Servers must be stopped before restoring so they can't rewrite the file mid-restore.
|
|
289
|
+
function cleanup() {
|
|
290
|
+
primaryProc.kill();
|
|
291
|
+
if (secondaryProc) secondaryProc.kill();
|
|
292
|
+
if (mockTelegramProc) mockTelegramProc.kill();
|
|
293
|
+
restorePreferences();
|
|
294
|
+
}
|
|
295
|
+
process.on('SIGINT', cleanup);
|
|
296
|
+
process.on('SIGTERM', cleanup);
|
|
297
|
+
// Final safety net for unexpected exits (must be synchronous)
|
|
298
|
+
process.on('exit', restorePreferences);
|
|
299
|
+
|
|
300
|
+
// Wait for servers
|
|
301
|
+
try {
|
|
302
|
+
if (mockTelegramProc) {
|
|
303
|
+
console.log(`\n⏳ Waiting for mock-telegram on port ${mockTelegramPort}...`);
|
|
304
|
+
await waitForReady(`http://127.0.0.1:${mockTelegramPort}/mock-telegram/health`, 'mock-telegram', 10_000);
|
|
305
|
+
}
|
|
306
|
+
console.log(`\n⏳ Waiting for primary server on port ${primaryPort}...`);
|
|
307
|
+
await waitForReady(`http://127.0.0.1:${primaryPort}/api/health`, 'primary server');
|
|
308
|
+
if (secondaryProc) {
|
|
309
|
+
console.log(`⏳ Waiting for secondary server on port ${secondaryPort}...`);
|
|
310
|
+
await waitForReady(`http://127.0.0.1:${secondaryPort}/api/health`, 'secondary server');
|
|
311
|
+
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
console.error('✗', err.message);
|
|
314
|
+
cleanup();
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Print connection info
|
|
319
|
+
// IMPORTANT: use `localhost` (not `127.0.0.1`) so cookies issued by the primary
|
|
320
|
+
// server are sent to secondary servers on the same host. `localhost:<portA>`
|
|
321
|
+
// and `localhost:<portB>` share a cookie jar for cookies set without an
|
|
322
|
+
// explicit domain; `127.0.0.1` is a separate origin for cookie purposes, so
|
|
323
|
+
// mixing hostnames breaks auto-login on the secondary server.
|
|
324
|
+
console.log('\n✓ Servers ready.\n');
|
|
325
|
+
console.log('─── Playwright MCP — Connection Info ───────────────────');
|
|
326
|
+
console.log(` Primary URL: http://localhost:${primaryPort}`);
|
|
327
|
+
if (secondaryProc) {
|
|
328
|
+
console.log(` Secondary URL: http://localhost:${secondaryPort} (TERMINAL_ENABLED=false)`);
|
|
329
|
+
}
|
|
330
|
+
console.log(' (Use `localhost`, not `127.0.0.1`, so session cookies are shared.)');
|
|
331
|
+
|
|
332
|
+
if (opts.permissionTimeout) {
|
|
333
|
+
console.log('\n─── Permission Timeout — browser_evaluate snippet ───────');
|
|
334
|
+
console.log(` Run this before permission-timeout scenarios:`);
|
|
335
|
+
console.log(` browser_evaluate("() => { window.__HAMMOC_PERMISSION_TIMEOUT_MS__ = ${opts.permissionTimeout}; return true }")`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (opts.withNotifications) {
|
|
339
|
+
console.log('\n─── Web Push Notifications ──────────────────────────────');
|
|
340
|
+
console.log(' Grant permission in Playwright context:');
|
|
341
|
+
console.log(' context.grantPermissions(["notifications"])');
|
|
342
|
+
console.log(' Or use Playwright MCP with browserContext option:');
|
|
343
|
+
console.log(' permissions: ["notifications"]');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (opts.botApiBase) {
|
|
347
|
+
console.log(`\n─── Telegram Mock ───────────────────────────────────────`);
|
|
348
|
+
console.log(` BOT_API_BASE_URL: ${opts.botApiBase}`);
|
|
349
|
+
if (mockTelegramProc) {
|
|
350
|
+
console.log(` Admin endpoints:`);
|
|
351
|
+
console.log(` GET ${opts.botApiBase}/mock-telegram/health`);
|
|
352
|
+
console.log(` GET ${opts.botApiBase}/mock-telegram/messages`);
|
|
353
|
+
console.log(` POST ${opts.botApiBase}/mock-telegram/reset`);
|
|
354
|
+
console.log(` POST ${opts.botApiBase}/mock-telegram/mode body: { mode: "ok"|"401"|"400"|"429" }`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
console.log('\n Press Ctrl+C to stop all servers.\n');
|
|
359
|
+
console.log('═══════════════════════════════════════════════════════\n');
|
|
360
|
+
|
|
361
|
+
// Keep process alive
|
|
362
|
+
await new Promise(() => {});
|