@hs-x/cli 0.4.7 → 0.4.9

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.
@@ -27,6 +27,7 @@ import { createControlPlaneTenantProvisioningAuthority, } from '../control-plane
27
27
  import { captureDeployRevision } from '../deploy-revision-capture.js';
28
28
  import { resolveDeployTargets } from '../deploy-targets.js';
29
29
  import { executeCliHttp } from '../effect-http.js';
30
+ import { discoverHubSpotAccounts } from '../hubspot-auth.js';
30
31
  import { DirectHubSpotAccountResolutionError, createDeployHubSpotDeveloperClient, resolveDirectHubSpotContext, } from '../hubspot-developer-client.js';
31
32
  import { HubSpotProjectArchiveSourceError, fingerprintHubSpotArchiveSource, writeLocalHubSpotProjectArchive, } from '../hubspot-project-archive.js';
32
33
  import { deployHubSpotProjectBuild, readHubSpotSubbuildStatuses, waitForHubSpotAutoDeploy, waitForHubSpotBuildStatus, } from '../hubspot-project-lifecycle.js';
@@ -1486,7 +1487,7 @@ export async function deployCommand({ argv, root, json, }) {
1486
1487
  projectId: controlPlanePlan.projectId,
1487
1488
  environment: resolveFlag(argv, '--env') ?? resolveFlag(argv, '--environment') ?? 'production',
1488
1489
  })
1489
- : (readOptionalHubSpotAppId(resolveFlag(argv, '--hubspot-app-id') ?? process.env.HSX_HUBSPOT_APP_ID) ?? (await readPersistedHubSpotAppId(root, await resolveHubSpotProjectName(argv, root))));
1490
+ : (readOptionalHubSpotAppId(resolveFlag(argv, '--hubspot-app-id') ?? process.env.HSX_HUBSPOT_APP_ID) ?? (await readPersistedHubSpotAppIdForDeploy(argv, root)));
1490
1491
  const uploadFirstOrigin = cloudflareDeployRequested &&
1491
1492
  !cloudflareDryRun &&
1492
1493
  !planOnly &&
@@ -2940,7 +2941,7 @@ async function executeCloudflareWorkerDeploy({ argv, root, worker, index, allowC
2940
2941
  // Resolution: flag → env → the binding persisted by the last successful
2941
2942
  // upload (.hs-x/hubspot.json). Users should never have to replay an app id
2942
2943
  // the CLI already learned (owner directive, run-008).
2943
- const hubspotAppId = readOptionalHubSpotAppId(resolveFlag(argv, '--hubspot-app-id') ?? process.env.HSX_HUBSPOT_APP_ID) ?? (await readPersistedHubSpotAppId(root, await resolveHubSpotProjectName(argv, root)));
2944
+ const hubspotAppId = readOptionalHubSpotAppId(resolveFlag(argv, '--hubspot-app-id') ?? process.env.HSX_HUBSPOT_APP_ID) ?? (await readPersistedHubSpotAppIdForDeploy(argv, root));
2944
2945
  const installRuntimeBinding = hubspotAppId
2945
2946
  ? controlPlaneUrl
2946
2947
  ? await ensureDeployInstallRuntimeBinding({
@@ -3069,7 +3070,10 @@ async function executeCloudflareWorkerDeploy({ argv, root, worker, index, allowC
3069
3070
  ...(installRuntimeBinding
3070
3071
  ? { installKvNamespaceId: installRuntimeBinding.installKvNamespaceId }
3071
3072
  : {}),
3072
- ...(controlPlaneUrl ? { checkpointDataset: 'hsx_checkpoint', observability: true } : {}),
3073
+ // Workers Logs is the observability source for both deployment modes.
3074
+ // Checkpoint remains linked-only and is added separately below.
3075
+ observability: true,
3076
+ ...(controlPlaneUrl ? { checkpointDataset: 'hsx_checkpoint' } : {}),
3073
3077
  ...(controlPlaneUrl ? { versionMetadataBinding: 'CF_VERSION_METADATA' } : {}),
3074
3078
  ...(tenantDataPlane
3075
3079
  ? {
@@ -4784,6 +4788,14 @@ async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = tr
4784
4788
  throw error;
4785
4789
  }
4786
4790
  const buildState = readStatus(buildStatus) ?? 'UNKNOWN';
4791
+ // Legacy ~/.hscli/config.yml accounts can authenticate and upload without
4792
+ // exposing their developer-account id through the selected account object.
4793
+ // HubSpot's build response is authoritative for that same id; retain it so
4794
+ // the app-auth handoff and later zero-selector log reads do not disappear.
4795
+ const resolvedDeveloperAccountId = resolveHubSpotDeveloperAccountIdFromBuild({
4796
+ developerAccountId,
4797
+ buildStatus,
4798
+ });
4787
4799
  buildStep?.[buildState === 'SUCCESS' ? 'ok' : 'fail'](`${buildState} · ${elapsedSince(buildStartedAt)}`);
4788
4800
  const autoDeployEnabled = readBooleanProperty(buildStatus, 'isAutoDeployEnabled');
4789
4801
  if (uploadOnly || readStatus(buildStatus) !== 'SUCCESS' || autoDeployEnabled) {
@@ -4794,7 +4806,7 @@ async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = tr
4794
4806
  await persistHubSpotAppBinding(root, {
4795
4807
  appId,
4796
4808
  projectName,
4797
- developerAccountId,
4809
+ developerAccountId: resolvedDeveloperAccountId,
4798
4810
  });
4799
4811
  // Build SUCCESS does NOT mean the app shipped: HubSpot's auto-deploy can
4800
4812
  // still fail (found live: "Apps are not allowed to have 0 redirect URLs"
@@ -4833,7 +4845,7 @@ async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = tr
4833
4845
  archivePath,
4834
4846
  appId,
4835
4847
  appUid,
4836
- developerAccountId,
4848
+ developerAccountId: resolvedDeveloperAccountId,
4837
4849
  buildId: upload.buildId,
4838
4850
  deployId: undefined,
4839
4851
  buildStatus,
@@ -4875,7 +4887,7 @@ async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = tr
4875
4887
  await persistHubSpotAppBinding(root, {
4876
4888
  appId,
4877
4889
  projectName,
4878
- developerAccountId,
4890
+ developerAccountId: resolvedDeveloperAccountId,
4879
4891
  });
4880
4892
  }
4881
4893
  return {
@@ -4883,7 +4895,7 @@ async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = tr
4883
4895
  archivePath,
4884
4896
  appId,
4885
4897
  appUid,
4886
- developerAccountId,
4898
+ developerAccountId: resolvedDeveloperAccountId,
4887
4899
  buildId: upload.buildId,
4888
4900
  deployId,
4889
4901
  buildStatus,
@@ -4894,12 +4906,27 @@ async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = tr
4894
4906
  };
4895
4907
  }
4896
4908
  async function optionalHubSpotAppId(client, projectName, appUid) {
4897
- try {
4898
- return await client.projects.resolveAppId(appUid === undefined ? { projectName } : { projectName, appUid });
4899
- }
4900
- catch {
4901
- return 0;
4909
+ return resolveHubSpotAppIdWithRetry({
4910
+ resolve: () => client.projects.resolveAppId(appUid === undefined ? { projectName } : { projectName, appUid }),
4911
+ });
4912
+ }
4913
+ /** @internal Exported for deterministic eventual-consistency regressions. */
4914
+ export async function resolveHubSpotAppIdWithRetry(input) {
4915
+ const attempts = Math.max(1, input.attempts ?? 4);
4916
+ const retryDelayMs = Math.max(0, input.retryDelayMs ?? 500);
4917
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
4918
+ try {
4919
+ const appId = await input.resolve();
4920
+ if (appId > 0)
4921
+ return appId;
4922
+ }
4923
+ catch {
4924
+ // A newly-created app can lag its successful build/deploy metadata.
4925
+ }
4926
+ if (attempt < attempts && retryDelayMs > 0)
4927
+ await sleep(retryDelayMs);
4902
4928
  }
4929
+ return 0;
4903
4930
  }
4904
4931
  async function readHubSpotProjectIntermediateRepresentation(root) {
4905
4932
  const hsproject = await readHubSpotProjectConfig(root);
@@ -5064,8 +5091,32 @@ async function readPersistedHubSpotAppBinding(root, expectedProjectName) {
5064
5091
  return undefined;
5065
5092
  }
5066
5093
  }
5067
- async function readPersistedHubSpotAppId(root, expectedProjectName) {
5068
- return (await readPersistedHubSpotAppBinding(root, expectedProjectName))?.appId;
5094
+ async function readPersistedHubSpotAppId(root, expectedProjectName, expectedDeveloperAccountId) {
5095
+ const binding = await readPersistedHubSpotAppBinding(root, expectedProjectName);
5096
+ if (!binding)
5097
+ return undefined;
5098
+ return hubSpotAppBindingMatchesDeveloperAccount(binding.developerAccountId, expectedDeveloperAccountId)
5099
+ ? binding.appId
5100
+ : undefined;
5101
+ }
5102
+ /** @internal Exported for deterministic account-switch regressions. */
5103
+ export function hubSpotAppBindingMatchesDeveloperAccount(persistedDeveloperAccountId, selectedDeveloperAccountId) {
5104
+ if (!selectedDeveloperAccountId)
5105
+ return true;
5106
+ return persistedDeveloperAccountId === selectedDeveloperAccountId;
5107
+ }
5108
+ async function resolveHubSpotDeployDeveloperAccountId(argv, root) {
5109
+ const explicit = resolveFlag(argv, '--developer-account-id') ?? process.env.HSX_HUBSPOT_DEVELOPER_ACCOUNT_ID;
5110
+ if (explicit?.trim())
5111
+ return explicit.trim();
5112
+ const configPath = resolveFlag(argv, '--config');
5113
+ const discovered = await discoverHubSpotAccounts(root, configPath ? { configPath } : {});
5114
+ const selected = discovered?.accounts.find((account) => account.isDefault) ??
5115
+ (discovered?.accounts.length === 1 ? discovered.accounts[0] : undefined);
5116
+ return selected ? String(selected.portalId) : undefined;
5117
+ }
5118
+ async function readPersistedHubSpotAppIdForDeploy(argv, root) {
5119
+ return readPersistedHubSpotAppId(root, await resolveHubSpotProjectName(argv, root), await resolveHubSpotDeployDeveloperAccountId(argv, root));
5069
5120
  }
5070
5121
  /**
5071
5122
  * Resolve the one HubSpot app a linked project already registered. The common
@@ -5076,7 +5127,7 @@ async function readPersistedHubSpotAppId(root, expectedProjectName) {
5076
5127
  async function resolveHubSpotAppIdForDeploy(input) {
5077
5128
  const explicit = readOptionalHubSpotAppId(resolveFlag(input.argv, '--hubspot-app-id') ?? process.env.HSX_HUBSPOT_APP_ID);
5078
5129
  const projectName = await resolveHubSpotProjectName(input.argv, input.root);
5079
- const persisted = await readPersistedHubSpotAppId(input.root, projectName);
5130
+ const persisted = await readPersistedHubSpotAppIdForDeploy(input.argv, input.root);
5080
5131
  const selected = explicit ?? persisted;
5081
5132
  if (selected || !input.controlPlaneUrl)
5082
5133
  return selected;
@@ -5120,6 +5171,18 @@ async function resolveHubSpotAppAuthUrl(argv, root) {
5120
5171
  function readStatus(value) {
5121
5172
  return isRecord(value) && typeof value.status === 'string' ? value.status : undefined;
5122
5173
  }
5174
+ /** @internal Exported for deterministic legacy-account deploy regressions. */
5175
+ export function resolveHubSpotDeveloperAccountIdFromBuild(input) {
5176
+ if (input.developerAccountId?.trim())
5177
+ return input.developerAccountId.trim();
5178
+ if (!isRecord(input.buildStatus))
5179
+ return undefined;
5180
+ const portalId = input.buildStatus.portalId;
5181
+ if (typeof portalId === 'number' && Number.isInteger(portalId) && portalId > 0) {
5182
+ return String(portalId);
5183
+ }
5184
+ return typeof portalId === 'string' && portalId.trim().length > 0 ? portalId.trim() : undefined;
5185
+ }
5123
5186
  const readSubbuildStatuses = readHubSpotSubbuildStatuses;
5124
5187
  const HUBSPOT_COMPONENT_TERMINAL_STATES = new Set(['SUCCESS', 'FAILURE', 'ERROR', 'CANCELLED']);
5125
5188
  /** Emit each HubSpot component once, when it reaches a terminal build state. */