@hs-x/cli 0.3.7 → 0.4.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.
- package/README.md +120 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +38 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/command-catalog.d.ts +2 -2
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +1 -0
- package/dist/command-catalog.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +268 -16
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +177 -20
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/control-plane-read.d.ts +28 -0
- package/dist/commands/control-plane-read.d.ts.map +1 -1
- package/dist/commands/control-plane-read.js +134 -28
- package/dist/commands/control-plane-read.js.map +1 -1
- package/dist/commands/deploy.d.ts +5 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +605 -102
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +19 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +1335 -83
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +27 -10
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/help-command.d.ts.map +1 -1
- package/dist/commands/help-command.js +145 -35
- package/dist/commands/help-command.js.map +1 -1
- package/dist/commands/project.d.ts +107 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +586 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +15 -5
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/validate.d.ts +19 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +26 -7
- package/dist/commands/validate.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/dev/account-selection.d.ts +16 -0
- package/dist/dev/account-selection.d.ts.map +1 -0
- package/dist/dev/account-selection.js +22 -0
- package/dist/dev/account-selection.js.map +1 -0
- package/dist/dev/browser-log-bridge.d.ts +32 -0
- package/dist/dev/browser-log-bridge.d.ts.map +1 -0
- package/dist/dev/browser-log-bridge.js +156 -0
- package/dist/dev/browser-log-bridge.js.map +1 -0
- package/dist/dev/compat-shim.d.ts +3 -3
- package/dist/dev/compat-shim.d.ts.map +1 -1
- package/dist/dev/compat-shim.js +7 -6
- package/dist/dev/compat-shim.js.map +1 -1
- package/dist/dev/component-selection.d.ts +25 -0
- package/dist/dev/component-selection.d.ts.map +1 -0
- package/dist/dev/component-selection.js +79 -0
- package/dist/dev/component-selection.js.map +1 -0
- package/dist/dev/console-capture.d.ts +18 -0
- package/dist/dev/console-capture.d.ts.map +1 -0
- package/dist/dev/console-capture.js +57 -0
- package/dist/dev/console-capture.js.map +1 -0
- package/dist/dev/event-bus.d.ts +1 -1
- package/dist/dev/event-bus.js +1 -1
- package/dist/dev/frontend-log-relay.d.ts +27 -0
- package/dist/dev/frontend-log-relay.d.ts.map +1 -0
- package/dist/dev/frontend-log-relay.js +102 -0
- package/dist/dev/frontend-log-relay.js.map +1 -0
- package/dist/dev/keyboard.d.ts +1 -0
- package/dist/dev/keyboard.d.ts.map +1 -1
- package/dist/dev/keyboard.js +9 -3
- package/dist/dev/keyboard.js.map +1 -1
- package/dist/dev/log-server.js +3 -0
- package/dist/dev/log-server.js.map +1 -1
- package/dist/dev/managed-session.d.ts +124 -0
- package/dist/dev/managed-session.d.ts.map +1 -0
- package/dist/dev/managed-session.js +597 -0
- package/dist/dev/managed-session.js.map +1 -0
- package/dist/dev/port-process.d.ts +21 -0
- package/dist/dev/port-process.d.ts.map +1 -0
- package/dist/dev/port-process.js +145 -0
- package/dist/dev/port-process.js.map +1 -0
- package/dist/dev/session-manager.d.ts +11 -1
- package/dist/dev/session-manager.d.ts.map +1 -1
- package/dist/dev/session-manager.js +62 -12
- package/dist/dev/session-manager.js.map +1 -1
- package/dist/dev/stream-event-format.d.ts +14 -0
- package/dist/dev/stream-event-format.d.ts.map +1 -0
- package/dist/dev/stream-event-format.js +262 -0
- package/dist/dev/stream-event-format.js.map +1 -0
- package/dist/dev/stream-filter.d.ts.map +1 -1
- package/dist/dev/stream-filter.js +16 -1
- package/dist/dev/stream-filter.js.map +1 -1
- package/dist/dev/stream-renderer.d.ts +4 -2
- package/dist/dev/stream-renderer.d.ts.map +1 -1
- package/dist/dev/stream-renderer.js +224 -84
- package/dist/dev/stream-renderer.js.map +1 -1
- package/dist/errors-registry.d.ts.map +1 -1
- package/dist/errors-registry.js +213 -0
- package/dist/errors-registry.js.map +1 -1
- package/dist/hubspot-app-logs.d.ts +38 -5
- package/dist/hubspot-app-logs.d.ts.map +1 -1
- package/dist/hubspot-app-logs.js +88 -17
- package/dist/hubspot-app-logs.js.map +1 -1
- package/dist/hubspot-auth.d.ts +19 -1
- package/dist/hubspot-auth.d.ts.map +1 -1
- package/dist/hubspot-auth.js +240 -85
- package/dist/hubspot-auth.js.map +1 -1
- package/dist/hubspot-developer-client.d.ts +87 -0
- package/dist/hubspot-developer-client.d.ts.map +1 -1
- package/dist/hubspot-developer-client.js +232 -1
- package/dist/hubspot-developer-client.js.map +1 -1
- package/dist/hubspot-project-archive.d.ts +21 -0
- package/dist/hubspot-project-archive.d.ts.map +1 -0
- package/dist/hubspot-project-archive.js +281 -0
- package/dist/hubspot-project-archive.js.map +1 -0
- package/dist/hubspot-project.d.ts +32 -0
- package/dist/hubspot-project.d.ts.map +1 -0
- package/dist/hubspot-project.js +269 -0
- package/dist/hubspot-project.js.map +1 -0
- package/dist/local-project-picker.js +1 -1
- package/dist/local-project-picker.js.map +1 -1
- package/dist/output-context.js +1 -1
- package/dist/output-context.js.map +1 -1
- package/dist/services/hubspot-dev-client.js +3 -0
- package/dist/services/hubspot-dev-client.js.map +1 -1
- package/package.json +14 -12
package/dist/commands/deploy.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createHash, randomBytes } from 'node:crypto';
|
|
3
|
-
import {
|
|
3
|
+
import { lstat, mkdir, readFile, readdir, realpath, rename, rmdir, stat, unlink, writeFile, } from 'node:fs/promises';
|
|
4
4
|
import { createServer } from 'node:http';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
7
6
|
import { pathToFileURL } from 'node:url';
|
|
8
7
|
import * as Command from '@effect/cli/Command';
|
|
@@ -25,7 +24,9 @@ import { createControlPlaneTenantProvisioningAuthority, } from '../control-plane
|
|
|
25
24
|
import { captureDeployRevision } from '../deploy-revision-capture.js';
|
|
26
25
|
import { resolveDeployTargets } from '../deploy-targets.js';
|
|
27
26
|
import { executeCliHttp } from '../effect-http.js';
|
|
28
|
-
import { createDeployHubSpotDeveloperClient } from '../hubspot-developer-client.js';
|
|
27
|
+
import { DirectHubSpotAccountResolutionError, createDeployHubSpotDeveloperClient, resolveDirectHubSpotContext, } from '../hubspot-developer-client.js';
|
|
28
|
+
import { HubSpotProjectArchiveSourceError, fingerprintHubSpotArchiveSource, writeLocalHubSpotProjectArchive, } from '../hubspot-project-archive.js';
|
|
29
|
+
import { detectLocalProjectMode, readHubSpotProjectDescriptor, validateHubSpotProject, } from '../hubspot-project.js';
|
|
29
30
|
import { hydrateAncestorEnv } from '../load-env.js';
|
|
30
31
|
import { LocalProjectPickerCancelled, resolveLocalProject } from '../local-project-picker.js';
|
|
31
32
|
import { getMachineId, getMachineSecret, previewMachineId } from '../machine-id.js';
|
|
@@ -541,6 +542,447 @@ function workerSourceSummaries(workers) {
|
|
|
541
542
|
function workerSyncSchemaModes(workers) {
|
|
542
543
|
return workers.flatMap((worker) => worker.capabilities.flatMap((capability) => capability.kind === 'sync' ? [`${capability.id}: ${capability.manageSchema ?? false}`] : []));
|
|
543
544
|
}
|
|
545
|
+
function includesCliFlag(argv, flag) {
|
|
546
|
+
return argv.some((argument) => argument === flag || argument.startsWith(`${flag}=`));
|
|
547
|
+
}
|
|
548
|
+
function directDeployAccountView(account) {
|
|
549
|
+
return {
|
|
550
|
+
id: account.id,
|
|
551
|
+
...(account.name ? { name: account.name } : {}),
|
|
552
|
+
source: account.source,
|
|
553
|
+
selectedBy: account.selectedBy,
|
|
554
|
+
authReady: account.authReady,
|
|
555
|
+
...(account.configSource ? { configSource: account.configSource } : {}),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function directDeployAccountLabel(account) {
|
|
559
|
+
if (!account)
|
|
560
|
+
return 'unresolved HubSpot account';
|
|
561
|
+
return account.name ? `${account.name} (${account.id})` : account.id;
|
|
562
|
+
}
|
|
563
|
+
function directDeployAccountFailure(cause) {
|
|
564
|
+
if (cause instanceof DirectHubSpotAccountResolutionError) {
|
|
565
|
+
const suffix = ` ${cause.hint}`;
|
|
566
|
+
return {
|
|
567
|
+
reasonCode: cause.code,
|
|
568
|
+
message: cause.message.endsWith(suffix)
|
|
569
|
+
? cause.message.slice(0, -suffix.length)
|
|
570
|
+
: cause.message,
|
|
571
|
+
hint: cause.hint,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
reasonCode: 'HSX_E_DIRECT_HUBSPOT_ACCOUNT_CONFIG',
|
|
576
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
577
|
+
hint: 'Fix the HubSpot CLI account configuration and retry.',
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function describeHubSpotDeployFailure(cause) {
|
|
581
|
+
const record = isRecord(cause) ? cause : undefined;
|
|
582
|
+
const data = isRecord(record?.data) ? record.data : undefined;
|
|
583
|
+
const body = isRecord(record?.body) ? record.body : undefined;
|
|
584
|
+
const validationErrors = Array.isArray(record?.validationErrors)
|
|
585
|
+
? record.validationErrors.filter((item) => typeof item === 'string')
|
|
586
|
+
: [];
|
|
587
|
+
const values = [
|
|
588
|
+
cause instanceof Error ? cause.message : String(cause),
|
|
589
|
+
typeof record?.status === 'number' ? `HTTP ${record.status}` : undefined,
|
|
590
|
+
typeof data?.message === 'string' ? data.message : undefined,
|
|
591
|
+
typeof body?.message === 'string' ? body.message : undefined,
|
|
592
|
+
typeof record?.category === 'string' ? record.category : undefined,
|
|
593
|
+
typeof data?.category === 'string' ? data.category : undefined,
|
|
594
|
+
...validationErrors,
|
|
595
|
+
typeof record?.correlationId === 'string'
|
|
596
|
+
? `correlation ${record.correlationId}`
|
|
597
|
+
: typeof data?.correlationId === 'string'
|
|
598
|
+
? `correlation ${data.correlationId}`
|
|
599
|
+
: typeof body?.correlationId === 'string'
|
|
600
|
+
? `correlation ${body.correlationId}`
|
|
601
|
+
: undefined,
|
|
602
|
+
].filter((value) => Boolean(value));
|
|
603
|
+
return [...new Set(values)].join(' · ');
|
|
604
|
+
}
|
|
605
|
+
function directDeployDoneMessage(upload) {
|
|
606
|
+
if (upload.deploySkipped && !upload.autoDeployExpected) {
|
|
607
|
+
return 'Uploaded and built in HubSpot; deployment skipped';
|
|
608
|
+
}
|
|
609
|
+
if (upload.autoDeployExpected)
|
|
610
|
+
return 'Uploaded, built, and auto-deployed in HubSpot';
|
|
611
|
+
return 'Uploaded, built, and deployed in HubSpot';
|
|
612
|
+
}
|
|
613
|
+
async function hubSpotDirectDeployCommand(input) {
|
|
614
|
+
try {
|
|
615
|
+
return await executeHubSpotDirectDeployCommand(input);
|
|
616
|
+
}
|
|
617
|
+
catch (cause) {
|
|
618
|
+
if (!(cause instanceof HubSpotProjectArchiveSourceError))
|
|
619
|
+
throw cause;
|
|
620
|
+
return inputValidationError({
|
|
621
|
+
argv: input.argv,
|
|
622
|
+
command: 'deploy',
|
|
623
|
+
json: input.json,
|
|
624
|
+
code: cause.code,
|
|
625
|
+
message: cause.message,
|
|
626
|
+
hint: cause.hint,
|
|
627
|
+
detail: 'No archive was retained and no upload or deploy request was made.',
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async function executeHubSpotDirectDeployCommand({ argv, root, json, }) {
|
|
632
|
+
const reporter = createReporter({ command: 'deploy', argv, entry: !json });
|
|
633
|
+
const machineOutput = json || reporter.mode === 'ndjson';
|
|
634
|
+
const unsupported = [
|
|
635
|
+
'--account-id',
|
|
636
|
+
'--project-id',
|
|
637
|
+
'--control-plane-url',
|
|
638
|
+
'--build-only',
|
|
639
|
+
'--cloudflare-deploy',
|
|
640
|
+
'--cloudflare-dry-run',
|
|
641
|
+
'--local-control-plane',
|
|
642
|
+
'--record-local',
|
|
643
|
+
'--promote-when-healthy',
|
|
644
|
+
'--no-record',
|
|
645
|
+
'--heartbeat',
|
|
646
|
+
'--no-heartbeat',
|
|
647
|
+
'--apply-schema',
|
|
648
|
+
'--portal-schema-live',
|
|
649
|
+
'--portal-schema-fixture',
|
|
650
|
+
'--local-hubspot-schema',
|
|
651
|
+
].find((flag) => includesCliFlag(argv, flag));
|
|
652
|
+
if (unsupported) {
|
|
653
|
+
return inputValidationError({
|
|
654
|
+
argv,
|
|
655
|
+
command: 'deploy',
|
|
656
|
+
json,
|
|
657
|
+
code: 'HSX_E_INPUT_INVALID',
|
|
658
|
+
message: `${unsupported} is an HS-X project feature.`,
|
|
659
|
+
hint: 'Remove the flag to deploy this native HubSpot project directly to HubSpot.',
|
|
660
|
+
detail: 'HubSpot-direct mode never contacts the HS-X control plane or Cloudflare.',
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
const preparationFingerprint = await fingerprintHubSpotArchiveSource(root, false);
|
|
664
|
+
const [descriptor, validation, projectName, intermediateRepresentation] = await Promise.all([
|
|
665
|
+
readHubSpotProjectDescriptor(root),
|
|
666
|
+
validateHubSpotProject({ root }),
|
|
667
|
+
resolveHubSpotProjectName(argv, root),
|
|
668
|
+
readHubSpotProjectIntermediateRepresentation(root),
|
|
669
|
+
]);
|
|
670
|
+
const appUid = readAppUidFromIntermediateRepresentation(intermediateRepresentation) ??
|
|
671
|
+
(await readAppUidFromProjectSource(root));
|
|
672
|
+
const sourceFingerprint = validation.ok
|
|
673
|
+
? await fingerprintHubSpotArchiveSource(root, intermediateRepresentation !== undefined)
|
|
674
|
+
: preparationFingerprint;
|
|
675
|
+
// Enclose every source-derived preparation read (validation, descriptor,
|
|
676
|
+
// IR, app uid, and scoped fingerprint) in one full-tree stability check.
|
|
677
|
+
// A later edit is still caught against sourceFingerprint in the staged copy.
|
|
678
|
+
if ((await fingerprintHubSpotArchiveSource(root, false)) !== preparationFingerprint) {
|
|
679
|
+
return inputValidationError({
|
|
680
|
+
argv,
|
|
681
|
+
command: 'deploy',
|
|
682
|
+
json,
|
|
683
|
+
code: 'HSX_E_INPUT_INVALID',
|
|
684
|
+
message: 'HubSpot project sources changed while preparing the deploy plan.',
|
|
685
|
+
hint: 'Wait for local edits or generators to settle, then review a fresh plan.',
|
|
686
|
+
detail: 'No upload or deploy request was made.',
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
const uploadOnly = argv.includes('--hubspot-upload-only') || argv.includes('--skip-deploy');
|
|
690
|
+
const planOnly = argv.includes('--plan') || argv.includes('--dry-run');
|
|
691
|
+
const localHubSpotAdapter = argv.includes('--local-hubspot-upload');
|
|
692
|
+
const platformVersion = resolveFlag(argv, '--platform-version') ?? descriptor.platformVersion ?? '2026.03';
|
|
693
|
+
const buildMessage = resolveFlag(argv, '--message') ?? 'HS-X HubSpot-direct deploy';
|
|
694
|
+
const buildTimeoutMs = Number(resolveFlag(argv, '--hubspot-build-timeout-ms') ?? '60000');
|
|
695
|
+
const force = argv.includes('--force');
|
|
696
|
+
let account;
|
|
697
|
+
let accountFailure;
|
|
698
|
+
let preparedUpload;
|
|
699
|
+
if (localHubSpotAdapter) {
|
|
700
|
+
account = {
|
|
701
|
+
id: resolveFlag(argv, '--developer-account-id') ?? 'local',
|
|
702
|
+
name: 'local HubSpot adapter',
|
|
703
|
+
source: 'local-adapter',
|
|
704
|
+
selectedBy: 'local-adapter',
|
|
705
|
+
authReady: true,
|
|
706
|
+
};
|
|
707
|
+
preparedUpload = {
|
|
708
|
+
projectName,
|
|
709
|
+
intermediateRepresentation,
|
|
710
|
+
appUid,
|
|
711
|
+
developerAccountId: resolveFlag(argv, '--developer-account-id') ?? process.env.HSX_HUBSPOT_DEVELOPER_ACCOUNT_ID,
|
|
712
|
+
platformVersion,
|
|
713
|
+
buildMessage,
|
|
714
|
+
buildTimeoutMs,
|
|
715
|
+
force,
|
|
716
|
+
sourceFingerprint,
|
|
717
|
+
createClient: () => createDeployHubSpotDeveloperClient({
|
|
718
|
+
argv,
|
|
719
|
+
local: true,
|
|
720
|
+
allowedOperations: ['hubspot-project-upload'],
|
|
721
|
+
cwd: root,
|
|
722
|
+
}),
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
try {
|
|
727
|
+
const context = await resolveDirectHubSpotContext({ argv, cwd: root });
|
|
728
|
+
account = directDeployAccountView(context.account);
|
|
729
|
+
if (!account.authReady) {
|
|
730
|
+
accountFailure = {
|
|
731
|
+
reasonCode: 'HSX_E_DIRECT_HUBSPOT_AUTH_REQUIRED',
|
|
732
|
+
message: `HubSpot account ${directDeployAccountLabel(account)} does not have deploy authentication available.`,
|
|
733
|
+
hint: 'Run `hs account auth` for that account, or pass --pak for this run.',
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
preparedUpload = {
|
|
738
|
+
projectName,
|
|
739
|
+
intermediateRepresentation,
|
|
740
|
+
appUid,
|
|
741
|
+
developerAccountId: context.account.id,
|
|
742
|
+
platformVersion,
|
|
743
|
+
buildMessage,
|
|
744
|
+
buildTimeoutMs,
|
|
745
|
+
force,
|
|
746
|
+
sourceFingerprint,
|
|
747
|
+
createClient: async () => context.createClient(),
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
catch (cause) {
|
|
752
|
+
accountFailure = directDeployAccountFailure(cause);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
const ready = validation.ok &&
|
|
756
|
+
account !== undefined &&
|
|
757
|
+
accountFailure === undefined &&
|
|
758
|
+
preparedUpload !== undefined;
|
|
759
|
+
const plan = {
|
|
760
|
+
schema_version: 1,
|
|
761
|
+
ok: ready,
|
|
762
|
+
command: 'deploy',
|
|
763
|
+
mode: 'hubspot-direct',
|
|
764
|
+
root,
|
|
765
|
+
project: {
|
|
766
|
+
name: projectName,
|
|
767
|
+
...(projectName !== descriptor.name ? { configuredName: descriptor.name } : {}),
|
|
768
|
+
platformVersion,
|
|
769
|
+
srcDir: descriptor.srcDir,
|
|
770
|
+
sourceFingerprint,
|
|
771
|
+
...(descriptor.appUid ? { appUid: descriptor.appUid } : {}),
|
|
772
|
+
},
|
|
773
|
+
account: account
|
|
774
|
+
? { ...account, ...(accountFailure ? { error: accountFailure } : {}) }
|
|
775
|
+
: {
|
|
776
|
+
authReady: false,
|
|
777
|
+
error: accountFailure,
|
|
778
|
+
},
|
|
779
|
+
targets: { hubspot: true, cloudflare: false, controlPlane: false },
|
|
780
|
+
action: uploadOnly ? 'upload-and-build' : 'upload-build-and-deploy',
|
|
781
|
+
options: {
|
|
782
|
+
force,
|
|
783
|
+
buildMessage,
|
|
784
|
+
buildTimeoutMs,
|
|
785
|
+
skipAutoDeploy: uploadOnly,
|
|
786
|
+
},
|
|
787
|
+
validation,
|
|
788
|
+
};
|
|
789
|
+
const rows = [
|
|
790
|
+
{ key: 'mode', value: 'HubSpot direct' },
|
|
791
|
+
{ key: 'source', value: root },
|
|
792
|
+
{ key: 'project', value: projectName },
|
|
793
|
+
...(account
|
|
794
|
+
? [
|
|
795
|
+
{
|
|
796
|
+
key: 'account',
|
|
797
|
+
value: directDeployAccountLabel(account),
|
|
798
|
+
status: account.authReady ? 'ok' : 'fail',
|
|
799
|
+
detail: account.configSource ?? account.selectedBy,
|
|
800
|
+
...(account.authReady
|
|
801
|
+
? {}
|
|
802
|
+
: { hint: 'Run `hs account auth` for this account, then retry.' }),
|
|
803
|
+
},
|
|
804
|
+
]
|
|
805
|
+
: [
|
|
806
|
+
{
|
|
807
|
+
key: 'account',
|
|
808
|
+
value: 'not ready',
|
|
809
|
+
status: 'fail',
|
|
810
|
+
...(accountFailure?.reasonCode ? { detail: accountFailure.reasonCode } : {}),
|
|
811
|
+
...(accountFailure?.hint ? { hint: accountFailure.hint } : {}),
|
|
812
|
+
},
|
|
813
|
+
]),
|
|
814
|
+
{ key: 'platform', value: platformVersion },
|
|
815
|
+
{ key: 'build message', value: buildMessage },
|
|
816
|
+
{ key: 'build timeout', value: `${buildTimeoutMs}ms` },
|
|
817
|
+
{
|
|
818
|
+
key: 'HubSpot',
|
|
819
|
+
value: uploadOnly ? 'upload + build only' : 'upload + build + deploy',
|
|
820
|
+
status: 'ok',
|
|
821
|
+
},
|
|
822
|
+
{ key: 'auto-deploy', value: uploadOnly ? 'explicitly skipped' : 'project setting' },
|
|
823
|
+
{ key: 'force', value: force ? 'yes' : 'no', ...(force ? { status: 'warn' } : {}) },
|
|
824
|
+
{ key: 'Cloudflare', value: 'not used' },
|
|
825
|
+
{ key: 'HS-X control plane', value: 'not used' },
|
|
826
|
+
{
|
|
827
|
+
key: 'validation',
|
|
828
|
+
value: validation.ok ? 'pass' : 'fail',
|
|
829
|
+
status: validation.ok ? 'ok' : 'fail',
|
|
830
|
+
},
|
|
831
|
+
...validation.diagnostics.map((diagnostic) => ({
|
|
832
|
+
key: diagnostic.code,
|
|
833
|
+
value: diagnostic.message,
|
|
834
|
+
status: diagnostic.severity === 'error' ? 'fail' : 'warn',
|
|
835
|
+
...(diagnostic.file ? { detail: diagnostic.file } : {}),
|
|
836
|
+
})),
|
|
837
|
+
];
|
|
838
|
+
if (planOnly || !ready) {
|
|
839
|
+
if (json) {
|
|
840
|
+
write(`${JSON.stringify(plan, null, 2)}\n`);
|
|
841
|
+
}
|
|
842
|
+
else if (reporter.mode === 'ndjson') {
|
|
843
|
+
reporter.data(plan);
|
|
844
|
+
reporter.done(ready ? 'Plan ready' : 'Plan not ready', ready ? 0 : 10);
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
reporter.header(projectName);
|
|
848
|
+
reporter.rows(rows);
|
|
849
|
+
reporter.done(ready ? 'Plan ready' : 'Plan not ready', ready ? 0 : 10);
|
|
850
|
+
}
|
|
851
|
+
return { exitCode: ready ? 0 : validation.ok ? 10 : 1 };
|
|
852
|
+
}
|
|
853
|
+
const summary = [
|
|
854
|
+
'HubSpot-direct deploy plan',
|
|
855
|
+
`Project: ${projectName}`,
|
|
856
|
+
`Account: ${directDeployAccountLabel(account)}`,
|
|
857
|
+
`Source: ${root}`,
|
|
858
|
+
`Action: ${uploadOnly ? 'upload and build only (skip auto-deploy)' : 'upload, wait for build, and deploy'}`,
|
|
859
|
+
`Platform: ${platformVersion}`,
|
|
860
|
+
`Force: ${force ? 'yes' : 'no'}`,
|
|
861
|
+
`Build message: ${buildMessage}`,
|
|
862
|
+
`Build timeout: ${buildTimeoutMs}ms`,
|
|
863
|
+
'Cloudflare: not used',
|
|
864
|
+
'HS-X control plane: not used',
|
|
865
|
+
].join('\n');
|
|
866
|
+
// MutationConsent requires the exact read-only preview to be visible before
|
|
867
|
+
// it asks. In machine modes the same plan is available via --plan; --yes is
|
|
868
|
+
// mandatory and no interactive output is emitted.
|
|
869
|
+
if (!machineOutput) {
|
|
870
|
+
reporter.header(projectName);
|
|
871
|
+
reporter.rows(rows);
|
|
872
|
+
}
|
|
873
|
+
const consent = await requestMutationConsent({
|
|
874
|
+
yes: argv.includes('--yes') || argv.includes('-y'),
|
|
875
|
+
machineOutput,
|
|
876
|
+
interactive: isInteractive(),
|
|
877
|
+
message: `Deploy ${projectName} to ${directDeployAccountLabel(account)}?`,
|
|
878
|
+
confirm: promptConfirm,
|
|
879
|
+
});
|
|
880
|
+
if (consent === 'require-yes') {
|
|
881
|
+
return inputValidationError({
|
|
882
|
+
argv,
|
|
883
|
+
command: 'deploy',
|
|
884
|
+
json,
|
|
885
|
+
code: 'HSX_E_INPUT_INVALID',
|
|
886
|
+
message: 'Deploy requires explicit consent in machine or non-interactive mode.',
|
|
887
|
+
hint: 'Review with `hs-x deploy --plan`, then re-run with --yes.',
|
|
888
|
+
detail: summary,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
if (consent === 'cancelled')
|
|
892
|
+
return cancelledResult(argv, 'deploy');
|
|
893
|
+
if (!preparedUpload) {
|
|
894
|
+
return inputValidationError({
|
|
895
|
+
argv,
|
|
896
|
+
command: 'deploy',
|
|
897
|
+
json,
|
|
898
|
+
code: 'HSX_E_DIRECT_HUBSPOT_ACCOUNT_CONFIG',
|
|
899
|
+
message: 'The HubSpot deploy target changed while preparing the execution plan.',
|
|
900
|
+
hint: 'Review a fresh plan and retry.',
|
|
901
|
+
detail: 'No upload or deploy request was made.',
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
const step = machineOutput ? undefined : reporter.step('Uploading to HubSpot');
|
|
906
|
+
const uploaded = await executeHubSpotOnlyUpload({
|
|
907
|
+
argv,
|
|
908
|
+
root,
|
|
909
|
+
local: localHubSpotAdapter,
|
|
910
|
+
persistBinding: false,
|
|
911
|
+
uploadOnly,
|
|
912
|
+
prepared: preparedUpload,
|
|
913
|
+
});
|
|
914
|
+
const buildState = readStatus(uploaded.buildStatus) ?? 'UNKNOWN';
|
|
915
|
+
const deployState = uploaded.deployStatus
|
|
916
|
+
? (readStatus(uploaded.deployStatus) ?? 'UNKNOWN')
|
|
917
|
+
: undefined;
|
|
918
|
+
const deployUnverified = uploaded.autoDeployExpected && deployState === undefined;
|
|
919
|
+
const ok = buildState === 'SUCCESS' && !deployUnverified && (!deployState || deployState === 'SUCCESS');
|
|
920
|
+
step?.[ok ? 'ok' : 'fail'](`build #${uploaded.buildId} ${buildState}${deployState ? ` · deploy ${deployState}` : deployUnverified ? ' · deploy unverified' : ''}`);
|
|
921
|
+
const result = {
|
|
922
|
+
schema_version: 1,
|
|
923
|
+
ok,
|
|
924
|
+
command: 'deploy',
|
|
925
|
+
mode: 'hubspot-direct',
|
|
926
|
+
account,
|
|
927
|
+
projectName: uploaded.projectName,
|
|
928
|
+
appId: uploaded.appId || undefined,
|
|
929
|
+
buildId: uploaded.buildId,
|
|
930
|
+
buildStatus: buildState,
|
|
931
|
+
...(uploaded.deployId !== undefined ? { deployId: uploaded.deployId } : {}),
|
|
932
|
+
...(deployState ? { deployStatus: deployState } : {}),
|
|
933
|
+
deploySkipped: uploaded.deploySkipped,
|
|
934
|
+
deployVerification: deployUnverified ? 'unverified' : deployState ? 'verified' : 'skipped',
|
|
935
|
+
controlPlaneUsed: false,
|
|
936
|
+
cloudflareUsed: false,
|
|
937
|
+
};
|
|
938
|
+
if (json) {
|
|
939
|
+
write(`${JSON.stringify(result, null, 2)}\n`);
|
|
940
|
+
}
|
|
941
|
+
else if (reporter.mode === 'ndjson') {
|
|
942
|
+
reporter.data(result);
|
|
943
|
+
reporter.done(ok ? directDeployDoneMessage(uploaded) : 'HubSpot deploy failed', ok ? 0 : 1);
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
streamHubSpotUploadOutcome(reporter, uploaded);
|
|
947
|
+
if (deployUnverified) {
|
|
948
|
+
reporter.error('HSX_E_DEPLOY_HUBSPOT', 'HubSpot auto-deploy could not be verified before the timeout.', { hint: 'Check the project deploy status in HubSpot, then retry if it did not ship.' });
|
|
949
|
+
}
|
|
950
|
+
reporter.done(ok ? directDeployDoneMessage(uploaded) : 'HubSpot deploy failed', ok ? 0 : 1);
|
|
951
|
+
}
|
|
952
|
+
return { exitCode: ok ? 0 : 1 };
|
|
953
|
+
}
|
|
954
|
+
catch (cause) {
|
|
955
|
+
if (cause instanceof HubSpotProjectArchiveSourceError)
|
|
956
|
+
throw cause;
|
|
957
|
+
const code = cause instanceof DirectHubSpotAccountResolutionError
|
|
958
|
+
? 'HSX_E_DEPLOY_HUBSPOT_AUTH'
|
|
959
|
+
: 'HSX_E_DEPLOY_HUBSPOT';
|
|
960
|
+
const message = describeHubSpotDeployFailure(cause);
|
|
961
|
+
const hint = cause instanceof DirectHubSpotAccountResolutionError
|
|
962
|
+
? cause.hint
|
|
963
|
+
: 'Review the HubSpot status and correlation id above, fix the reported issue, and retry.';
|
|
964
|
+
if (json) {
|
|
965
|
+
write(`${JSON.stringify({
|
|
966
|
+
schema_version: 1,
|
|
967
|
+
command: 'deploy',
|
|
968
|
+
ok: false,
|
|
969
|
+
error: { code, message, hint },
|
|
970
|
+
}, null, 2)}\n`);
|
|
971
|
+
}
|
|
972
|
+
else if (reporter.mode === 'ndjson') {
|
|
973
|
+
reporter.error(code, message, { hint });
|
|
974
|
+
reporter.done('HubSpot deploy failed', 1);
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
reporter.error(code, message, {
|
|
978
|
+
hint,
|
|
979
|
+
docs_url: `https://hs-x.dev/errors/${code}`,
|
|
980
|
+
});
|
|
981
|
+
reporter.done('HubSpot deploy failed', 1);
|
|
982
|
+
}
|
|
983
|
+
return { exitCode: 1 };
|
|
984
|
+
}
|
|
985
|
+
}
|
|
544
986
|
export async function deployCommand({ argv, root, json, }) {
|
|
545
987
|
// Stamp the whole invocation up front: the human summary reporter is created
|
|
546
988
|
// late (render time), so without this "Deployed … in 1ms" measured only the
|
|
@@ -567,6 +1009,9 @@ export async function deployCommand({ argv, root, json, }) {
|
|
|
567
1009
|
throw error;
|
|
568
1010
|
}
|
|
569
1011
|
}
|
|
1012
|
+
if ((await detectLocalProjectMode(root)) === 'hubspot-direct') {
|
|
1013
|
+
return hubSpotDirectDeployCommand({ argv, root, json });
|
|
1014
|
+
}
|
|
570
1015
|
// bun only auto-loads .env/.env.local from the CWD; when deploying from a
|
|
571
1016
|
// nested app dir, pull in a repo-root .env.local (CLOUDFLARE_ACCOUNT_ID, etc.)
|
|
572
1017
|
// so the deploy doesn't fail for vars that are set at the root.
|
|
@@ -3773,44 +4218,56 @@ function runCloudflareCommand(command, options) {
|
|
|
3773
4218
|
},
|
|
3774
4219
|
})));
|
|
3775
4220
|
}
|
|
3776
|
-
async function executeHubSpotOnlyUpload({ argv, root, local, uploadOnly, }) {
|
|
4221
|
+
async function executeHubSpotOnlyUpload({ argv, root, local, persistBinding = true, uploadOnly, prepared, }) {
|
|
3777
4222
|
// The HubSpot project name must match the name baked into the generated
|
|
3778
4223
|
// bundle (hsproject.json + app-hsmeta uid), not the on-disk directory name.
|
|
3779
4224
|
// Using basename(root) breaks when the project lives in a generic dir (e.g.
|
|
3780
4225
|
// `app/`): the upload targets a project named "app" while the bundle declares
|
|
3781
4226
|
// the real project id, and HubSpot rejects the mismatch with an opaque 400.
|
|
3782
|
-
const projectName = await resolveHubSpotProjectName(argv, root);
|
|
3783
|
-
const intermediateRepresentation =
|
|
3784
|
-
|
|
4227
|
+
const projectName = prepared?.projectName ?? (await resolveHubSpotProjectName(argv, root));
|
|
4228
|
+
const intermediateRepresentation = prepared?.intermediateRepresentation ??
|
|
4229
|
+
(await readHubSpotProjectIntermediateRepresentation(root));
|
|
4230
|
+
const appUid = prepared?.appUid ??
|
|
4231
|
+
readAppUidFromIntermediateRepresentation(intermediateRepresentation) ??
|
|
3785
4232
|
(await readAppUidFromProjectSource(root));
|
|
3786
4233
|
// For the post-deploy app Auth URL (unlinked installs). Resolved from
|
|
3787
4234
|
// flag/env here so the URL is correct on the unlinked path; on a
|
|
3788
4235
|
// control-plane lease path it stays undefined and the URL is skipped.
|
|
3789
|
-
const developerAccountId =
|
|
4236
|
+
const developerAccountId = prepared?.developerAccountId ??
|
|
4237
|
+
resolveFlag(argv, '--developer-account-id') ??
|
|
4238
|
+
process.env.HSX_HUBSPOT_DEVELOPER_ACCOUNT_ID;
|
|
3790
4239
|
const archivePath = await writeLocalHubSpotProjectArchive(root, projectName, {
|
|
3791
4240
|
sourceOnly: intermediateRepresentation !== undefined,
|
|
4241
|
+
...(prepared ? { expectedSourceFingerprint: prepared.sourceFingerprint } : {}),
|
|
3792
4242
|
});
|
|
3793
|
-
const client =
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
4243
|
+
const client = prepared
|
|
4244
|
+
? await prepared.createClient()
|
|
4245
|
+
: await createDeployHubSpotDeveloperClient({
|
|
4246
|
+
argv,
|
|
4247
|
+
local,
|
|
4248
|
+
allowedOperations: ['hubspot-project-upload'],
|
|
4249
|
+
cwd: root,
|
|
4250
|
+
});
|
|
3799
4251
|
await client.projects.ensureProject({ projectName });
|
|
3800
4252
|
const hsproject = await readHubSpotProjectConfig(root);
|
|
3801
4253
|
const configuredPlatformVersion = typeof hsproject.platformVersion === 'string' ? hsproject.platformVersion : undefined;
|
|
3802
4254
|
const upload = await client.projects.upload({
|
|
3803
4255
|
projectName,
|
|
3804
4256
|
archivePath,
|
|
3805
|
-
message: resolveFlag(argv, '--message') ?? 'HS-X HubSpot-only deploy',
|
|
3806
|
-
platformVersion:
|
|
4257
|
+
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',
|
|
3807
4262
|
intermediateRepresentation,
|
|
4263
|
+
skipAutoDeploy: uploadOnly,
|
|
3808
4264
|
});
|
|
4265
|
+
const timeoutMs = prepared?.buildTimeoutMs ?? Number(resolveFlag(argv, '--hubspot-build-timeout-ms') ?? '60000');
|
|
3809
4266
|
const buildStatus = await waitForHubSpotBuildStatus({
|
|
3810
4267
|
client,
|
|
3811
4268
|
projectName,
|
|
3812
4269
|
buildId: upload.buildId,
|
|
3813
|
-
timeoutMs
|
|
4270
|
+
timeoutMs,
|
|
3814
4271
|
intervalMs: 1000,
|
|
3815
4272
|
});
|
|
3816
4273
|
const autoDeployEnabled = readBooleanProperty(buildStatus, 'isAutoDeployEnabled');
|
|
@@ -3818,7 +4275,7 @@ async function executeHubSpotOnlyUpload({ argv, root, local, uploadOnly, }) {
|
|
|
3818
4275
|
const appId = readStatus(buildStatus) === 'SUCCESS'
|
|
3819
4276
|
? await optionalHubSpotAppId(client, projectName, appUid)
|
|
3820
4277
|
: 0;
|
|
3821
|
-
if (appId)
|
|
4278
|
+
if (persistBinding && appId)
|
|
3822
4279
|
await persistHubSpotAppBinding(root, {
|
|
3823
4280
|
appId,
|
|
3824
4281
|
projectName,
|
|
@@ -3827,10 +4284,17 @@ async function executeHubSpotOnlyUpload({ argv, root, local, uploadOnly, }) {
|
|
|
3827
4284
|
// Build SUCCESS does NOT mean the app shipped: HubSpot's auto-deploy can
|
|
3828
4285
|
// still fail (found live: "Apps are not allowed to have 0 redirect URLs"
|
|
3829
4286
|
// failed deploy #4 while the CLI reported green). Verify the deploy too.
|
|
3830
|
-
//
|
|
3831
|
-
//
|
|
3832
|
-
const
|
|
3833
|
-
|
|
4287
|
+
// Normal uploads can auto-deploy; uploadOnly sends skipAutoDeploy and is
|
|
4288
|
+
// therefore a strict build-only result.
|
|
4289
|
+
const autoDeployExpected = !uploadOnly && autoDeployEnabled === true && readStatus(buildStatus) === 'SUCCESS';
|
|
4290
|
+
const autoDeployStatus = autoDeployExpected
|
|
4291
|
+
? await waitForHubSpotAutoDeploy({
|
|
4292
|
+
client,
|
|
4293
|
+
projectName,
|
|
4294
|
+
buildId: upload.buildId,
|
|
4295
|
+
buildStatus,
|
|
4296
|
+
timeoutMs,
|
|
4297
|
+
})
|
|
3834
4298
|
: undefined;
|
|
3835
4299
|
return {
|
|
3836
4300
|
projectName,
|
|
@@ -3843,27 +4307,37 @@ async function executeHubSpotOnlyUpload({ argv, root, local, uploadOnly, }) {
|
|
|
3843
4307
|
buildStatus,
|
|
3844
4308
|
deployStatus: autoDeployStatus,
|
|
3845
4309
|
deploySkipped: Boolean(uploadOnly || autoDeployEnabled),
|
|
4310
|
+
autoDeployExpected,
|
|
3846
4311
|
local,
|
|
3847
4312
|
};
|
|
3848
4313
|
}
|
|
3849
4314
|
const deploy = await withTransientDeployRetry(() => client.projects.deployBuild({
|
|
3850
4315
|
projectName,
|
|
3851
4316
|
buildId: upload.buildId,
|
|
3852
|
-
force: argv.includes('--force'),
|
|
4317
|
+
force: prepared?.force ?? argv.includes('--force'),
|
|
3853
4318
|
}));
|
|
3854
|
-
const
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
});
|
|
3861
|
-
|
|
3862
|
-
await
|
|
3863
|
-
|
|
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,
|
|
3864
4329
|
projectName,
|
|
3865
|
-
|
|
4330
|
+
deployId,
|
|
4331
|
+
timeoutMs,
|
|
3866
4332
|
});
|
|
4333
|
+
const appId = await optionalHubSpotAppId(client, projectName, appUid);
|
|
4334
|
+
if (persistBinding) {
|
|
4335
|
+
await persistHubSpotAppBinding(root, {
|
|
4336
|
+
appId,
|
|
4337
|
+
projectName,
|
|
4338
|
+
developerAccountId,
|
|
4339
|
+
});
|
|
4340
|
+
}
|
|
3867
4341
|
return {
|
|
3868
4342
|
projectName,
|
|
3869
4343
|
archivePath,
|
|
@@ -3875,6 +4349,7 @@ async function executeHubSpotOnlyUpload({ argv, root, local, uploadOnly, }) {
|
|
|
3875
4349
|
buildStatus,
|
|
3876
4350
|
deployStatus,
|
|
3877
4351
|
deploySkipped: false,
|
|
4352
|
+
autoDeployExpected: false,
|
|
3878
4353
|
local,
|
|
3879
4354
|
};
|
|
3880
4355
|
}
|
|
@@ -3892,20 +4367,26 @@ async function optionalHubSpotAppId(client, projectName, appUid) {
|
|
|
3892
4367
|
* (deployStatusTaskLocator.id); absent/transient lookups resolve undefined —
|
|
3893
4368
|
* the caller treats that as "could not verify", never as success.
|
|
3894
4369
|
*/
|
|
3895
|
-
async function waitForHubSpotAutoDeploy({ client, projectName, buildStatus, timeoutMs = 60_000, intervalMs = 2_000, }) {
|
|
3896
|
-
const locator = buildStatus && typeof buildStatus === 'object'
|
|
3897
|
-
? buildStatus.deployStatusTaskLocator
|
|
3898
|
-
: undefined;
|
|
3899
|
-
const deployId = locator && typeof locator.id === 'number'
|
|
3900
|
-
? locator.id
|
|
3901
|
-
: locator && typeof locator.id === 'string'
|
|
3902
|
-
? Number.parseInt(locator.id, 10)
|
|
3903
|
-
: undefined;
|
|
3904
|
-
if (deployId === undefined || Number.isNaN(deployId))
|
|
3905
|
-
return undefined;
|
|
4370
|
+
async function waitForHubSpotAutoDeploy({ client, projectName, buildId, buildStatus, timeoutMs = 60_000, intervalMs = 2_000, }) {
|
|
3906
4371
|
const deadline = Date.now() + timeoutMs;
|
|
4372
|
+
let latestBuildStatus = buildStatus;
|
|
4373
|
+
let deployId = readHubSpotDeployLocator(latestBuildStatus);
|
|
3907
4374
|
let last;
|
|
3908
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
|
+
}
|
|
3909
4390
|
try {
|
|
3910
4391
|
last = await client.projects.getDeployStatus({ projectName, deployId });
|
|
3911
4392
|
}
|
|
@@ -3913,13 +4394,47 @@ async function waitForHubSpotAutoDeploy({ client, projectName, buildStatus, time
|
|
|
3913
4394
|
last = undefined;
|
|
3914
4395
|
}
|
|
3915
4396
|
const status = readStatus(last);
|
|
3916
|
-
if (status && status
|
|
4397
|
+
if (status && !isHubSpotDeployInProgress(status)) {
|
|
3917
4398
|
return last;
|
|
3918
4399
|
}
|
|
3919
4400
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
3920
4401
|
}
|
|
3921
4402
|
return last;
|
|
3922
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
|
+
}
|
|
3923
4438
|
async function waitForHubSpotBuildStatus({ client, projectName, buildId, timeoutMs, intervalMs, }) {
|
|
3924
4439
|
const startedAt = Date.now();
|
|
3925
4440
|
let latest;
|
|
@@ -3936,38 +4451,6 @@ async function waitForHubSpotBuildStatus({ client, projectName, buildId, timeout
|
|
|
3936
4451
|
function sleep(ms) {
|
|
3937
4452
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3938
4453
|
}
|
|
3939
|
-
async function writeLocalHubSpotProjectArchive(root, projectName, options = { sourceOnly: false }) {
|
|
3940
|
-
const outDir = await mkdtemp(join(tmpdir(), 'hs-x-hubspot-project-'));
|
|
3941
|
-
const archivePath = join(outDir, `${projectName.replace(/[^a-zA-Z0-9._-]+/g, '-')}-hubspot-project.zip`);
|
|
3942
|
-
const stageDir = join(outDir, 'project');
|
|
3943
|
-
await cp(root, stageDir, {
|
|
3944
|
-
recursive: true,
|
|
3945
|
-
filter(source) {
|
|
3946
|
-
return (!source.includes(`${join(root, 'node_modules')}/`) &&
|
|
3947
|
-
!source.includes(`${join(root, '.hs-x')}/`));
|
|
3948
|
-
},
|
|
3949
|
-
});
|
|
3950
|
-
await patchHubSpotProjectName(stageDir, projectName);
|
|
3951
|
-
await runZip(options.sourceOnly ? await hubSpotProjectSourceRoot(stageDir) : stageDir, archivePath);
|
|
3952
|
-
return archivePath;
|
|
3953
|
-
}
|
|
3954
|
-
async function hubSpotProjectSourceRoot(root) {
|
|
3955
|
-
const hsproject = await readHubSpotProjectConfig(root);
|
|
3956
|
-
const srcDir = typeof hsproject.srcDir === 'string' ? hsproject.srcDir : 'src';
|
|
3957
|
-
return join(root, srcDir);
|
|
3958
|
-
}
|
|
3959
|
-
async function patchHubSpotProjectName(root, projectName) {
|
|
3960
|
-
const path = join(root, 'hsproject.json');
|
|
3961
|
-
const raw = await readFile(path, 'utf8').catch(() => undefined);
|
|
3962
|
-
if (!raw) {
|
|
3963
|
-
return;
|
|
3964
|
-
}
|
|
3965
|
-
const parsed = JSON.parse(raw);
|
|
3966
|
-
if (!isRecord(parsed)) {
|
|
3967
|
-
return;
|
|
3968
|
-
}
|
|
3969
|
-
await writeFile(path, `${JSON.stringify({ ...parsed, name: projectName }, null, 2)}\n`);
|
|
3970
|
-
}
|
|
3971
4454
|
async function readHubSpotProjectIntermediateRepresentation(root) {
|
|
3972
4455
|
const hsproject = await readHubSpotProjectConfig(root);
|
|
3973
4456
|
const srcDir = typeof hsproject.srcDir === 'string' ? hsproject.srcDir : 'src';
|
|
@@ -4078,30 +4561,40 @@ async function collectHubSpotMetaFiles(root) {
|
|
|
4078
4561
|
await walk(root);
|
|
4079
4562
|
return files.sort();
|
|
4080
4563
|
}
|
|
4081
|
-
function
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
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));
|
|
4100
4593
|
});
|
|
4101
4594
|
});
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
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.';
|
|
4105
4598
|
}
|
|
4106
4599
|
function readBooleanProperty(value, key) {
|
|
4107
4600
|
return isRecord(value) && typeof value[key] === 'boolean' ? value[key] : undefined;
|
|
@@ -4844,8 +5337,12 @@ const deployOptions = {
|
|
|
4844
5337
|
hubspotClientId: Options.text('hubspot-client-id').pipe(Options.optional), // --hubspot-client-id
|
|
4845
5338
|
hubspotClientSecret: Options.text('hubspot-client-secret').pipe(Options.optional), // --hubspot-client-secret
|
|
4846
5339
|
developerAccountId: Options.text('developer-account-id').pipe(Options.optional), // --developer-account-id
|
|
5340
|
+
developerApiKey: Options.text('developer-api-key').pipe(Options.optional), // --developer-api-key
|
|
5341
|
+
pak: Options.text('pak').pipe(Options.optional), // --pak
|
|
5342
|
+
account: Options.text('account').pipe(Options.withAlias('a'), Options.optional), // --account / -a
|
|
5343
|
+
config: Options.text('config').pipe(Options.withAlias('c'), Options.optional), // --config / -c
|
|
4847
5344
|
hubspotBuildTimeoutMs: Options.text('hubspot-build-timeout-ms').pipe(Options.optional), // --hubspot-build-timeout-ms
|
|
4848
|
-
message: Options.text('message').pipe(Options.optional), // --message
|
|
5345
|
+
message: Options.text('message').pipe(Options.withAlias('m'), Options.optional), // --message / -m
|
|
4849
5346
|
platformVersion: Options.text('platform-version').pipe(Options.optional), // --platform-version
|
|
4850
5347
|
hubspotProjectName: Options.text('hubspot-project-name').pipe(Options.optional), // --hubspot-project-name
|
|
4851
5348
|
// ── boolean flags (argv.includes) ──
|
|
@@ -4866,13 +5363,14 @@ const deployOptions = {
|
|
|
4866
5363
|
applySchema: Options.boolean('apply-schema').pipe(Options.withDefault(false)), // --apply-schema
|
|
4867
5364
|
heartbeat: Options.boolean('heartbeat').pipe(Options.withDefault(false)), // --heartbeat
|
|
4868
5365
|
noHeartbeat: Options.boolean('no-heartbeat').pipe(Options.withDefault(false)), // --no-heartbeat
|
|
5366
|
+
useEnv: Options.boolean('use-env').pipe(Options.withDefault(false)), // --use-env
|
|
4869
5367
|
// Declared explicitly: without it @effect/cli treats `--no-record` as the
|
|
4870
5368
|
// negation of an undeclared `record` boolean and fails routing with a
|
|
4871
5369
|
// confusing root-level "Invalid subcommand" error (the flag is documented).
|
|
4872
5370
|
noRecord: Options.boolean('no-record').pipe(Options.withDefault(false)), // --no-record
|
|
4873
5371
|
portalSchemaLive: Options.boolean('portal-schema-live').pipe(Options.withDefault(false)), // --portal-schema-live
|
|
4874
5372
|
localHubspotSchema: Options.boolean('local-hubspot-schema').pipe(Options.withDefault(false)), // --local-hubspot-schema
|
|
4875
|
-
force: Options.boolean('force').pipe(Options.withDefault(false)), // --force
|
|
5373
|
+
force: Options.boolean('force').pipe(Options.withAlias('f'), Options.withDefault(false)), // --force / -f
|
|
4876
5374
|
yes: Options.boolean('yes').pipe(Options.withAlias('y'), Options.withDefault(false)), // --yes / -y
|
|
4877
5375
|
};
|
|
4878
5376
|
const deployBaseCmd = Command.make('deploy', deployOptions, (opts) => runHandler('deploy', { json: opts.json }, Effect.gen(function* () {
|
|
@@ -4904,6 +5402,10 @@ const deployBaseCmd = Command.make('deploy', deployOptions, (opts) => runHandler
|
|
|
4904
5402
|
push('--hubspot-client-id', opts.hubspotClientId);
|
|
4905
5403
|
push('--hubspot-client-secret', opts.hubspotClientSecret);
|
|
4906
5404
|
push('--developer-account-id', opts.developerAccountId);
|
|
5405
|
+
push('--developer-api-key', opts.developerApiKey);
|
|
5406
|
+
push('--pak', opts.pak);
|
|
5407
|
+
push('--account', opts.account);
|
|
5408
|
+
push('--config', opts.config);
|
|
4907
5409
|
push('--hubspot-build-timeout-ms', opts.hubspotBuildTimeoutMs);
|
|
4908
5410
|
push('--message', opts.message);
|
|
4909
5411
|
push('--platform-version', opts.platformVersion);
|
|
@@ -4925,6 +5427,7 @@ const deployBaseCmd = Command.make('deploy', deployOptions, (opts) => runHandler
|
|
|
4925
5427
|
flag('--apply-schema', opts.applySchema);
|
|
4926
5428
|
flag('--heartbeat', opts.heartbeat);
|
|
4927
5429
|
flag('--no-heartbeat', opts.noHeartbeat);
|
|
5430
|
+
flag('--use-env', opts.useEnv);
|
|
4928
5431
|
flag('--no-record', opts.noRecord);
|
|
4929
5432
|
flag('--portal-schema-live', opts.portalSchemaLive);
|
|
4930
5433
|
flag('--local-hubspot-schema', opts.localHubspotSchema);
|