@hs-x/cli 0.3.7 → 0.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 +120 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +38 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/command-catalog.d.ts +2 -2
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +1 -0
- package/dist/command-catalog.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +268 -16
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +177 -20
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/control-plane-read.d.ts +28 -0
- package/dist/commands/control-plane-read.d.ts.map +1 -1
- package/dist/commands/control-plane-read.js +134 -28
- package/dist/commands/control-plane-read.js.map +1 -1
- package/dist/commands/deploy.d.ts +5 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +605 -102
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +19 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +1335 -83
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +27 -10
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/help-command.d.ts.map +1 -1
- package/dist/commands/help-command.js +145 -35
- package/dist/commands/help-command.js.map +1 -1
- package/dist/commands/project.d.ts +107 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +586 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +15 -5
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/validate.d.ts +19 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +26 -7
- package/dist/commands/validate.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/dev/account-selection.d.ts +16 -0
- package/dist/dev/account-selection.d.ts.map +1 -0
- package/dist/dev/account-selection.js +22 -0
- package/dist/dev/account-selection.js.map +1 -0
- package/dist/dev/browser-log-bridge.d.ts +32 -0
- package/dist/dev/browser-log-bridge.d.ts.map +1 -0
- package/dist/dev/browser-log-bridge.js +156 -0
- package/dist/dev/browser-log-bridge.js.map +1 -0
- package/dist/dev/compat-shim.d.ts +3 -3
- package/dist/dev/compat-shim.d.ts.map +1 -1
- package/dist/dev/compat-shim.js +7 -6
- package/dist/dev/compat-shim.js.map +1 -1
- package/dist/dev/component-selection.d.ts +25 -0
- package/dist/dev/component-selection.d.ts.map +1 -0
- package/dist/dev/component-selection.js +79 -0
- package/dist/dev/component-selection.js.map +1 -0
- package/dist/dev/console-capture.d.ts +18 -0
- package/dist/dev/console-capture.d.ts.map +1 -0
- package/dist/dev/console-capture.js +57 -0
- package/dist/dev/console-capture.js.map +1 -0
- package/dist/dev/event-bus.d.ts +1 -1
- package/dist/dev/event-bus.js +1 -1
- package/dist/dev/frontend-log-relay.d.ts +27 -0
- package/dist/dev/frontend-log-relay.d.ts.map +1 -0
- package/dist/dev/frontend-log-relay.js +102 -0
- package/dist/dev/frontend-log-relay.js.map +1 -0
- package/dist/dev/keyboard.d.ts +1 -0
- package/dist/dev/keyboard.d.ts.map +1 -1
- package/dist/dev/keyboard.js +9 -3
- package/dist/dev/keyboard.js.map +1 -1
- package/dist/dev/log-server.js +3 -0
- package/dist/dev/log-server.js.map +1 -1
- package/dist/dev/managed-session.d.ts +124 -0
- package/dist/dev/managed-session.d.ts.map +1 -0
- package/dist/dev/managed-session.js +597 -0
- package/dist/dev/managed-session.js.map +1 -0
- package/dist/dev/port-process.d.ts +21 -0
- package/dist/dev/port-process.d.ts.map +1 -0
- package/dist/dev/port-process.js +145 -0
- package/dist/dev/port-process.js.map +1 -0
- package/dist/dev/session-manager.d.ts +11 -1
- package/dist/dev/session-manager.d.ts.map +1 -1
- package/dist/dev/session-manager.js +62 -12
- package/dist/dev/session-manager.js.map +1 -1
- package/dist/dev/stream-event-format.d.ts +14 -0
- package/dist/dev/stream-event-format.d.ts.map +1 -0
- package/dist/dev/stream-event-format.js +262 -0
- package/dist/dev/stream-event-format.js.map +1 -0
- package/dist/dev/stream-filter.d.ts.map +1 -1
- package/dist/dev/stream-filter.js +16 -1
- package/dist/dev/stream-filter.js.map +1 -1
- package/dist/dev/stream-renderer.d.ts +4 -2
- package/dist/dev/stream-renderer.d.ts.map +1 -1
- package/dist/dev/stream-renderer.js +224 -84
- package/dist/dev/stream-renderer.js.map +1 -1
- package/dist/errors-registry.d.ts.map +1 -1
- package/dist/errors-registry.js +213 -0
- package/dist/errors-registry.js.map +1 -1
- package/dist/hubspot-app-logs.d.ts +38 -5
- package/dist/hubspot-app-logs.d.ts.map +1 -1
- package/dist/hubspot-app-logs.js +88 -17
- package/dist/hubspot-app-logs.js.map +1 -1
- package/dist/hubspot-auth.d.ts +19 -1
- package/dist/hubspot-auth.d.ts.map +1 -1
- package/dist/hubspot-auth.js +228 -83
- package/dist/hubspot-auth.js.map +1 -1
- package/dist/hubspot-developer-client.d.ts +87 -0
- package/dist/hubspot-developer-client.d.ts.map +1 -1
- package/dist/hubspot-developer-client.js +232 -1
- package/dist/hubspot-developer-client.js.map +1 -1
- package/dist/hubspot-project-archive.d.ts +21 -0
- package/dist/hubspot-project-archive.d.ts.map +1 -0
- package/dist/hubspot-project-archive.js +281 -0
- package/dist/hubspot-project-archive.js.map +1 -0
- package/dist/hubspot-project.d.ts +32 -0
- package/dist/hubspot-project.d.ts.map +1 -0
- package/dist/hubspot-project.js +269 -0
- package/dist/hubspot-project.js.map +1 -0
- package/dist/local-project-picker.js +1 -1
- package/dist/local-project-picker.js.map +1 -1
- package/dist/output-context.js +1 -1
- package/dist/output-context.js.map +1 -1
- package/dist/services/hubspot-dev-client.js +3 -0
- package/dist/services/hubspot-dev-client.js.map +1 -1
- package/package.json +14 -12
package/dist/commands/dev.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
1
2
|
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { closeSync, existsSync, openSync } from 'node:fs';
|
|
2
4
|
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
3
5
|
import { createServer } from 'node:http';
|
|
4
|
-
import { join } from 'node:path';
|
|
6
|
+
import { join, resolve as resolvePath } from 'node:path';
|
|
5
7
|
import * as Args from '@effect/cli/Args';
|
|
6
8
|
import * as Command from '@effect/cli/Command';
|
|
7
9
|
import * as Options from '@effect/cli/Options';
|
|
@@ -13,13 +15,20 @@ import { cwdOption, jsonOption, runHandler, runQuarantined } from '../cli/kit.js
|
|
|
13
15
|
import { formatConfigUrl, loadCliConfig } from '../config.js';
|
|
14
16
|
import { CLI_VERSION } from '../constants.js';
|
|
15
17
|
import { controlPlaneAuthHeaders } from '../control-plane-fetch.js';
|
|
18
|
+
import { selectHubSpotDirectDevAccounts } from '../dev/account-selection.js';
|
|
19
|
+
import { listHubSpotDevComponentCandidates, resolveHubSpotDevComponentSelectors, } from '../dev/component-selection.js';
|
|
20
|
+
import { createManagedDevEventWriter, createManagedDevSession, markManagedDevSessionReady, markManagedDevSessionState, prepareManagedDevSessionRestart, publicManagedDevSession, readManagedDevEvents, readManagedDevSession, reconcileManagedDevSession, setManagedDevSessionPid, stopManagedDevSessionProcess, waitForManagedDevSessionOwner, } from '../dev/managed-session.js';
|
|
21
|
+
import { findListeningProcess, isHubSpotDevPortConflict, stopProcessCommand, terminateProcessGracefully, } from '../dev/port-process.js';
|
|
16
22
|
import { executeCliHttp } from '../effect-http.js';
|
|
23
|
+
import { discoverHubSpotAccounts, exchangePak } from '../hubspot-auth.js';
|
|
24
|
+
import { detectLocalProjectMode, validateHubSpotProject } from '../hubspot-project.js';
|
|
17
25
|
import { LocalProjectPickerCancelled, resolveLocalProject } from '../local-project-picker.js';
|
|
18
26
|
import { requestMutationConsent } from '../mutation-consent.js';
|
|
19
27
|
import { isMachineOutput } from '../output-context.js';
|
|
20
28
|
import { isInteractive, promptConfirm, promptMultiSelect, promptSelect } from '../prompt.js';
|
|
21
29
|
import { createReporter } from '../reporter/index.js';
|
|
22
30
|
import { Cwd } from '../services/cwd.js';
|
|
31
|
+
import { Reporter as ReporterService } from '../services/reporter.js';
|
|
23
32
|
async function hostedHttp(input) {
|
|
24
33
|
const headers = { ...(input.headers ?? {}) };
|
|
25
34
|
let request = HttpClientRequest.make(input.method ?? 'GET')(input.url).pipe(HttpClientRequest.setHeaders(headers));
|
|
@@ -35,6 +44,21 @@ function isRecord(value) {
|
|
|
35
44
|
function isErrnoException(value) {
|
|
36
45
|
return value instanceof Error && typeof value.code === 'string';
|
|
37
46
|
}
|
|
47
|
+
function describeDevSessionFailure(cause) {
|
|
48
|
+
const error = isRecord(cause) ? cause : undefined;
|
|
49
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
50
|
+
const status = typeof error?.status === 'number' ? `HTTP ${error.status}` : undefined;
|
|
51
|
+
const data = isRecord(error?.data) ? error.data : undefined;
|
|
52
|
+
const responseMessage = typeof data?.message === 'string' ? data.message : undefined;
|
|
53
|
+
const validationErrors = Array.isArray(error?.validationErrors)
|
|
54
|
+
? error.validationErrors.filter((item) => typeof item === 'string')
|
|
55
|
+
: [];
|
|
56
|
+
const correlationId = typeof error?.correlationId === 'string' ? `correlation ${error.correlationId}` : undefined;
|
|
57
|
+
return [...new Set([message, status, responseMessage, ...validationErrors, correlationId])]
|
|
58
|
+
.filter((item) => Boolean(item))
|
|
59
|
+
.join(' · ');
|
|
60
|
+
}
|
|
61
|
+
const HUBSPOT_UI_DEV_PORT = 5173;
|
|
38
62
|
function makeDevLogPaint(env, stderr) {
|
|
39
63
|
const isTTY = Boolean(stderr.isTTY);
|
|
40
64
|
const color = isTTY && env.NO_COLOR !== '1';
|
|
@@ -179,7 +203,512 @@ async function resolveDefaultPortalId(input) {
|
|
|
179
203
|
}
|
|
180
204
|
return undefined;
|
|
181
205
|
}
|
|
182
|
-
|
|
206
|
+
async function recoverHubSpotDevPort(input) {
|
|
207
|
+
const listener = await findListeningProcess(HUBSPOT_UI_DEV_PORT);
|
|
208
|
+
if (!listener)
|
|
209
|
+
return { kind: 'none' };
|
|
210
|
+
const summary = `Port ${listener.port} is held by PID ${listener.pid}${listener.command ? ` (${listener.command})` : ''}.`;
|
|
211
|
+
const recovery = stopProcessCommand(listener.pid);
|
|
212
|
+
if (input.json) {
|
|
213
|
+
write(`${JSON.stringify({
|
|
214
|
+
schema_version: 1,
|
|
215
|
+
command: 'dev',
|
|
216
|
+
ok: false,
|
|
217
|
+
error: {
|
|
218
|
+
code: 'HSX_E_DEV_PORT_IN_USE',
|
|
219
|
+
message: summary,
|
|
220
|
+
process: listener,
|
|
221
|
+
recovery,
|
|
222
|
+
},
|
|
223
|
+
}, null, 2)}\n`);
|
|
224
|
+
return { kind: 'exit', result: { exitCode: 1 } };
|
|
225
|
+
}
|
|
226
|
+
input.reporter.header('HubSpot dev server already running');
|
|
227
|
+
input.reporter.rows([
|
|
228
|
+
{ key: 'port', value: String(listener.port), status: 'warn' },
|
|
229
|
+
{ key: 'pid', value: String(listener.pid) },
|
|
230
|
+
...(listener.cwd ? [{ key: 'directory', value: listener.cwd }] : []),
|
|
231
|
+
...(listener.command ? [{ key: 'command', value: listener.command }] : []),
|
|
232
|
+
]);
|
|
233
|
+
if (!isInteractive()) {
|
|
234
|
+
input.reporter.error('HSX_E_DEV_PORT_IN_USE', summary, {
|
|
235
|
+
hint: `Stop that exact process with \`${recovery}\`, or rerun in an interactive terminal to confirm and retry automatically.`,
|
|
236
|
+
docs_url: 'https://hs-x.dev/errors/HSX_E_DEV_PORT_IN_USE',
|
|
237
|
+
});
|
|
238
|
+
input.reporter.done(undefined, 1);
|
|
239
|
+
return { kind: 'exit', result: { exitCode: 1 } };
|
|
240
|
+
}
|
|
241
|
+
const confirmed = await promptConfirm({
|
|
242
|
+
message: `Stop PID ${listener.pid} and retry this dev session?`,
|
|
243
|
+
default: false,
|
|
244
|
+
});
|
|
245
|
+
if (confirmed === undefined) {
|
|
246
|
+
return { kind: 'exit', result: cancelledResult(input.argv, 'dev') };
|
|
247
|
+
}
|
|
248
|
+
if (!confirmed) {
|
|
249
|
+
input.reporter.info(`Left PID ${listener.pid} running.`);
|
|
250
|
+
input.reporter.done('Dev session not started', 1);
|
|
251
|
+
return { kind: 'exit', result: { exitCode: 1 } };
|
|
252
|
+
}
|
|
253
|
+
const step = input.reporter.step(`Stopping PID ${listener.pid}`);
|
|
254
|
+
const stopped = await terminateProcessGracefully(listener.pid);
|
|
255
|
+
if (!stopped.ok) {
|
|
256
|
+
step.warn(stopped.message ?? 'could not stop process');
|
|
257
|
+
input.reporter.error('HSX_E_DEV_PORT_IN_USE', `Could not stop PID ${listener.pid}; it was left running.`, { hint: `Stop it manually with \`${recovery}\`, then retry.` });
|
|
258
|
+
input.reporter.done(undefined, 1);
|
|
259
|
+
return { kind: 'exit', result: { exitCode: 1 } };
|
|
260
|
+
}
|
|
261
|
+
step.ok('stopped; retrying');
|
|
262
|
+
return { kind: 'stopped' };
|
|
263
|
+
}
|
|
264
|
+
async function hubSpotDirectDevCommand({ argv, root, json, }) {
|
|
265
|
+
const reporter = createReporter({ command: 'dev', argv, entry: !json });
|
|
266
|
+
const profile = resolveFlag(argv, '--profile');
|
|
267
|
+
const validation = await validateHubSpotProject({ root, ...(profile ? { profile } : {}) });
|
|
268
|
+
if (!validation.ok) {
|
|
269
|
+
const result = {
|
|
270
|
+
ok: false,
|
|
271
|
+
command: 'dev',
|
|
272
|
+
mode: 'hubspot-direct',
|
|
273
|
+
error: 'HubSpot project validation failed.',
|
|
274
|
+
diagnostics: validation.diagnostics,
|
|
275
|
+
};
|
|
276
|
+
if (json)
|
|
277
|
+
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
278
|
+
else {
|
|
279
|
+
reporter.header('HubSpot direct');
|
|
280
|
+
reporter.rows(validation.diagnostics.map((diagnostic) => ({
|
|
281
|
+
key: diagnostic.code,
|
|
282
|
+
value: diagnostic.message,
|
|
283
|
+
status: diagnostic.severity === 'error' ? 'fail' : 'warn',
|
|
284
|
+
...(diagnostic.file ? { detail: diagnostic.file } : {}),
|
|
285
|
+
})));
|
|
286
|
+
reporter.done('Validation failed', 1);
|
|
287
|
+
}
|
|
288
|
+
return { exitCode: 1 };
|
|
289
|
+
}
|
|
290
|
+
const discovered = await discoverHubSpotAccounts(root);
|
|
291
|
+
const accounts = discovered?.accounts ?? [];
|
|
292
|
+
const requestedProjectAccount = resolveFlag(argv, '--project-account') ??
|
|
293
|
+
resolveFlag(argv, '--account') ??
|
|
294
|
+
resolveFlag(argv, '--portal');
|
|
295
|
+
const requestedTestingAccount = resolveFlag(argv, '--testing-account');
|
|
296
|
+
const { projectAccount, testingAccount: selectedTestingAccount } = selectHubSpotDirectDevAccounts({
|
|
297
|
+
accounts,
|
|
298
|
+
...(requestedProjectAccount ? { requestedProjectAccount } : {}),
|
|
299
|
+
...(requestedTestingAccount ? { requestedTestingAccount } : {}),
|
|
300
|
+
});
|
|
301
|
+
if (requestedProjectAccount && !projectAccount) {
|
|
302
|
+
const message = `HubSpot project account "${requestedProjectAccount}" is not configured. Run \`hs account list\` or pass a configured --project-account.`;
|
|
303
|
+
if (json) {
|
|
304
|
+
write(`${JSON.stringify({
|
|
305
|
+
schema_version: 1,
|
|
306
|
+
command: 'dev',
|
|
307
|
+
ok: false,
|
|
308
|
+
error: { code: 'HSX_E_DEV_SESSION_NO_HUBSPOT_ACCOUNT', message },
|
|
309
|
+
}, null, 2)}\n`);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
reporter.error('HSX_E_DEV_SESSION_NO_HUBSPOT_ACCOUNT', message);
|
|
313
|
+
reporter.done(undefined, 1);
|
|
314
|
+
}
|
|
315
|
+
return { exitCode: 1 };
|
|
316
|
+
}
|
|
317
|
+
if (!projectAccount) {
|
|
318
|
+
const message = 'HubSpot-direct dev needs an authenticated HubSpot project account. Run `hs account auth`, select a default with `hs account use`, or pass --project-account.';
|
|
319
|
+
if (json) {
|
|
320
|
+
write(`${JSON.stringify({
|
|
321
|
+
schema_version: 1,
|
|
322
|
+
command: 'dev',
|
|
323
|
+
ok: false,
|
|
324
|
+
error: { code: 'HSX_E_DEV_SESSION_NO_HUBSPOT_ACCOUNT', message },
|
|
325
|
+
}, null, 2)}\n`);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
reporter.error('HSX_E_DEV_SESSION_NO_HUBSPOT_ACCOUNT', message);
|
|
329
|
+
reporter.done(undefined, 1);
|
|
330
|
+
}
|
|
331
|
+
return { exitCode: 1 };
|
|
332
|
+
}
|
|
333
|
+
if (requestedTestingAccount && !selectedTestingAccount) {
|
|
334
|
+
const message = `HubSpot testing account "${requestedTestingAccount}" is not configured. Run \`hs account list\` or pass a configured --testing-account.`;
|
|
335
|
+
if (json) {
|
|
336
|
+
write(`${JSON.stringify({
|
|
337
|
+
schema_version: 1,
|
|
338
|
+
command: 'dev',
|
|
339
|
+
ok: false,
|
|
340
|
+
error: { code: 'HSX_E_DEV_SESSION_NO_HUBSPOT_ACCOUNT', message },
|
|
341
|
+
}, null, 2)}\n`);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
reporter.error('HSX_E_DEV_SESSION_NO_HUBSPOT_ACCOUNT', message);
|
|
345
|
+
reporter.done(undefined, 1);
|
|
346
|
+
}
|
|
347
|
+
return { exitCode: 1 };
|
|
348
|
+
}
|
|
349
|
+
const testingAccount = selectedTestingAccount ?? projectAccount;
|
|
350
|
+
if (testingAccount.personalAccessKey) {
|
|
351
|
+
try {
|
|
352
|
+
const exchanged = await exchangePak(testingAccount.personalAccessKey);
|
|
353
|
+
if (exchanged.portalId !== testingAccount.portalId) {
|
|
354
|
+
throw new Error(`the configured PAK belongs to account ${exchanged.portalId}, not ${testingAccount.portalId}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch (cause) {
|
|
358
|
+
const message = `HubSpot testing account ${testingAccount.name} (${testingAccount.portalId}) needs fresh authentication: ${cause instanceof Error ? cause.message : String(cause)} Run \`hs account auth\` for that account, then retry.`;
|
|
359
|
+
if (json) {
|
|
360
|
+
write(`${JSON.stringify({
|
|
361
|
+
schema_version: 1,
|
|
362
|
+
command: 'dev',
|
|
363
|
+
ok: false,
|
|
364
|
+
error: { code: 'HSX_E_DEV_SESSION_NO_HUBSPOT_ACCOUNT', message },
|
|
365
|
+
}, null, 2)}\n`);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
reporter.error('HSX_E_DEV_SESSION_NO_HUBSPOT_ACCOUNT', message);
|
|
369
|
+
reporter.done(undefined, 1);
|
|
370
|
+
}
|
|
371
|
+
return { exitCode: 1 };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Check the fixed HubSpot UI-extension port before translating the project;
|
|
375
|
+
// otherwise a conflict is discovered only after the comparatively expensive
|
|
376
|
+
// IR probe/build path has already run.
|
|
377
|
+
const existingDevServer = await recoverHubSpotDevPort({ argv, json, reporter });
|
|
378
|
+
if (existingDevServer.kind === 'exit')
|
|
379
|
+
return existingDevServer.result;
|
|
380
|
+
const uiExtensionNodes = await probeHubSpotUiNodes({
|
|
381
|
+
root,
|
|
382
|
+
portalIdFlag: String(projectAccount.portalId),
|
|
383
|
+
accountId: undefined,
|
|
384
|
+
...(profile ? { profile } : {}),
|
|
385
|
+
});
|
|
386
|
+
const requestedComponents = resolveFlags(argv, '--component');
|
|
387
|
+
const resolvedComponents = resolveHubSpotDevComponentSelectors(uiExtensionNodes?.candidates ?? [], requestedComponents);
|
|
388
|
+
if (!resolvedComponents.ok) {
|
|
389
|
+
if (json) {
|
|
390
|
+
write(`${JSON.stringify({
|
|
391
|
+
schema_version: 1,
|
|
392
|
+
command: 'dev',
|
|
393
|
+
ok: false,
|
|
394
|
+
error: { code: 'HSX_E_DEV_SESSION_NO_NODES', message: resolvedComponents.message },
|
|
395
|
+
}, null, 2)}\n`);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
reporter.error('HSX_E_DEV_SESSION_NO_NODES', resolvedComponents.message);
|
|
399
|
+
reporter.done(undefined, 1);
|
|
400
|
+
}
|
|
401
|
+
return { exitCode: 1 };
|
|
402
|
+
}
|
|
403
|
+
let selectedNodeUids = uiExtensionNodes && uiExtensionNodes.candidates.length > 0
|
|
404
|
+
? resolvedComponents.uids
|
|
405
|
+
: undefined;
|
|
406
|
+
if (requestedComponents.length === 0 &&
|
|
407
|
+
uiExtensionNodes &&
|
|
408
|
+
uiExtensionNodes.candidates.length > 1 &&
|
|
409
|
+
!json &&
|
|
410
|
+
isInteractive()) {
|
|
411
|
+
const picked = await promptMultiSelect({
|
|
412
|
+
message: `Which HubSpot components should run in dev mode? (testing account ${testingAccount.portalId})`,
|
|
413
|
+
initial: [],
|
|
414
|
+
minSelected: 1,
|
|
415
|
+
options: uiExtensionNodes.candidates.map((candidate) => ({
|
|
416
|
+
value: candidate.uid,
|
|
417
|
+
label: candidate.label,
|
|
418
|
+
description: candidate.description,
|
|
419
|
+
})),
|
|
420
|
+
});
|
|
421
|
+
if (picked === undefined)
|
|
422
|
+
return cancelledResult(argv, 'dev');
|
|
423
|
+
selectedNodeUids = picked;
|
|
424
|
+
}
|
|
425
|
+
let directDevLogStream;
|
|
426
|
+
if (!json) {
|
|
427
|
+
const hsxLogPortValue = resolveFlag(argv, '--hsx-log-port') ?? '9099';
|
|
428
|
+
const hsxLogPort = Number(hsxLogPortValue);
|
|
429
|
+
if (!Number.isInteger(hsxLogPort) || hsxLogPort < 1 || hsxLogPort > 65535) {
|
|
430
|
+
const code = 'HSX_E_INPUT_BAD_FIELD';
|
|
431
|
+
reporter.error(code, `--hsx-log-port must be an integer between 1 and 65535 (got \`${hsxLogPortValue}\`).`, {
|
|
432
|
+
hint: 'Example: hs-x dev --hsx-log-port 9099',
|
|
433
|
+
docs_url: `https://hs-x.dev/errors/${code}`,
|
|
434
|
+
});
|
|
435
|
+
reporter.done(undefined, 10);
|
|
436
|
+
return { exitCode: 10 };
|
|
437
|
+
}
|
|
438
|
+
const { parseOnlySelectors } = await import('../dev/stream-filter.js');
|
|
439
|
+
const onlyParsed = parseOnlySelectors(resolveFlags(argv, '--only'));
|
|
440
|
+
if (!onlyParsed.ok) {
|
|
441
|
+
const code = 'HSX_E_DEV_BAD_ONLY';
|
|
442
|
+
reporter.error(code, onlyParsed.message, {
|
|
443
|
+
hint: 'Selectors: frontend, hubspot, errors, warnings, or free text. Repeat --only to combine.',
|
|
444
|
+
docs_url: `https://hs-x.dev/errors/${code}`,
|
|
445
|
+
});
|
|
446
|
+
reporter.done(undefined, 2);
|
|
447
|
+
return { exitCode: 2 };
|
|
448
|
+
}
|
|
449
|
+
const managedSessionId = resolveFlag(argv, '--managed-session');
|
|
450
|
+
directDevLogStream = await startHsxDevLogStream({
|
|
451
|
+
port: hsxLogPort,
|
|
452
|
+
root,
|
|
453
|
+
reporter,
|
|
454
|
+
filter: onlyParsed.filter,
|
|
455
|
+
...(managedSessionId ? { managedSessionId } : {}),
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
const { startDevSession } = await import('../dev/session-manager.js');
|
|
459
|
+
const startDirectSession = () => startDevSession({
|
|
460
|
+
projectAccountId: projectAccount.portalId,
|
|
461
|
+
testingAccountId: testingAccount.portalId,
|
|
462
|
+
projectDir: root,
|
|
463
|
+
...(selectedNodeUids ? { selectedNodeUids } : {}),
|
|
464
|
+
...(profile ? { profile } : {}),
|
|
465
|
+
...(directDevLogStream
|
|
466
|
+
? {
|
|
467
|
+
onFrontendLog: (entry) => {
|
|
468
|
+
directDevLogStream?.bus.emit({
|
|
469
|
+
lane: 'frontend',
|
|
470
|
+
level: entry.level,
|
|
471
|
+
ts: entry.ts,
|
|
472
|
+
msg: entry.msg,
|
|
473
|
+
...(entry.source ? { source: entry.source } : {}),
|
|
474
|
+
...(entry.fields ? { fields: entry.fields } : {}),
|
|
475
|
+
});
|
|
476
|
+
},
|
|
477
|
+
onBackendLog: (entry) => {
|
|
478
|
+
directDevLogStream?.bus.emit({
|
|
479
|
+
lane: 'backend',
|
|
480
|
+
level: entry.level,
|
|
481
|
+
ts: Date.now(),
|
|
482
|
+
msg: entry.message,
|
|
483
|
+
source: `console.${entry.method}`,
|
|
484
|
+
});
|
|
485
|
+
},
|
|
486
|
+
}
|
|
487
|
+
: {}),
|
|
488
|
+
});
|
|
489
|
+
let session;
|
|
490
|
+
let sessionFailure;
|
|
491
|
+
try {
|
|
492
|
+
session = await startDirectSession();
|
|
493
|
+
}
|
|
494
|
+
catch (initialCause) {
|
|
495
|
+
sessionFailure = initialCause;
|
|
496
|
+
if (isHubSpotDevPortConflict(initialCause)) {
|
|
497
|
+
const recovery = await recoverHubSpotDevPort({ argv, json, reporter });
|
|
498
|
+
if (recovery.kind === 'exit') {
|
|
499
|
+
try {
|
|
500
|
+
await directDevLogStream?.cleanup();
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
// The port-conflict response is already complete.
|
|
504
|
+
}
|
|
505
|
+
return recovery.result;
|
|
506
|
+
}
|
|
507
|
+
if (recovery.kind === 'stopped') {
|
|
508
|
+
try {
|
|
509
|
+
session = await startDirectSession();
|
|
510
|
+
sessionFailure = undefined;
|
|
511
|
+
}
|
|
512
|
+
catch (retryCause) {
|
|
513
|
+
sessionFailure = retryCause;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (!session) {
|
|
519
|
+
if (directDevLogStream) {
|
|
520
|
+
try {
|
|
521
|
+
await directDevLogStream.cleanup();
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
// The dev-session error below is the actionable failure.
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const message = describeDevSessionFailure(sessionFailure);
|
|
528
|
+
if (json) {
|
|
529
|
+
write(`${JSON.stringify({
|
|
530
|
+
schema_version: 1,
|
|
531
|
+
command: 'dev',
|
|
532
|
+
ok: false,
|
|
533
|
+
error: { code: 'HSX_E_DEV_SESSION_REGISTER', message },
|
|
534
|
+
}, null, 2)}\n`);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
reporter.error('HSX_E_DEV_SESSION_REGISTER', message);
|
|
538
|
+
reporter.done(undefined, 1);
|
|
539
|
+
}
|
|
540
|
+
return { exitCode: 1 };
|
|
541
|
+
}
|
|
542
|
+
const managedSessionId = resolveFlag(argv, '--managed-session');
|
|
543
|
+
if (managedSessionId) {
|
|
544
|
+
await markManagedDevSessionReady(managedSessionId, {
|
|
545
|
+
projectAccountId: projectAccount.portalId,
|
|
546
|
+
testingAccountId: testingAccount.portalId,
|
|
547
|
+
components: session.nodeUids,
|
|
548
|
+
...(directDevLogStream
|
|
549
|
+
? { logPort: Number(resolveFlag(argv, '--hsx-log-port') ?? '9099') }
|
|
550
|
+
: {}),
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
const started = {
|
|
554
|
+
ok: true,
|
|
555
|
+
command: 'dev',
|
|
556
|
+
mode: 'hubspot-direct',
|
|
557
|
+
portalId: testingAccount.portalId,
|
|
558
|
+
projectAccountId: projectAccount.portalId,
|
|
559
|
+
testingAccountId: testingAccount.portalId,
|
|
560
|
+
sessionId: session.sessionId,
|
|
561
|
+
components: session.nodeUids,
|
|
562
|
+
controlPlaneUsed: false,
|
|
563
|
+
cloudflareUsed: false,
|
|
564
|
+
};
|
|
565
|
+
if (json)
|
|
566
|
+
write(`${JSON.stringify(started, null, 2)}\n`);
|
|
567
|
+
else {
|
|
568
|
+
reporter.header(`${projectAccount.name} → ${testingAccount.name}`);
|
|
569
|
+
reporter.rows([
|
|
570
|
+
{ key: 'mode', value: 'HubSpot direct' },
|
|
571
|
+
{ key: 'project-account', value: `${projectAccount.name} (${projectAccount.portalId})` },
|
|
572
|
+
{ key: 'testing-account', value: `${testingAccount.name} (${testingAccount.portalId})` },
|
|
573
|
+
{ key: 'ui-extensions', value: `session ${session.sessionId}`, status: 'ok' },
|
|
574
|
+
{ key: 'components', value: String(session.nodeUids.length) },
|
|
575
|
+
...(directDevLogStream
|
|
576
|
+
? [
|
|
577
|
+
{
|
|
578
|
+
key: 'logs',
|
|
579
|
+
value: 'frontend + backend + HubSpot events',
|
|
580
|
+
detail: directDevLogStream.url,
|
|
581
|
+
status: 'ok',
|
|
582
|
+
},
|
|
583
|
+
]
|
|
584
|
+
: []),
|
|
585
|
+
{ key: 'Cloudflare', value: 'not used' },
|
|
586
|
+
{ key: 'HS-X control plane', value: 'not used' },
|
|
587
|
+
]);
|
|
588
|
+
if (!directDevLogStream?.renderer.hasStatusLine())
|
|
589
|
+
reporter.info('Press Ctrl+C to stop');
|
|
590
|
+
}
|
|
591
|
+
const directKeyboard = directDevLogStream
|
|
592
|
+
? (await import('../dev/keyboard.js')).attachDevKeyboard({
|
|
593
|
+
renderer: directDevLogStream.renderer,
|
|
594
|
+
onQuit: () => process.kill(process.pid, 'SIGINT'),
|
|
595
|
+
})
|
|
596
|
+
: undefined;
|
|
597
|
+
directDevLogStream?.renderer.ready();
|
|
598
|
+
await new Promise((resolveClose) => {
|
|
599
|
+
let closing = false;
|
|
600
|
+
const close = async () => {
|
|
601
|
+
if (closing)
|
|
602
|
+
return;
|
|
603
|
+
closing = true;
|
|
604
|
+
directKeyboard?.stop();
|
|
605
|
+
if (managedSessionId) {
|
|
606
|
+
try {
|
|
607
|
+
await markManagedDevSessionState(managedSessionId, 'stopping', undefined, {
|
|
608
|
+
expectedPid: process.pid,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
// Continue cleanup even if the managed state record is unavailable.
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
await session.cleanup();
|
|
617
|
+
}
|
|
618
|
+
finally {
|
|
619
|
+
try {
|
|
620
|
+
await directDevLogStream?.cleanup();
|
|
621
|
+
}
|
|
622
|
+
finally {
|
|
623
|
+
if (managedSessionId) {
|
|
624
|
+
try {
|
|
625
|
+
await markManagedDevSessionState(managedSessionId, 'stopped', undefined, {
|
|
626
|
+
expectedPid: process.pid,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
catch {
|
|
630
|
+
// The process exit will be reconciled by the next status request.
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
resolveClose();
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
process.once('SIGINT', close);
|
|
638
|
+
process.once('SIGTERM', close);
|
|
639
|
+
});
|
|
640
|
+
return { exitCode: 0 };
|
|
641
|
+
}
|
|
642
|
+
const MANAGED_DEV_SUBCOMMANDS = new Set(['start', 'logs', 'stop', 'restart']);
|
|
643
|
+
const KNOWN_DEV_SUBCOMMANDS = new Set([...MANAGED_DEV_SUBCOMMANDS, 'invoke', 'cleanup', 'status']);
|
|
644
|
+
const MANAGED_DEV_LANES = new Set(['frontend', 'backend', 'request', 'hubspot']);
|
|
645
|
+
const MANAGED_DEV_LEVELS = new Set(['debug', 'info', 'warn', 'error']);
|
|
646
|
+
export async function devCommand({ argv, root, json, reporter, }) {
|
|
647
|
+
const managedSessionId = resolveFlag(argv, '--managed-session');
|
|
648
|
+
if (managedSessionId) {
|
|
649
|
+
await waitForManagedDevSessionOwner(managedSessionId);
|
|
650
|
+
try {
|
|
651
|
+
const result = await devCommandCore({ argv, root, json: false });
|
|
652
|
+
if (result.exitCode !== 0) {
|
|
653
|
+
const current = await readManagedDevSession(managedSessionId);
|
|
654
|
+
if (current.state === 'starting') {
|
|
655
|
+
await markManagedDevSessionState(managedSessionId, 'failed', `hs-x dev exited with code ${result.exitCode} during startup.`, { expectedGeneration: current.generation, expectedPid: process.pid });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return result;
|
|
659
|
+
}
|
|
660
|
+
catch (cause) {
|
|
661
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
662
|
+
try {
|
|
663
|
+
const current = await readManagedDevSession(managedSessionId);
|
|
664
|
+
if (current.state !== 'stopped' && current.state !== 'failed') {
|
|
665
|
+
await markManagedDevSessionState(managedSessionId, 'failed', message, {
|
|
666
|
+
expectedPid: process.pid,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
// Preserve the original startup failure if the state file is unavailable.
|
|
672
|
+
}
|
|
673
|
+
throw cause;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const subcommand = argv[1];
|
|
677
|
+
if (subcommand && !subcommand.startsWith('-') && !KNOWN_DEV_SUBCOMMANDS.has(subcommand)) {
|
|
678
|
+
const outputReporter = reporter ?? createReporter({ command: 'dev', argv });
|
|
679
|
+
outputReporter.error('HSX_E_INPUT_INVALID', `Unknown dev subcommand "${subcommand}".`, {
|
|
680
|
+
hint: 'Expected start, logs, stop, restart, invoke, cleanup, or status. Run `hs-x dev --help`.',
|
|
681
|
+
});
|
|
682
|
+
outputReporter.done(undefined, 2);
|
|
683
|
+
return { exitCode: 2 };
|
|
684
|
+
}
|
|
685
|
+
if (subcommand &&
|
|
686
|
+
KNOWN_DEV_SUBCOMMANDS.has(subcommand) &&
|
|
687
|
+
subcommand !== 'invoke' &&
|
|
688
|
+
argv[2] !== undefined &&
|
|
689
|
+
!argv[2].startsWith('-')) {
|
|
690
|
+
const outputReporter = reporter ?? createReporter({ command: `dev ${subcommand}`, argv });
|
|
691
|
+
outputReporter.error('HSX_E_INPUT_INVALID', `Unexpected argument "${argv[2]}" after \`hs-x dev ${subcommand}\`.`);
|
|
692
|
+
outputReporter.done(undefined, 2);
|
|
693
|
+
return { exitCode: 2 };
|
|
694
|
+
}
|
|
695
|
+
const reporterInput = reporter ? { reporter } : {};
|
|
696
|
+
if (subcommand === 'logs')
|
|
697
|
+
return managedDevLogsCommand({ argv, json, ...reporterInput });
|
|
698
|
+
if (subcommand === 'stop')
|
|
699
|
+
return managedDevStopCommand({ argv, json, ...reporterInput });
|
|
700
|
+
if (subcommand === 'restart') {
|
|
701
|
+
return managedDevRestartCommand({ argv, json, ...reporterInput });
|
|
702
|
+
}
|
|
703
|
+
if (subcommand === 'status' && resolveFlag(argv, '--session')) {
|
|
704
|
+
return managedDevStatusCommand({ argv, json, ...reporterInput });
|
|
705
|
+
}
|
|
706
|
+
if (subcommand === 'start' || argv.includes('--detach')) {
|
|
707
|
+
return managedDevStartCommand({ argv, root, json, ...reporterInput });
|
|
708
|
+
}
|
|
709
|
+
return devCommandCore({ argv, root, json });
|
|
710
|
+
}
|
|
711
|
+
async function devCommandCore({ argv, root, json, }) {
|
|
183
712
|
if (argv[1] === 'cleanup') {
|
|
184
713
|
return await devCleanupCommand({ argv, json });
|
|
185
714
|
}
|
|
@@ -195,7 +724,7 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
195
724
|
prompts: { select: promptSelect },
|
|
196
725
|
});
|
|
197
726
|
if (selected && selected.root !== root) {
|
|
198
|
-
return await
|
|
727
|
+
return await devCommandCore({ argv, root: selected.root, json });
|
|
199
728
|
}
|
|
200
729
|
}
|
|
201
730
|
catch (error) {
|
|
@@ -207,6 +736,9 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
207
736
|
if (argv[1] === 'invoke') {
|
|
208
737
|
return await devInvokeCommand({ argv, root, json });
|
|
209
738
|
}
|
|
739
|
+
if ((await detectLocalProjectMode(root)) === 'hubspot-direct') {
|
|
740
|
+
return await hubSpotDirectDevCommand({ argv, root, json });
|
|
741
|
+
}
|
|
210
742
|
const portFlag = resolveFlag(argv, '--port');
|
|
211
743
|
const port = Number(portFlag ?? '8787');
|
|
212
744
|
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
@@ -665,11 +1197,13 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
665
1197
|
reporter.done(undefined, 2);
|
|
666
1198
|
return { exitCode: 2 };
|
|
667
1199
|
}
|
|
1200
|
+
const managedLogSessionId = resolveFlag(argv, '--managed-session');
|
|
668
1201
|
const devLogStream = await startHsxDevLogStream({
|
|
669
1202
|
port: hsxLogPort,
|
|
670
1203
|
root,
|
|
671
1204
|
reporter,
|
|
672
1205
|
filter: onlyParsed.filter,
|
|
1206
|
+
...(managedLogSessionId ? { managedSessionId: managedLogSessionId } : {}),
|
|
673
1207
|
});
|
|
674
1208
|
if (devLogStream) {
|
|
675
1209
|
const paint = makeDevLogPaint(process.env, process.stderr);
|
|
@@ -729,6 +1263,14 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
729
1263
|
}),
|
|
730
1264
|
});
|
|
731
1265
|
}
|
|
1266
|
+
const managedSessionId = resolveFlag(argv, '--managed-session');
|
|
1267
|
+
if (managedSessionId) {
|
|
1268
|
+
await markManagedDevSessionReady(managedSessionId, {
|
|
1269
|
+
components: selectedUiNodeUids ?? capabilityIds,
|
|
1270
|
+
devPort: port,
|
|
1271
|
+
...(devLogStream ? { logPort: hsxLogPort } : {}),
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
732
1274
|
// Railed startup checklist: identity header, then one row per surface.
|
|
733
1275
|
reporter.header(subject);
|
|
734
1276
|
reporter.rows([
|
|
@@ -796,8 +1338,22 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
796
1338
|
: undefined;
|
|
797
1339
|
devLogStream?.renderer.ready();
|
|
798
1340
|
await new Promise((resolveClose) => {
|
|
1341
|
+
let closing = false;
|
|
799
1342
|
const close = async () => {
|
|
1343
|
+
if (closing)
|
|
1344
|
+
return;
|
|
1345
|
+
closing = true;
|
|
800
1346
|
keyboard?.stop();
|
|
1347
|
+
if (managedSessionId) {
|
|
1348
|
+
try {
|
|
1349
|
+
await markManagedDevSessionState(managedSessionId, 'stopping', undefined, {
|
|
1350
|
+
expectedPid: process.pid,
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
catch {
|
|
1354
|
+
// Continue cleanup even if the managed state record is unavailable.
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
801
1357
|
// Signal-driven shutdown can terminate the Bun process before awaited
|
|
802
1358
|
// cleanup settles. Restore temporarily stamped migration sources
|
|
803
1359
|
// synchronously first; the remaining network/process cleanup is
|
|
@@ -850,13 +1406,615 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
850
1406
|
// best-effort: sidecar shutdown failures should not block exit.
|
|
851
1407
|
}
|
|
852
1408
|
}
|
|
853
|
-
server.close(() =>
|
|
1409
|
+
server.close(async () => {
|
|
1410
|
+
if (managedSessionId) {
|
|
1411
|
+
try {
|
|
1412
|
+
await markManagedDevSessionState(managedSessionId, 'stopped', undefined, {
|
|
1413
|
+
expectedPid: process.pid,
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
catch {
|
|
1417
|
+
// The process exit will be reconciled by the next status request.
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
resolveClose();
|
|
1421
|
+
});
|
|
854
1422
|
};
|
|
855
1423
|
process.once('SIGINT', close);
|
|
856
1424
|
process.once('SIGTERM', close);
|
|
857
1425
|
});
|
|
858
1426
|
return { exitCode: 0 };
|
|
859
1427
|
}
|
|
1428
|
+
async function managedDevStartCommand(input) {
|
|
1429
|
+
const reporter = input.reporter ??
|
|
1430
|
+
createReporter({ command: 'dev start', argv: input.argv, entry: !input.json });
|
|
1431
|
+
let waitMs;
|
|
1432
|
+
try {
|
|
1433
|
+
waitMs = parsePositiveSeconds(input.argv, '--wait-seconds', 60) * 1_000;
|
|
1434
|
+
}
|
|
1435
|
+
catch (cause) {
|
|
1436
|
+
return managedDevCommandFailure(reporter, cause, 'HSX_E_INPUT_BAD_FIELD');
|
|
1437
|
+
}
|
|
1438
|
+
const root = resolvePath(input.root);
|
|
1439
|
+
const consent = await requestMutationConsent({
|
|
1440
|
+
yes: input.argv.some((argument) => argument === '--yes' || argument === '-y'),
|
|
1441
|
+
machineOutput: input.json || isMachineOutput(),
|
|
1442
|
+
interactive: isInteractive(),
|
|
1443
|
+
message: `Start an agent-managed background dev session in ${root}?`,
|
|
1444
|
+
confirm: promptConfirm,
|
|
1445
|
+
});
|
|
1446
|
+
if (consent !== 'proceed') {
|
|
1447
|
+
const message = consent === 'require-yes'
|
|
1448
|
+
? 'Starting a managed dev session in non-interactive or machine-output mode requires --yes.'
|
|
1449
|
+
: 'Start cancelled.';
|
|
1450
|
+
reporter.data({ target: { root } });
|
|
1451
|
+
reporter.error('HSX_E_INPUT_INVALID', message, {
|
|
1452
|
+
hint: 'Review the target root, then rerun `hs-x dev start --yes` with the same dev flags.',
|
|
1453
|
+
});
|
|
1454
|
+
reporter.done(undefined, consent === 'require-yes' ? 2 : 130);
|
|
1455
|
+
return { exitCode: consent === 'require-yes' ? 2 : 130 };
|
|
1456
|
+
}
|
|
1457
|
+
const launchArgv = managedDevLaunchArgv(input.argv);
|
|
1458
|
+
const session = await createManagedDevSession({
|
|
1459
|
+
root,
|
|
1460
|
+
launchArgv,
|
|
1461
|
+
});
|
|
1462
|
+
try {
|
|
1463
|
+
await launchManagedDevChild(session);
|
|
1464
|
+
const result = await waitForManagedDevStartup(session.sessionId, waitMs);
|
|
1465
|
+
const safe = await publicManagedDevSession(result);
|
|
1466
|
+
if (safe.state === 'failed' || safe.state === 'stopped') {
|
|
1467
|
+
reporter.data({ session: safe });
|
|
1468
|
+
reporter.error('HSX_E_DEV_MANAGED_START', safe.error ?? `Managed dev session ${safe.sessionId} stopped before it became ready.`, { hint: `Inspect ${safe.stderrPath} for the complete startup output.` });
|
|
1469
|
+
reporter.done(undefined, 1);
|
|
1470
|
+
return { exitCode: 1 };
|
|
1471
|
+
}
|
|
1472
|
+
renderManagedDevSession(reporter, safe, 'started');
|
|
1473
|
+
reporter.data({
|
|
1474
|
+
session: safe,
|
|
1475
|
+
commands: managedDevSessionCommands(safe.sessionId),
|
|
1476
|
+
});
|
|
1477
|
+
if (safe.state !== 'ready') {
|
|
1478
|
+
reporter.warn('HSX_W_DEV_MANAGED_STARTING', `Managed dev session ${safe.sessionId} is still starting. Check it with \`hs-x dev status --session ${safe.sessionId}\`.`);
|
|
1479
|
+
}
|
|
1480
|
+
else if (!safe.logPort) {
|
|
1481
|
+
reporter.warn('HSX_W_DEV_MANAGED_LOG_STREAM', 'The dev server is ready, but its unified log sidecar is unavailable. Inspect stderrPath and restart with a free --hsx-log-port.');
|
|
1482
|
+
}
|
|
1483
|
+
reporter.done(safe.state === 'ready' ? 'Dev session ready' : 'Dev session still starting');
|
|
1484
|
+
return { exitCode: 0 };
|
|
1485
|
+
}
|
|
1486
|
+
catch (cause) {
|
|
1487
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
1488
|
+
try {
|
|
1489
|
+
await markManagedDevSessionState(session.sessionId, 'failed', message, {
|
|
1490
|
+
expectedGeneration: session.generation,
|
|
1491
|
+
expectedStates: ['starting'],
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
catch {
|
|
1495
|
+
// Keep the launch error as the primary failure.
|
|
1496
|
+
}
|
|
1497
|
+
reporter.error('HSX_E_DEV_MANAGED_START', message, {
|
|
1498
|
+
hint: `Inspect ${session.stderrPath} for startup output.`,
|
|
1499
|
+
});
|
|
1500
|
+
reporter.done(undefined, 1);
|
|
1501
|
+
return { exitCode: 1 };
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
async function managedDevStatusCommand(input) {
|
|
1505
|
+
const reporter = input.reporter ?? createReporter({ command: 'dev status', argv: input.argv });
|
|
1506
|
+
const sessionId = requireManagedSessionId(input.argv, reporter);
|
|
1507
|
+
if (!sessionId)
|
|
1508
|
+
return { exitCode: 2 };
|
|
1509
|
+
try {
|
|
1510
|
+
const safe = await publicManagedDevSession(await reconcileManagedDevSession(sessionId));
|
|
1511
|
+
renderManagedDevSession(reporter, safe, 'status');
|
|
1512
|
+
reporter.data({ session: safe, commands: managedDevSessionCommands(sessionId) });
|
|
1513
|
+
reporter.done();
|
|
1514
|
+
return { exitCode: 0 };
|
|
1515
|
+
}
|
|
1516
|
+
catch (cause) {
|
|
1517
|
+
return managedDevCommandFailure(reporter, cause, 'HSX_E_DEV_MANAGED_SESSION');
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
async function managedDevStopCommand(input) {
|
|
1521
|
+
const reporter = input.reporter ?? createReporter({ command: 'dev stop', argv: input.argv });
|
|
1522
|
+
const sessionId = requireManagedSessionId(input.argv, reporter);
|
|
1523
|
+
if (!sessionId)
|
|
1524
|
+
return { exitCode: 2 };
|
|
1525
|
+
try {
|
|
1526
|
+
const current = await reconcileManagedDevSession(sessionId);
|
|
1527
|
+
const safeCurrent = await publicManagedDevSession(current);
|
|
1528
|
+
renderManagedDevSession(reporter, safeCurrent, 'stop target');
|
|
1529
|
+
if (current.state === 'stopped' || current.state === 'failed') {
|
|
1530
|
+
reporter.data({ session: safeCurrent });
|
|
1531
|
+
reporter.done('Dev session is already stopped');
|
|
1532
|
+
return { exitCode: 0 };
|
|
1533
|
+
}
|
|
1534
|
+
const consent = await requestMutationConsent({
|
|
1535
|
+
yes: input.argv.includes('--yes'),
|
|
1536
|
+
machineOutput: input.json || isMachineOutput(),
|
|
1537
|
+
interactive: isInteractive(),
|
|
1538
|
+
message: `Stop managed dev session ${sessionId} (PID ${current.pid})?`,
|
|
1539
|
+
confirm: promptConfirm,
|
|
1540
|
+
});
|
|
1541
|
+
if (consent !== 'proceed') {
|
|
1542
|
+
const message = consent === 'require-yes'
|
|
1543
|
+
? 'Stopping a managed dev session in non-interactive or machine-output mode requires --yes.'
|
|
1544
|
+
: 'Stop cancelled.';
|
|
1545
|
+
reporter.data({ session: safeCurrent });
|
|
1546
|
+
reporter.error('HSX_E_INPUT_INVALID', message, {
|
|
1547
|
+
hint: `Review the session above, then run \`hs-x dev stop --session ${sessionId} --yes\`.`,
|
|
1548
|
+
});
|
|
1549
|
+
reporter.done(undefined, consent === 'require-yes' ? 2 : 130);
|
|
1550
|
+
return { exitCode: consent === 'require-yes' ? 2 : 130 };
|
|
1551
|
+
}
|
|
1552
|
+
const stopped = await stopManagedDevSessionProcess(sessionId, {
|
|
1553
|
+
force: input.argv.includes('--force'),
|
|
1554
|
+
});
|
|
1555
|
+
const safe = await publicManagedDevSession(stopped);
|
|
1556
|
+
reporter.data({ session: safe });
|
|
1557
|
+
reporter.step(`Stopped managed dev session ${sessionId}`).ok(`PID ${current.pid}`);
|
|
1558
|
+
reporter.done('Dev session stopped');
|
|
1559
|
+
return { exitCode: 0 };
|
|
1560
|
+
}
|
|
1561
|
+
catch (cause) {
|
|
1562
|
+
return managedDevCommandFailure(reporter, cause, 'HSX_E_DEV_MANAGED_STOP');
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
async function managedDevRestartCommand(input) {
|
|
1566
|
+
const reporter = input.reporter ?? createReporter({ command: 'dev restart', argv: input.argv });
|
|
1567
|
+
const sessionId = requireManagedSessionId(input.argv, reporter);
|
|
1568
|
+
if (!sessionId)
|
|
1569
|
+
return { exitCode: 2 };
|
|
1570
|
+
try {
|
|
1571
|
+
const waitMs = parsePositiveSeconds(input.argv, '--wait-seconds', 60) * 1_000;
|
|
1572
|
+
const current = await reconcileManagedDevSession(sessionId);
|
|
1573
|
+
const safeCurrent = await publicManagedDevSession(current);
|
|
1574
|
+
renderManagedDevSession(reporter, safeCurrent, 'restart target');
|
|
1575
|
+
const consent = await requestMutationConsent({
|
|
1576
|
+
yes: input.argv.includes('--yes'),
|
|
1577
|
+
machineOutput: input.json || isMachineOutput(),
|
|
1578
|
+
interactive: isInteractive(),
|
|
1579
|
+
message: `Restart managed dev session ${sessionId}${safeCurrent.running ? ` (PID ${current.pid})` : ''}?`,
|
|
1580
|
+
confirm: promptConfirm,
|
|
1581
|
+
});
|
|
1582
|
+
if (consent !== 'proceed') {
|
|
1583
|
+
const message = consent === 'require-yes'
|
|
1584
|
+
? 'Restarting a managed dev session in non-interactive or machine-output mode requires --yes.'
|
|
1585
|
+
: 'Restart cancelled.';
|
|
1586
|
+
reporter.data({ session: safeCurrent });
|
|
1587
|
+
reporter.error('HSX_E_INPUT_INVALID', message, {
|
|
1588
|
+
hint: `Review the session above, then run \`hs-x dev restart --session ${sessionId} --yes\`.`,
|
|
1589
|
+
});
|
|
1590
|
+
reporter.done(undefined, consent === 'require-yes' ? 2 : 130);
|
|
1591
|
+
return { exitCode: consent === 'require-yes' ? 2 : 130 };
|
|
1592
|
+
}
|
|
1593
|
+
const restartBase = safeCurrent.running
|
|
1594
|
+
? await stopManagedDevSessionProcess(sessionId, { force: input.argv.includes('--force') })
|
|
1595
|
+
: current;
|
|
1596
|
+
const restarting = await prepareManagedDevSessionRestart(sessionId, {
|
|
1597
|
+
expectedGeneration: restartBase.generation,
|
|
1598
|
+
expectedPid: restartBase.pid,
|
|
1599
|
+
expectedStates: [restartBase.state],
|
|
1600
|
+
});
|
|
1601
|
+
await launchManagedDevChild(restarting);
|
|
1602
|
+
const result = await waitForManagedDevStartup(sessionId, waitMs);
|
|
1603
|
+
const safe = await publicManagedDevSession(result);
|
|
1604
|
+
if (safe.state === 'failed' || safe.state === 'stopped') {
|
|
1605
|
+
reporter.data({ session: safe });
|
|
1606
|
+
reporter.error('HSX_E_DEV_MANAGED_RESTART', safe.error ?? `Managed dev session ${sessionId} stopped while restarting.`, { hint: `Inspect ${safe.stderrPath} for the complete restart output.` });
|
|
1607
|
+
reporter.done(undefined, 1);
|
|
1608
|
+
return { exitCode: 1 };
|
|
1609
|
+
}
|
|
1610
|
+
renderManagedDevSession(reporter, safe, 'restarted');
|
|
1611
|
+
reporter.data({ session: safe, commands: managedDevSessionCommands(sessionId) });
|
|
1612
|
+
if (safe.state === 'ready' && !safe.logPort) {
|
|
1613
|
+
reporter.warn('HSX_W_DEV_MANAGED_LOG_STREAM', 'The dev server restarted, but its unified log sidecar is unavailable. Inspect stderrPath and retry with a free --hsx-log-port.');
|
|
1614
|
+
}
|
|
1615
|
+
reporter.done(safe.state === 'ready' ? 'Dev session restarted' : 'Dev session still starting');
|
|
1616
|
+
return { exitCode: 0 };
|
|
1617
|
+
}
|
|
1618
|
+
catch (cause) {
|
|
1619
|
+
return managedDevCommandFailure(reporter, cause, 'HSX_E_DEV_MANAGED_RESTART');
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
async function managedDevLogsCommand(input) {
|
|
1623
|
+
const reporter = input.reporter ?? createReporter({ command: 'dev logs', argv: input.argv });
|
|
1624
|
+
const sessionId = requireManagedSessionId(input.argv, reporter);
|
|
1625
|
+
if (!sessionId)
|
|
1626
|
+
return { exitCode: 2 };
|
|
1627
|
+
if (input.argv.includes('--follow') && input.json) {
|
|
1628
|
+
reporter.error('HSX_E_DEV_LOGS_OUTPUT', '`--follow` is an event stream and cannot be combined with `--json`.', { hint: 'Use `--json-stream` for machine-readable follow mode.' });
|
|
1629
|
+
reporter.done(undefined, 2);
|
|
1630
|
+
return { exitCode: 2 };
|
|
1631
|
+
}
|
|
1632
|
+
try {
|
|
1633
|
+
let session = await reconcileManagedDevSession(sessionId);
|
|
1634
|
+
const cursor = parseNonNegativeInteger(input.argv, '--cursor', 0);
|
|
1635
|
+
const limit = parsePositiveInteger(input.argv, '--limit', 200, 10_000);
|
|
1636
|
+
const filter = parseManagedDevEventFilter(input.argv);
|
|
1637
|
+
if (!input.argv.includes('--follow')) {
|
|
1638
|
+
const result = await readManagedDevEvents({ session, cursor, limit, filter });
|
|
1639
|
+
if (reporter.mode === 'json' || reporter.mode === 'ndjson') {
|
|
1640
|
+
reporter.data({
|
|
1641
|
+
session_id: sessionId,
|
|
1642
|
+
cursor,
|
|
1643
|
+
next_cursor: result.nextCursor,
|
|
1644
|
+
end_cursor: result.endCursor,
|
|
1645
|
+
events: result.events,
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
else {
|
|
1649
|
+
reporter.header(`${sessionId} · ${result.events.length} event${result.events.length === 1 ? '' : 's'}`);
|
|
1650
|
+
const view = await startManagedDevLogView({ follow: false, filter });
|
|
1651
|
+
try {
|
|
1652
|
+
for (const match of result.events)
|
|
1653
|
+
view.emit(match.event);
|
|
1654
|
+
}
|
|
1655
|
+
finally {
|
|
1656
|
+
view.stop();
|
|
1657
|
+
}
|
|
1658
|
+
reporter.info(`Next cursor: ${result.nextCursor}`);
|
|
1659
|
+
}
|
|
1660
|
+
reporter.done();
|
|
1661
|
+
return { exitCode: 0 };
|
|
1662
|
+
}
|
|
1663
|
+
let nextCursor = cursor;
|
|
1664
|
+
let interrupted = false;
|
|
1665
|
+
let terminalSince;
|
|
1666
|
+
const interrupt = () => {
|
|
1667
|
+
interrupted = true;
|
|
1668
|
+
};
|
|
1669
|
+
process.once('SIGINT', interrupt);
|
|
1670
|
+
process.once('SIGTERM', interrupt);
|
|
1671
|
+
if (reporter.mode !== 'ndjson') {
|
|
1672
|
+
reporter.header(`${sessionId} · following from cursor ${cursor}`);
|
|
1673
|
+
}
|
|
1674
|
+
const followReadFilter = reporter.mode === 'ndjson' ? filter : managedDevFollowReadFilter(filter);
|
|
1675
|
+
let view;
|
|
1676
|
+
try {
|
|
1677
|
+
view =
|
|
1678
|
+
reporter.mode === 'ndjson'
|
|
1679
|
+
? undefined
|
|
1680
|
+
: await startManagedDevLogView({ follow: true, filter, onQuit: interrupt });
|
|
1681
|
+
view?.ready();
|
|
1682
|
+
while (!interrupted) {
|
|
1683
|
+
const previousCursor = nextCursor;
|
|
1684
|
+
const result = await readManagedDevEvents({
|
|
1685
|
+
session,
|
|
1686
|
+
cursor: nextCursor,
|
|
1687
|
+
limit,
|
|
1688
|
+
...(followReadFilter ? { filter: followReadFilter } : {}),
|
|
1689
|
+
});
|
|
1690
|
+
for (const match of result.events) {
|
|
1691
|
+
if (reporter.mode === 'ndjson') {
|
|
1692
|
+
reporter.event('dev.logs.event', {
|
|
1693
|
+
session_id: sessionId,
|
|
1694
|
+
cursor: match.cursor,
|
|
1695
|
+
event: match.event,
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
else {
|
|
1699
|
+
view?.emit(match.event);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
nextCursor = result.nextCursor;
|
|
1703
|
+
session = await reconcileManagedDevSession(sessionId);
|
|
1704
|
+
const terminal = session.state === 'stopped' || session.state === 'failed';
|
|
1705
|
+
if (terminal && nextCursor >= result.endCursor) {
|
|
1706
|
+
terminalSince ??= Date.now();
|
|
1707
|
+
if (Date.now() - terminalSince >= 1_000)
|
|
1708
|
+
break;
|
|
1709
|
+
}
|
|
1710
|
+
else {
|
|
1711
|
+
terminalSince = undefined;
|
|
1712
|
+
}
|
|
1713
|
+
if (nextCursor >= result.endCursor || nextCursor === previousCursor)
|
|
1714
|
+
await delayMs(250);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
finally {
|
|
1718
|
+
process.removeListener('SIGINT', interrupt);
|
|
1719
|
+
process.removeListener('SIGTERM', interrupt);
|
|
1720
|
+
view?.stop();
|
|
1721
|
+
}
|
|
1722
|
+
if (reporter.mode === 'ndjson') {
|
|
1723
|
+
reporter.event('dev.logs.cursor', { session_id: sessionId, next_cursor: nextCursor });
|
|
1724
|
+
}
|
|
1725
|
+
else {
|
|
1726
|
+
reporter.info(`Detached at cursor ${nextCursor}`);
|
|
1727
|
+
}
|
|
1728
|
+
reporter.done();
|
|
1729
|
+
return { exitCode: 0 };
|
|
1730
|
+
}
|
|
1731
|
+
catch (cause) {
|
|
1732
|
+
return managedDevCommandFailure(reporter, cause, 'HSX_E_DEV_MANAGED_LOGS');
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
function managedDevLaunchArgv(argv) {
|
|
1736
|
+
const output = [];
|
|
1737
|
+
const orchestrationValueFlags = new Set([
|
|
1738
|
+
'--managed-session',
|
|
1739
|
+
'--session',
|
|
1740
|
+
'--cursor',
|
|
1741
|
+
'--lane',
|
|
1742
|
+
'--level',
|
|
1743
|
+
'--search',
|
|
1744
|
+
'--since',
|
|
1745
|
+
'--limit',
|
|
1746
|
+
'--wait-seconds',
|
|
1747
|
+
]);
|
|
1748
|
+
const orchestrationBooleanFlags = new Set([
|
|
1749
|
+
'--detach',
|
|
1750
|
+
'--follow',
|
|
1751
|
+
'--json',
|
|
1752
|
+
'--json-stream',
|
|
1753
|
+
'--yes',
|
|
1754
|
+
'-y',
|
|
1755
|
+
]);
|
|
1756
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1757
|
+
const value = argv[index];
|
|
1758
|
+
if (!value)
|
|
1759
|
+
continue;
|
|
1760
|
+
if (index === 1 && value === 'start')
|
|
1761
|
+
continue;
|
|
1762
|
+
if (orchestrationBooleanFlags.has(value))
|
|
1763
|
+
continue;
|
|
1764
|
+
if (orchestrationValueFlags.has(value)) {
|
|
1765
|
+
index += 1;
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
if ([...orchestrationValueFlags].some((flag) => value.startsWith(`${flag}=`)))
|
|
1769
|
+
continue;
|
|
1770
|
+
output.push(value);
|
|
1771
|
+
}
|
|
1772
|
+
return output;
|
|
1773
|
+
}
|
|
1774
|
+
async function launchManagedDevChild(session) {
|
|
1775
|
+
const entryCandidate = process.argv[1];
|
|
1776
|
+
const entry = entryCandidate ? resolvePath(entryCandidate) : undefined;
|
|
1777
|
+
const entryArgs = entry && existsSync(entry) ? [entry] : [];
|
|
1778
|
+
const stdoutFd = openSync(session.stdoutPath, 'a', 0o600);
|
|
1779
|
+
const stderrFd = openSync(session.stderrPath, 'a', 0o600);
|
|
1780
|
+
try {
|
|
1781
|
+
const child = spawn(process.execPath, [...entryArgs, ...session.launchArgv, '--managed-session', session.sessionId], {
|
|
1782
|
+
cwd: session.root,
|
|
1783
|
+
env: process.env,
|
|
1784
|
+
detached: true,
|
|
1785
|
+
stdio: ['ignore', stdoutFd, stderrFd],
|
|
1786
|
+
});
|
|
1787
|
+
await new Promise((resolveSpawn, rejectSpawn) => {
|
|
1788
|
+
child.once('spawn', resolveSpawn);
|
|
1789
|
+
child.once('error', rejectSpawn);
|
|
1790
|
+
});
|
|
1791
|
+
if (!child.pid)
|
|
1792
|
+
throw new Error('Detached hs-x dev process did not report a PID.');
|
|
1793
|
+
try {
|
|
1794
|
+
await setManagedDevSessionPid(session.sessionId, child.pid, {
|
|
1795
|
+
expectedGeneration: session.generation,
|
|
1796
|
+
expectedPid: 0,
|
|
1797
|
+
expectedStates: ['starting'],
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
catch (cause) {
|
|
1801
|
+
try {
|
|
1802
|
+
child.kill('SIGTERM');
|
|
1803
|
+
}
|
|
1804
|
+
catch {
|
|
1805
|
+
// The guarded state update is still the primary failure.
|
|
1806
|
+
}
|
|
1807
|
+
throw cause;
|
|
1808
|
+
}
|
|
1809
|
+
child.unref();
|
|
1810
|
+
return child.pid;
|
|
1811
|
+
}
|
|
1812
|
+
finally {
|
|
1813
|
+
closeSync(stdoutFd);
|
|
1814
|
+
closeSync(stderrFd);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
async function waitForManagedDevStartup(sessionId, timeoutMs) {
|
|
1818
|
+
const deadline = Date.now() + timeoutMs;
|
|
1819
|
+
let session = await reconcileManagedDevSession(sessionId);
|
|
1820
|
+
while (Date.now() < deadline && session.state === 'starting') {
|
|
1821
|
+
await delayMs(100);
|
|
1822
|
+
session = await reconcileManagedDevSession(sessionId);
|
|
1823
|
+
}
|
|
1824
|
+
return session;
|
|
1825
|
+
}
|
|
1826
|
+
function managedDevSessionCommands(sessionId) {
|
|
1827
|
+
return {
|
|
1828
|
+
logs: `hs-x dev logs --session ${sessionId} --follow`,
|
|
1829
|
+
logs_json: `hs-x dev logs --session ${sessionId} --follow --json-stream`,
|
|
1830
|
+
status: `hs-x dev status --session ${sessionId} --json`,
|
|
1831
|
+
restart: `hs-x dev restart --session ${sessionId} --yes`,
|
|
1832
|
+
stop: `hs-x dev stop --session ${sessionId} --yes`,
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
function renderManagedDevSession(reporter, session, subject) {
|
|
1836
|
+
if (reporter.mode === 'json' || reporter.mode === 'ndjson')
|
|
1837
|
+
return;
|
|
1838
|
+
reporter.header(`${subject} · ${session.sessionId}`);
|
|
1839
|
+
reporter.rows([
|
|
1840
|
+
{
|
|
1841
|
+
key: 'state',
|
|
1842
|
+
value: session.state,
|
|
1843
|
+
status: session.state === 'ready' ? 'ok' : session.state === 'failed' ? 'fail' : 'warn',
|
|
1844
|
+
},
|
|
1845
|
+
{ key: 'pid', value: session.pid > 1 ? String(session.pid) : 'not assigned' },
|
|
1846
|
+
{ key: 'generation', value: String(session.generation) },
|
|
1847
|
+
{ key: 'root', value: session.root },
|
|
1848
|
+
...(session.projectAccountId
|
|
1849
|
+
? [{ key: 'project-account', value: String(session.projectAccountId) }]
|
|
1850
|
+
: []),
|
|
1851
|
+
...(session.testingAccountId
|
|
1852
|
+
? [{ key: 'testing-account', value: String(session.testingAccountId) }]
|
|
1853
|
+
: []),
|
|
1854
|
+
...(session.components
|
|
1855
|
+
? [{ key: 'components', value: session.components.join(', ') || 'none' }]
|
|
1856
|
+
: []),
|
|
1857
|
+
...(session.logPort ? [{ key: 'log-port', value: String(session.logPort) }] : []),
|
|
1858
|
+
]);
|
|
1859
|
+
if (session.error)
|
|
1860
|
+
reporter.info(`Last error: ${session.error}`);
|
|
1861
|
+
reporter.info(`Logs: hs-x dev logs --session ${session.sessionId} --follow`);
|
|
1862
|
+
}
|
|
1863
|
+
function requireManagedSessionId(argv, reporter) {
|
|
1864
|
+
const sessionId = resolveFlag(argv, '--session');
|
|
1865
|
+
if (sessionId)
|
|
1866
|
+
return sessionId;
|
|
1867
|
+
reporter.error('HSX_E_INPUT_INVALID', 'A managed dev session id is required.', {
|
|
1868
|
+
hint: 'Pass the id returned by `hs-x dev start`, for example `--session dev_…`.',
|
|
1869
|
+
});
|
|
1870
|
+
reporter.done(undefined, 2);
|
|
1871
|
+
return undefined;
|
|
1872
|
+
}
|
|
1873
|
+
function managedDevCommandFailure(reporter, cause, code) {
|
|
1874
|
+
reporter.error(code, cause instanceof Error ? cause.message : String(cause));
|
|
1875
|
+
reporter.done(undefined, 1);
|
|
1876
|
+
return { exitCode: 1 };
|
|
1877
|
+
}
|
|
1878
|
+
function parseManagedDevEventFilter(argv) {
|
|
1879
|
+
const laneValues = resolveFlags(argv, '--lane');
|
|
1880
|
+
for (const lane of laneValues) {
|
|
1881
|
+
if (!MANAGED_DEV_LANES.has(lane)) {
|
|
1882
|
+
throw new Error(`Unknown --lane "${lane}". Expected frontend, backend, request, or hubspot.`);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
const levelValues = resolveFlags(argv, '--level');
|
|
1886
|
+
for (const level of levelValues) {
|
|
1887
|
+
if (!MANAGED_DEV_LEVELS.has(level)) {
|
|
1888
|
+
throw new Error(`Unknown --level "${level}". Expected debug, info, warn, or error.`);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
const sinceValue = resolveFlag(argv, '--since');
|
|
1892
|
+
const searchValue = resolveFlag(argv, '--search');
|
|
1893
|
+
return {
|
|
1894
|
+
...(laneValues.length > 0
|
|
1895
|
+
? { lanes: new Set(laneValues) }
|
|
1896
|
+
: {}),
|
|
1897
|
+
...(levelValues.length > 0
|
|
1898
|
+
? { levels: new Set(levelValues) }
|
|
1899
|
+
: {}),
|
|
1900
|
+
...(searchValue ? { search: searchValue } : {}),
|
|
1901
|
+
...(sinceValue ? { since: parseManagedSince(sinceValue) } : {}),
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
function parseManagedSince(value) {
|
|
1905
|
+
const relative = /^(\d+)(ms|s|m|h|d)$/.exec(value);
|
|
1906
|
+
if (relative) {
|
|
1907
|
+
const amount = Number(relative[1]);
|
|
1908
|
+
const unit = relative[2];
|
|
1909
|
+
const multiplier = unit === 'ms'
|
|
1910
|
+
? 1
|
|
1911
|
+
: unit === 's'
|
|
1912
|
+
? 1_000
|
|
1913
|
+
: unit === 'm'
|
|
1914
|
+
? 60_000
|
|
1915
|
+
: unit === 'h'
|
|
1916
|
+
? 3_600_000
|
|
1917
|
+
: 86_400_000;
|
|
1918
|
+
return Date.now() - amount * multiplier;
|
|
1919
|
+
}
|
|
1920
|
+
const parsed = Date.parse(value);
|
|
1921
|
+
if (!Number.isFinite(parsed)) {
|
|
1922
|
+
throw new Error(`Invalid --since value "${value}". Use an ISO timestamp or a duration like 30s, 5m, or 2h.`);
|
|
1923
|
+
}
|
|
1924
|
+
return parsed;
|
|
1925
|
+
}
|
|
1926
|
+
function parseNonNegativeInteger(argv, flag, fallback) {
|
|
1927
|
+
const raw = resolveFlag(argv, flag);
|
|
1928
|
+
if (raw === undefined)
|
|
1929
|
+
return fallback;
|
|
1930
|
+
const value = Number(raw);
|
|
1931
|
+
if (!Number.isInteger(value) || value < 0)
|
|
1932
|
+
throw new Error(`${flag} must be a non-negative integer.`);
|
|
1933
|
+
return value;
|
|
1934
|
+
}
|
|
1935
|
+
function parsePositiveInteger(argv, flag, fallback, maximum = Number.MAX_SAFE_INTEGER) {
|
|
1936
|
+
const raw = resolveFlag(argv, flag);
|
|
1937
|
+
if (raw === undefined)
|
|
1938
|
+
return fallback;
|
|
1939
|
+
const value = Number(raw);
|
|
1940
|
+
if (!Number.isInteger(value) || value < 1 || value > maximum) {
|
|
1941
|
+
throw new Error(`${flag} must be an integer between 1 and ${maximum}.`);
|
|
1942
|
+
}
|
|
1943
|
+
return value;
|
|
1944
|
+
}
|
|
1945
|
+
function parsePositiveSeconds(argv, flag, fallback) {
|
|
1946
|
+
return parsePositiveInteger(argv, flag, fallback, 600);
|
|
1947
|
+
}
|
|
1948
|
+
async function startManagedDevLogView(input) {
|
|
1949
|
+
const [eventBusModule, keyboardModule, formatModule, rendererModule] = await Promise.all([
|
|
1950
|
+
import('../dev/event-bus.js'),
|
|
1951
|
+
import('../dev/keyboard.js'),
|
|
1952
|
+
import('../dev/stream-event-format.js'),
|
|
1953
|
+
import('../dev/stream-renderer.js'),
|
|
1954
|
+
]);
|
|
1955
|
+
const bus = eventBusModule.createDevEventBus();
|
|
1956
|
+
const renderer = rendererModule.startDevStreamRenderer({
|
|
1957
|
+
bus,
|
|
1958
|
+
filter: managedDevEventFilterToStreamFilter(input.filter),
|
|
1959
|
+
...(input.follow ? { deferStatus: true } : { isTTY: false }),
|
|
1960
|
+
});
|
|
1961
|
+
const keyboard = input.follow
|
|
1962
|
+
? keyboardModule.attachDevKeyboard({
|
|
1963
|
+
renderer,
|
|
1964
|
+
onQuit: input.onQuit ?? (() => { }),
|
|
1965
|
+
})
|
|
1966
|
+
: undefined;
|
|
1967
|
+
return {
|
|
1968
|
+
emit(event) {
|
|
1969
|
+
bus.emit(formatModule.managedDevEventToDevEvent(event));
|
|
1970
|
+
},
|
|
1971
|
+
ready() {
|
|
1972
|
+
renderer.ready();
|
|
1973
|
+
},
|
|
1974
|
+
stop() {
|
|
1975
|
+
keyboard?.stop();
|
|
1976
|
+
renderer.stop();
|
|
1977
|
+
},
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
function managedDevEventFilterToStreamFilter(filter) {
|
|
1981
|
+
const levels = filter.levels;
|
|
1982
|
+
const level = levels?.size === 1 && levels.has('error')
|
|
1983
|
+
? 'error'
|
|
1984
|
+
: levels?.size === 2 && levels.has('warn') && levels.has('error')
|
|
1985
|
+
? 'warn'
|
|
1986
|
+
: 'all';
|
|
1987
|
+
return {
|
|
1988
|
+
lanes: filter.lanes,
|
|
1989
|
+
level,
|
|
1990
|
+
texts: filter.search ? [filter.search] : [],
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Human follow mode keeps lane/search/threshold filtering in the renderer so
|
|
1995
|
+
* keyboard controls can widen the retained view. Timestamp and exact level
|
|
1996
|
+
* sets that the threshold model cannot express remain storage-side. Machine
|
|
1997
|
+
* streams never use this split and retain their original cursor/filter path.
|
|
1998
|
+
*/
|
|
1999
|
+
function managedDevFollowReadFilter(filter) {
|
|
2000
|
+
const levels = filter.levels;
|
|
2001
|
+
const rendererCanRepresentLevels = levels === undefined ||
|
|
2002
|
+
(levels.size === 1 && levels.has('error')) ||
|
|
2003
|
+
(levels.size === 2 && levels.has('warn') && levels.has('error')) ||
|
|
2004
|
+
(levels.size === 4 &&
|
|
2005
|
+
levels.has('debug') &&
|
|
2006
|
+
levels.has('info') &&
|
|
2007
|
+
levels.has('warn') &&
|
|
2008
|
+
levels.has('error'));
|
|
2009
|
+
const readFilter = {
|
|
2010
|
+
...(filter.since !== undefined ? { since: filter.since } : {}),
|
|
2011
|
+
...(!rendererCanRepresentLevels && levels ? { levels } : {}),
|
|
2012
|
+
};
|
|
2013
|
+
return Object.keys(readFilter).length > 0 ? readFilter : undefined;
|
|
2014
|
+
}
|
|
2015
|
+
function delayMs(ms) {
|
|
2016
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2017
|
+
}
|
|
860
2018
|
async function startHsxDevLogStream(input) {
|
|
861
2019
|
try {
|
|
862
2020
|
const [{ createDevEventBus }, { startDevLogServer }, { startDevStreamRenderer }] = await Promise.all([
|
|
@@ -872,6 +2030,12 @@ async function startHsxDevLogStream(input) {
|
|
|
872
2030
|
deferStatus: true,
|
|
873
2031
|
...(input.filter ? { filter: input.filter } : {}),
|
|
874
2032
|
});
|
|
2033
|
+
const managedEventWriter = input.managedSessionId
|
|
2034
|
+
? createManagedDevEventWriter(await readManagedDevSession(input.managedSessionId))
|
|
2035
|
+
: undefined;
|
|
2036
|
+
const unsubscribeManagedEvents = managedEventWriter
|
|
2037
|
+
? bus.subscribe((event) => managedEventWriter.log(event))
|
|
2038
|
+
: undefined;
|
|
875
2039
|
// HubSpot-side lane: renders/proxy failures that never reach our Worker
|
|
876
2040
|
// only exist in HubSpot's app logs — poll them into the same stream.
|
|
877
2041
|
// Missing binding/credentials just means the lane stays off; `hs-x dev`
|
|
@@ -903,8 +2067,9 @@ async function startHsxDevLogStream(input) {
|
|
|
903
2067
|
bus,
|
|
904
2068
|
async cleanup() {
|
|
905
2069
|
hubspotPoller?.stop();
|
|
2070
|
+
unsubscribeManagedEvents?.();
|
|
906
2071
|
renderer.stop();
|
|
907
|
-
await server.close();
|
|
2072
|
+
await Promise.all([server.close(), managedEventWriter?.close()]);
|
|
908
2073
|
},
|
|
909
2074
|
};
|
|
910
2075
|
}
|
|
@@ -954,8 +2119,9 @@ async function probeHubSpotUiNodes(input) {
|
|
|
954
2119
|
projectSourceDir: pathJoin(input.root, projectConfig.srcDir),
|
|
955
2120
|
platformVersion: projectConfig.platformVersion,
|
|
956
2121
|
accountId: portalId,
|
|
957
|
-
}, {});
|
|
958
|
-
|
|
2122
|
+
}, input.profile ? { profile: input.profile } : {});
|
|
2123
|
+
const candidates = listHubSpotDevComponentCandidates(ir.intermediateNodesIndexedByUid);
|
|
2124
|
+
return { uids: candidates.map((candidate) => candidate.uid), candidates, portalId };
|
|
959
2125
|
}
|
|
960
2126
|
catch {
|
|
961
2127
|
// IR translate failed — caller falls back to capability-only behavior.
|
|
@@ -973,10 +2139,12 @@ async function maybeStartHubSpotDevSession(input) {
|
|
|
973
2139
|
}
|
|
974
2140
|
try {
|
|
975
2141
|
const { startDevSession } = await import('../dev/session-manager.js');
|
|
2142
|
+
const profile = resolveFlag(input.argv, '--profile');
|
|
976
2143
|
const session = await startDevSession({
|
|
977
2144
|
accountId: portalId,
|
|
978
2145
|
projectDir: input.root,
|
|
979
2146
|
selectedNodeUids: input.selectedNodeUids,
|
|
2147
|
+
...(profile ? { profile } : {}),
|
|
980
2148
|
});
|
|
981
2149
|
input.reporter
|
|
982
2150
|
.step('HubSpot UI-extension session')
|
|
@@ -984,7 +2152,7 @@ async function maybeStartHubSpotDevSession(input) {
|
|
|
984
2152
|
return session;
|
|
985
2153
|
}
|
|
986
2154
|
catch (error) {
|
|
987
|
-
input.reporter.warn('HSX_W_DEV_HUBSPOT_SESSION_FAILED', `Could not start UI-extension dev session: ${
|
|
2155
|
+
input.reporter.warn('HSX_W_DEV_HUBSPOT_SESSION_FAILED', `Could not start UI-extension dev session: ${describeDevSessionFailure(error)}`);
|
|
988
2156
|
return undefined;
|
|
989
2157
|
}
|
|
990
2158
|
}
|
|
@@ -1757,11 +2925,11 @@ function write(message) {
|
|
|
1757
2925
|
// as a silent `exitWith(code)` (the legacy body renders its own output) so
|
|
1758
2926
|
// `runHandler` does not re-render.
|
|
1759
2927
|
//
|
|
1760
|
-
// The first positional argument selects the
|
|
2928
|
+
// The first positional argument selects the managed-session and legacy subcommands
|
|
1761
2929
|
// the legacy body branches on via `argv[1]`.
|
|
1762
2930
|
// A positional (not an option) so `hs-x dev cleanup` / `dev status` parse — the
|
|
1763
2931
|
// legacy body branches on `argv[1]`.
|
|
1764
|
-
const devSubcommandArg = Args.text({ name: 'subcommand' }).pipe(Args.withDescription('Optional dev subcommand: invoke | cleanup | status'), Args.optional);
|
|
2932
|
+
const devSubcommandArg = Args.text({ name: 'subcommand' }).pipe(Args.withDescription('Optional dev subcommand: start | logs | stop | restart | invoke | cleanup | status'), Args.optional);
|
|
1765
2933
|
// Second positional: the capability id for `dev invoke <capability-id>`.
|
|
1766
2934
|
const devTargetArg = Args.text({ name: 'target' }).pipe(Args.withDescription('Capability id for `dev invoke`'), Args.optional);
|
|
1767
2935
|
const devInputOption = Options.text('input').pipe(Options.optional);
|
|
@@ -1772,6 +2940,10 @@ const devRemoteOption = Options.boolean('remote').pipe(Options.withDefault(false
|
|
|
1772
2940
|
const devUrlOption = Options.text('url').pipe(Options.optional);
|
|
1773
2941
|
const portOption = Options.text('port').pipe(Options.optional);
|
|
1774
2942
|
const portalOption = Options.text('portal').pipe(Options.optional);
|
|
2943
|
+
const accountOption = Options.text('account').pipe(Options.optional);
|
|
2944
|
+
const projectAccountOption = Options.text('project-account').pipe(Options.optional);
|
|
2945
|
+
const testingAccountOption = Options.text('testing-account').pipe(Options.optional);
|
|
2946
|
+
const profileOption = Options.text('profile').pipe(Options.withAlias('p'), Options.optional);
|
|
1775
2947
|
const targetOriginOption = Options.text('target-origin').pipe(Options.optional);
|
|
1776
2948
|
const telemetryOriginOption = Options.text('telemetry-origin').pipe(Options.optional);
|
|
1777
2949
|
const accountIdOption = Options.text('account-id').pipe(Options.optional);
|
|
@@ -1779,12 +2951,24 @@ const projectIdOption = Options.text('project-id').pipe(Options.optional);
|
|
|
1779
2951
|
const developerIdOption = Options.text('developer-id').pipe(Options.optional);
|
|
1780
2952
|
const userIdOption = Options.text('user-id').pipe(Options.optional);
|
|
1781
2953
|
const sessionIdOption = Options.text('session-id').pipe(Options.optional);
|
|
2954
|
+
const managedSessionOption = Options.text('session').pipe(Options.optional);
|
|
2955
|
+
const internalManagedSessionOption = Options.text('managed-session').pipe(Options.optional);
|
|
1782
2956
|
const ttlSecondsOption = Options.text('ttl-seconds').pipe(Options.optional);
|
|
1783
2957
|
const hsxLogPortOption = Options.text('hsx-log-port').pipe(Options.optional);
|
|
1784
2958
|
const controlPlaneUrlOption = Options.text('control-plane-url').pipe(Options.optional);
|
|
1785
2959
|
const runtimeControlPlaneUrlOption = Options.text('runtime-control-plane-url').pipe(Options.optional);
|
|
1786
2960
|
const capabilityOption = Options.text('capability').pipe(Options.repeated);
|
|
2961
|
+
const componentOption = Options.text('component').pipe(Options.repeated);
|
|
1787
2962
|
const onlyOption = Options.text('only').pipe(Options.repeated);
|
|
2963
|
+
const laneOption = Options.text('lane').pipe(Options.repeated);
|
|
2964
|
+
const levelOption = Options.text('level').pipe(Options.repeated);
|
|
2965
|
+
const searchOption = Options.text('search').pipe(Options.optional);
|
|
2966
|
+
const sinceOption = Options.text('since').pipe(Options.optional);
|
|
2967
|
+
const cursorOption = Options.text('cursor').pipe(Options.optional);
|
|
2968
|
+
const limitOption = Options.text('limit').pipe(Options.optional);
|
|
2969
|
+
const waitSecondsOption = Options.text('wait-seconds').pipe(Options.optional);
|
|
2970
|
+
const detachOption = Options.boolean('detach').pipe(Options.withDefault(false));
|
|
2971
|
+
const followOption = Options.boolean('follow').pipe(Options.withDefault(false));
|
|
1788
2972
|
const pickOption = Options.boolean('pick').pipe(Options.withDefault(false));
|
|
1789
2973
|
const noHubspotOption = Options.boolean('no-hubspot').pipe(Options.withDefault(false));
|
|
1790
2974
|
const noUiExtensionsOption = Options.boolean('no-ui-extensions').pipe(Options.withDefault(false));
|
|
@@ -1803,6 +2987,10 @@ export const devCmd = Command.make('dev', {
|
|
|
1803
2987
|
url: devUrlOption,
|
|
1804
2988
|
port: portOption,
|
|
1805
2989
|
portal: portalOption,
|
|
2990
|
+
account: accountOption,
|
|
2991
|
+
projectAccount: projectAccountOption,
|
|
2992
|
+
testingAccount: testingAccountOption,
|
|
2993
|
+
profile: profileOption,
|
|
1806
2994
|
targetOrigin: targetOriginOption,
|
|
1807
2995
|
telemetryOrigin: telemetryOriginOption,
|
|
1808
2996
|
accountId: accountIdOption,
|
|
@@ -1810,90 +2998,151 @@ export const devCmd = Command.make('dev', {
|
|
|
1810
2998
|
developerId: developerIdOption,
|
|
1811
2999
|
userId: userIdOption,
|
|
1812
3000
|
sessionId: sessionIdOption,
|
|
3001
|
+
session: managedSessionOption,
|
|
3002
|
+
managedSession: internalManagedSessionOption,
|
|
1813
3003
|
ttlSeconds: ttlSecondsOption,
|
|
1814
3004
|
hsxLogPort: hsxLogPortOption,
|
|
1815
3005
|
controlPlaneUrl: controlPlaneUrlOption,
|
|
1816
3006
|
runtimeControlPlaneUrl: runtimeControlPlaneUrlOption,
|
|
1817
3007
|
capability: capabilityOption,
|
|
3008
|
+
component: componentOption,
|
|
1818
3009
|
only: onlyOption,
|
|
3010
|
+
lane: laneOption,
|
|
3011
|
+
level: levelOption,
|
|
3012
|
+
search: searchOption,
|
|
3013
|
+
since: sinceOption,
|
|
3014
|
+
cursor: cursorOption,
|
|
3015
|
+
limit: limitOption,
|
|
3016
|
+
waitSeconds: waitSecondsOption,
|
|
3017
|
+
detach: detachOption,
|
|
3018
|
+
follow: followOption,
|
|
1819
3019
|
pick: pickOption,
|
|
1820
3020
|
noHubspot: noHubspotOption,
|
|
1821
3021
|
noUiExtensions: noUiExtensionsOption,
|
|
1822
3022
|
yes: yesOption,
|
|
1823
3023
|
force: forceOption,
|
|
1824
|
-
}, (opts) =>
|
|
1825
|
-
|
|
1826
|
-
const
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
argv
|
|
1836
|
-
|
|
1837
|
-
argv
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
3024
|
+
}, (opts) => {
|
|
3025
|
+
const subcommand = Option.isSome(opts.subcommand) ? opts.subcommand.value : undefined;
|
|
3026
|
+
const managedSubcommand = subcommand !== undefined &&
|
|
3027
|
+
(MANAGED_DEV_SUBCOMMANDS.has(subcommand) ||
|
|
3028
|
+
(subcommand === 'status' && Option.isSome(opts.session)));
|
|
3029
|
+
const reportCommand = managedSubcommand ? `dev ${subcommand}` : 'dev';
|
|
3030
|
+
return runHandler(reportCommand, { json: opts.json }, Effect.gen(function* () {
|
|
3031
|
+
const effectReporter = yield* ReporterService;
|
|
3032
|
+
// root: injected cwd, overridden by --cwd.
|
|
3033
|
+
const injectedCwd = yield* Cwd;
|
|
3034
|
+
const root = Option.getOrElse(opts.cwd, () => injectedCwd);
|
|
3035
|
+
// Reconstruct the legacy argv the quarantined body parses (it reads
|
|
3036
|
+
// its flags off argv via `resolveFlag`/`resolveFlags` and branches on
|
|
3037
|
+
// `argv[1]` for the cleanup/status subcommands).
|
|
3038
|
+
const argv = ['dev'];
|
|
3039
|
+
if (Option.isSome(opts.subcommand))
|
|
3040
|
+
argv.push(opts.subcommand.value);
|
|
3041
|
+
if (Option.isSome(opts.target))
|
|
3042
|
+
argv.push(opts.target.value);
|
|
3043
|
+
if (Option.isSome(opts.input))
|
|
3044
|
+
argv.push('--input', opts.input.value);
|
|
3045
|
+
if (Option.isSome(opts.object))
|
|
3046
|
+
argv.push('--object', opts.object.value);
|
|
3047
|
+
if (Option.isSome(opts.install))
|
|
3048
|
+
argv.push('--install', opts.install.value);
|
|
3049
|
+
if (opts.last)
|
|
3050
|
+
argv.push('--last');
|
|
3051
|
+
if (opts.remote)
|
|
3052
|
+
argv.push('--remote');
|
|
3053
|
+
if (Option.isSome(opts.url))
|
|
3054
|
+
argv.push('--url', opts.url.value);
|
|
3055
|
+
if (opts.json)
|
|
3056
|
+
argv.push('--json');
|
|
3057
|
+
if (Option.isSome(opts.port))
|
|
3058
|
+
argv.push('--port', opts.port.value);
|
|
3059
|
+
if (Option.isSome(opts.portal))
|
|
3060
|
+
argv.push('--portal', opts.portal.value);
|
|
3061
|
+
if (Option.isSome(opts.account))
|
|
3062
|
+
argv.push('--account', opts.account.value);
|
|
3063
|
+
if (Option.isSome(opts.projectAccount)) {
|
|
3064
|
+
argv.push('--project-account', opts.projectAccount.value);
|
|
3065
|
+
}
|
|
3066
|
+
if (Option.isSome(opts.testingAccount)) {
|
|
3067
|
+
argv.push('--testing-account', opts.testingAccount.value);
|
|
3068
|
+
}
|
|
3069
|
+
if (Option.isSome(opts.profile))
|
|
3070
|
+
argv.push('--profile', opts.profile.value);
|
|
3071
|
+
if (Option.isSome(opts.targetOrigin)) {
|
|
3072
|
+
argv.push('--target-origin', opts.targetOrigin.value);
|
|
3073
|
+
}
|
|
3074
|
+
if (Option.isSome(opts.telemetryOrigin)) {
|
|
3075
|
+
argv.push('--telemetry-origin', opts.telemetryOrigin.value);
|
|
3076
|
+
}
|
|
3077
|
+
if (Option.isSome(opts.accountId))
|
|
3078
|
+
argv.push('--account-id', opts.accountId.value);
|
|
3079
|
+
if (Option.isSome(opts.projectId))
|
|
3080
|
+
argv.push('--project-id', opts.projectId.value);
|
|
3081
|
+
if (Option.isSome(opts.developerId)) {
|
|
3082
|
+
argv.push('--developer-id', opts.developerId.value);
|
|
3083
|
+
}
|
|
3084
|
+
if (Option.isSome(opts.userId))
|
|
3085
|
+
argv.push('--user-id', opts.userId.value);
|
|
3086
|
+
if (Option.isSome(opts.sessionId))
|
|
3087
|
+
argv.push('--session-id', opts.sessionId.value);
|
|
3088
|
+
if (Option.isSome(opts.session))
|
|
3089
|
+
argv.push('--session', opts.session.value);
|
|
3090
|
+
if (Option.isSome(opts.managedSession)) {
|
|
3091
|
+
argv.push('--managed-session', opts.managedSession.value);
|
|
3092
|
+
}
|
|
3093
|
+
if (Option.isSome(opts.ttlSeconds))
|
|
3094
|
+
argv.push('--ttl-seconds', opts.ttlSeconds.value);
|
|
3095
|
+
if (Option.isSome(opts.hsxLogPort))
|
|
3096
|
+
argv.push('--hsx-log-port', opts.hsxLogPort.value);
|
|
3097
|
+
if (Option.isSome(opts.controlPlaneUrl)) {
|
|
3098
|
+
argv.push('--control-plane-url', opts.controlPlaneUrl.value);
|
|
3099
|
+
}
|
|
3100
|
+
if (Option.isSome(opts.runtimeControlPlaneUrl)) {
|
|
3101
|
+
argv.push('--runtime-control-plane-url', opts.runtimeControlPlaneUrl.value);
|
|
3102
|
+
}
|
|
3103
|
+
for (const capability of opts.capability)
|
|
3104
|
+
argv.push('--capability', capability);
|
|
3105
|
+
for (const component of opts.component)
|
|
3106
|
+
argv.push('--component', component);
|
|
3107
|
+
for (const only of opts.only)
|
|
3108
|
+
argv.push('--only', only);
|
|
3109
|
+
for (const lane of opts.lane)
|
|
3110
|
+
argv.push('--lane', lane);
|
|
3111
|
+
for (const level of opts.level)
|
|
3112
|
+
argv.push('--level', level);
|
|
3113
|
+
if (Option.isSome(opts.search))
|
|
3114
|
+
argv.push('--search', opts.search.value);
|
|
3115
|
+
if (Option.isSome(opts.since))
|
|
3116
|
+
argv.push('--since', opts.since.value);
|
|
3117
|
+
if (Option.isSome(opts.cursor))
|
|
3118
|
+
argv.push('--cursor', opts.cursor.value);
|
|
3119
|
+
if (Option.isSome(opts.limit))
|
|
3120
|
+
argv.push('--limit', opts.limit.value);
|
|
3121
|
+
if (Option.isSome(opts.waitSeconds)) {
|
|
3122
|
+
argv.push('--wait-seconds', opts.waitSeconds.value);
|
|
3123
|
+
}
|
|
3124
|
+
if (opts.detach)
|
|
3125
|
+
argv.push('--detach');
|
|
3126
|
+
if (opts.follow)
|
|
3127
|
+
argv.push('--follow');
|
|
3128
|
+
if (opts.pick)
|
|
3129
|
+
argv.push('--pick');
|
|
3130
|
+
if (opts.noHubspot)
|
|
3131
|
+
argv.push('--no-hubspot');
|
|
3132
|
+
if (opts.noUiExtensions)
|
|
3133
|
+
argv.push('--no-ui-extensions');
|
|
3134
|
+
if (opts.yes)
|
|
3135
|
+
argv.push('--yes');
|
|
3136
|
+
if (opts.force)
|
|
3137
|
+
argv.push('--force');
|
|
3138
|
+
yield* runQuarantined(reportCommand, opts.json, () => devCommand({
|
|
3139
|
+
argv,
|
|
3140
|
+
root,
|
|
3141
|
+
json: opts.json,
|
|
3142
|
+
reporter: effectReporter.implementation,
|
|
3143
|
+
}));
|
|
3144
|
+
}));
|
|
3145
|
+
});
|
|
1897
3146
|
/**
|
|
1898
3147
|
* `hs-x dev invoke --remote`: dispatch at the DEPLOYED Worker, where install
|
|
1899
3148
|
* tokens live — handlers using ctx.hubspot run against the real portal. URL
|
|
@@ -1910,6 +3159,9 @@ async function devInvokeRemote(args) {
|
|
|
1910
3159
|
}
|
|
1911
3160
|
let url = explicitUrl;
|
|
1912
3161
|
if (!url) {
|
|
3162
|
+
if (!mapping) {
|
|
3163
|
+
return fail('HSX_E_DEV_UNKNOWN_CAPABILITY', `No capability \`${capabilityId}\` in .hs-x/manifest.json.`);
|
|
3164
|
+
}
|
|
1913
3165
|
const { resolveCloudflareCredentials } = await import('../cloudflare-kv.js');
|
|
1914
3166
|
const credentials = resolveCloudflareCredentials(argv);
|
|
1915
3167
|
const { loadCloudflarePointer } = await import('../cloudflare-pointer.js');
|