@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/connect.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
import { HttpClientRequest, Schema, schemas } from '@hs-x/types';
|
|
4
|
-
import { loadCreateControlPlane } from '../control-plane-loader.js';
|
|
5
4
|
import { Effect } from 'effect';
|
|
6
|
-
import {
|
|
5
|
+
import { isCustomControlPlane, resolveControlPlaneUrl } from '../config.js';
|
|
7
6
|
import { controlPlaneAuthHeaders } from '../control-plane-fetch.js';
|
|
7
|
+
import { loadCreateControlPlane } from '../control-plane-loader.js';
|
|
8
8
|
import { executeCliHttp } from '../effect-http.js';
|
|
9
|
-
import {
|
|
10
|
-
import { createReporter } from '../reporter/index.js';
|
|
9
|
+
import { emitHelp } from '../help.js';
|
|
11
10
|
import { isInteractive, promptConfirm, promptSelect, promptText } from '../prompt.js';
|
|
11
|
+
import { createReporter } from '../reporter/index.js';
|
|
12
12
|
import { connectHelpText } from './help-command.js';
|
|
13
13
|
async function hostedHttp(input) {
|
|
14
14
|
const headers = { ...(input.headers ?? {}) };
|
|
@@ -107,26 +107,25 @@ async function connectWizard({ argv, cwd, json, }) {
|
|
|
107
107
|
async function connectHubspotCommand({ argv, cwd, json, }) {
|
|
108
108
|
const interactive = !json && isInteractive();
|
|
109
109
|
const authMethod = resolveFlag(argv, '--auth-method') ?? 'pak';
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
// the
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// Surface which HS-X account this HubSpot account will bind into.
|
|
110
|
+
// ADR-025: the default `connect` is stored-direct (mode 2) — capture the
|
|
111
|
+
// credential, store it in local custody, and report success WITHOUT an HS-X
|
|
112
|
+
// account. The linked/hosted platform path (mode 3) is opt-in: taken only
|
|
113
|
+
// when the user is logged in (`isLinked()`), explicitly targets a custom
|
|
114
|
+
// control plane (`--control-plane-url` / `HSX_CONTROL_PLANE_URL`), or
|
|
115
|
+
// exercises the local contract handler (`--local-control-plane`).
|
|
116
|
+
// `--no-control-plane` always forces stored-direct. A HubSpot account still
|
|
117
|
+
// binds 1:1 into an HS-X account, but only on the opt-in linked path.
|
|
118
|
+
const localControlPlane = argv.includes('--local-control-plane');
|
|
119
|
+
const noControlPlane = argv.includes('--no-control-plane');
|
|
120
|
+
const { isLinked, loadStore } = await import('../account-store.js');
|
|
121
|
+
const linked = localControlPlane
|
|
122
|
+
? true
|
|
123
|
+
: noControlPlane
|
|
124
|
+
? false
|
|
125
|
+
: isCustomControlPlane(resolveControlPlaneUrl(argv)) || (await isLinked());
|
|
126
|
+
if (interactive && linked && !localControlPlane) {
|
|
127
|
+
// Opt-in linked path: surface which HS-X account this HubSpot account
|
|
128
|
+
// will bind into.
|
|
130
129
|
const store = await loadStore();
|
|
131
130
|
const active = store.defaultAccountId ? store.accounts[store.defaultAccountId] : undefined;
|
|
132
131
|
if (active) {
|
|
@@ -317,11 +316,88 @@ async function connectHubspotCommand({ argv, cwd, json, }) {
|
|
|
317
316
|
displayName: request.displayName,
|
|
318
317
|
authMethod: request.authMethod,
|
|
319
318
|
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
319
|
+
// Stored-direct (ADR-025 mode 2) — the DEFAULT. No HS-X account, no control
|
|
320
|
+
// plane: capture the credential into local custody (HS-X store + HubSpot CLI
|
|
321
|
+
// config, where `hs-x deploy` reads it) and report success. Network-free: the
|
|
322
|
+
// PAK is validated at deploy time, not here, so a scripted `--pak` connect
|
|
323
|
+
// works offline.
|
|
324
|
+
if (!linked) {
|
|
325
|
+
await persistConnectedAccount({
|
|
326
|
+
id: accountId,
|
|
327
|
+
displayName: request.displayName,
|
|
328
|
+
hubspotPortalId: Number(developerAccountId),
|
|
329
|
+
});
|
|
330
|
+
const hscliResult = personalAccessKey && Number.isFinite(Number(developerAccountId))
|
|
331
|
+
? await persistPakToHubSpotCli({
|
|
332
|
+
portalId: Number(developerAccountId),
|
|
333
|
+
pak: personalAccessKey,
|
|
334
|
+
...(discoveredDefault?.persistInfo?.hubName
|
|
335
|
+
? { hubName: discoveredDefault.persistInfo.hubName }
|
|
336
|
+
: {}),
|
|
337
|
+
})
|
|
338
|
+
: undefined;
|
|
339
|
+
const result = {
|
|
340
|
+
ok: true,
|
|
341
|
+
command: 'connect hubspot',
|
|
342
|
+
mode: 'stored-direct',
|
|
343
|
+
accountId,
|
|
344
|
+
request: safeRequest,
|
|
345
|
+
};
|
|
346
|
+
if (json) {
|
|
347
|
+
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
const reporter = createReporter({ command: 'connect hubspot', argv });
|
|
351
|
+
reporter.header(accountId);
|
|
352
|
+
reporter.step('Stored HubSpot developer credential').ok(request.displayName);
|
|
353
|
+
renderCredentialDestinations(reporter, {
|
|
354
|
+
hsxStore: defaultStorePathForLog(),
|
|
355
|
+
...(hscliResult?.written ? { hscli: hscliResult.path } : {}),
|
|
356
|
+
});
|
|
357
|
+
reporter.info('');
|
|
358
|
+
reporter.info('Stored locally — no HS-X account needed. Run `hs-x deploy` to ship. To link this into an HS-X account (dashboard, teams, hosted billing), run `hs-x login`, then re-run `hs-x connect hubspot`.');
|
|
359
|
+
reporter.done('Connected');
|
|
360
|
+
}
|
|
361
|
+
return { exitCode: 0 };
|
|
362
|
+
}
|
|
363
|
+
// Linked local contract handler (`--local-control-plane`).
|
|
364
|
+
if (localControlPlane) {
|
|
365
|
+
const connection = await requestLocalControlPlaneHubSpotConnect({ accountId, request });
|
|
366
|
+
const result = {
|
|
367
|
+
ok: true,
|
|
368
|
+
command: 'connect hubspot',
|
|
369
|
+
mode: 'local-control-plane-connect',
|
|
370
|
+
accountId,
|
|
371
|
+
request: safeRequest,
|
|
372
|
+
connection,
|
|
373
|
+
};
|
|
374
|
+
await persistConnectedAccount({
|
|
375
|
+
id: accountId,
|
|
376
|
+
displayName: connection.displayName,
|
|
377
|
+
hubspotPortalId: Number(developerAccountId),
|
|
378
|
+
});
|
|
379
|
+
const hscliResult = await maybePersistToHubSpotCli(discoveredDefault, personalAccessKey);
|
|
380
|
+
if (json) {
|
|
381
|
+
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
const reporter = createReporter({ command: 'connect hubspot', argv });
|
|
385
|
+
reporter.header(connection.accountId);
|
|
386
|
+
reporter
|
|
387
|
+
.step('Connected HubSpot developer account (local control plane)')
|
|
388
|
+
.ok(connection.displayName);
|
|
389
|
+
renderCredentialDestinations(reporter, {
|
|
390
|
+
hsxStore: defaultStorePathForLog(),
|
|
391
|
+
hscli: hscliResult?.written ? hscliResult.path : null,
|
|
392
|
+
controlPlane: 'local (in-memory)',
|
|
393
|
+
});
|
|
394
|
+
reporter.done();
|
|
395
|
+
}
|
|
396
|
+
return { exitCode: 0 };
|
|
397
|
+
}
|
|
398
|
+
// Linked hosted platform path (logged in, or explicit `--control-plane-url`).
|
|
399
|
+
{
|
|
400
|
+
const controlPlaneUrl = resolveControlPlaneUrl(argv);
|
|
325
401
|
// Validate the PAK with a cheap exchange BEFORE storing it, so a typo'd or
|
|
326
402
|
// revoked key is rejected here with a pointed error instead of persisting
|
|
327
403
|
// garbage that fails on every later deploy. The browser flow has already
|
|
@@ -394,60 +470,6 @@ async function connectHubspotCommand({ argv, cwd, json, }) {
|
|
|
394
470
|
}
|
|
395
471
|
return { exitCode: 0 };
|
|
396
472
|
}
|
|
397
|
-
if (!argv.includes('--local-control-plane')) {
|
|
398
|
-
const result = {
|
|
399
|
-
ok: true,
|
|
400
|
-
command: 'connect hubspot',
|
|
401
|
-
mode: 'request',
|
|
402
|
-
accountId,
|
|
403
|
-
request: safeRequest,
|
|
404
|
-
nextStep: 'Pass --control-plane-url to connect through the control plane or --local-control-plane to exercise the local contract handler.',
|
|
405
|
-
};
|
|
406
|
-
if (json) {
|
|
407
|
-
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
const reporter = createReporter({ command: 'connect hubspot', argv });
|
|
411
|
-
reporter.header(accountId);
|
|
412
|
-
reporter.info(`Prepared HubSpot connect request for ${request.displayName}.`);
|
|
413
|
-
reporter.warn('HSX_W_CONNECT_NO_TRANSPORT', 'Hosted transport is not wired yet.');
|
|
414
|
-
reporter.info('Pass --control-plane-url or --local-control-plane to complete the connect.');
|
|
415
|
-
reporter.done('Prepared');
|
|
416
|
-
}
|
|
417
|
-
return { exitCode: 0 };
|
|
418
|
-
}
|
|
419
|
-
const connection = await requestLocalControlPlaneHubSpotConnect({ accountId, request });
|
|
420
|
-
const result = {
|
|
421
|
-
ok: true,
|
|
422
|
-
command: 'connect hubspot',
|
|
423
|
-
mode: 'local-control-plane-connect',
|
|
424
|
-
accountId,
|
|
425
|
-
request: safeRequest,
|
|
426
|
-
connection,
|
|
427
|
-
};
|
|
428
|
-
await persistConnectedAccount({
|
|
429
|
-
id: accountId,
|
|
430
|
-
displayName: connection.displayName,
|
|
431
|
-
hubspotPortalId: Number(developerAccountId),
|
|
432
|
-
});
|
|
433
|
-
const hscliResult = await maybePersistToHubSpotCli(discoveredDefault, personalAccessKey);
|
|
434
|
-
if (json) {
|
|
435
|
-
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
const reporter = createReporter({ command: 'connect hubspot', argv });
|
|
439
|
-
reporter.header(connection.accountId);
|
|
440
|
-
reporter
|
|
441
|
-
.step('Connected HubSpot developer account (local control plane)')
|
|
442
|
-
.ok(connection.displayName);
|
|
443
|
-
renderCredentialDestinations(reporter, {
|
|
444
|
-
hsxStore: defaultStorePathForLog(),
|
|
445
|
-
hscli: hscliResult?.written ? hscliResult.path : null,
|
|
446
|
-
controlPlane: 'local (in-memory)',
|
|
447
|
-
});
|
|
448
|
-
reporter.done();
|
|
449
|
-
}
|
|
450
|
-
return { exitCode: 0 };
|
|
451
473
|
}
|
|
452
474
|
function renderCredentialDestinations(reporter, destinations) {
|
|
453
475
|
reporter.info('');
|
|
@@ -489,6 +511,25 @@ async function maybePersistToHubSpotCli(discoveredDefault, pak) {
|
|
|
489
511
|
return undefined;
|
|
490
512
|
}
|
|
491
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Stored-direct persistence: write a raw PAK to the HubSpot CLI config (where
|
|
516
|
+
* `hs-x deploy` discovers it) without an OAuth exchange. Best-effort — a write
|
|
517
|
+
* failure never fails the connect; deploy also accepts `--pak` / `HSX_HUBSPOT_PAK`.
|
|
518
|
+
*/
|
|
519
|
+
async function persistPakToHubSpotCli(input) {
|
|
520
|
+
try {
|
|
521
|
+
const { persistToHubSpotCliConfig } = await import('../hubspot-auth.js');
|
|
522
|
+
const result = await persistToHubSpotCliConfig({
|
|
523
|
+
portalId: input.portalId,
|
|
524
|
+
pak: input.pak,
|
|
525
|
+
...(input.hubName ? { hubName: input.hubName } : {}),
|
|
526
|
+
});
|
|
527
|
+
return result.written ? result : undefined;
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
492
533
|
async function resolveAccountIdOrPrompt(input) {
|
|
493
534
|
const fromFlag = resolveFlag(input.argv, '--account-id') ?? process.env.HSX_ACCOUNT_ID;
|
|
494
535
|
if (fromFlag && fromFlag.length > 0)
|
|
@@ -624,7 +665,7 @@ async function runCloudflareOAuthFlow(input) {
|
|
|
624
665
|
return Effect.runPromise(Effect.promise(() => runCloudflareOAuthFlowBody(input)).pipe(Effect.withSpan('cli.cloudflare_oauth.flow', {
|
|
625
666
|
attributes: {
|
|
626
667
|
account_id: input.hsxAccountId,
|
|
627
|
-
brokered:
|
|
668
|
+
brokered: input.brokered,
|
|
628
669
|
},
|
|
629
670
|
})));
|
|
630
671
|
}
|
|
@@ -677,7 +718,7 @@ async function runCloudflareOAuthFlowBody(input) {
|
|
|
677
718
|
// browser on this machine completes the redirect.
|
|
678
719
|
reporter.info('No TTY detected — not launching a browser.');
|
|
679
720
|
reporter.info(`Authorize HS-X by opening: ${authorizeUrl}`);
|
|
680
|
-
reporter.info(`Waiting up to ${Math.round(timeoutMs / 1000)}s for the OAuth callback. For headless runs use --auth-method api-token with HSX_CLOUDFLARE_API_TOKEN instead.`);
|
|
721
|
+
reporter.info(`Waiting up to ${Math.round(timeoutMs / 1000)}s for the OAuth callback. For headless runs use --auth-method api-token with CLOUDFLARE_API_TOKEN (alias: HSX_CLOUDFLARE_API_TOKEN) instead.`);
|
|
681
722
|
}
|
|
682
723
|
let code;
|
|
683
724
|
try {
|
|
@@ -703,12 +744,15 @@ async function runCloudflareOAuthFlowBody(input) {
|
|
|
703
744
|
throw error;
|
|
704
745
|
}
|
|
705
746
|
await listener.close();
|
|
706
|
-
// Brokered path
|
|
707
|
-
//
|
|
708
|
-
//
|
|
747
|
+
// Brokered path (opt-in linked, mode 3): the control plane exchanges the
|
|
748
|
+
// code, stores the refresh token server-side against the HS-X account, and
|
|
749
|
+
// returns the CloudflareConnection in one round trip. Taken only when the
|
|
750
|
+
// caller opted into the linked path AND we didn't take the dev env override.
|
|
751
|
+
// The stored-direct default (`brokered` false) falls through to the local
|
|
752
|
+
// exchange below, which keeps the refresh token in local custody.
|
|
709
753
|
const envHasOverride = typeof process.env.HSX_CLOUDFLARE_OAUTH_CLIENT_ID === 'string' &&
|
|
710
754
|
process.env.HSX_CLOUDFLARE_OAUTH_CLIENT_ID.length > 0;
|
|
711
|
-
if (controlPlaneUrl && !envHasOverride) {
|
|
755
|
+
if (input.brokered && controlPlaneUrl && !envHasOverride) {
|
|
712
756
|
try {
|
|
713
757
|
const connection = await postCloudflareOAuthComplete({
|
|
714
758
|
controlPlaneUrl,
|
|
@@ -768,17 +812,27 @@ async function runCloudflareOAuthFlowBody(input) {
|
|
|
768
812
|
}
|
|
769
813
|
let discoveredAccountId;
|
|
770
814
|
try {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
815
|
+
// Resolve the account id via /accounts, NOT verifyCloudflareToken: OAuth
|
|
816
|
+
// access tokens 400 on the API-token verify endpoint, which left the id
|
|
817
|
+
// undefined and silently skipped persistence below (the credential is keyed
|
|
818
|
+
// by account id). /accounts works with the granted account-read scope.
|
|
819
|
+
const { resolveCloudflareAccountId } = await import('../cloudflare-auth.js');
|
|
820
|
+
discoveredAccountId = await resolveCloudflareAccountId(tokens.accessToken);
|
|
774
821
|
}
|
|
775
822
|
catch {
|
|
776
|
-
//
|
|
823
|
+
// account discovery is best-effort
|
|
777
824
|
}
|
|
778
|
-
|
|
825
|
+
// Persist the credential so a later `hs-x deploy` can reuse this connection
|
|
826
|
+
// without re-authorizing. Cloudflare OAuth returns no refresh token, so the
|
|
827
|
+
// access token IS what we store (with its expiry); deploy uses it directly
|
|
828
|
+
// while valid and prompts a reconnect once it lapses.
|
|
829
|
+
if (discoveredAccountId && (tokens.refreshToken || tokens.accessToken)) {
|
|
779
830
|
try {
|
|
780
831
|
await saveCloudflareOAuthCredential(discoveredAccountId, {
|
|
781
|
-
refreshToken: tokens.refreshToken,
|
|
832
|
+
...(tokens.refreshToken ? { refreshToken: tokens.refreshToken } : {}),
|
|
833
|
+
...(tokens.accessToken
|
|
834
|
+
? { accessToken: tokens.accessToken, accessTokenExpiresAt: tokens.expiresAt }
|
|
835
|
+
: {}),
|
|
782
836
|
...(tokens.scope ? { scope: tokens.scope } : {}),
|
|
783
837
|
});
|
|
784
838
|
}
|
|
@@ -894,19 +948,21 @@ function defaultStorePathForLog() {
|
|
|
894
948
|
return join(process.env.HOME ?? '~', '.hsx', 'config.json');
|
|
895
949
|
}
|
|
896
950
|
async function connectCloudflareCommand({ argv, json, }) {
|
|
951
|
+
// ADR-025: the default is stored-direct (mode 2) — connect Cloudflare and
|
|
952
|
+
// keep the refresh token in local custody, with NO HS-X account. The linked
|
|
953
|
+
// path (mode 3) is opt-in: taken only when logged in, when a custom control
|
|
954
|
+
// plane is targeted, or when exercising the local contract. When there's no
|
|
955
|
+
// HS-X account we still proceed under a local placeholder id (the local
|
|
956
|
+
// OAuth store keys the credential by Cloudflare account id, not this id).
|
|
957
|
+
const localControlPlane = argv.includes('--local-control-plane');
|
|
958
|
+
const { isLinked } = await import('../account-store.js');
|
|
959
|
+
const linked = localControlPlane
|
|
960
|
+
? true
|
|
961
|
+
: isCustomControlPlane(resolveControlPlaneUrl(argv)) || (await isLinked());
|
|
897
962
|
const accountId = resolveFlag(argv, '--account-id') ??
|
|
898
963
|
process.env.HSX_ACCOUNT_ID ??
|
|
899
|
-
(await defaultHsxAccountId())
|
|
900
|
-
|
|
901
|
-
const reporter = createReporter({ command: 'connect cloudflare', argv });
|
|
902
|
-
const code = 'HSX_E_INPUT_NO_HSX_ACCOUNT';
|
|
903
|
-
reporter.error(code, 'No HS-X account to bind Cloudflare to.', {
|
|
904
|
-
hint: 'Run `hs-x connect` first to create an HS-X account.',
|
|
905
|
-
docs_url: `https://hs-x.dev/errors/${code}`,
|
|
906
|
-
});
|
|
907
|
-
reporter.done(undefined, 10);
|
|
908
|
-
return { exitCode: 10 };
|
|
909
|
-
}
|
|
964
|
+
(await defaultHsxAccountId()) ??
|
|
965
|
+
'acct_local';
|
|
910
966
|
const interactive = !json && isInteractive();
|
|
911
967
|
let cloudflareAccountId = resolveFlag(argv, '--cloudflare-account-id');
|
|
912
968
|
let displayName = resolveFlag(argv, '--display-name');
|
|
@@ -925,6 +981,13 @@ async function connectCloudflareCommand({ argv, json, }) {
|
|
|
925
981
|
reporter.done(undefined, 10);
|
|
926
982
|
return { exitCode: 10 };
|
|
927
983
|
}
|
|
984
|
+
// One visual timeline: identity header first, then the OAuth steps, then
|
|
985
|
+
// the railed result — with elapsed measured from here, not from a reporter
|
|
986
|
+
// constructed at summary time (which always read "0ms").
|
|
987
|
+
const connectStartedAt = Date.now();
|
|
988
|
+
if (!json) {
|
|
989
|
+
createReporter({ command: 'connect cloudflare', argv }).header(accountId);
|
|
990
|
+
}
|
|
928
991
|
// No TTY pre-gate: runCloudflareOAuthFlow degrades on its own — without a
|
|
929
992
|
// TTY it prints the authorize URL + instructions instead of spawning a
|
|
930
993
|
// browser, so headless terminals can still complete consent.
|
|
@@ -932,6 +995,7 @@ async function connectCloudflareCommand({ argv, json, }) {
|
|
|
932
995
|
argv,
|
|
933
996
|
json,
|
|
934
997
|
hsxAccountId: accountId,
|
|
998
|
+
brokered: linked,
|
|
935
999
|
...(displayName ? { displayName } : {}),
|
|
936
1000
|
});
|
|
937
1001
|
if (oauthOutcome.kind === 'cancelled') {
|
|
@@ -955,20 +1019,56 @@ async function connectCloudflareCommand({ argv, json, }) {
|
|
|
955
1019
|
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
956
1020
|
}
|
|
957
1021
|
else {
|
|
958
|
-
const reporter = createReporter({
|
|
959
|
-
|
|
960
|
-
|
|
1022
|
+
const reporter = createReporter({
|
|
1023
|
+
command: 'connect cloudflare',
|
|
1024
|
+
argv,
|
|
1025
|
+
startedAt: connectStartedAt,
|
|
1026
|
+
});
|
|
1027
|
+
reporter.rows(cloudflareConnectRows(oauthOutcome.connection));
|
|
1028
|
+
const siblings = oauthOutcome.connection.visibleSiblingAccountIds;
|
|
1029
|
+
if (siblings && siblings.length > 0) {
|
|
1030
|
+
reporter.info(`Also connected to HS-X account(s) ${siblings.join(', ')} — resources are namespaced per HS-X account; Cloudflare quota is shared.`);
|
|
1031
|
+
}
|
|
961
1032
|
reporter.done('Connected');
|
|
962
1033
|
}
|
|
963
1034
|
return { exitCode: 0 };
|
|
964
1035
|
}
|
|
965
|
-
// success-local:
|
|
966
|
-
//
|
|
967
|
-
//
|
|
1036
|
+
// success-local: the CLI exchanged the code itself and saved the refresh
|
|
1037
|
+
// token in local custody (cloudflare-oauth-store). Used for the stored-direct
|
|
1038
|
+
// default and the sandbox-client dev path (HSX_CLOUDFLARE_OAUTH_CLIENT_ID).
|
|
968
1039
|
const apiToken = oauthOutcome.accessToken;
|
|
969
1040
|
if (!cloudflareAccountId && oauthOutcome.cloudflareAccountId) {
|
|
970
1041
|
cloudflareAccountId = oauthOutcome.cloudflareAccountId;
|
|
971
1042
|
}
|
|
1043
|
+
// Stored-direct (ADR-025 mode 2): the credential is already persisted
|
|
1044
|
+
// locally; there's no HS-X account to register it against, so report success
|
|
1045
|
+
// and stop. The opt-in linked path continues to the CP connect call below.
|
|
1046
|
+
if (!linked) {
|
|
1047
|
+
const result = {
|
|
1048
|
+
ok: true,
|
|
1049
|
+
command: 'connect cloudflare',
|
|
1050
|
+
mode: 'stored-direct',
|
|
1051
|
+
accountId,
|
|
1052
|
+
...(cloudflareAccountId ? { cloudflareAccountId } : {}),
|
|
1053
|
+
};
|
|
1054
|
+
if (json) {
|
|
1055
|
+
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
const reporter = createReporter({
|
|
1059
|
+
command: 'connect cloudflare',
|
|
1060
|
+
argv,
|
|
1061
|
+
startedAt: connectStartedAt,
|
|
1062
|
+
});
|
|
1063
|
+
reporter
|
|
1064
|
+
.step('Stored Cloudflare credential')
|
|
1065
|
+
.ok(cloudflareAccountId ? `account ${cloudflareAccountId}` : 'token stored');
|
|
1066
|
+
reporter.info('');
|
|
1067
|
+
reporter.info('Stored locally — no HS-X account needed. Run `hs-x deploy` to ship. To link this into an HS-X account, run `hs-x login`, then re-run `hs-x connect cloudflare`.');
|
|
1068
|
+
reporter.done('Connected');
|
|
1069
|
+
}
|
|
1070
|
+
return { exitCode: 0 };
|
|
1071
|
+
}
|
|
972
1072
|
if (interactive && !cloudflareAccountId) {
|
|
973
1073
|
const answer = await promptText({
|
|
974
1074
|
message: 'Cloudflare account id',
|
|
@@ -1038,9 +1138,16 @@ async function connectCloudflareCommand({ argv, json, }) {
|
|
|
1038
1138
|
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1039
1139
|
}
|
|
1040
1140
|
else {
|
|
1041
|
-
const reporter = createReporter({
|
|
1042
|
-
|
|
1043
|
-
|
|
1141
|
+
const reporter = createReporter({
|
|
1142
|
+
command: 'connect cloudflare',
|
|
1143
|
+
argv,
|
|
1144
|
+
startedAt: connectStartedAt,
|
|
1145
|
+
});
|
|
1146
|
+
reporter.rows(cloudflareConnectRows(connection));
|
|
1147
|
+
const siblings = connection.visibleSiblingAccountIds;
|
|
1148
|
+
if (siblings && siblings.length > 0) {
|
|
1149
|
+
reporter.info(`Also connected to HS-X account(s) ${siblings.join(', ')} — resources are namespaced per HS-X account; Cloudflare quota is shared.`);
|
|
1150
|
+
}
|
|
1044
1151
|
reporter.done('Connected');
|
|
1045
1152
|
}
|
|
1046
1153
|
return { exitCode: 0 };
|
|
@@ -1133,11 +1240,18 @@ async function requestHostedControlPlaneCloudflareConnect({ accountId, request,
|
|
|
1133
1240
|
}
|
|
1134
1241
|
return Schema.decodeUnknownSync(schemas.CloudflareConnection)(body);
|
|
1135
1242
|
}
|
|
1136
|
-
function
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1243
|
+
function cloudflareConnectRows(connection) {
|
|
1244
|
+
return [
|
|
1245
|
+
{
|
|
1246
|
+
status: 'ok',
|
|
1247
|
+
key: 'cloudflare',
|
|
1248
|
+
value: connection.displayName,
|
|
1249
|
+
...(connection.cloudflareAccountId
|
|
1250
|
+
? { detail: `account ${connection.cloudflareAccountId}` }
|
|
1251
|
+
: {}),
|
|
1252
|
+
},
|
|
1253
|
+
{ key: 'hs-x account', value: connection.accountId },
|
|
1254
|
+
];
|
|
1141
1255
|
}
|
|
1142
1256
|
function writeDiagnostics(diagnostics) {
|
|
1143
1257
|
for (const diagnostic of diagnostics) {
|
|
@@ -1230,7 +1344,7 @@ const connectCloudflareCmd = CommandNative.make('cloudflare', {
|
|
|
1230
1344
|
argv.push('--local-control-plane');
|
|
1231
1345
|
const defaultCwd = yield* Cwd;
|
|
1232
1346
|
const cwd = Option.getOrElse(opts.cwd, () => defaultCwd);
|
|
1233
|
-
yield* runQuarantined(opts.json, () => connectCommand({ argv, cwd, json: opts.json }));
|
|
1347
|
+
yield* runQuarantined('connect', opts.json, () => connectCommand({ argv, cwd, json: opts.json }));
|
|
1234
1348
|
})));
|
|
1235
1349
|
// connect hubspot
|
|
1236
1350
|
const connectDeveloperAccountIdOption = OptionsNative.text('developer-account-id').pipe(OptionsNative.optional);
|
|
@@ -1285,7 +1399,7 @@ const connectHubspotCmd = CommandNative.make('hubspot', {
|
|
|
1285
1399
|
argv.push('--yes');
|
|
1286
1400
|
const defaultCwd = yield* Cwd;
|
|
1287
1401
|
const cwd = Option.getOrElse(opts.cwd, () => defaultCwd);
|
|
1288
|
-
yield* runQuarantined(opts.json, () => connectCommand({ argv, cwd, json: opts.json }));
|
|
1402
|
+
yield* runQuarantined('connect', opts.json, () => connectCommand({ argv, cwd, json: opts.json }));
|
|
1289
1403
|
})));
|
|
1290
1404
|
// Bare `hs-x connect` runs the interactive wizard (HubSpot, then optionally
|
|
1291
1405
|
// Cloudflare); non-TTY/--json invocations fall back to the curated help text
|
|
@@ -1350,7 +1464,7 @@ export const connectCmd = CommandNative.make('connect', {
|
|
|
1350
1464
|
argv.push('--yes');
|
|
1351
1465
|
const defaultCwd = yield* Cwd;
|
|
1352
1466
|
const cwd = Option.getOrElse(opts.cwd, () => defaultCwd);
|
|
1353
|
-
yield* runQuarantined(opts.json, () => connectCommand({ argv, cwd, json: opts.json }));
|
|
1467
|
+
yield* runQuarantined('connect', opts.json, () => connectCommand({ argv, cwd, json: opts.json }));
|
|
1354
1468
|
}))).pipe(CommandNative.withSubcommands([connectCloudflareCmd, connectHubspotCmd]));
|
|
1355
1469
|
// Appended imports (ES module imports are hoisted; placement at file end keeps
|
|
1356
1470
|
// this additive change off the existing import block to avoid merge conflicts).
|
|
@@ -1358,6 +1472,6 @@ import * as CommandNative from '@effect/cli/Command';
|
|
|
1358
1472
|
import * as OptionsNative from '@effect/cli/Options';
|
|
1359
1473
|
import { Option } from 'effect';
|
|
1360
1474
|
import { exitWith } from '../cli-error.js';
|
|
1361
|
-
import {
|
|
1475
|
+
import { accountIdOption, controlPlaneUrlOption, cwdOption, jsonOption, runHandler, runQuarantined, userIdOption, } from '../cli/kit.js';
|
|
1362
1476
|
import { Cwd } from '../services/cwd.js';
|
|
1363
1477
|
//# sourceMappingURL=connect.js.map
|