@hs-x/cli 0.4.0 → 0.4.2-next.0

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 (48) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/commands/completion.d.ts.map +1 -1
  4. package/dist/commands/completion.js +35 -1
  5. package/dist/commands/completion.js.map +1 -1
  6. package/dist/commands/deploy.d.ts.map +1 -1
  7. package/dist/commands/deploy.js +18 -145
  8. package/dist/commands/deploy.js.map +1 -1
  9. package/dist/commands/help-command.js +3 -0
  10. package/dist/commands/help-command.js.map +1 -1
  11. package/dist/commands/project-lifecycle-schema.d.ts +67 -0
  12. package/dist/commands/project-lifecycle-schema.d.ts.map +1 -0
  13. package/dist/commands/project-lifecycle-schema.js +143 -0
  14. package/dist/commands/project-lifecycle-schema.js.map +1 -0
  15. package/dist/commands/project-lifecycle.d.ts +61 -0
  16. package/dist/commands/project-lifecycle.d.ts.map +1 -0
  17. package/dist/commands/project-lifecycle.js +641 -0
  18. package/dist/commands/project-lifecycle.js.map +1 -0
  19. package/dist/commands/project.d.ts +37 -0
  20. package/dist/commands/project.d.ts.map +1 -1
  21. package/dist/commands/project.js +77 -0
  22. package/dist/commands/project.js.map +1 -1
  23. package/dist/constants.d.ts +1 -1
  24. package/dist/constants.d.ts.map +1 -1
  25. package/dist/constants.js +1 -1
  26. package/dist/constants.js.map +1 -1
  27. package/dist/errors-registry.d.ts.map +1 -1
  28. package/dist/errors-registry.js +50 -0
  29. package/dist/errors-registry.js.map +1 -1
  30. package/dist/hubspot-auth.d.ts.map +1 -1
  31. package/dist/hubspot-auth.js +20 -10
  32. package/dist/hubspot-auth.js.map +1 -1
  33. package/dist/hubspot-developer-client.d.ts +4 -3
  34. package/dist/hubspot-developer-client.d.ts.map +1 -1
  35. package/dist/hubspot-developer-client.js +15 -11
  36. package/dist/hubspot-developer-client.js.map +1 -1
  37. package/dist/hubspot-project-archive.d.ts.map +1 -1
  38. package/dist/hubspot-project-archive.js +15 -1
  39. package/dist/hubspot-project-archive.js.map +1 -1
  40. package/dist/hubspot-project-lifecycle.d.ts +64 -0
  41. package/dist/hubspot-project-lifecycle.d.ts.map +1 -0
  42. package/dist/hubspot-project-lifecycle.js +227 -0
  43. package/dist/hubspot-project-lifecycle.js.map +1 -0
  44. package/dist/hubspot-project-workspaces.d.ts +10 -0
  45. package/dist/hubspot-project-workspaces.d.ts.map +1 -0
  46. package/dist/hubspot-project-workspaces.js +224 -0
  47. package/dist/hubspot-project-workspaces.js.map +1 -0
  48. package/package.json +10 -9
@@ -0,0 +1,641 @@
1
+ import { rm } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import * as Command from '@effect/cli/Command';
4
+ import * as Options from '@effect/cli/Options';
5
+ import { PLATFORM_VERSIONS } from '@hubspot/project-parsing-lib/constants';
6
+ import { loadHsProfileFile } from '@hubspot/project-parsing-lib/profiles';
7
+ import { isLegacyProject, meetsMinimumPlatformVersion, } from '@hubspot/project-parsing-lib/projects';
8
+ import { translate } from '@hubspot/project-parsing-lib/translate';
9
+ import { findAndParsePackageJsonFiles } from '@hubspot/project-parsing-lib/workspaces';
10
+ import { runNpmAuditJson } from '@hubspot/ui-extensions-dev-server';
11
+ import { Effect, Option } from 'effect';
12
+ import { cwdOption, jsonOption, runHandler, runQuarantined } from '../cli/kit.js';
13
+ import { resolveDirectHubSpotContext, } from '../hubspot-developer-client.js';
14
+ import { writeLocalHubSpotProjectArchive } from '../hubspot-project-archive.js';
15
+ import { HubSpotProjectDeployBlockedError, deployHubSpotProjectBuild, lastSuccessfulHubSpotBuild, readHubSpotAutoDeployEnabled, readHubSpotBuilds, readHubSpotDeployedBuildId, readHubSpotStatus, waitForHubSpotAutoDeploy, waitForHubSpotBuildStatus, } from '../hubspot-project-lifecycle.js';
16
+ import { readHubSpotProjectDescriptor } from '../hubspot-project.js';
17
+ import { requestMutationConsent } from '../mutation-consent.js';
18
+ import { isInteractive, promptConfirm, promptSelect } from '../prompt.js';
19
+ import { createReporter } from '../reporter/index.js';
20
+ import { Cwd } from '../services/cwd.js';
21
+ import { PROJECT_DEPLOY_JSON_SCHEMA, PROJECT_UPLOAD_JSON_SCHEMA, } from './project-lifecycle-schema.js';
22
+ const accountOption = Options.text('account').pipe(Options.withAlias('a'), Options.optional);
23
+ const configOption = Options.text('config').pipe(Options.withAlias('c'), Options.optional);
24
+ const useEnvOption = Options.boolean('use-env').pipe(Options.withDefault(false));
25
+ const formatJsonOption = Options.boolean('format-output-as-json').pipe(Options.withDefault(false));
26
+ const jsonSchemaOption = Options.boolean('json-schema').pipe(Options.withDefault(false));
27
+ const debugOption = Options.boolean('debug').pipe(Options.withAlias('d'), Options.withDefault(false));
28
+ const profileOption = Options.text('profile').pipe(Options.withAlias('p'), Options.optional);
29
+ const pakOption = Options.text('pak').pipe(Options.optional);
30
+ const yesOption = Options.boolean('yes').pipe(Options.withAlias('y'), Options.withDefault(false));
31
+ const planOption = Options.boolean('plan').pipe(Options.withDefault(false));
32
+ const timeoutOption = Options.text('timeout-ms').pipe(Options.optional);
33
+ const sharedMutationOptions = {
34
+ account: accountOption,
35
+ config: configOption,
36
+ useEnv: useEnvOption,
37
+ json: jsonOption,
38
+ formatJson: formatJsonOption,
39
+ jsonSchema: jsonSchemaOption,
40
+ debug: debugOption,
41
+ profile: profileOption,
42
+ pak: pakOption,
43
+ yes: yesOption,
44
+ plan: planOption,
45
+ cwd: cwdOption,
46
+ timeout: timeoutOption,
47
+ };
48
+ const projectUploadCmd = Command.make('upload', {
49
+ ...sharedMutationOptions,
50
+ force: Options.boolean('force').pipe(Options.withAlias('f'), Options.withDefault(false)),
51
+ forceCreate: Options.boolean('force-create').pipe(Options.withDefault(false)),
52
+ message: Options.text('message').pipe(Options.withAlias('m'), Options.optional),
53
+ skipNpmAudit: Options.boolean('skip-npm-audit').pipe(Options.withDefault(false)),
54
+ skipAutoDeploy: Options.boolean('skip-auto-deploy').pipe(Options.withDefault(false)),
55
+ }, (opts) => {
56
+ const json = opts.json || opts.formatJson;
57
+ return runHandler('project upload', { json, debug: opts.debug }, Effect.gen(function* () {
58
+ const cwd = yield* Cwd;
59
+ const root = Option.getOrElse(opts.cwd, () => cwd);
60
+ const argv = projectUploadArgv(opts, json);
61
+ yield* runQuarantined('project upload', json, () => projectUploadCommand({ argv, root, json }));
62
+ }));
63
+ });
64
+ const projectDeployCmd = Command.make('deploy', {
65
+ ...sharedMutationOptions,
66
+ project: Options.text('project').pipe(Options.optional),
67
+ build: Options.text('build').pipe(Options.withAlias('build-id'), Options.optional),
68
+ deployLatestBuild: Options.boolean('deployLatestBuild').pipe(Options.withAlias('deploy-latest-build'), Options.withDefault(false)),
69
+ force: Options.boolean('force').pipe(Options.withAlias('f'), Options.withDefault(false)),
70
+ }, (opts) => {
71
+ const json = opts.json || opts.formatJson;
72
+ return runHandler('project deploy', { json, debug: opts.debug }, Effect.gen(function* () {
73
+ const cwd = yield* Cwd;
74
+ const root = Option.getOrElse(opts.cwd, () => cwd);
75
+ const argv = projectDeployArgv(opts, json);
76
+ yield* runQuarantined('project deploy', json, () => projectDeployCommand({ argv, root, json }));
77
+ }));
78
+ });
79
+ export { projectDeployCmd, projectUploadCmd };
80
+ const projectLifecycleCommandDependencies = {
81
+ resolveContext: resolveDirectHubSpotContext,
82
+ translateProject: translate,
83
+ };
84
+ export async function projectUploadCommand(input, dependencyOverrides = {}) {
85
+ const dependencies = { ...projectLifecycleCommandDependencies, ...dependencyOverrides };
86
+ const reporter = createReporter({ command: 'project upload', argv: input.argv });
87
+ if (hasFlag(input.argv, '--json-schema')) {
88
+ reporter.raw(`${JSON.stringify(PROJECT_UPLOAD_JSON_SCHEMA, null, 2)}\n`);
89
+ return { exitCode: 0 };
90
+ }
91
+ let archivePath;
92
+ try {
93
+ rejectConflictingSelectors(input.argv, 'upload');
94
+ const descriptor = await readHubSpotProjectDescriptor(input.root);
95
+ const profile = resolveFlag(input.argv, '--profile');
96
+ const accountArgv = await accountArgvForProfile(input.argv, input.root, descriptor.srcDir, profile);
97
+ const context = await dependencies.resolveContext({ argv: accountArgv, cwd: input.root });
98
+ if (!context.account.authReady) {
99
+ throw new Error(`HubSpot account ${accountLabel(context.account)} does not have deploy authentication available.`);
100
+ }
101
+ const client = context.createClient();
102
+ const projectExists = await hubSpotProjectExists(client, descriptor.name);
103
+ const message = resolveFlag(input.argv, '--message') ?? '';
104
+ const skipAutoDeploy = hasFlag(input.argv, '--skip-auto-deploy');
105
+ const skipNpmAudit = hasFlag(input.argv, '--skip-npm-audit');
106
+ const force = hasFlag(input.argv, '--force') || hasFlag(input.argv, '-f');
107
+ const forceCreate = force || hasFlag(input.argv, '--force-create');
108
+ const timeoutMs = readTimeout(input.argv);
109
+ const plan = {
110
+ mode: 'hubspot-direct',
111
+ operation: 'upload',
112
+ account: safeAccount(context.account),
113
+ project: {
114
+ name: descriptor.name,
115
+ platformVersion: descriptor.platformVersion,
116
+ source: join(input.root, descriptor.srcDir),
117
+ exists: projectExists,
118
+ },
119
+ options: {
120
+ ...(profile ? { profile } : {}),
121
+ message,
122
+ skipNpmAudit,
123
+ skipAutoDeploy,
124
+ forceCreate,
125
+ timeoutMs,
126
+ },
127
+ targets: { hubspot: true, cloudflare: false, controlPlane: false },
128
+ };
129
+ renderPlan(reporter, `${descriptor.name} upload`, uploadPlanRows(plan));
130
+ if (hasFlag(input.argv, '--plan')) {
131
+ emitMachineData(reporter, plan);
132
+ reporter.done('Upload plan ready');
133
+ return { exitCode: 0 };
134
+ }
135
+ if (!projectExists && !forceCreate && (!isInteractive() || reporter.mode !== 'human')) {
136
+ throw new Error(`HubSpot project ${descriptor.name} does not exist. Re-run interactively or pass --force to create it.`);
137
+ }
138
+ const consent = await requestMutationConsent({
139
+ yes: hasFlag(input.argv, '--yes') || hasFlag(input.argv, '-y') || force,
140
+ machineOutput: reporter.mode === 'json' || reporter.mode === 'ndjson',
141
+ interactive: isInteractive(),
142
+ message: projectExists
143
+ ? `Upload ${descriptor.name} to ${accountLabel(context.account)}?`
144
+ : `Create and upload ${descriptor.name} in ${accountLabel(context.account)}?`,
145
+ confirm: promptConfirm,
146
+ });
147
+ if (consent === 'cancelled') {
148
+ reporter.done('Cancelled', 130);
149
+ return { exitCode: 130 };
150
+ }
151
+ if (consent === 'require-yes') {
152
+ throw new Error('Project upload requires --yes or --force in non-interactive mode.');
153
+ }
154
+ const sourceDir = join(input.root, descriptor.srcDir);
155
+ const auditWarnings = skipNpmAudit ? [] : await auditProjectDependencies(sourceDir, input.root);
156
+ for (const warning of auditWarnings)
157
+ reporter.warn('HSX_W_HUBSPOT_NPM_AUDIT', warning);
158
+ const translated = await dependencies.translateProject({
159
+ projectSourceDir: sourceDir,
160
+ platformVersion: descriptor.platformVersion,
161
+ accountId: Number(context.account.id),
162
+ }, profile ? { profile } : undefined);
163
+ if (translated.skippedHsMetaFiles.length > 0 && !force) {
164
+ throw new Error(`HubSpot skipped ${translated.skippedHsMetaFiles.length} component metadata file(s): ${translated.skippedHsMetaFiles.join(', ')}. Review them or re-run with --force.`);
165
+ }
166
+ archivePath = await writeLocalHubSpotProjectArchive(input.root, descriptor.name, {
167
+ sourceOnly: true,
168
+ });
169
+ const uploadStep = reporter.step(projectExists ? 'Uploading project' : 'Creating project');
170
+ await client.projects.ensureProject({ projectName: descriptor.name });
171
+ const upload = await client.projects.upload({
172
+ projectName: descriptor.name,
173
+ archivePath,
174
+ message,
175
+ platformVersion: descriptor.platformVersion,
176
+ intermediateRepresentation: translated.intermediateRepresentation,
177
+ skipAutoDeploy,
178
+ });
179
+ uploadStep.ok(`build #${upload.buildId}`);
180
+ const buildStep = reporter.step('Building project');
181
+ const buildStatus = await waitForHubSpotBuildStatus({
182
+ client,
183
+ projectName: descriptor.name,
184
+ buildId: upload.buildId,
185
+ timeoutMs,
186
+ });
187
+ const buildState = readHubSpotStatus(buildStatus) ?? 'UNKNOWN';
188
+ if (buildState !== 'SUCCESS') {
189
+ buildStep.fail(buildState);
190
+ throw new Error(`HubSpot build #${upload.buildId} finished with ${buildState}.`);
191
+ }
192
+ buildStep.ok(`build #${upload.buildId}`);
193
+ let deployId;
194
+ let deployStatus;
195
+ const autoDeployEnabled = readHubSpotAutoDeployEnabled(buildStatus) === true;
196
+ if (!skipAutoDeploy && autoDeployEnabled) {
197
+ const deployStep = reporter.step('Auto-deploying project');
198
+ const deployed = await waitForHubSpotAutoDeploy({
199
+ client,
200
+ projectName: descriptor.name,
201
+ buildId: upload.buildId,
202
+ buildStatus,
203
+ timeoutMs,
204
+ });
205
+ deployId = deployed.deployId;
206
+ deployStatus = readHubSpotStatus(deployed.status) ?? 'UNKNOWN';
207
+ if (deployStatus !== 'SUCCESS') {
208
+ deployStep.fail(deployStatus);
209
+ throw new Error(`HubSpot auto-deploy #${deployId} finished with ${deployStatus}.`);
210
+ }
211
+ deployStep.ok(`deploy #${deployId}`);
212
+ }
213
+ const releaseRequired = meetsMinimumPlatformVersion(descriptor.platformVersion, PLATFORM_VERSIONS.v2026_09_BETA);
214
+ const nextCommand = deployStatus === 'SUCCESS'
215
+ ? undefined
216
+ : releaseRequired
217
+ ? `hs project release create --build=${upload.buildId}`
218
+ : `hs-x project deploy --build ${upload.buildId}`;
219
+ const result = {
220
+ mode: 'hubspot-direct',
221
+ operation: 'upload',
222
+ account: safeAccount(context.account),
223
+ projectName: descriptor.name,
224
+ buildId: upload.buildId,
225
+ buildStatus: buildState,
226
+ autoDeployEnabled,
227
+ autoDeploySkipped: skipAutoDeploy,
228
+ ...(deployId === undefined ? {} : { deployId }),
229
+ ...(deployStatus ? { deployStatus } : {}),
230
+ ...(nextCommand ? { nextCommand } : {}),
231
+ };
232
+ emitMachineData(reporter, result);
233
+ if (nextCommand)
234
+ reporter.info(`Next: ${nextCommand}`);
235
+ reporter.done(deployStatus === 'SUCCESS'
236
+ ? `Uploaded, built, and deployed ${descriptor.name}`
237
+ : `Uploaded and built ${descriptor.name}`);
238
+ return { exitCode: 0 };
239
+ }
240
+ catch (cause) {
241
+ return lifecycleFailure(reporter, 'HSX_E_PROJECT_UPLOAD', cause);
242
+ }
243
+ finally {
244
+ if (archivePath)
245
+ await rm(dirname(archivePath), { recursive: true, force: true });
246
+ }
247
+ }
248
+ export async function projectDeployCommand(input, dependencyOverrides = {}) {
249
+ const dependencies = { ...projectLifecycleCommandDependencies, ...dependencyOverrides };
250
+ const reporter = createReporter({ command: 'project deploy', argv: input.argv });
251
+ if (hasFlag(input.argv, '--json-schema')) {
252
+ reporter.raw(`${JSON.stringify(PROJECT_DEPLOY_JSON_SCHEMA, null, 2)}\n`);
253
+ return { exitCode: 0 };
254
+ }
255
+ try {
256
+ rejectConflictingSelectors(input.argv, 'deploy');
257
+ const descriptor = await readHubSpotProjectDescriptor(input.root).catch(() => undefined);
258
+ const projectName = resolveFlag(input.argv, '--project') ?? descriptor?.name;
259
+ if (!projectName) {
260
+ throw new Error('Run from a HubSpot project or pass --project <name>.');
261
+ }
262
+ const profile = resolveFlag(input.argv, '--profile');
263
+ if (profile && !descriptor)
264
+ throw new Error('--profile requires a local hsproject.json.');
265
+ const accountArgv = await accountArgvForProfile(input.argv, input.root, descriptor?.srcDir ?? 'src', profile);
266
+ const context = await dependencies.resolveContext({ argv: accountArgv, cwd: input.root });
267
+ if (!context.account.authReady) {
268
+ throw new Error(`HubSpot account ${accountLabel(context.account)} does not have deploy authentication available.`);
269
+ }
270
+ const client = context.createClient();
271
+ const [project, buildsResponse] = await Promise.all([
272
+ client.projects.getProject({ projectName }),
273
+ client.projects.listBuilds({ projectName, limit: 100 }),
274
+ ]);
275
+ const builds = readHubSpotBuilds(buildsResponse);
276
+ const deployedBuildId = readHubSpotDeployedBuildId(project);
277
+ const explicitBuild = resolveFlag(input.argv, '--build') ?? resolveFlag(input.argv, '--build-id');
278
+ let buildId = explicitBuild ? readPositiveInteger(explicitBuild, '--build') : undefined;
279
+ if (buildId === undefined && hasDeployLatestBuildFlag(input.argv)) {
280
+ buildId = lastSuccessfulHubSpotBuild(builds)?.id;
281
+ }
282
+ if (buildId === undefined && reporter.mode === 'human' && isInteractive()) {
283
+ const successful = builds.filter((build) => build.status?.toUpperCase() === 'SUCCESS');
284
+ if (successful.length === 0) {
285
+ throw new Error(`${projectName} does not have a successful build to deploy.`);
286
+ }
287
+ const latest = lastSuccessfulHubSpotBuild(successful);
288
+ const selected = await promptSelect({
289
+ message: 'Which successful build should be deployed?',
290
+ options: successful.map((build) => ({
291
+ value: String(build.id),
292
+ label: `#${build.id}${build.id === deployedBuildId ? ' (deployed)' : ''}`,
293
+ description: [build.platformVersion, build.createdAt, build.message]
294
+ .filter(Boolean)
295
+ .join(' · '),
296
+ })),
297
+ ...(latest ? { default: String(latest.id) } : {}),
298
+ });
299
+ if (!selected) {
300
+ reporter.done('Cancelled', 130);
301
+ return { exitCode: 130 };
302
+ }
303
+ buildId = readPositiveInteger(selected, 'build');
304
+ }
305
+ if (buildId === undefined) {
306
+ throw new Error('No successful build was selected. Pass --build <id> or --deploy-latest-build.');
307
+ }
308
+ const build = builds.find((candidate) => candidate.id === buildId);
309
+ if (!build)
310
+ throw new Error(`HubSpot build #${buildId} was not found in ${projectName}.`);
311
+ if (build.status?.toUpperCase() !== 'SUCCESS') {
312
+ throw new Error(`HubSpot build #${buildId} is ${build.status ?? 'not successful'} and cannot deploy.`);
313
+ }
314
+ const alreadyDeployed = deployedBuildId === buildId;
315
+ const platformVersion = build.platformVersion ?? descriptor?.platformVersion;
316
+ if (!alreadyDeployed &&
317
+ platformVersion &&
318
+ meetsMinimumPlatformVersion(platformVersion, PLATFORM_VERSIONS.v2026_09_BETA)) {
319
+ throw new Error(`Build #${buildId} uses release management. Run hs project release create --build=${buildId}.`);
320
+ }
321
+ const force = hasFlag(input.argv, '--force') || hasFlag(input.argv, '-f');
322
+ const timeoutMs = readTimeout(input.argv);
323
+ const plan = {
324
+ mode: 'hubspot-direct',
325
+ operation: 'deploy',
326
+ account: safeAccount(context.account),
327
+ project: {
328
+ name: projectName,
329
+ buildId,
330
+ alreadyDeployed,
331
+ ...(platformVersion ? { platformVersion } : {}),
332
+ },
333
+ options: { ...(profile ? { profile } : {}), force, timeoutMs },
334
+ targets: { hubspot: true, cloudflare: false, controlPlane: false },
335
+ };
336
+ renderPlan(reporter, `${projectName} deploy`, deployPlanRows(plan));
337
+ if (hasFlag(input.argv, '--plan')) {
338
+ emitMachineData(reporter, plan);
339
+ reporter.done('Deploy plan ready');
340
+ return { exitCode: 0 };
341
+ }
342
+ if (alreadyDeployed) {
343
+ emitMachineData(reporter, {
344
+ mode: 'hubspot-direct',
345
+ operation: 'deploy',
346
+ account: safeAccount(context.account),
347
+ projectName,
348
+ buildId,
349
+ changed: false,
350
+ status: 'ALREADY_DEPLOYED',
351
+ });
352
+ reporter.done(`${projectName} build #${buildId} is already deployed`);
353
+ return { exitCode: 0 };
354
+ }
355
+ const consent = await requestMutationConsent({
356
+ yes: hasFlag(input.argv, '--yes') || hasFlag(input.argv, '-y') || force,
357
+ machineOutput: reporter.mode === 'json' || reporter.mode === 'ndjson',
358
+ interactive: isInteractive(),
359
+ message: `Deploy build #${buildId} of ${projectName} to ${accountLabel(context.account)}?`,
360
+ confirm: promptConfirm,
361
+ });
362
+ if (consent === 'cancelled') {
363
+ reporter.done('Cancelled', 130);
364
+ return { exitCode: 130 };
365
+ }
366
+ if (consent === 'require-yes') {
367
+ throw new Error('Project deploy requires --yes or --force in non-interactive mode.');
368
+ }
369
+ const step = reporter.step(`Deploying build #${buildId}`);
370
+ const deployed = await deployHubSpotProjectBuild({
371
+ client,
372
+ projectName,
373
+ buildId,
374
+ force,
375
+ legacy: platformVersion ? isLegacyProject(platformVersion) : false,
376
+ timeoutMs,
377
+ });
378
+ const status = readHubSpotStatus(deployed.status) ?? 'UNKNOWN';
379
+ if (status !== 'SUCCESS') {
380
+ step.fail(status);
381
+ throw new Error(`HubSpot deploy #${deployed.deployId} finished with ${status}.`);
382
+ }
383
+ step.ok(`deploy #${deployed.deployId}`);
384
+ emitMachineData(reporter, {
385
+ mode: 'hubspot-direct',
386
+ operation: 'deploy',
387
+ account: safeAccount(context.account),
388
+ projectName,
389
+ buildId,
390
+ deployId: deployed.deployId,
391
+ changed: true,
392
+ status,
393
+ });
394
+ reporter.done(`Deployed ${projectName} build #${buildId}`);
395
+ return { exitCode: 0 };
396
+ }
397
+ catch (cause) {
398
+ return lifecycleFailure(reporter, cause instanceof HubSpotProjectDeployBlockedError
399
+ ? 'HSX_E_PROJECT_DEPLOY_BLOCKED'
400
+ : 'HSX_E_PROJECT_DEPLOY', cause);
401
+ }
402
+ }
403
+ function projectUploadArgv(opts, json) {
404
+ const argv = ['project', 'upload'];
405
+ pushSharedArgs(argv, opts, json);
406
+ pushOption(argv, '--message', opts.message);
407
+ pushFlag(argv, '--force', opts.force);
408
+ pushFlag(argv, '--force-create', opts.forceCreate);
409
+ pushFlag(argv, '--skip-npm-audit', opts.skipNpmAudit);
410
+ pushFlag(argv, '--skip-auto-deploy', opts.skipAutoDeploy);
411
+ return argv;
412
+ }
413
+ function projectDeployArgv(opts, json) {
414
+ const argv = ['project', 'deploy'];
415
+ pushSharedArgs(argv, opts, json);
416
+ pushOption(argv, '--project', opts.project);
417
+ pushOption(argv, '--build', opts.build);
418
+ pushFlag(argv, '--deploy-latest-build', opts.deployLatestBuild);
419
+ pushFlag(argv, '--force', opts.force);
420
+ return argv;
421
+ }
422
+ // These identity declarations give the two argv builders the exact inferred
423
+ // Effect option types without exporting or duplicating hand-written interfaces.
424
+ function projectUploadCommandOptionsMarker(opts) {
425
+ void opts;
426
+ }
427
+ function projectDeployCommandOptionsMarker(opts) {
428
+ void opts;
429
+ }
430
+ function pushSharedArgs(argv, opts, json) {
431
+ pushOption(argv, '--account', opts.account);
432
+ pushOption(argv, '--config', opts.config);
433
+ pushOption(argv, '--profile', opts.profile);
434
+ pushOption(argv, '--pak', opts.pak);
435
+ pushOption(argv, '--timeout-ms', opts.timeout);
436
+ pushFlag(argv, '--use-env', opts.useEnv);
437
+ pushFlag(argv, '--json-schema', opts.jsonSchema);
438
+ pushFlag(argv, '--yes', opts.yes);
439
+ pushFlag(argv, '--plan', opts.plan);
440
+ pushFlag(argv, '--json', json);
441
+ }
442
+ function pushOption(argv, flag, value) {
443
+ if (Option.isSome(value))
444
+ argv.push(flag, value.value);
445
+ }
446
+ function pushFlag(argv, flag, enabled) {
447
+ if (enabled)
448
+ argv.push(flag);
449
+ }
450
+ async function accountArgvForProfile(argv, root, srcDir, profile) {
451
+ if (!profile)
452
+ return argv;
453
+ const loaded = loadHsProfileFile(join(root, srcDir), profile);
454
+ if (!loaded?.accountId) {
455
+ throw new Error(`hsprofile.${profile}.json is missing accountId.`);
456
+ }
457
+ return [...argv, '--developer-account-id', String(loaded.accountId)];
458
+ }
459
+ function rejectConflictingSelectors(argv, operation) {
460
+ if (resolveFlag(argv, '--profile') && resolveFlag(argv, '--account')) {
461
+ throw new Error('--profile cannot be combined with --account.');
462
+ }
463
+ if (operation === 'deploy' && resolveFlag(argv, '--profile') && resolveFlag(argv, '--project')) {
464
+ throw new Error('--profile cannot be combined with --project.');
465
+ }
466
+ }
467
+ async function hubSpotProjectExists(client, projectName) {
468
+ try {
469
+ await client.projects.getProject({ projectName });
470
+ return true;
471
+ }
472
+ catch (cause) {
473
+ if (readHttpStatus(cause) === 404 &&
474
+ !/auth|credential|permission/i.test(describeCause(cause))) {
475
+ return false;
476
+ }
477
+ throw cause;
478
+ }
479
+ }
480
+ async function auditProjectDependencies(sourceDir, projectRoot) {
481
+ const parsed = await findAndParsePackageJsonFiles(sourceDir);
482
+ const roots = new Set(parsed.length > 0 ? parsed.map((entry) => entry.dir) : [sourceDir]);
483
+ const results = await Promise.all([...roots].map(async (root) => ({ root, result: await runNpmAuditJson(root) })));
484
+ return results.flatMap(({ root, result }) => {
485
+ if (result.skipped || result.exitCode === 0)
486
+ return [];
487
+ const relativeRoot = root.startsWith(projectRoot) ? root.slice(projectRoot.length + 1) : root;
488
+ const summary = summarizeNpmAudit(result.source);
489
+ if (result.exitCode === 127)
490
+ return [`npm is unavailable; skipped audit for ${relativeRoot}.`];
491
+ return [`npm audit reported ${summary ?? `exit ${result.exitCode}`} in ${relativeRoot}.`];
492
+ });
493
+ }
494
+ function summarizeNpmAudit(source) {
495
+ try {
496
+ const parsed = JSON.parse(source);
497
+ const metadata = asRecord(parsed.metadata);
498
+ const vulnerabilities = asRecord(metadata?.vulnerabilities);
499
+ const total = readNumber(vulnerabilities?.total);
500
+ if (!total)
501
+ return undefined;
502
+ const parts = ['critical', 'high', 'moderate', 'low', 'info'].flatMap((severity) => {
503
+ const count = readNumber(vulnerabilities?.[severity]);
504
+ return count ? [`${count} ${severity}`] : [];
505
+ });
506
+ return `${total} total${parts.length ? ` (${parts.join(', ')})` : ''}`;
507
+ }
508
+ catch {
509
+ return undefined;
510
+ }
511
+ }
512
+ function renderPlan(reporter, title, rows) {
513
+ if (reporter.mode !== 'human' && reporter.mode !== 'plain')
514
+ return;
515
+ reporter.header(title);
516
+ reporter.rows(rows);
517
+ }
518
+ function emitMachineData(reporter, payload) {
519
+ if (reporter.mode === 'json' || reporter.mode === 'ndjson')
520
+ reporter.data(payload);
521
+ }
522
+ function uploadPlanRows(plan) {
523
+ return [
524
+ { key: 'account', value: accountLabel(plan.account), status: 'ok' },
525
+ { key: 'project', value: plan.project.name },
526
+ { key: 'platform', value: plan.project.platformVersion },
527
+ {
528
+ key: 'remote project',
529
+ value: plan.project.exists ? 'exists' : 'will be created',
530
+ status: plan.project.exists ? 'ok' : 'warn',
531
+ },
532
+ ...(plan.options.profile ? [{ key: 'profile', value: plan.options.profile }] : []),
533
+ { key: 'message', value: plan.options.message || '(none)' },
534
+ { key: 'npm audit', value: plan.options.skipNpmAudit ? 'skipped' : 'enabled' },
535
+ {
536
+ key: 'auto-deploy',
537
+ value: plan.options.skipAutoDeploy ? 'explicitly skipped' : 'project setting',
538
+ },
539
+ { key: 'Cloudflare', value: 'not used' },
540
+ { key: 'HS-X control plane', value: 'not used' },
541
+ ];
542
+ }
543
+ function deployPlanRows(plan) {
544
+ return [
545
+ { key: 'account', value: accountLabel(plan.account), status: 'ok' },
546
+ { key: 'project', value: plan.project.name },
547
+ { key: 'build', value: `#${plan.project.buildId}` },
548
+ ...(plan.project.platformVersion
549
+ ? [{ key: 'platform', value: plan.project.platformVersion }]
550
+ : []),
551
+ ...(plan.options.profile ? [{ key: 'profile', value: plan.options.profile }] : []),
552
+ {
553
+ key: 'action',
554
+ value: plan.project.alreadyDeployed ? 'no changes — already deployed' : 'deploy build',
555
+ ...(plan.project.alreadyDeployed ? { status: 'ok' } : {}),
556
+ },
557
+ {
558
+ key: 'force',
559
+ value: plan.options.force ? 'yes' : 'no',
560
+ ...(plan.options.force ? { status: 'warn' } : {}),
561
+ },
562
+ { key: 'Cloudflare', value: 'not used' },
563
+ { key: 'HS-X control plane', value: 'not used' },
564
+ ];
565
+ }
566
+ function lifecycleFailure(reporter, code, cause) {
567
+ reporter.error(code, describeCause(cause), {
568
+ hint: cause instanceof HubSpotProjectDeployBlockedError
569
+ ? 'Review the component warnings above; use --force only when the removals are intentional.'
570
+ : 'Fix the reported HubSpot project issue and retry.',
571
+ });
572
+ reporter.done(undefined, 1);
573
+ return { exitCode: 1 };
574
+ }
575
+ function readTimeout(argv) {
576
+ const raw = resolveFlag(argv, '--timeout-ms') ?? '120000';
577
+ return readPositiveInteger(raw, '--timeout-ms');
578
+ }
579
+ function readPositiveInteger(value, label) {
580
+ const parsed = Number.parseInt(value, 10);
581
+ if (!Number.isInteger(parsed) || parsed <= 0)
582
+ throw new Error(`${label} must be positive.`);
583
+ return parsed;
584
+ }
585
+ function hasDeployLatestBuildFlag(argv) {
586
+ return hasFlag(argv, '--deploy-latest-build') || hasFlag(argv, '--deployLatestBuild');
587
+ }
588
+ function hasFlag(argv, flag) {
589
+ return argv.includes(flag);
590
+ }
591
+ function resolveFlag(argv, flag) {
592
+ const index = argv.indexOf(flag);
593
+ const next = index === -1 ? undefined : argv[index + 1];
594
+ if (next && !next.startsWith('-'))
595
+ return next;
596
+ const prefix = `${flag}=`;
597
+ return argv.find((token) => token.startsWith(prefix))?.slice(prefix.length) || undefined;
598
+ }
599
+ function safeAccount(account) {
600
+ return {
601
+ id: account.id,
602
+ ...(account.name ? { name: account.name } : {}),
603
+ source: account.source,
604
+ selectedBy: account.selectedBy,
605
+ authReady: account.authReady,
606
+ ...(account.configSource ? { configSource: account.configSource } : {}),
607
+ };
608
+ }
609
+ function accountLabel(account) {
610
+ return account.name ? `${account.name} (${account.id})` : account.id;
611
+ }
612
+ function readHttpStatus(cause) {
613
+ const record = asRecord(cause);
614
+ const response = asRecord(record?.response);
615
+ return (readNumber(record?.status) ??
616
+ readNumber(record?.statusCode) ??
617
+ readNumber(response?.status) ??
618
+ readNumber(response?.statusCode));
619
+ }
620
+ function describeCause(cause) {
621
+ const record = asRecord(cause);
622
+ const data = asRecord(record?.data) ?? asRecord(asRecord(record?.response)?.data);
623
+ return [
624
+ cause instanceof Error ? cause.message : String(cause),
625
+ typeof data?.message === 'string' ? data.message : undefined,
626
+ typeof data?.category === 'string' ? data.category : undefined,
627
+ typeof data?.correlationId === 'string' ? `correlation ${data.correlationId}` : undefined,
628
+ ]
629
+ .filter((value) => Boolean(value))
630
+ .filter((value, index, values) => values.indexOf(value) === index)
631
+ .join(' · ');
632
+ }
633
+ function asRecord(value) {
634
+ return typeof value === 'object' && value !== null
635
+ ? value
636
+ : undefined;
637
+ }
638
+ function readNumber(value) {
639
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
640
+ }
641
+ //# sourceMappingURL=project-lifecycle.js.map