@hs-x/cli 0.2.7 → 0.3.6
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/LICENSE +202 -0
- package/README.md +25 -8
- package/dist/audit-interactive.d.ts +29 -0
- package/dist/audit-interactive.d.ts.map +1 -0
- package/dist/audit-interactive.js +156 -0
- package/dist/audit-interactive.js.map +1 -0
- package/dist/bin/hs-x.js.map +1 -1
- package/dist/checkpoint-interactive.d.ts +24 -0
- package/dist/checkpoint-interactive.d.ts.map +1 -0
- package/dist/checkpoint-interactive.js +65 -0
- package/dist/checkpoint-interactive.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +266 -39
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/kit.d.ts +1 -1
- package/dist/cli/kit.d.ts.map +1 -1
- package/dist/cli/kit.js +19 -3
- package/dist/cli/kit.js.map +1 -1
- package/dist/cloudflare-auth.d.ts +8 -0
- package/dist/cloudflare-auth.d.ts.map +1 -1
- package/dist/cloudflare-auth.js +12 -2
- package/dist/cloudflare-auth.js.map +1 -1
- package/dist/cloudflare-oauth-store.d.ts +9 -1
- package/dist/cloudflare-oauth-store.d.ts.map +1 -1
- package/dist/cloudflare-oauth-store.js +23 -4
- package/dist/cloudflare-oauth-store.js.map +1 -1
- package/dist/cloudflare-oauth.d.ts +1 -0
- package/dist/cloudflare-oauth.d.ts.map +1 -1
- package/dist/cloudflare-oauth.js +6 -2
- package/dist/cloudflare-oauth.js.map +1 -1
- package/dist/commands/account.d.ts +28 -13
- package/dist/commands/account.d.ts.map +1 -1
- package/dist/commands/account.js +219 -69
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/api.d.ts.map +1 -1
- package/dist/commands/api.js +7 -11
- package/dist/commands/api.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +13 -7
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +239 -125
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/control-plane-read.d.ts +71 -10
- package/dist/commands/control-plane-read.d.ts.map +1 -1
- package/dist/commands/control-plane-read.js +1035 -58
- package/dist/commands/control-plane-read.js.map +1 -1
- package/dist/commands/deploy-promote.d.ts +25 -3
- package/dist/commands/deploy-promote.d.ts.map +1 -1
- package/dist/commands/deploy-promote.js +314 -38
- package/dist/commands/deploy-promote.js.map +1 -1
- package/dist/commands/deploy.d.ts +69 -4
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +1512 -276
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +5 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +502 -107
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +14 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +131 -20
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/flags.d.ts +108 -6
- package/dist/commands/flags.d.ts.map +1 -1
- package/dist/commands/flags.js +996 -75
- package/dist/commands/flags.js.map +1 -1
- package/dist/commands/help-command.d.ts +14 -0
- package/dist/commands/help-command.d.ts.map +1 -1
- package/dist/commands/help-command.js +144 -44
- package/dist/commands/help-command.js.map +1 -1
- package/dist/commands/history.js +14 -10
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/init.d.ts +4 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +98 -17
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +2 -3
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +201 -101
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +22 -5
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/migrate.d.ts +37 -17
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +500 -73
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/rollback.d.ts +42 -2
- package/dist/commands/rollback.d.ts.map +1 -1
- package/dist/commands/rollback.js +422 -58
- package/dist/commands/rollback.js.map +1 -1
- package/dist/commands/secrets.d.ts +76 -3
- package/dist/commands/secrets.d.ts.map +1 -1
- package/dist/commands/secrets.js +328 -41
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/status.d.ts +13 -2
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +154 -17
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/unlink.d.ts +2 -2
- package/dist/commands/unlink.d.ts.map +1 -1
- package/dist/commands/unlink.js +2 -2
- package/dist/commands/unlink.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +4 -3
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.d.ts +12 -3
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +70 -8
- package/dist/commands/validate.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/control-plane-fetch.d.ts.map +1 -1
- package/dist/control-plane-fetch.js +5 -4
- package/dist/control-plane-fetch.js.map +1 -1
- package/dist/deploy-revision-capture.d.ts +48 -0
- package/dist/deploy-revision-capture.d.ts.map +1 -0
- package/dist/deploy-revision-capture.js +125 -0
- package/dist/deploy-revision-capture.js.map +1 -0
- package/dist/dev/event-bus.d.ts +5 -2
- package/dist/dev/event-bus.d.ts.map +1 -1
- package/dist/dev/event-bus.js +4 -1
- package/dist/dev/event-bus.js.map +1 -1
- package/dist/dev/hubspot-logs-poller.d.ts +35 -0
- package/dist/dev/hubspot-logs-poller.d.ts.map +1 -0
- package/dist/dev/hubspot-logs-poller.js +94 -0
- package/dist/dev/hubspot-logs-poller.js.map +1 -0
- package/dist/dev/invoke.d.ts +9 -1
- package/dist/dev/invoke.d.ts.map +1 -1
- package/dist/dev/invoke.js +69 -20
- package/dist/dev/invoke.js.map +1 -1
- package/dist/dev/keyboard.d.ts +31 -0
- package/dist/dev/keyboard.d.ts.map +1 -0
- package/dist/dev/keyboard.js +143 -0
- package/dist/dev/keyboard.js.map +1 -0
- package/dist/dev/log-server.d.ts +7 -0
- package/dist/dev/log-server.d.ts.map +1 -1
- package/dist/dev/log-server.js +68 -31
- package/dist/dev/log-server.js.map +1 -1
- package/dist/dev/migrated-card.d.ts +17 -0
- package/dist/dev/migrated-card.d.ts.map +1 -0
- package/dist/dev/migrated-card.js +114 -0
- package/dist/dev/migrated-card.js.map +1 -0
- package/dist/dev/stream-filter.d.ts +40 -0
- package/dist/dev/stream-filter.d.ts.map +1 -0
- package/dist/dev/stream-filter.js +93 -0
- package/dist/dev/stream-filter.js.map +1 -0
- package/dist/dev/stream-renderer.d.ts +34 -0
- package/dist/dev/stream-renderer.d.ts.map +1 -1
- package/dist/dev/stream-renderer.js +222 -8
- package/dist/dev/stream-renderer.js.map +1 -1
- package/dist/errors-registry.d.ts.map +1 -1
- package/dist/errors-registry.js +280 -36
- package/dist/errors-registry.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +8 -0
- package/dist/help.js.map +1 -1
- package/dist/hubspot-app-logs.d.ts +76 -0
- package/dist/hubspot-app-logs.d.ts.map +1 -0
- package/dist/hubspot-app-logs.js +323 -0
- package/dist/hubspot-app-logs.js.map +1 -0
- package/dist/hubspot-auth.d.ts +8 -2
- package/dist/hubspot-auth.d.ts.map +1 -1
- package/dist/hubspot-auth.js +9 -9
- package/dist/hubspot-auth.js.map +1 -1
- package/dist/hubspot-developer-client.js +119 -11
- package/dist/hubspot-developer-client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init/templates.d.ts +11 -1
- package/dist/init/templates.d.ts.map +1 -1
- package/dist/init/templates.js +228 -15
- package/dist/init/templates.js.map +1 -1
- package/dist/link-migration-journal.d.ts +131 -0
- package/dist/link-migration-journal.d.ts.map +1 -0
- package/dist/link-migration-journal.js +395 -0
- package/dist/link-migration-journal.js.map +1 -0
- package/dist/link-preflight.d.ts +78 -0
- package/dist/link-preflight.d.ts.map +1 -0
- package/dist/link-preflight.js +231 -0
- package/dist/link-preflight.js.map +1 -0
- package/dist/local-project-picker.d.ts +34 -0
- package/dist/local-project-picker.d.ts.map +1 -0
- package/dist/local-project-picker.js +121 -0
- package/dist/local-project-picker.js.map +1 -0
- package/dist/logs-interactive.d.ts +37 -0
- package/dist/logs-interactive.d.ts.map +1 -0
- package/dist/logs-interactive.js +110 -0
- package/dist/logs-interactive.js.map +1 -0
- package/dist/output-context.d.ts +13 -0
- package/dist/output-context.d.ts.map +1 -0
- package/dist/output-context.js +46 -0
- package/dist/output-context.js.map +1 -0
- package/dist/project-context.d.ts +55 -0
- package/dist/project-context.d.ts.map +1 -0
- package/dist/project-context.js +115 -0
- package/dist/project-context.js.map +1 -0
- package/dist/project-picker.d.ts +65 -0
- package/dist/project-picker.d.ts.map +1 -0
- package/dist/project-picker.js +227 -0
- package/dist/project-picker.js.map +1 -0
- package/dist/prompt.d.ts +8 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +81 -13
- package/dist/prompt.js.map +1 -1
- package/dist/reporter/human.d.ts +10 -2
- package/dist/reporter/human.d.ts.map +1 -1
- package/dist/reporter/human.js +82 -14
- package/dist/reporter/human.js.map +1 -1
- package/dist/reporter/index.d.ts.map +1 -1
- package/dist/reporter/index.js +7 -5
- package/dist/reporter/index.js.map +1 -1
- package/dist/reporter/json.d.ts +2 -1
- package/dist/reporter/json.d.ts.map +1 -1
- package/dist/reporter/json.js +10 -7
- package/dist/reporter/json.js.map +1 -1
- package/dist/reporter/style.d.ts +19 -0
- package/dist/reporter/style.d.ts.map +1 -1
- package/dist/reporter/style.js +126 -6
- package/dist/reporter/style.js.map +1 -1
- package/dist/reporter/types.d.ts +34 -4
- package/dist/reporter/types.d.ts.map +1 -1
- package/dist/reporter/types.js.map +1 -1
- package/dist/services/account-store.d.ts +3 -1
- package/dist/services/account-store.d.ts.map +1 -1
- package/dist/services/account-store.js +2 -1
- package/dist/services/account-store.js.map +1 -1
- package/dist/services/app-paths.d.ts.map +1 -1
- package/dist/services/app-paths.js +3 -1
- package/dist/services/app-paths.js.map +1 -1
- package/dist/services/cloudflare-auth.d.ts +1 -1
- package/dist/services/cloudflare-auth.d.ts.map +1 -1
- package/dist/services/cloudflare-auth.js +2 -2
- package/dist/services/cloudflare-auth.js.map +1 -1
- package/dist/services/cloudflare-kv.d.ts.map +1 -1
- package/dist/services/cloudflare-kv.js.map +1 -1
- package/dist/services/command-history.d.ts.map +1 -1
- package/dist/services/command-history.js.map +1 -1
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +1 -1
- package/dist/services/config.js.map +1 -1
- package/dist/services/control-plane.d.ts.map +1 -1
- package/dist/services/control-plane.js +3 -3
- package/dist/services/control-plane.js.map +1 -1
- package/dist/services/env-loader.d.ts.map +1 -1
- package/dist/services/env-loader.js +1 -3
- package/dist/services/env-loader.js.map +1 -1
- package/dist/services/http.d.ts.map +1 -1
- package/dist/services/http.js.map +1 -1
- package/dist/services/hubspot-dev-client.d.ts.map +1 -1
- package/dist/services/hubspot-dev-client.js +30 -0
- package/dist/services/hubspot-dev-client.js.map +1 -1
- package/dist/services/live.d.ts +2 -2
- package/dist/services/live.d.ts.map +1 -1
- package/dist/services/live.js +1 -1
- package/dist/services/live.js.map +1 -1
- package/dist/services/machine-id.d.ts.map +1 -1
- package/dist/services/machine-id.js.map +1 -1
- package/dist/services/reporter.d.ts +8 -5
- package/dist/services/reporter.d.ts.map +1 -1
- package/dist/services/reporter.js +6 -4
- package/dist/services/reporter.js.map +1 -1
- package/dist/services/tenant-state.d.ts.map +1 -1
- package/dist/services/tenant-state.js.map +1 -1
- package/dist/state-store.d.ts +4 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +15 -0
- package/dist/state-store.js.map +1 -1
- package/dist/status-drilldown.d.ts +24 -0
- package/dist/status-drilldown.d.ts.map +1 -0
- package/dist/status-drilldown.js +50 -0
- package/dist/status-drilldown.js.map +1 -0
- package/dist/tenant-state.d.ts.map +1 -1
- package/dist/tenant-state.js +3 -2
- package/dist/tenant-state.js.map +1 -1
- package/dist/token-key-custody.d.ts +91 -0
- package/dist/token-key-custody.d.ts.map +1 -0
- package/dist/token-key-custody.js +375 -0
- package/dist/token-key-custody.js.map +1 -0
- package/package.json +14 -15
package/dist/commands/dev.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
1
2
|
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
2
3
|
import { createServer } from 'node:http';
|
|
3
4
|
import { join } from 'node:path';
|
|
@@ -7,15 +8,16 @@ import * as Options from '@effect/cli/Options';
|
|
|
7
8
|
import { generateProjectArtifacts } from '@hs-x/codegen';
|
|
8
9
|
import { HttpClientRequest, Schema, schemas } from '@hs-x/types';
|
|
9
10
|
import { Effect, Option } from 'effect';
|
|
10
|
-
import { BRAND_ORANGE_SGR, CLI_VERSION } from '../constants.js';
|
|
11
11
|
import { exitWith } from '../cli-error.js';
|
|
12
|
+
import { cwdOption, jsonOption, runHandler, runQuarantined } from '../cli/kit.js';
|
|
13
|
+
import { formatConfigUrl, loadCliConfig } from '../config.js';
|
|
14
|
+
import { CLI_VERSION } from '../constants.js';
|
|
12
15
|
import { controlPlaneAuthHeaders } from '../control-plane-fetch.js';
|
|
13
16
|
import { executeCliHttp } from '../effect-http.js';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { Cwd } from '../services/cwd.js';
|
|
17
|
+
import { LocalProjectPickerCancelled, resolveLocalProject } from '../local-project-picker.js';
|
|
18
|
+
import { isInteractive, promptMultiSelect, promptSelect } from '../prompt.js';
|
|
17
19
|
import { createReporter } from '../reporter/index.js';
|
|
18
|
-
import {
|
|
20
|
+
import { Cwd } from '../services/cwd.js';
|
|
19
21
|
async function hostedHttp(input) {
|
|
20
22
|
const headers = { ...(input.headers ?? {}) };
|
|
21
23
|
let request = HttpClientRequest.make(input.method ?? 'GET')(input.url).pipe(HttpClientRequest.setHeaders(headers));
|
|
@@ -31,40 +33,46 @@ function isRecord(value) {
|
|
|
31
33
|
function isErrnoException(value) {
|
|
32
34
|
return value instanceof Error && typeof value.code === 'string';
|
|
33
35
|
}
|
|
34
|
-
function
|
|
36
|
+
function makeDevLogPaint(env, stderr) {
|
|
35
37
|
const isTTY = Boolean(stderr.isTTY);
|
|
36
38
|
const color = isTTY && env.NO_COLOR !== '1';
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
const wrap = (open) => (s) => (color ? `\x1b[${open}m${s}\x1b[0m` : s);
|
|
40
|
+
return {
|
|
41
|
+
dim: wrap('2'),
|
|
42
|
+
green: wrap('32'),
|
|
43
|
+
red: wrap('31'),
|
|
44
|
+
yellow: wrap('33'),
|
|
45
|
+
cyan: wrap('36'),
|
|
46
|
+
magenta: wrap('35'),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* One request-summary line (plus an optional `└ detail` second line), colored
|
|
51
|
+
* via `paint`. Shared by the direct fallback logger and the event-bus lane so
|
|
52
|
+
* the columns look identical wherever the line lands.
|
|
53
|
+
*/
|
|
54
|
+
function formatDevLogEntry(entry, paint, defaultAccountId) {
|
|
55
|
+
const { method, path, status, durationMs, source = 'local', accountId, detail } = entry;
|
|
56
|
+
const statusColor = status >= 500 ? paint.red : status >= 400 ? paint.yellow : paint.green;
|
|
57
|
+
const symbol = status >= 400 ? '✗' : '✓';
|
|
58
|
+
const methodStr = method.padEnd(5);
|
|
59
|
+
const pathStr = path.length > 35 ? path : path.padEnd(35);
|
|
60
|
+
const statusStr = statusColor(String(status).padStart(3));
|
|
61
|
+
const durationStr = `${durationMs}ms`.padStart(7);
|
|
62
|
+
const effectiveAccountId = accountId ?? defaultAccountId;
|
|
63
|
+
const acct = effectiveAccountId ? ` ${paint.dim(effectiveAccountId)}` : '';
|
|
64
|
+
const sourceColor = source === 'prod' ? paint.magenta : paint.cyan;
|
|
65
|
+
const sourceLabel = sourceColor(source.padEnd(5));
|
|
66
|
+
const line = `${methodStr} ${pathStr} ${statusStr} ${durationStr}${acct} ${statusColor(symbol)} ${sourceLabel}`;
|
|
67
|
+
return detail ? `${line}\n ${paint.dim('└')} ${paint.dim(detail)}` : line;
|
|
43
68
|
}
|
|
44
69
|
function makeDevRequestLogger(env, stderr, defaultAccountId) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const cyan = (s) => (color ? `\x1b[36m${s}\x1b[0m` : s);
|
|
52
|
-
const magenta = (s) => (color ? `\x1b[35m${s}\x1b[0m` : s);
|
|
53
|
-
return ({ method, path, status, durationMs, source = 'local', accountId, detail }) => {
|
|
54
|
-
const statusColor = status >= 500 ? red : status >= 400 ? yellow : green;
|
|
55
|
-
const symbol = status >= 400 ? '✗' : '✓';
|
|
56
|
-
const methodStr = method.padEnd(5);
|
|
57
|
-
const pathStr = path.length > 35 ? path : path.padEnd(35);
|
|
58
|
-
const statusStr = statusColor(String(status).padStart(3));
|
|
59
|
-
const durationStr = `${durationMs}ms`.padStart(7);
|
|
60
|
-
const effectiveAccountId = accountId ?? defaultAccountId;
|
|
61
|
-
const acct = effectiveAccountId ? ` ${dim(effectiveAccountId)}` : '';
|
|
62
|
-
const sourceColor = source === 'prod' ? magenta : cyan;
|
|
63
|
-
const sourceLabel = sourceColor(source.padEnd(5));
|
|
64
|
-
stderr.write(` ${methodStr} ${pathStr} ${statusStr} ${durationStr}${acct} ${statusColor(symbol)} ${sourceLabel}\n`);
|
|
65
|
-
if (detail) {
|
|
66
|
-
stderr.write(` ${dim('└')} ${dim(detail)}\n`);
|
|
67
|
-
}
|
|
70
|
+
const paint = makeDevLogPaint(env, stderr);
|
|
71
|
+
return (entry) => {
|
|
72
|
+
const [line, detailLine] = formatDevLogEntry(entry, paint, defaultAccountId).split('\n');
|
|
73
|
+
stderr.write(` ${line}\n`);
|
|
74
|
+
if (detailLine)
|
|
75
|
+
stderr.write(` ${detailLine}\n`);
|
|
68
76
|
};
|
|
69
77
|
}
|
|
70
78
|
async function resolveAccountIdOrPrompt(input) {
|
|
@@ -87,14 +95,18 @@ async function resolveAccountIdOrPrompt(input) {
|
|
|
87
95
|
if (ids.length === 0)
|
|
88
96
|
return { fromPrompt: false };
|
|
89
97
|
const defaultId = store.defaultAccountId;
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
// A configured default — or a single account — is an answer, not a question:
|
|
99
|
+
// use it silently, exactly like non-interactive runs. `--account-id` and
|
|
100
|
+
// `hs-x accounts switch` override; the picker remains only for the genuinely
|
|
101
|
+
// ambiguous case (multiple accounts, no default).
|
|
102
|
+
if (defaultId && ids.includes(defaultId)) {
|
|
103
|
+
return { accountId: defaultId, fromPrompt: false };
|
|
104
|
+
}
|
|
105
|
+
const soleAccount = ids.length === 1 ? ids[0] : undefined;
|
|
106
|
+
if (soleAccount) {
|
|
107
|
+
return { accountId: soleAccount, fromPrompt: false };
|
|
108
|
+
}
|
|
109
|
+
if (input.json || !isInteractive()) {
|
|
98
110
|
return { fromPrompt: false };
|
|
99
111
|
}
|
|
100
112
|
const picked = await promptSelect({
|
|
@@ -113,15 +125,6 @@ async function resolveAccountIdOrPrompt(input) {
|
|
|
113
125
|
});
|
|
114
126
|
return picked ? { accountId: picked, fromPrompt: true } : { fromPrompt: false };
|
|
115
127
|
}
|
|
116
|
-
async function promptMissingText(message, current, validate) {
|
|
117
|
-
if (current && current.length > 0)
|
|
118
|
-
return current;
|
|
119
|
-
const answer = await promptText({
|
|
120
|
-
message,
|
|
121
|
-
...(validate ? { validate } : {}),
|
|
122
|
-
});
|
|
123
|
-
return answer === undefined ? undefined : answer;
|
|
124
|
-
}
|
|
125
128
|
async function readHsxConfigProjectId(root) {
|
|
126
129
|
try {
|
|
127
130
|
const contents = await readFile(join(root, 'hsx.config.ts'), 'utf8');
|
|
@@ -144,6 +147,36 @@ function cancelledResult(argv, command) {
|
|
|
144
147
|
reporter.info('Cancelled.');
|
|
145
148
|
return { exitCode: 130 };
|
|
146
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* The default portal for a dev session, discovered rather than prompted:
|
|
152
|
+
* the resolved HS-X account's bound portal first (that pairing is explicit),
|
|
153
|
+
* then the HubSpot CLI config's default account — the same portal
|
|
154
|
+
* `hs project dev` would use. Undefined means local-only dev.
|
|
155
|
+
*/
|
|
156
|
+
async function resolveDefaultPortalId(input) {
|
|
157
|
+
try {
|
|
158
|
+
const { loadStore } = await import('../account-store.js');
|
|
159
|
+
const store = await loadStore();
|
|
160
|
+
const account = (input.accountId ? store.accounts[input.accountId] : undefined) ??
|
|
161
|
+
(store.defaultAccountId ? store.accounts[store.defaultAccountId] : undefined);
|
|
162
|
+
if (account?.hubspotPortalId)
|
|
163
|
+
return String(account.hubspotPortalId);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// fall through to the HubSpot CLI config
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const { discoverHubSpotAccounts } = await import('../hubspot-auth.js');
|
|
170
|
+
const discovered = await discoverHubSpotAccounts(input.root);
|
|
171
|
+
const account = discovered?.accounts.find((a) => a.isDefault) ?? discovered?.accounts[0];
|
|
172
|
+
if (account)
|
|
173
|
+
return String(account.portalId);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// no portal anywhere — run local-only
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
147
180
|
export async function devCommand({ argv, root, json, }) {
|
|
148
181
|
if (argv[1] === 'cleanup') {
|
|
149
182
|
return await devCleanupCommand({ argv, json });
|
|
@@ -151,6 +184,24 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
151
184
|
if (argv[1] === 'status') {
|
|
152
185
|
return await devStatusCommand({ argv, json });
|
|
153
186
|
}
|
|
187
|
+
if (!json && isInteractive()) {
|
|
188
|
+
try {
|
|
189
|
+
const selected = await resolveLocalProject({
|
|
190
|
+
root,
|
|
191
|
+
allowPrompt: true,
|
|
192
|
+
message: 'Which local project should run in dev mode?',
|
|
193
|
+
prompts: { select: promptSelect },
|
|
194
|
+
});
|
|
195
|
+
if (selected && selected.root !== root) {
|
|
196
|
+
return await devCommand({ argv, root: selected.root, json });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (error instanceof LocalProjectPickerCancelled)
|
|
201
|
+
return cancelledResult(argv, 'dev');
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
154
205
|
if (argv[1] === 'invoke') {
|
|
155
206
|
return await devInvokeCommand({ argv, root, json });
|
|
156
207
|
}
|
|
@@ -169,7 +220,9 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
169
220
|
const workers = await discoverWorkerManifests(root);
|
|
170
221
|
await generateProjectArtifacts({ root, workers });
|
|
171
222
|
let portalId = resolveFlag(argv, '--portal');
|
|
172
|
-
|
|
223
|
+
const targetOrigin = resolveFlag(argv, '--target-origin');
|
|
224
|
+
const telemetryOrigin = resolveFlag(argv, '--telemetry-origin');
|
|
225
|
+
const forceDevOverride = argv.includes('--force');
|
|
173
226
|
const resolvedDevAcct = await resolveAccountIdOrPrompt({
|
|
174
227
|
argv,
|
|
175
228
|
json,
|
|
@@ -191,11 +244,10 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
191
244
|
// Control plane is a single deployed instance; default to it. Overrides
|
|
192
245
|
// (env or flag) exist for self-host / local-cp test runs only.
|
|
193
246
|
const controlPlaneUrl = formatConfigUrl(devCliConfig.controlPlaneUrl);
|
|
194
|
-
const developerId = resolveFlag(argv, '--developer-id') ??
|
|
195
|
-
devCliConfig.userId;
|
|
247
|
+
const developerId = resolveFlag(argv, '--developer-id') ?? devCliConfig.userId;
|
|
196
248
|
const sessionId = resolveFlag(argv, '--session-id') ??
|
|
197
249
|
`devsess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
198
|
-
|
|
250
|
+
const ttlSeconds = Number(resolveFlag(argv, '--ttl-seconds') ?? '7200');
|
|
199
251
|
const selectedCapabilities = resolveFlags(argv, '--capability');
|
|
200
252
|
const allCapabilities = workers.flatMap((worker) => worker.capabilities
|
|
201
253
|
.filter((capability) => capability.runtimeNeeds.includes('worker'))
|
|
@@ -205,37 +257,23 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
205
257
|
worker: worker.name,
|
|
206
258
|
})));
|
|
207
259
|
const allCapabilityIds = allCapabilities.map((c) => c.id);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
return 'Enter a valid URL.';
|
|
221
|
-
}
|
|
260
|
+
// Zero-prompt startup, like `hs project dev`: the portal id resolves from
|
|
261
|
+
// --portal → the HS-X account store → the HubSpot CLI config default; the
|
|
262
|
+
// override defaults to observe mode (no public target origin needed) and
|
|
263
|
+
// the TTL to 2h. --target-origin/--ttl-seconds stay as advanced flags, and
|
|
264
|
+
// nothing here ever blocks on a question.
|
|
265
|
+
if (portalId && !/^\d+$/.test(portalId)) {
|
|
266
|
+
const reporter = createReporter({ command: 'dev', argv });
|
|
267
|
+
const code = 'HSX_E_INPUT_BAD_FIELD';
|
|
268
|
+
reporter.error(code, `--portal must be numeric (got \`${portalId}\`).`, {
|
|
269
|
+
hint: 'Example: hs-x dev --portal 46993937',
|
|
270
|
+
docs_url: `https://hs-x.dev/errors/${code}`,
|
|
222
271
|
});
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
default: '7200',
|
|
229
|
-
options: [
|
|
230
|
-
{ value: '3600', label: '1 hour', description: 'short local session' },
|
|
231
|
-
{ value: '7200', label: '2 hours', description: 'default' },
|
|
232
|
-
{ value: '14400', label: '4 hours', description: 'long pairing/debugging session' },
|
|
233
|
-
],
|
|
234
|
-
});
|
|
235
|
-
if (ttl === undefined)
|
|
236
|
-
return cancelledResult(argv, 'dev');
|
|
237
|
-
ttlSeconds = Number(ttl);
|
|
238
|
-
}
|
|
272
|
+
reporter.done(undefined, 10);
|
|
273
|
+
return { exitCode: 10 };
|
|
274
|
+
}
|
|
275
|
+
if (!portalId) {
|
|
276
|
+
portalId = await resolveDefaultPortalId({ root, accountId });
|
|
239
277
|
}
|
|
240
278
|
// Probe the HubSpot project IR up front so the picker can show UI-extension
|
|
241
279
|
// nodes alongside HS-X capabilities. No-op if hsproject.json isn't present.
|
|
@@ -250,6 +288,7 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
250
288
|
const totalCandidates = allCapabilities.length + (uiExtensionNodes?.uids.length ?? 0);
|
|
251
289
|
if (portalId &&
|
|
252
290
|
!json &&
|
|
291
|
+
argv.includes('--pick') &&
|
|
253
292
|
selectedCapabilities.length === 0 &&
|
|
254
293
|
totalCandidates > 1 &&
|
|
255
294
|
isInteractive()) {
|
|
@@ -284,16 +323,23 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
284
323
|
.map((v) => v.slice(UI_PREFIX.length));
|
|
285
324
|
}
|
|
286
325
|
let devOverrideRegistration;
|
|
326
|
+
let forcedDevOverrideCleanup = [];
|
|
287
327
|
// We register a dev override whenever the user has a HubSpot portal in
|
|
288
328
|
// play. `hs-x connect` gives us the portal id implicitly; only edge cases
|
|
289
329
|
// (no connected account, no --portal flag) skip registration.
|
|
290
330
|
const shouldRegisterOverride = Boolean(portalId);
|
|
291
331
|
if (shouldRegisterOverride) {
|
|
332
|
+
const portalIdForOverride = portalId;
|
|
333
|
+
if (!portalIdForOverride) {
|
|
334
|
+
throw new Error('Dev override needs a HubSpot portal id. Pass --portal <id>.');
|
|
335
|
+
}
|
|
292
336
|
if (!accountId) {
|
|
293
|
-
throw new Error('Dev override needs a connected HubSpot account.
|
|
337
|
+
throw new Error('Dev override needs a connected HubSpot account. An authenticated personal access key ' +
|
|
338
|
+
'(what `hs-x doctor` reports) is not the same thing — dev needs the account bound to ' +
|
|
339
|
+
'this machine. Run `hs-x connect hubspot` or pass --account-id.');
|
|
294
340
|
}
|
|
295
341
|
if (!projectId) {
|
|
296
|
-
throw new Error('Could not
|
|
342
|
+
throw new Error('Could not find an HS-X source project. Run from a directory containing `hsx.config.ts`, pass --cwd <project>, or set --project-id/HSX_PROJECT_ID for scripted override registration.');
|
|
297
343
|
}
|
|
298
344
|
if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {
|
|
299
345
|
throw new Error('--ttl-seconds must be a positive number.');
|
|
@@ -309,13 +355,23 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
309
355
|
if (!targetOrigin) {
|
|
310
356
|
throw new Error('Dev override registration in --json mode requires --target-origin <url>.');
|
|
311
357
|
}
|
|
358
|
+
forcedDevOverrideCleanup = forceDevOverride
|
|
359
|
+
? await forceClearDevOverrideSessions({
|
|
360
|
+
controlPlaneUrl,
|
|
361
|
+
accountId,
|
|
362
|
+
userId: developerId,
|
|
363
|
+
projectId,
|
|
364
|
+
portalId: portalIdForOverride,
|
|
365
|
+
capabilityIds,
|
|
366
|
+
})
|
|
367
|
+
: [];
|
|
312
368
|
devOverrideRegistration = await requestDevOverrideRegistration({
|
|
313
369
|
controlPlaneUrl,
|
|
314
370
|
accountId,
|
|
315
371
|
userId: developerId,
|
|
316
372
|
request: Schema.decodeUnknownSync(schemas.DevOverrideRegisterRequest)({
|
|
317
373
|
projectId,
|
|
318
|
-
portalId,
|
|
374
|
+
portalId: portalIdForOverride,
|
|
319
375
|
targetOrigin,
|
|
320
376
|
developerId,
|
|
321
377
|
sessionId,
|
|
@@ -331,6 +387,10 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
331
387
|
command: 'dev',
|
|
332
388
|
url: `http://127.0.0.1:${port}`,
|
|
333
389
|
workers,
|
|
390
|
+
...(telemetryOrigin ? { telemetryOrigin } : {}),
|
|
391
|
+
...(forcedDevOverrideCleanup.length > 0
|
|
392
|
+
? { forcedCleanup: forcedDevOverrideCleanup }
|
|
393
|
+
: {}),
|
|
334
394
|
...(devOverrideRegistration
|
|
335
395
|
? { mode: 'control-plane-dev-override', devOverrideRegistration }
|
|
336
396
|
: {}),
|
|
@@ -338,7 +398,17 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
338
398
|
return { exitCode: 0 };
|
|
339
399
|
}
|
|
340
400
|
const startupBegan = Date.now();
|
|
341
|
-
|
|
401
|
+
// Requests print directly until the log-stream bus exists, then route
|
|
402
|
+
// through it as the `request` lane — so they respect the active filter and
|
|
403
|
+
// never trample the pinned status line.
|
|
404
|
+
const directRequestLog = makeDevRequestLogger(process.env, process.stderr, accountId);
|
|
405
|
+
let requestSink;
|
|
406
|
+
const renderRequestLog = (entry) => {
|
|
407
|
+
if (requestSink)
|
|
408
|
+
requestSink(entry);
|
|
409
|
+
else
|
|
410
|
+
directRequestLog(entry);
|
|
411
|
+
};
|
|
342
412
|
const server = createServer((request, response) => {
|
|
343
413
|
const requestStarted = Date.now();
|
|
344
414
|
const method = request.method ?? 'GET';
|
|
@@ -362,6 +432,79 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
362
432
|
response.end(JSON.stringify({ workers }, null, 2));
|
|
363
433
|
return;
|
|
364
434
|
}
|
|
435
|
+
// Migrated UI-extension compatibility bridge. During `hs-x dev` its
|
|
436
|
+
// temporary trycloudflare.com origin points here, preserving the same
|
|
437
|
+
// /_hsx/cards/<id> contract as the deployed runtime while dispatching the
|
|
438
|
+
// current local handler body.
|
|
439
|
+
const cardMatch = /^\/_hsx\/cards\/([^/?]+)$/.exec(path.split('?', 1)[0] ?? path);
|
|
440
|
+
if (cardMatch && method === 'POST') {
|
|
441
|
+
const capabilityId = decodeURIComponent(cardMatch[1] ?? '');
|
|
442
|
+
const isCardBackend = allCapabilities.some((capability) => capability.id === capabilityId && capability.kind === 'card-backend');
|
|
443
|
+
if (!isCardBackend) {
|
|
444
|
+
response.writeHead(404, { 'content-type': 'application/json' });
|
|
445
|
+
response.end(JSON.stringify({ ok: false, error: 'unknown_card_backend', capabilityId }));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const chunks = [];
|
|
449
|
+
request.on('data', (chunk) => chunks.push(chunk));
|
|
450
|
+
request.on('end', () => {
|
|
451
|
+
void (async () => {
|
|
452
|
+
const respond = (status, body) => {
|
|
453
|
+
response.writeHead(status, { 'content-type': 'application/json' });
|
|
454
|
+
response.end(JSON.stringify(body, null, 2));
|
|
455
|
+
};
|
|
456
|
+
let parsed;
|
|
457
|
+
try {
|
|
458
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
459
|
+
const value = raw.length > 0 ? JSON.parse(raw) : {};
|
|
460
|
+
parsed = isRecord(value) ? value : {};
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
respond(400, { ok: false, error: 'invalid_json' });
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
const { invokeCapabilityLocally, DevInvokeError, describeInvocationError } = await import('../dev/invoke.js');
|
|
468
|
+
try {
|
|
469
|
+
const outcome = await invokeCapabilityLocally({
|
|
470
|
+
root,
|
|
471
|
+
capabilityId,
|
|
472
|
+
...(parsed.input !== undefined ? { input: parsed.input } : {}),
|
|
473
|
+
...(parsed.enrolledObject !== undefined
|
|
474
|
+
? { enrolledObject: parsed.enrolledObject }
|
|
475
|
+
: {}),
|
|
476
|
+
...(parsed.install !== undefined ? { install: parsed.install } : {}),
|
|
477
|
+
});
|
|
478
|
+
respond(outcome.status, outcome.body ?? null);
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
if (error instanceof DevInvokeError) {
|
|
482
|
+
respond(error.code === 'HSX_E_DEV_UNKNOWN_CAPABILITY' ? 404 : 500, {
|
|
483
|
+
ok: false,
|
|
484
|
+
error: error.code,
|
|
485
|
+
message: error.message,
|
|
486
|
+
...(error.hint ? { hint: error.hint } : {}),
|
|
487
|
+
});
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
respond(500, {
|
|
491
|
+
ok: false,
|
|
492
|
+
error: 'HSX_E_INTERNAL',
|
|
493
|
+
message: describeInvocationError(error),
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
respond(500, {
|
|
499
|
+
ok: false,
|
|
500
|
+
error: 'HSX_E_INTERNAL',
|
|
501
|
+
message: error instanceof Error ? error.message : String(error),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
})();
|
|
505
|
+
});
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
365
508
|
// Invocation harness over HTTP (P2): same engine as `hs-x dev invoke`,
|
|
366
509
|
// for agents/CI/programs. Body: {input?, enrolledObject?, install?}.
|
|
367
510
|
const invokeMatch = /^\/_hsx\/invoke\/([^/?]+)$/.exec(path.split('?', 1)[0] ?? path);
|
|
@@ -472,6 +615,25 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
472
615
|
const capabilityCount = workers.reduce((c, w) => c + w.capabilities.length, 0);
|
|
473
616
|
const subject = devOverrideRegistration ? `portal ${devOverrideRegistration.portalId}` : 'local';
|
|
474
617
|
const url = `http://127.0.0.1:${port}`;
|
|
618
|
+
// A freshly migrated card has no deployed Worker origin yet. Give its
|
|
619
|
+
// generated compatibility shim a public quick-tunnel origin that points at
|
|
620
|
+
// this local server, and stamp the source/allow-list before HubSpot's
|
|
621
|
+
// UI-extension dev session translates and bundles the project.
|
|
622
|
+
const { hasMigratedCardServerlessShim, prepareMigratedCardDevArtifacts } = await import('../dev/migrated-card.js');
|
|
623
|
+
const shouldStartMigratedCardTunnel = Boolean(selectedUiNodeUids?.length) && (await hasMigratedCardServerlessShim(root));
|
|
624
|
+
const migratedCardTunnel = shouldStartMigratedCardTunnel
|
|
625
|
+
? await startHsxDevTunnel({
|
|
626
|
+
port,
|
|
627
|
+
reporter,
|
|
628
|
+
label: 'Migrated card backend tunnel',
|
|
629
|
+
})
|
|
630
|
+
: undefined;
|
|
631
|
+
const migratedCardDevArtifacts = migratedCardTunnel
|
|
632
|
+
? await prepareMigratedCardDevArtifacts({
|
|
633
|
+
root,
|
|
634
|
+
runtimeOrigin: migratedCardTunnel.url,
|
|
635
|
+
})
|
|
636
|
+
: undefined;
|
|
475
637
|
// Optional: start a HubSpot UI-extension dev session if this directory
|
|
476
638
|
// contains an hsproject.json AND the user selected at least one UI node.
|
|
477
639
|
const hubSpotDevSession = selectedUiNodeUids && selectedUiNodeUids.length > 0
|
|
@@ -488,22 +650,64 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
488
650
|
// events) from inside the card iframe and surfaces them in the terminal.
|
|
489
651
|
// Stable default port 9099 so card code can hard-default the dev-log URL.
|
|
490
652
|
const hsxLogPort = Number(resolveFlag(argv, '--hsx-log-port') ?? '9099');
|
|
653
|
+
// `--only` selectors pre-filter the stream (same grammar as the interactive
|
|
654
|
+
// keyboard, see dev/stream-filter.ts). Invalid selectors are a usage error.
|
|
655
|
+
const { parseOnlySelectors } = await import('../dev/stream-filter.js');
|
|
656
|
+
const onlyParsed = parseOnlySelectors(resolveFlags(argv, '--only'));
|
|
657
|
+
if (!onlyParsed.ok) {
|
|
658
|
+
const code = 'HSX_E_DEV_BAD_ONLY';
|
|
659
|
+
reporter.error(code, onlyParsed.message, {
|
|
660
|
+
hint: 'Selectors: frontend, backend, request, hubspot, errors, warnings, cap:<id>, or free text. Repeat --only to combine.',
|
|
661
|
+
docs_url: `https://hs-x.dev/errors/${code}`,
|
|
662
|
+
});
|
|
663
|
+
reporter.done(undefined, 2);
|
|
664
|
+
return { exitCode: 2 };
|
|
665
|
+
}
|
|
491
666
|
const devLogStream = await startHsxDevLogStream({
|
|
492
667
|
port: hsxLogPort,
|
|
668
|
+
root,
|
|
493
669
|
reporter,
|
|
670
|
+
filter: onlyParsed.filter,
|
|
494
671
|
});
|
|
672
|
+
if (devLogStream) {
|
|
673
|
+
const paint = makeDevLogPaint(process.env, process.stderr);
|
|
674
|
+
requestSink = (entry) => {
|
|
675
|
+
devLogStream.bus.emit({
|
|
676
|
+
lane: 'request',
|
|
677
|
+
level: entry.status >= 500 ? 'error' : entry.status >= 400 ? 'warn' : 'info',
|
|
678
|
+
ts: Date.now(),
|
|
679
|
+
msg: formatDevLogEntry(entry, paint, accountId),
|
|
680
|
+
});
|
|
681
|
+
};
|
|
682
|
+
}
|
|
495
683
|
// Auto-managed Cloudflare quick tunnel — gives the deployed Worker a public
|
|
496
684
|
// URL to POST telemetry envelopes to the local sidecar. The developer must
|
|
497
685
|
// never have to run cloudflared themselves; that's a non-negotiable default.
|
|
498
|
-
const devTunnel = shouldRegisterOverride
|
|
686
|
+
const devTunnel = shouldRegisterOverride && !telemetryOrigin
|
|
499
687
|
? await startHsxDevTunnel({ port: hsxLogPort, reporter })
|
|
500
688
|
: undefined;
|
|
689
|
+
const telemetryBaseUrl = telemetryOrigin ?? devTunnel?.url;
|
|
501
690
|
// Now register the dev override. If the developer didn't pass
|
|
502
691
|
// `--target-origin`, default to observe mode and let the deployed Worker
|
|
503
692
|
// run as normal — we only need the tunnel for the telemetry tee.
|
|
504
693
|
if (shouldRegisterOverride && controlPlaneUrl && portalId && accountId && projectId) {
|
|
694
|
+
if (forceDevOverride) {
|
|
695
|
+
forcedDevOverrideCleanup = await forceClearDevOverrideSessions({
|
|
696
|
+
controlPlaneUrl,
|
|
697
|
+
accountId,
|
|
698
|
+
userId: developerId,
|
|
699
|
+
projectId,
|
|
700
|
+
portalId,
|
|
701
|
+
capabilityIds,
|
|
702
|
+
});
|
|
703
|
+
if (forcedDevOverrideCleanup.length > 0) {
|
|
704
|
+
reporter
|
|
705
|
+
.step(`Cleared ${forcedDevOverrideCleanup.reduce((count, item) => count + item.cleared.length, 0)} existing dev override${forcedDevOverrideCleanup.length === 1 ? '' : 's'}`)
|
|
706
|
+
.ok();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
505
709
|
const overrideMode = targetOrigin ? 'proxy' : 'observe';
|
|
506
|
-
const effectiveTargetOrigin = targetOrigin ??
|
|
710
|
+
const effectiveTargetOrigin = targetOrigin ?? telemetryBaseUrl ?? `http://127.0.0.1:${hsxLogPort}`;
|
|
507
711
|
devOverrideRegistration = await requestDevOverrideRegistration({
|
|
508
712
|
controlPlaneUrl,
|
|
509
713
|
accountId,
|
|
@@ -517,18 +721,86 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
517
721
|
capabilityIds,
|
|
518
722
|
ttlSeconds,
|
|
519
723
|
mode: overrideMode,
|
|
520
|
-
...(
|
|
724
|
+
...(telemetryBaseUrl && devLogStream
|
|
725
|
+
? { telemetryUrl: devTelemetryUrl(telemetryBaseUrl, devLogStream.authToken) }
|
|
726
|
+
: {}),
|
|
521
727
|
}),
|
|
522
728
|
});
|
|
523
729
|
}
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
reporter.
|
|
527
|
-
|
|
528
|
-
|
|
730
|
+
// Railed startup checklist: identity header, then one row per surface.
|
|
731
|
+
reporter.header(subject);
|
|
732
|
+
reporter.rows([
|
|
733
|
+
{ status: 'ok', key: 'server', value: url, detail: `ready in ${elapsed}ms` },
|
|
734
|
+
{
|
|
735
|
+
status: 'ok',
|
|
736
|
+
key: 'workers',
|
|
737
|
+
value: `${workers.length} worker${workers.length === 1 ? '' : 's'} · ${capabilityCount} capabilit${capabilityCount === 1 ? 'y' : 'ies'}`,
|
|
738
|
+
},
|
|
739
|
+
...(devOverrideRegistration
|
|
740
|
+
? [
|
|
741
|
+
{
|
|
742
|
+
status: 'ok',
|
|
743
|
+
key: 'portal',
|
|
744
|
+
value: String(devOverrideRegistration.portalId),
|
|
745
|
+
detail: 'dev override registered',
|
|
746
|
+
},
|
|
747
|
+
]
|
|
748
|
+
: []),
|
|
749
|
+
...(hubSpotDevSession
|
|
750
|
+
? [
|
|
751
|
+
{
|
|
752
|
+
status: 'ok',
|
|
753
|
+
key: 'ui-extensions',
|
|
754
|
+
value: `session ${hubSpotDevSession.sessionId}`,
|
|
755
|
+
},
|
|
756
|
+
]
|
|
757
|
+
: []),
|
|
758
|
+
...(devLogStream
|
|
759
|
+
? [{ status: 'ok', key: 'logs', value: `streaming on ${devLogStream.url}` }]
|
|
760
|
+
: []),
|
|
761
|
+
...(devTunnel ? [{ status: 'ok', key: 'tunnel', value: devTunnel.url }] : []),
|
|
762
|
+
...(migratedCardDevArtifacts
|
|
763
|
+
? [
|
|
764
|
+
{
|
|
765
|
+
status: 'ok',
|
|
766
|
+
key: 'card backends',
|
|
767
|
+
value: migratedCardDevArtifacts.runtimeOrigin,
|
|
768
|
+
detail: 'temporary dev origin',
|
|
769
|
+
},
|
|
770
|
+
]
|
|
771
|
+
: []),
|
|
772
|
+
...(telemetryOrigin
|
|
773
|
+
? [{ status: 'ok', key: 'telemetry', value: telemetryOrigin }]
|
|
774
|
+
: []),
|
|
775
|
+
]);
|
|
776
|
+
if (!portalId) {
|
|
777
|
+
reporter.info('No HubSpot portal found — running local-only. Pass `--portal <id>` (or set a default with `hs auth` / `hs-x login`) to mirror a live portal.');
|
|
778
|
+
}
|
|
779
|
+
// The pinned status line carries the key map (incl. quit); repeating it in
|
|
780
|
+
// an info line doubled the hints. Only sessions without the status line
|
|
781
|
+
// (non-TTY, NO_COLOR) get a stop instruction.
|
|
782
|
+
if (!devLogStream?.renderer.hasStatusLine()) {
|
|
783
|
+
reporter.info('');
|
|
784
|
+
reporter.info('Press Ctrl+C to stop');
|
|
785
|
+
}
|
|
529
786
|
reporter.info('');
|
|
787
|
+
// Interactive stream controls — single keystrokes filter/pause the unified
|
|
788
|
+
// log stream. No-op when stdin is not a TTY.
|
|
789
|
+
const keyboard = devLogStream
|
|
790
|
+
? (await import('../dev/keyboard.js')).attachDevKeyboard({
|
|
791
|
+
renderer: devLogStream.renderer,
|
|
792
|
+
onQuit: () => process.kill(process.pid, 'SIGINT'),
|
|
793
|
+
})
|
|
794
|
+
: undefined;
|
|
795
|
+
devLogStream?.renderer.ready();
|
|
530
796
|
await new Promise((resolveClose) => {
|
|
531
797
|
const close = async () => {
|
|
798
|
+
keyboard?.stop();
|
|
799
|
+
// Signal-driven shutdown can terminate the Bun process before awaited
|
|
800
|
+
// cleanup settles. Restore temporarily stamped migration sources
|
|
801
|
+
// synchronously first; the remaining network/process cleanup is
|
|
802
|
+
// best-effort and may safely await.
|
|
803
|
+
migratedCardDevArtifacts?.restoreSync();
|
|
532
804
|
if (hubSpotDevSession) {
|
|
533
805
|
try {
|
|
534
806
|
await hubSpotDevSession.cleanup();
|
|
@@ -552,6 +824,22 @@ export async function devCommand({ argv, root, json, }) {
|
|
|
552
824
|
// best-effort: tunnel cleanup failures should not block exit.
|
|
553
825
|
}
|
|
554
826
|
}
|
|
827
|
+
if (migratedCardDevArtifacts) {
|
|
828
|
+
try {
|
|
829
|
+
await migratedCardDevArtifacts.restore();
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
// best-effort: source restoration should not block shutdown.
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (migratedCardTunnel) {
|
|
836
|
+
try {
|
|
837
|
+
await migratedCardTunnel.cleanup();
|
|
838
|
+
}
|
|
839
|
+
catch {
|
|
840
|
+
// best-effort: tunnel cleanup failures should not block exit.
|
|
841
|
+
}
|
|
842
|
+
}
|
|
555
843
|
if (devLogStream) {
|
|
556
844
|
try {
|
|
557
845
|
await devLogStream.cleanup();
|
|
@@ -575,11 +863,44 @@ async function startHsxDevLogStream(input) {
|
|
|
575
863
|
import('../dev/stream-renderer.js'),
|
|
576
864
|
]);
|
|
577
865
|
const bus = createDevEventBus();
|
|
578
|
-
const
|
|
579
|
-
const
|
|
866
|
+
const authToken = randomBytes(32).toString('base64url');
|
|
867
|
+
const server = await startDevLogServer({ bus, port: input.port, authToken });
|
|
868
|
+
const renderer = startDevStreamRenderer({
|
|
869
|
+
bus,
|
|
870
|
+
deferStatus: true,
|
|
871
|
+
...(input.filter ? { filter: input.filter } : {}),
|
|
872
|
+
});
|
|
873
|
+
// HubSpot-side lane: renders/proxy failures that never reach our Worker
|
|
874
|
+
// only exist in HubSpot's app logs — poll them into the same stream.
|
|
875
|
+
// Missing binding/credentials just means the lane stays off; `hs-x dev`
|
|
876
|
+
// must work without HubSpot credentials.
|
|
877
|
+
let hubspotPoller;
|
|
878
|
+
try {
|
|
879
|
+
const [{ resolveHubSpotLogsSource }, { startHubSpotLogsPoller }] = await Promise.all([
|
|
880
|
+
import('../hubspot-app-logs.js'),
|
|
881
|
+
import('../dev/hubspot-logs-poller.js'),
|
|
882
|
+
]);
|
|
883
|
+
const resolution = await resolveHubSpotLogsSource({ cwd: input.root });
|
|
884
|
+
if (resolution.ok) {
|
|
885
|
+
hubspotPoller = startHubSpotLogsPoller({ bus, source: resolution.source });
|
|
886
|
+
bus.emit({
|
|
887
|
+
lane: 'hubspot',
|
|
888
|
+
level: 'debug',
|
|
889
|
+
ts: Date.now(),
|
|
890
|
+
msg: `lane active — polling HubSpot app logs for app ${resolution.source.appId}`,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
catch {
|
|
895
|
+
// The HubSpot lane is additive; never fail the dev stream over it.
|
|
896
|
+
}
|
|
580
897
|
return {
|
|
581
898
|
url: server.url,
|
|
899
|
+
authToken,
|
|
900
|
+
renderer,
|
|
901
|
+
bus,
|
|
582
902
|
async cleanup() {
|
|
903
|
+
hubspotPoller?.stop();
|
|
583
904
|
renderer.stop();
|
|
584
905
|
await server.close();
|
|
585
906
|
},
|
|
@@ -590,8 +911,13 @@ async function startHsxDevLogStream(input) {
|
|
|
590
911
|
return undefined;
|
|
591
912
|
}
|
|
592
913
|
}
|
|
914
|
+
function devTelemetryUrl(tunnelUrl, authToken) {
|
|
915
|
+
const url = new URL('/__hsx/event', tunnelUrl);
|
|
916
|
+
url.searchParams.set('hsx_dev_token', authToken);
|
|
917
|
+
return url.toString();
|
|
918
|
+
}
|
|
593
919
|
async function startHsxDevTunnel(input) {
|
|
594
|
-
const step = input.reporter.step('Cloudflare quick tunnel');
|
|
920
|
+
const step = input.reporter.step(input.label ?? 'Cloudflare quick tunnel');
|
|
595
921
|
try {
|
|
596
922
|
const { startCloudflaredTunnel } = await import('../dev/tunnel.js');
|
|
597
923
|
const tunnel = await startCloudflaredTunnel({ port: input.port });
|
|
@@ -803,12 +1129,45 @@ async function devInvokeCommand({ argv, root, json, }) {
|
|
|
803
1129
|
else {
|
|
804
1130
|
step.fail(`status ${outcome.status}`);
|
|
805
1131
|
}
|
|
806
|
-
|
|
807
|
-
|
|
1132
|
+
// Human view: the handler's output fields on the rail at full brightness —
|
|
1133
|
+
// the wire envelope stays on `--json`. Falls back to the raw body when the
|
|
1134
|
+
// response has no extractable output record (errors, bespoke shapes).
|
|
1135
|
+
const outputRecord = extractInvokeOutput(responseBody);
|
|
1136
|
+
if (invocationOk && outputRecord) {
|
|
1137
|
+
reporter.rows(Object.entries(outputRecord).map(([key, value]) => ({
|
|
1138
|
+
key,
|
|
1139
|
+
value: typeof value === 'string' ? value : JSON.stringify(value),
|
|
1140
|
+
})));
|
|
1141
|
+
reporter.info('');
|
|
1142
|
+
reporter.info('Full payload: re-run with `--json`');
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
for (const line of JSON.stringify(responseBody ?? null, null, 2).split('\n')) {
|
|
1146
|
+
reporter.info(` ${line}`);
|
|
1147
|
+
}
|
|
808
1148
|
}
|
|
809
1149
|
reporter.done(invocationOk ? 'Invoked' : 'Failed', invocationOk ? 0 : 1);
|
|
810
1150
|
return { exitCode: invocationOk ? 0 : 1 };
|
|
811
1151
|
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Pull the handler's own output out of the invoke response envelope:
|
|
1154
|
+
* tools/actions nest it at `result.output`; card backends return the
|
|
1155
|
+
* handler's object as the body. Returns undefined when there is nothing
|
|
1156
|
+
* row-shaped to show.
|
|
1157
|
+
*/
|
|
1158
|
+
function extractInvokeOutput(body) {
|
|
1159
|
+
if (!isRecordValue(body))
|
|
1160
|
+
return undefined;
|
|
1161
|
+
const result = body.result;
|
|
1162
|
+
if (isRecordValue(result) && isRecordValue(result.output)) {
|
|
1163
|
+
return result.output;
|
|
1164
|
+
}
|
|
1165
|
+
if (isRecordValue(result) && !('output' in result))
|
|
1166
|
+
return result;
|
|
1167
|
+
if (!('ok' in body) && !('result' in body))
|
|
1168
|
+
return body;
|
|
1169
|
+
return undefined;
|
|
1170
|
+
}
|
|
812
1171
|
function isRecordValue(value) {
|
|
813
1172
|
return typeof value === 'object' && value !== null;
|
|
814
1173
|
}
|
|
@@ -1009,6 +1368,27 @@ async function requestDevOverrideCleanup({ controlPlaneUrl, accountId, userId, s
|
|
|
1009
1368
|
}
|
|
1010
1369
|
return Schema.decodeUnknownSync(schemas.DevOverrideClearResponse)(body);
|
|
1011
1370
|
}
|
|
1371
|
+
async function forceClearDevOverrideSessions({ controlPlaneUrl, accountId, userId, projectId, portalId, capabilityIds, }) {
|
|
1372
|
+
const active = await requestDevOverrideStatus({ controlPlaneUrl, accountId, userId });
|
|
1373
|
+
const wantedCapabilities = new Set(capabilityIds);
|
|
1374
|
+
const sessions = [
|
|
1375
|
+
...new Set(active.overrides
|
|
1376
|
+
.filter((override) => override.projectId === projectId &&
|
|
1377
|
+
override.portalId === portalId &&
|
|
1378
|
+
wantedCapabilities.has(override.capabilityId))
|
|
1379
|
+
.map((override) => override.sessionId)),
|
|
1380
|
+
];
|
|
1381
|
+
const cleared = [];
|
|
1382
|
+
for (const sessionId of sessions) {
|
|
1383
|
+
cleared.push(await requestDevOverrideCleanup({
|
|
1384
|
+
controlPlaneUrl,
|
|
1385
|
+
accountId,
|
|
1386
|
+
userId,
|
|
1387
|
+
sessionId,
|
|
1388
|
+
}));
|
|
1389
|
+
}
|
|
1390
|
+
return cleared;
|
|
1391
|
+
}
|
|
1012
1392
|
async function discoverWorkerManifests(root, options = {}) {
|
|
1013
1393
|
const files = await collectFiles(join(root, 'src'));
|
|
1014
1394
|
const workers = [];
|
|
@@ -1355,6 +1735,7 @@ const devUrlOption = Options.text('url').pipe(Options.optional);
|
|
|
1355
1735
|
const portOption = Options.text('port').pipe(Options.optional);
|
|
1356
1736
|
const portalOption = Options.text('portal').pipe(Options.optional);
|
|
1357
1737
|
const targetOriginOption = Options.text('target-origin').pipe(Options.optional);
|
|
1738
|
+
const telemetryOriginOption = Options.text('telemetry-origin').pipe(Options.optional);
|
|
1358
1739
|
const accountIdOption = Options.text('account-id').pipe(Options.optional);
|
|
1359
1740
|
const projectIdOption = Options.text('project-id').pipe(Options.optional);
|
|
1360
1741
|
const developerIdOption = Options.text('developer-id').pipe(Options.optional);
|
|
@@ -1365,9 +1746,12 @@ const hsxLogPortOption = Options.text('hsx-log-port').pipe(Options.optional);
|
|
|
1365
1746
|
const controlPlaneUrlOption = Options.text('control-plane-url').pipe(Options.optional);
|
|
1366
1747
|
const runtimeControlPlaneUrlOption = Options.text('runtime-control-plane-url').pipe(Options.optional);
|
|
1367
1748
|
const capabilityOption = Options.text('capability').pipe(Options.repeated);
|
|
1749
|
+
const onlyOption = Options.text('only').pipe(Options.repeated);
|
|
1750
|
+
const pickOption = Options.boolean('pick').pipe(Options.withDefault(false));
|
|
1368
1751
|
const noHubspotOption = Options.boolean('no-hubspot').pipe(Options.withDefault(false));
|
|
1369
1752
|
const noUiExtensionsOption = Options.boolean('no-ui-extensions').pipe(Options.withDefault(false));
|
|
1370
1753
|
const yesOption = Options.boolean('yes').pipe(Options.withAlias('y'), Options.withDefault(false));
|
|
1754
|
+
const forceOption = Options.boolean('force').pipe(Options.withDefault(false));
|
|
1371
1755
|
export const devCmd = Command.make('dev', {
|
|
1372
1756
|
json: jsonOption,
|
|
1373
1757
|
cwd: cwdOption,
|
|
@@ -1382,6 +1766,7 @@ export const devCmd = Command.make('dev', {
|
|
|
1382
1766
|
port: portOption,
|
|
1383
1767
|
portal: portalOption,
|
|
1384
1768
|
targetOrigin: targetOriginOption,
|
|
1769
|
+
telemetryOrigin: telemetryOriginOption,
|
|
1385
1770
|
accountId: accountIdOption,
|
|
1386
1771
|
projectId: projectIdOption,
|
|
1387
1772
|
developerId: developerIdOption,
|
|
@@ -1392,9 +1777,12 @@ export const devCmd = Command.make('dev', {
|
|
|
1392
1777
|
controlPlaneUrl: controlPlaneUrlOption,
|
|
1393
1778
|
runtimeControlPlaneUrl: runtimeControlPlaneUrlOption,
|
|
1394
1779
|
capability: capabilityOption,
|
|
1780
|
+
only: onlyOption,
|
|
1781
|
+
pick: pickOption,
|
|
1395
1782
|
noHubspot: noHubspotOption,
|
|
1396
1783
|
noUiExtensions: noUiExtensionsOption,
|
|
1397
1784
|
yes: yesOption,
|
|
1785
|
+
force: forceOption,
|
|
1398
1786
|
}, (opts) => runHandler('dev', { json: opts.json }, Effect.gen(function* () {
|
|
1399
1787
|
// root: injected cwd, overridden by --cwd.
|
|
1400
1788
|
const injectedCwd = yield* Cwd;
|
|
@@ -1428,6 +1816,9 @@ export const devCmd = Command.make('dev', {
|
|
|
1428
1816
|
if (Option.isSome(opts.targetOrigin)) {
|
|
1429
1817
|
argv.push('--target-origin', opts.targetOrigin.value);
|
|
1430
1818
|
}
|
|
1819
|
+
if (Option.isSome(opts.telemetryOrigin)) {
|
|
1820
|
+
argv.push('--telemetry-origin', opts.telemetryOrigin.value);
|
|
1821
|
+
}
|
|
1431
1822
|
if (Option.isSome(opts.accountId))
|
|
1432
1823
|
argv.push('--account-id', opts.accountId.value);
|
|
1433
1824
|
if (Option.isSome(opts.projectId))
|
|
@@ -1451,13 +1842,19 @@ export const devCmd = Command.make('dev', {
|
|
|
1451
1842
|
}
|
|
1452
1843
|
for (const capability of opts.capability)
|
|
1453
1844
|
argv.push('--capability', capability);
|
|
1845
|
+
for (const only of opts.only)
|
|
1846
|
+
argv.push('--only', only);
|
|
1847
|
+
if (opts.pick)
|
|
1848
|
+
argv.push('--pick');
|
|
1454
1849
|
if (opts.noHubspot)
|
|
1455
1850
|
argv.push('--no-hubspot');
|
|
1456
1851
|
if (opts.noUiExtensions)
|
|
1457
1852
|
argv.push('--no-ui-extensions');
|
|
1458
1853
|
if (opts.yes)
|
|
1459
1854
|
argv.push('--yes');
|
|
1460
|
-
|
|
1855
|
+
if (opts.force)
|
|
1856
|
+
argv.push('--force');
|
|
1857
|
+
yield* runQuarantined('dev', opts.json, () => devCommand({ argv, root, json: opts.json }));
|
|
1461
1858
|
})));
|
|
1462
1859
|
/**
|
|
1463
1860
|
* `hs-x dev invoke --remote`: dispatch at the DEPLOYED Worker, where install
|
|
@@ -1520,9 +1917,7 @@ async function devInvokeRemote(args) {
|
|
|
1520
1917
|
});
|
|
1521
1918
|
const durationMs = Date.now() - startedAt;
|
|
1522
1919
|
const body = outcome.body;
|
|
1523
|
-
const ok = outcome.status >= 200 &&
|
|
1524
|
-
outcome.status < 300 &&
|
|
1525
|
-
(!isRecordValue(body) || body.ok !== false);
|
|
1920
|
+
const ok = outcome.status >= 200 && outcome.status < 300 && (!isRecordValue(body) || body.ok !== false);
|
|
1526
1921
|
const reporter = createReporter({ command: 'dev invoke', argv, entry: !json });
|
|
1527
1922
|
if (json) {
|
|
1528
1923
|
process.stdout.write(`${JSON.stringify({
|