@hs-x/cli 0.4.1 → 0.4.2-next.1

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.
Files changed (82) hide show
  1. package/README.md +7 -0
  2. package/dist/cli/index.d.ts.map +1 -1
  3. package/dist/cli/index.js +5 -1
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/command-catalog.d.ts +2 -2
  6. package/dist/command-catalog.d.ts.map +1 -1
  7. package/dist/command-catalog.js +1 -0
  8. package/dist/command-catalog.js.map +1 -1
  9. package/dist/commands/account.d.ts.map +1 -1
  10. package/dist/commands/account.js +28 -4
  11. package/dist/commands/account.js.map +1 -1
  12. package/dist/commands/api.d.ts.map +1 -1
  13. package/dist/commands/api.js +4 -3
  14. package/dist/commands/api.js.map +1 -1
  15. package/dist/commands/completion.d.ts.map +1 -1
  16. package/dist/commands/completion.js +36 -1
  17. package/dist/commands/completion.js.map +1 -1
  18. package/dist/commands/connect.js +5 -5
  19. package/dist/commands/connect.js.map +1 -1
  20. package/dist/commands/deploy.d.ts +3 -1
  21. package/dist/commands/deploy.d.ts.map +1 -1
  22. package/dist/commands/deploy.js +51 -145
  23. package/dist/commands/deploy.js.map +1 -1
  24. package/dist/commands/help-command.d.ts.map +1 -1
  25. package/dist/commands/help-command.js +20 -0
  26. package/dist/commands/help-command.js.map +1 -1
  27. package/dist/commands/migrate.d.ts.map +1 -1
  28. package/dist/commands/migrate.js +39 -9
  29. package/dist/commands/migrate.js.map +1 -1
  30. package/dist/commands/project-lifecycle-schema.d.ts +67 -0
  31. package/dist/commands/project-lifecycle-schema.d.ts.map +1 -0
  32. package/dist/commands/project-lifecycle-schema.js +143 -0
  33. package/dist/commands/project-lifecycle-schema.js.map +1 -0
  34. package/dist/commands/project-lifecycle.d.ts +61 -0
  35. package/dist/commands/project-lifecycle.d.ts.map +1 -0
  36. package/dist/commands/project-lifecycle.js +641 -0
  37. package/dist/commands/project-lifecycle.js.map +1 -0
  38. package/dist/commands/project.d.ts +37 -0
  39. package/dist/commands/project.d.ts.map +1 -1
  40. package/dist/commands/project.js +77 -0
  41. package/dist/commands/project.js.map +1 -1
  42. package/dist/commands/react-health.d.ts +73 -0
  43. package/dist/commands/react-health.d.ts.map +1 -0
  44. package/dist/commands/react-health.js +677 -0
  45. package/dist/commands/react-health.js.map +1 -0
  46. package/dist/commands/react.d.ts +71 -0
  47. package/dist/commands/react.d.ts.map +1 -0
  48. package/dist/commands/react.js +884 -0
  49. package/dist/commands/react.js.map +1 -0
  50. package/dist/constants.d.ts +1 -1
  51. package/dist/constants.d.ts.map +1 -1
  52. package/dist/constants.js +1 -1
  53. package/dist/constants.js.map +1 -1
  54. package/dist/control-plane-tenant-provisioning.d.ts.map +1 -1
  55. package/dist/control-plane-tenant-provisioning.js +9 -0
  56. package/dist/control-plane-tenant-provisioning.js.map +1 -1
  57. package/dist/errors-registry.d.ts.map +1 -1
  58. package/dist/errors-registry.js +54 -4
  59. package/dist/errors-registry.js.map +1 -1
  60. package/dist/hubspot-developer-client.d.ts +4 -3
  61. package/dist/hubspot-developer-client.d.ts.map +1 -1
  62. package/dist/hubspot-developer-client.js +15 -11
  63. package/dist/hubspot-developer-client.js.map +1 -1
  64. package/dist/hubspot-project-archive.d.ts.map +1 -1
  65. package/dist/hubspot-project-archive.js +15 -1
  66. package/dist/hubspot-project-archive.js.map +1 -1
  67. package/dist/hubspot-project-lifecycle.d.ts +64 -0
  68. package/dist/hubspot-project-lifecycle.d.ts.map +1 -0
  69. package/dist/hubspot-project-lifecycle.js +227 -0
  70. package/dist/hubspot-project-lifecycle.js.map +1 -0
  71. package/dist/hubspot-project-workspaces.d.ts +10 -0
  72. package/dist/hubspot-project-workspaces.d.ts.map +1 -0
  73. package/dist/hubspot-project-workspaces.js +224 -0
  74. package/dist/hubspot-project-workspaces.js.map +1 -0
  75. package/dist/init/templates.d.ts +1 -0
  76. package/dist/init/templates.d.ts.map +1 -1
  77. package/dist/init/templates.js +11 -8
  78. package/dist/init/templates.js.map +1 -1
  79. package/dist/reporter/style.d.ts.map +1 -1
  80. package/dist/reporter/style.js +10 -1
  81. package/dist/reporter/style.js.map +1 -1
  82. package/package.json +12 -9
@@ -9,6 +9,7 @@ import * as Options from '@effect/cli/Options';
9
9
  import { cloudflareResourceName, generateHubSpotRuntimeProject, generateProjectArtifacts, planPortalSchemaManagement, planWorkerCronTriggers, renderAlchemyProgram, renderCloudflareWorkerEntrypoint, stampMigratedRunServerlessShimOrigin, } from '@hs-x/codegen';
10
10
  import { HttpClientRequest, Schema, schemas, signDeploymentAttestationGrant } from '@hs-x/types';
11
11
  import { validateProject } from '@hs-x/validator';
12
+ import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
12
13
  import { Effect, Option } from 'effect';
13
14
  import { isLinked } from '../account-store.js';
14
15
  import { exitWith } from '../cli-error.js';
@@ -26,6 +27,7 @@ import { resolveDeployTargets } from '../deploy-targets.js';
26
27
  import { executeCliHttp } from '../effect-http.js';
27
28
  import { DirectHubSpotAccountResolutionError, createDeployHubSpotDeveloperClient, resolveDirectHubSpotContext, } from '../hubspot-developer-client.js';
28
29
  import { HubSpotProjectArchiveSourceError, fingerprintHubSpotArchiveSource, writeLocalHubSpotProjectArchive, } from '../hubspot-project-archive.js';
30
+ import { deployHubSpotProjectBuild, waitForHubSpotAutoDeploy, waitForHubSpotBuildStatus, } from '../hubspot-project-lifecycle.js';
29
31
  import { detectLocalProjectMode, readHubSpotProjectDescriptor, validateHubSpotProject, } from '../hubspot-project.js';
30
32
  import { hydrateAncestorEnv } from '../load-env.js';
31
33
  import { LocalProjectPickerCancelled, resolveLocalProject } from '../local-project-picker.js';
@@ -487,6 +489,9 @@ async function waitForHealthyControlPlaneDrift({ controlPlaneUrl, projectId, dep
487
489
  } while (Date.now() - startedAt < timeoutMs);
488
490
  throw new Error(`Timed out waiting for deploy ${deployId} to become healthy; latest drift state was ${lastState}${lastReason ? `: ${lastReason}` : ''}.`);
489
491
  }
492
+ function sleep(ms) {
493
+ return new Promise((resolve) => setTimeout(resolve, ms));
494
+ }
490
495
  function isRecord(value) {
491
496
  return typeof value === 'object' && value !== null;
492
497
  }
@@ -648,6 +653,7 @@ async function executeHubSpotDirectDeployCommand({ argv, root, json, }) {
648
653
  '--portal-schema-live',
649
654
  '--portal-schema-fixture',
650
655
  '--local-hubspot-schema',
656
+ '--diagnostic-card-signature-capture',
651
657
  ].find((flag) => includesCliFlag(argv, flag));
652
658
  if (unsupported) {
653
659
  return inputValidationError({
@@ -1032,6 +1038,7 @@ export async function deployCommand({ argv, root, json, }) {
1032
1038
  const cloudflareDeploy = argv.includes('--cloudflare-deploy');
1033
1039
  const cloudflareDryRun = argv.includes('--cloudflare-dry-run');
1034
1040
  const explicitCloudflareDeployRequested = cloudflareDeploy || cloudflareDryRun;
1041
+ const diagnosticCardSignatureCapture = argv.includes('--diagnostic-card-signature-capture');
1035
1042
  const noManageSchema = argv.includes('--no-manage-schema');
1036
1043
  const localControlPlane = argv.includes('--local-control-plane');
1037
1044
  const recordLocal = argv.includes('--record-local');
@@ -1684,6 +1691,7 @@ export async function deployCommand({ argv, root, json, }) {
1684
1691
  ? 'plan'
1685
1692
  : 'local-artifacts',
1686
1693
  deploymentMode,
1694
+ diagnosticCardSignatureCapture,
1687
1695
  targets: {
1688
1696
  cloudflare: {
1689
1697
  requested: cloudflareDeployRequested,
@@ -1941,6 +1949,15 @@ function buildDeploySummaryRows(input) {
1941
1949
  value: input.plan.deploymentMode,
1942
1950
  },
1943
1951
  ];
1952
+ if (input.plan.diagnosticCardSignatureCapture) {
1953
+ rows.push({
1954
+ key: 'card signature capture',
1955
+ value: 'enabled for this deploy',
1956
+ status: 'warn',
1957
+ detail: 'exact signed card requests will be written to Worker logs',
1958
+ hint: 'Capture one test request, then immediately redeploy without --diagnostic-card-signature-capture.',
1959
+ });
1960
+ }
1944
1961
  if (input.controlPlanePlan) {
1945
1962
  rows.push({
1946
1963
  key: 'deploy',
@@ -2738,6 +2755,8 @@ async function executeCloudflareWorkerDeploy({ argv, root, worker, index, allowC
2738
2755
  await writeFile(entrypointPath, renderCloudflareWorkerEntrypoint({
2739
2756
  workerImportPath: relativeImportPath(dirname(entrypointPath), workerSourcePath),
2740
2757
  requireActiveCardInstall: appConfig.auth === 'oauth',
2758
+ ...(appConfig.install ? { installSuccessUrl: appConfig.install.successUrl } : {}),
2759
+ diagnosticCardSignatureCapture: argv.includes('--diagnostic-card-signature-capture'),
2741
2760
  // Tenant D1/KV and the grant secret are shared across the linked app's
2742
2761
  // Workers. Wire the matching routes on every linked Worker even though
2743
2762
  // revision-bound heartbeat/attestation remains primary-only below.
@@ -3886,6 +3905,8 @@ export async function readHsxAppConfig(root) {
3886
3905
  }
3887
3906
  }
3888
3907
  }
3908
+ if (loaded?.install)
3909
+ assertInstallSuccessUrl(loaded.install.successUrl);
3889
3910
  return {
3890
3911
  appName: loaded?.name ?? /name\s*:\s*["'`]([^"'`]+)["'`]/.exec(raw)?.[1] ?? basename(root),
3891
3912
  // No regex fallback for description: a raw-text match could pick up a
@@ -3907,6 +3928,7 @@ export async function readHsxAppConfig(root) {
3907
3928
  ...(loaded?.rateLimits !== undefined ? { rateLimits: loaded.rateLimits } : {}),
3908
3929
  ...(loaded?.alerts !== undefined ? { alerts: loaded.alerts } : {}),
3909
3930
  ...(loaded?.redaction !== undefined ? { redaction: loaded.redaction } : {}),
3931
+ ...(loaded?.install !== undefined ? { install: loaded.install } : {}),
3910
3932
  // Helper-based declarations are only recoverable from the loaded module
3911
3933
  // (the literal parser cannot evaluate card(...) or mcpServer(...) calls).
3912
3934
  cards: loaded?.cards ?? [],
@@ -3969,6 +3991,20 @@ async function loadHsxAppConfigModule(root) {
3969
3991
  ? app
3970
3992
  : undefined;
3971
3993
  }
3994
+ function assertInstallSuccessUrl(value) {
3995
+ let url;
3996
+ try {
3997
+ url = new URL(value);
3998
+ }
3999
+ catch {
4000
+ throw new Error('install.successUrl must be an absolute HTTPS URL.');
4001
+ }
4002
+ const localHttp = url.protocol === 'http:' &&
4003
+ (url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]');
4004
+ if ((url.protocol !== 'https:' && !localHttp) || url.username || url.password) {
4005
+ throw new Error('install.successUrl must use HTTPS without embedded credentials (localhost HTTP is allowed for development).');
4006
+ }
4007
+ }
3972
4008
  function readStringArrayProperty(source, propertyName) {
3973
4009
  const body = new RegExp(`${propertyName}\\s*:\\s*\\[([\\s\\S]*?)\\]`).exec(source)?.[1];
3974
4010
  if (!body) {
@@ -4251,14 +4287,15 @@ async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = tr
4251
4287
  await client.projects.ensureProject({ projectName });
4252
4288
  const hsproject = await readHubSpotProjectConfig(root);
4253
4289
  const configuredPlatformVersion = typeof hsproject.platformVersion === 'string' ? hsproject.platformVersion : undefined;
4290
+ const platformVersion = prepared?.platformVersion ??
4291
+ resolveFlag(argv, '--platform-version') ??
4292
+ configuredPlatformVersion ??
4293
+ '2026.03';
4254
4294
  const upload = await client.projects.upload({
4255
4295
  projectName,
4256
4296
  archivePath,
4257
4297
  message: prepared?.buildMessage ?? resolveFlag(argv, '--message') ?? 'HS-X HubSpot-only deploy',
4258
- platformVersion: prepared?.platformVersion ??
4259
- resolveFlag(argv, '--platform-version') ??
4260
- configuredPlatformVersion ??
4261
- '2026.03',
4298
+ platformVersion,
4262
4299
  intermediateRepresentation,
4263
4300
  skipAutoDeploy: uploadOnly,
4264
4301
  });
@@ -4288,13 +4325,13 @@ async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = tr
4288
4325
  // therefore a strict build-only result.
4289
4326
  const autoDeployExpected = !uploadOnly && autoDeployEnabled === true && readStatus(buildStatus) === 'SUCCESS';
4290
4327
  const autoDeployStatus = autoDeployExpected
4291
- ? await waitForHubSpotAutoDeploy({
4328
+ ? (await waitForHubSpotAutoDeploy({
4292
4329
  client,
4293
4330
  projectName,
4294
4331
  buildId: upload.buildId,
4295
4332
  buildStatus,
4296
4333
  timeoutMs,
4297
- })
4334
+ })).status
4298
4335
  : undefined;
4299
4336
  return {
4300
4337
  projectName,
@@ -4311,25 +4348,17 @@ async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = tr
4311
4348
  local,
4312
4349
  };
4313
4350
  }
4314
- const deploy = await withTransientDeployRetry(() => client.projects.deployBuild({
4351
+ const deployed = await deployHubSpotProjectBuild({
4352
+ client,
4315
4353
  projectName,
4316
4354
  buildId: upload.buildId,
4317
4355
  force: prepared?.force ?? argv.includes('--force'),
4318
- }));
4319
- const deployResultType = readStringProperty(deploy, 'buildResultType');
4320
- if (deployResultType === 'DEPLOY_BLOCKED') {
4321
- throw new Error(describeBlockedHubSpotDeploy(deploy));
4322
- }
4323
- const deployId = readNumericProperty(deploy, 'deployId') ?? readNumericProperty(deploy, 'id');
4324
- if (deployId === undefined) {
4325
- throw new Error(`HubSpot returned ${deployResultType ?? 'an unrecognized response'} without a deploy id.`);
4326
- }
4327
- const deployStatus = await waitForHubSpotDeployStatus({
4328
- client,
4329
- projectName,
4330
- deployId,
4356
+ legacy: isLegacyProject(platformVersion),
4331
4357
  timeoutMs,
4358
+ executeDeploy: (request) => withTransientDeployRetry(() => client.projects.deployBuild(request)),
4332
4359
  });
4360
+ const deployId = deployed.deployId;
4361
+ const deployStatus = deployed.status;
4333
4362
  const appId = await optionalHubSpotAppId(client, projectName, appUid);
4334
4363
  if (persistBinding) {
4335
4364
  await persistHubSpotAppBinding(root, {
@@ -4361,96 +4390,6 @@ async function optionalHubSpotAppId(client, projectName, appUid) {
4361
4390
  return 0;
4362
4391
  }
4363
4392
  }
4364
- /**
4365
- * Poll the auto-deploy triggered by a successful build until it reaches a
4366
- * terminal state. The build status carries the deploy task locator
4367
- * (deployStatusTaskLocator.id); absent/transient lookups resolve undefined —
4368
- * the caller treats that as "could not verify", never as success.
4369
- */
4370
- async function waitForHubSpotAutoDeploy({ client, projectName, buildId, buildStatus, timeoutMs = 60_000, intervalMs = 2_000, }) {
4371
- const deadline = Date.now() + timeoutMs;
4372
- let latestBuildStatus = buildStatus;
4373
- let deployId = readHubSpotDeployLocator(latestBuildStatus);
4374
- let last;
4375
- while (Date.now() < deadline) {
4376
- if (deployId === undefined) {
4377
- try {
4378
- latestBuildStatus = await client.projects.getBuildStatus({ projectName, buildId });
4379
- deployId = readHubSpotDeployLocator(latestBuildStatus);
4380
- }
4381
- catch {
4382
- // The build may be visible before its auto-deploy locator. Keep trying
4383
- // until the same bounded timeout instead of declaring success.
4384
- }
4385
- if (deployId === undefined) {
4386
- await sleep(intervalMs);
4387
- continue;
4388
- }
4389
- }
4390
- try {
4391
- last = await client.projects.getDeployStatus({ projectName, deployId });
4392
- }
4393
- catch {
4394
- last = undefined;
4395
- }
4396
- const status = readStatus(last);
4397
- if (status && !isHubSpotDeployInProgress(status)) {
4398
- return last;
4399
- }
4400
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
4401
- }
4402
- return last;
4403
- }
4404
- async function waitForHubSpotDeployStatus({ client, projectName, deployId, timeoutMs, intervalMs = 1_000, }) {
4405
- const deadline = Date.now() + timeoutMs;
4406
- let latest;
4407
- do {
4408
- latest = await client.projects.getDeployStatus({ projectName, deployId });
4409
- const status = readStatus(latest);
4410
- if (status && !isHubSpotDeployInProgress(status))
4411
- return latest;
4412
- await sleep(intervalMs);
4413
- } while (Date.now() < deadline);
4414
- return latest;
4415
- }
4416
- function isHubSpotDeployInProgress(status) {
4417
- return new Set([
4418
- 'PENDING',
4419
- 'QUEUED',
4420
- 'BUILDING',
4421
- 'DEPLOYING',
4422
- 'PROCESSING',
4423
- 'STARTED',
4424
- 'IN_PROGRESS',
4425
- ]).has(status.toUpperCase());
4426
- }
4427
- function readHubSpotDeployLocator(value) {
4428
- if (!isRecord(value) || !isRecord(value.deployStatusTaskLocator))
4429
- return undefined;
4430
- const id = value.deployStatusTaskLocator.id;
4431
- if (typeof id === 'number')
4432
- return id;
4433
- if (typeof id !== 'string')
4434
- return undefined;
4435
- const parsed = Number.parseInt(id, 10);
4436
- return Number.isNaN(parsed) ? undefined : parsed;
4437
- }
4438
- async function waitForHubSpotBuildStatus({ client, projectName, buildId, timeoutMs, intervalMs, }) {
4439
- const startedAt = Date.now();
4440
- let latest;
4441
- do {
4442
- latest = await client.projects.getBuildStatus({ projectName, buildId });
4443
- const status = readStatus(latest);
4444
- if (status === 'SUCCESS' || status === 'FAILURE') {
4445
- return latest;
4446
- }
4447
- await sleep(intervalMs);
4448
- } while (Date.now() - startedAt < timeoutMs);
4449
- return latest;
4450
- }
4451
- function sleep(ms) {
4452
- return new Promise((resolve) => setTimeout(resolve, ms));
4453
- }
4454
4393
  async function readHubSpotProjectIntermediateRepresentation(root) {
4455
4394
  const hsproject = await readHubSpotProjectConfig(root);
4456
4395
  const srcDir = typeof hsproject.srcDir === 'string' ? hsproject.srcDir : 'src';
@@ -4561,41 +4500,6 @@ async function collectHubSpotMetaFiles(root) {
4561
4500
  await walk(root);
4562
4501
  return files.sort();
4563
4502
  }
4564
- function readNumericProperty(value, key) {
4565
- if (!isRecord(value))
4566
- return undefined;
4567
- const property = value[key];
4568
- if (typeof property === 'number')
4569
- return property;
4570
- if (typeof property !== 'string')
4571
- return undefined;
4572
- const parsed = Number.parseInt(property, 10);
4573
- return Number.isNaN(parsed) ? undefined : parsed;
4574
- }
4575
- function readStringProperty(value, key) {
4576
- return isRecord(value) && typeof value[key] === 'string' ? value[key] : undefined;
4577
- }
4578
- function describeBlockedHubSpotDeploy(value) {
4579
- if (!isRecord(value))
4580
- return 'HubSpot blocked the deploy.';
4581
- const candidates = [value.issues, value.blockingIssues, value.errors, value.message];
4582
- const details = candidates.flatMap((candidate) => {
4583
- if (typeof candidate === 'string')
4584
- return [candidate];
4585
- if (!Array.isArray(candidate))
4586
- return [];
4587
- return candidate.map((item) => {
4588
- if (typeof item === 'string')
4589
- return item;
4590
- if (!isRecord(item))
4591
- return String(item);
4592
- return String(item.message ?? item.reason ?? item.description ?? JSON.stringify(item));
4593
- });
4594
- });
4595
- return details.length > 0
4596
- ? `HubSpot blocked the deploy: ${details.join('; ')}`
4597
- : 'HubSpot blocked the deploy. Re-run with --force only after reviewing its removal warnings.';
4598
- }
4599
4503
  function readBooleanProperty(value, key) {
4600
4504
  return isRecord(value) && typeof value[key] === 'boolean' ? value[key] : undefined;
4601
4505
  }
@@ -5364,6 +5268,7 @@ const deployOptions = {
5364
5268
  heartbeat: Options.boolean('heartbeat').pipe(Options.withDefault(false)), // --heartbeat
5365
5269
  noHeartbeat: Options.boolean('no-heartbeat').pipe(Options.withDefault(false)), // --no-heartbeat
5366
5270
  useEnv: Options.boolean('use-env').pipe(Options.withDefault(false)), // --use-env
5271
+ diagnosticCardSignatureCapture: Options.boolean('diagnostic-card-signature-capture').pipe(Options.withDefault(false)),
5367
5272
  // Declared explicitly: without it @effect/cli treats `--no-record` as the
5368
5273
  // negation of an undeclared `record` boolean and fails routing with a
5369
5274
  // confusing root-level "Invalid subcommand" error (the flag is documented).
@@ -5428,6 +5333,7 @@ const deployBaseCmd = Command.make('deploy', deployOptions, (opts) => runHandler
5428
5333
  flag('--heartbeat', opts.heartbeat);
5429
5334
  flag('--no-heartbeat', opts.noHeartbeat);
5430
5335
  flag('--use-env', opts.useEnv);
5336
+ flag('--diagnostic-card-signature-capture', opts.diagnosticCardSignatureCapture);
5431
5337
  flag('--no-record', opts.noRecord);
5432
5338
  flag('--portal-schema-live', opts.portalSchemaLive);
5433
5339
  flag('--local-hubspot-schema', opts.localHubspotSchema);