@hs-x/cli 0.4.2-next.1 → 0.4.2
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/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -0
- 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/deploy.d.ts +7 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +26 -4
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +71 -5
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/help-command.d.ts.map +1 -1
- package/dist/commands/help-command.js +164 -0
- package/dist/commands/help-command.js.map +1 -1
- package/dist/commands/react-health.js +6 -10
- package/dist/commands/react-health.js.map +1 -1
- package/dist/commands/react.js +2 -2
- package/dist/commands/sync.d.ts +129 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +771 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/dev/compat-shim.d.ts +36 -0
- package/dist/dev/compat-shim.d.ts.map +1 -1
- package/dist/dev/compat-shim.js +36 -1
- package/dist/dev/compat-shim.js.map +1 -1
- package/dist/dev/component-selection.d.ts +18 -1
- package/dist/dev/component-selection.d.ts.map +1 -1
- package/dist/dev/component-selection.js +53 -5
- package/dist/dev/component-selection.js.map +1 -1
- package/dist/dev/hubspot-logs-poller.d.ts +6 -0
- package/dist/dev/hubspot-logs-poller.d.ts.map +1 -1
- package/dist/dev/hubspot-logs-poller.js +36 -10
- package/dist/dev/hubspot-logs-poller.js.map +1 -1
- package/dist/dev/hubspot-token-repair.d.ts +36 -0
- package/dist/dev/hubspot-token-repair.d.ts.map +1 -0
- package/dist/dev/hubspot-token-repair.js +88 -0
- package/dist/dev/hubspot-token-repair.js.map +1 -0
- package/dist/dev/session-manager.d.ts +19 -0
- package/dist/dev/session-manager.d.ts.map +1 -1
- package/dist/dev/session-manager.js +41 -8
- package/dist/dev/session-manager.js.map +1 -1
- package/dist/errors-registry.d.ts.map +1 -1
- package/dist/errors-registry.js +8 -0
- package/dist/errors-registry.js.map +1 -1
- package/dist/prompt.d.ts +2 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +6 -0
- package/dist/prompt.js.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
import * as Args from '@effect/cli/Args';
|
|
2
|
+
import * as Command from '@effect/cli/Command';
|
|
3
|
+
import * as Options from '@effect/cli/Options';
|
|
4
|
+
import { RuntimeSyncOperationsError, createRuntimeSyncOperationsClient, createRuntimeSyncRunRequest, } from '@hs-x/runtime';
|
|
5
|
+
import { Config, Effect, Option } from 'effect';
|
|
6
|
+
import { activeSession } from '../account-store.js';
|
|
7
|
+
import { cliError } from '../cli-error.js';
|
|
8
|
+
import { accountIdOption, controlPlaneUrlOption, jsonOption, projectIdOption, runHandler, } from '../cli/kit.js';
|
|
9
|
+
import { DEFAULT_CONTROL_PLANE_URL } from '../config.js';
|
|
10
|
+
import { isMachineOutput } from '../output-context.js';
|
|
11
|
+
import { readLocalProjectContext } from '../project-context.js';
|
|
12
|
+
import { discoverProjectRuntimeBindings, discoverProjects, resolveAccountId, resolveProjectSelection, resolveRuntimeBinding, } from '../project-picker.js';
|
|
13
|
+
import { isInteractive, promptConfirm } from '../prompt.js';
|
|
14
|
+
import { AccountStore } from '../services/account-store.js';
|
|
15
|
+
import { ControlPlaneClient } from '../services/control-plane.js';
|
|
16
|
+
import { Cwd } from '../services/cwd.js';
|
|
17
|
+
import { Reporter } from '../services/reporter.js';
|
|
18
|
+
import { loadTenantStateStore } from '../tenant-state.js';
|
|
19
|
+
import { resolveFlagsContext } from './flags.js';
|
|
20
|
+
const syncIdArg = Args.text({ name: 'sync-id' });
|
|
21
|
+
const poisonRowArg = Args.text({ name: 'row-key' });
|
|
22
|
+
const runIdArg = Args.text({ name: 'run-id' });
|
|
23
|
+
const portalOption = Options.text('portal').pipe(Options.withAlias('portal-id'), Options.optional);
|
|
24
|
+
const environmentOption = Options.text('environment').pipe(Options.optional);
|
|
25
|
+
const envOption = Options.text('env').pipe(Options.optional);
|
|
26
|
+
const appIdOption = Options.integer('app-id').pipe(Options.optional);
|
|
27
|
+
const runtimeOriginOption = Options.text('runtime-origin').pipe(Options.optional);
|
|
28
|
+
const cloudflareApiTokenOption = Options.text('cloudflare-api-token').pipe(Options.optional);
|
|
29
|
+
const limitOption = Options.integer('limit').pipe(Options.withDefault(20));
|
|
30
|
+
const previewOption = Options.boolean('preview').pipe(Options.withDefault(false));
|
|
31
|
+
const yesOption = Options.boolean('yes').pipe(Options.withAlias('y'), Options.withDefault(false));
|
|
32
|
+
const identityOptions = {
|
|
33
|
+
accountId: accountIdOption,
|
|
34
|
+
projectId: projectIdOption,
|
|
35
|
+
environment: environmentOption,
|
|
36
|
+
env: envOption,
|
|
37
|
+
appId: appIdOption,
|
|
38
|
+
runtimeOrigin: runtimeOriginOption,
|
|
39
|
+
cloudflareApiToken: cloudflareApiTokenOption,
|
|
40
|
+
controlPlaneUrl: controlPlaneUrlOption,
|
|
41
|
+
json: jsonOption,
|
|
42
|
+
};
|
|
43
|
+
function operationsError(error) {
|
|
44
|
+
if (!(error instanceof RuntimeSyncOperationsError)) {
|
|
45
|
+
return cliError('HSX_E_API_NETWORK', error instanceof Error ? error.message : String(error));
|
|
46
|
+
}
|
|
47
|
+
if (error.status === 401 || error.status === 403) {
|
|
48
|
+
return cliError('HSX_E_API_UNAUTHORIZED', error.message);
|
|
49
|
+
}
|
|
50
|
+
if (error.status === 404)
|
|
51
|
+
return cliError('HSX_E_API_NOT_FOUND', error.message);
|
|
52
|
+
if (error.status >= 500)
|
|
53
|
+
return cliError('HSX_E_API_SERVER', error.message);
|
|
54
|
+
return cliError('HSX_E_API_BAD_REQUEST', error.message);
|
|
55
|
+
}
|
|
56
|
+
function resolveSyncContext(opts) {
|
|
57
|
+
return Effect.gen(function* () {
|
|
58
|
+
const root = yield* Cwd;
|
|
59
|
+
const accounts = yield* AccountStore;
|
|
60
|
+
const stored = yield* accounts.load;
|
|
61
|
+
const session = activeSession(stored);
|
|
62
|
+
const forceDirect = Option.isSome(opts.runtimeOrigin) || Option.isSome(opts.cloudflareApiToken) || !session;
|
|
63
|
+
if (!forceDirect) {
|
|
64
|
+
return yield* resolveControlPlaneSyncContext(opts, root, stored);
|
|
65
|
+
}
|
|
66
|
+
return yield* resolveDirectSyncContext(opts, root);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function resolveDirectSyncContext(opts, root) {
|
|
70
|
+
return Effect.gen(function* () {
|
|
71
|
+
const envProjectId = yield* Config.string('HSX_PROJECT_ID').pipe(Config.withDefault(''), Effect.orDie);
|
|
72
|
+
const envAppId = yield* Config.string('HSX_APP_ID').pipe(Config.withDefault(''), Effect.orDie);
|
|
73
|
+
const envHubSpotAppId = yield* Config.string('HSX_HUBSPOT_APP_ID').pipe(Config.withDefault(''), Effect.orDie);
|
|
74
|
+
const envRuntimeBaseUrl = yield* Config.string('HSX_RUNTIME_BASE_URL').pipe(Config.withDefault(''), Effect.orDie);
|
|
75
|
+
const envEnvironment = yield* Config.string('HSX_ENVIRONMENT').pipe(Config.withDefault(''), Effect.orDie);
|
|
76
|
+
const local = yield* Effect.promise(() => readLocalProjectContext(root));
|
|
77
|
+
const argv = Option.match(opts.cloudflareApiToken, {
|
|
78
|
+
onNone: () => [],
|
|
79
|
+
onSome: (token) => ['--cloudflare-api-token', token],
|
|
80
|
+
});
|
|
81
|
+
const tenant = yield* Effect.tryPromise({
|
|
82
|
+
try: () => loadTenantStateStore({ argv, root }),
|
|
83
|
+
catch: (cause) => cliError('HSX_E_INPUT_INVALID', cause instanceof Error ? cause.message : String(cause)),
|
|
84
|
+
});
|
|
85
|
+
if (!tenant) {
|
|
86
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', 'No deployed tenant state was found. Run `hs-x deploy` in this checkout first.'));
|
|
87
|
+
}
|
|
88
|
+
const resolved = yield* Effect.tryPromise({
|
|
89
|
+
try: () => resolveFlagsContext({
|
|
90
|
+
stateStore: tenant.stateStore,
|
|
91
|
+
local,
|
|
92
|
+
explicitProjectId: Option.getOrUndefined(opts.projectId),
|
|
93
|
+
envProjectId: envProjectId || undefined,
|
|
94
|
+
explicitEnvironment: Option.getOrUndefined(opts.environment) ?? Option.getOrUndefined(opts.env),
|
|
95
|
+
envEnvironment: envEnvironment || undefined,
|
|
96
|
+
explicitAppId: Option.getOrUndefined(opts.appId),
|
|
97
|
+
envAppId: envAppId || undefined,
|
|
98
|
+
compatibilityEnvAppId: envHubSpotAppId || undefined,
|
|
99
|
+
explicitRuntimeOrigin: Option.getOrUndefined(opts.runtimeOrigin),
|
|
100
|
+
envRuntimeOrigin: envRuntimeBaseUrl || undefined,
|
|
101
|
+
allowPrompt: false,
|
|
102
|
+
}),
|
|
103
|
+
catch: (cause) => cliError('HSX_E_INPUT_INVALID', cause instanceof Error ? cause.message : String(cause)),
|
|
104
|
+
});
|
|
105
|
+
const scope = {
|
|
106
|
+
accountId: resolved.binding.tenantScopeAccountId,
|
|
107
|
+
projectId: resolved.projectId,
|
|
108
|
+
environment: resolved.environment,
|
|
109
|
+
hubSpotAppId: resolved.hubSpotAppId,
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
runtimeBaseUrl: resolved.runtimeBaseUrl,
|
|
113
|
+
grantSecret: resolved.binding.syncGrantSecretValue,
|
|
114
|
+
scope,
|
|
115
|
+
client: createRuntimeSyncOperationsClient({
|
|
116
|
+
runtimeBaseUrl: resolved.runtimeBaseUrl,
|
|
117
|
+
grantSecret: resolved.binding.syncGrantSecretValue,
|
|
118
|
+
scope,
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function resolveControlPlaneSyncContext(opts, root, stored) {
|
|
124
|
+
return Effect.gen(function* () {
|
|
125
|
+
const local = yield* Effect.promise(() => readLocalProjectContext(root));
|
|
126
|
+
const envAccountId = yield* Config.string('HSX_ACCOUNT_ID').pipe(Config.withDefault(''), Effect.orDie);
|
|
127
|
+
const envProjectId = yield* Config.string('HSX_PROJECT_ID').pipe(Config.withDefault(''), Effect.orDie);
|
|
128
|
+
const envEnvironment = yield* Config.string('HSX_ENVIRONMENT').pipe(Config.withDefault(''), Effect.orDie);
|
|
129
|
+
const envAppId = yield* Config.string('HSX_APP_ID').pipe(Config.withDefault(''), Effect.orDie);
|
|
130
|
+
const envHubSpotAppId = yield* Config.string('HSX_HUBSPOT_APP_ID').pipe(Config.withDefault(''), Effect.orDie);
|
|
131
|
+
const envControlPlaneUrl = yield* Config.string('HSX_CONTROL_PLANE_URL').pipe(Config.withDefault(''), Effect.orDie);
|
|
132
|
+
const accountId = resolveAccountId({
|
|
133
|
+
...(Option.getOrUndefined(opts.accountId)
|
|
134
|
+
? { explicitAccountId: Option.getOrUndefined(opts.accountId) }
|
|
135
|
+
: {}),
|
|
136
|
+
...(envAccountId ? { envAccountId } : {}),
|
|
137
|
+
...(local.projectBinding?.accountId
|
|
138
|
+
? { localAccountId: local.projectBinding.accountId }
|
|
139
|
+
: {}),
|
|
140
|
+
...(stored.defaultAccountId ? { defaultAccountId: stored.defaultAccountId } : {}),
|
|
141
|
+
});
|
|
142
|
+
if (!accountId) {
|
|
143
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', 'Sync operations require an HS-X account. Run `hs-x login`, or pass --account-id.'));
|
|
144
|
+
}
|
|
145
|
+
const controlPlaneUrl = Option.getOrUndefined(opts.controlPlaneUrl) ??
|
|
146
|
+
(envControlPlaneUrl || DEFAULT_CONTROL_PLANE_URL);
|
|
147
|
+
const cp = yield* ControlPlaneClient;
|
|
148
|
+
const request = (input) => Effect.runPromise(cp.fetch({
|
|
149
|
+
controlPlaneUrl,
|
|
150
|
+
path: input.path,
|
|
151
|
+
...(input.method ? { method: input.method } : {}),
|
|
152
|
+
...(input.body === undefined ? {} : { body: input.body }),
|
|
153
|
+
}));
|
|
154
|
+
return yield* Effect.tryPromise({
|
|
155
|
+
try: async () => {
|
|
156
|
+
const projects = await discoverProjects({ accountId, controlPlane: { request } });
|
|
157
|
+
const selectedProjectId = Option.getOrUndefined(opts.projectId) ??
|
|
158
|
+
(envProjectId || local.projectBinding?.projectId);
|
|
159
|
+
const project = await resolveProjectSelection({
|
|
160
|
+
accountId,
|
|
161
|
+
projects,
|
|
162
|
+
...(selectedProjectId ? { explicitProjectId: selectedProjectId } : {}),
|
|
163
|
+
...(local.projectBinding?.projectId
|
|
164
|
+
? { localProjectId: local.projectBinding.projectId }
|
|
165
|
+
: {}),
|
|
166
|
+
allowPrompt: false,
|
|
167
|
+
});
|
|
168
|
+
const bindings = await discoverProjectRuntimeBindings({
|
|
169
|
+
accountId,
|
|
170
|
+
projectId: project.id,
|
|
171
|
+
controlPlane: { request },
|
|
172
|
+
});
|
|
173
|
+
const appIdRaw = envAppId || envHubSpotAppId || undefined;
|
|
174
|
+
const explicitAppId = Option.getOrUndefined(opts.appId) ??
|
|
175
|
+
(appIdRaw === undefined ? undefined : Number(appIdRaw));
|
|
176
|
+
const selectedEnvironment = Option.getOrUndefined(opts.environment) ??
|
|
177
|
+
Option.getOrUndefined(opts.env) ??
|
|
178
|
+
(envEnvironment || undefined);
|
|
179
|
+
const binding = await resolveRuntimeBinding({
|
|
180
|
+
project,
|
|
181
|
+
bindings,
|
|
182
|
+
...(selectedEnvironment ? { explicitEnvironment: selectedEnvironment } : {}),
|
|
183
|
+
...(explicitAppId === undefined ? {} : { explicitAppId }),
|
|
184
|
+
...(local.hubSpotBinding?.appId ? { localAppId: local.hubSpotBinding.appId } : {}),
|
|
185
|
+
allowPrompt: false,
|
|
186
|
+
});
|
|
187
|
+
const base = `/v1/accounts/${encodeURIComponent(accountId)}/projects/${encodeURIComponent(project.id)}/hubspot-apps/${binding.hubSpotAppId}`;
|
|
188
|
+
const query = `?environment=${encodeURIComponent(binding.environment)}`;
|
|
189
|
+
const [secretResponse, runtimeResponse] = await Promise.all([
|
|
190
|
+
request({ path: `${base}/sync-grant-secret${query}` }),
|
|
191
|
+
request({ path: `${base}/install-runtime${query}` }),
|
|
192
|
+
]);
|
|
193
|
+
if (!secretResponse.ok || !runtimeResponse.ok) {
|
|
194
|
+
throw new Error(`Sync operations binding is unavailable (secret ${secretResponse.status}, runtime ${runtimeResponse.status}).`);
|
|
195
|
+
}
|
|
196
|
+
const secretBody = (await secretResponse.json());
|
|
197
|
+
const runtimeBody = (await runtimeResponse.json());
|
|
198
|
+
if (!secretBody.syncGrantSecret || !runtimeBody.runtimeBaseUrl) {
|
|
199
|
+
throw new Error('Sync operations binding is incomplete. Re-deploy the linked project.');
|
|
200
|
+
}
|
|
201
|
+
const runtimeOrigin = new URL(runtimeBody.runtimeBaseUrl);
|
|
202
|
+
const loopbackHttp = runtimeOrigin.protocol === 'http:' &&
|
|
203
|
+
['127.0.0.1', 'localhost', '[::1]'].includes(runtimeOrigin.hostname);
|
|
204
|
+
if ((runtimeOrigin.protocol !== 'https:' && !loopbackHttp) ||
|
|
205
|
+
runtimeOrigin.username ||
|
|
206
|
+
runtimeOrigin.password) {
|
|
207
|
+
throw new Error('The registered tenant Worker URL is not a safe HTTPS origin.');
|
|
208
|
+
}
|
|
209
|
+
const scope = {
|
|
210
|
+
accountId,
|
|
211
|
+
projectId: project.id,
|
|
212
|
+
environment: binding.environment,
|
|
213
|
+
hubSpotAppId: binding.hubSpotAppId,
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
runtimeBaseUrl: runtimeOrigin.origin,
|
|
217
|
+
grantSecret: secretBody.syncGrantSecret,
|
|
218
|
+
scope,
|
|
219
|
+
client: createRuntimeSyncOperationsClient({
|
|
220
|
+
runtimeBaseUrl: runtimeOrigin.origin,
|
|
221
|
+
grantSecret: secretBody.syncGrantSecret,
|
|
222
|
+
scope,
|
|
223
|
+
}),
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
catch: (cause) => cliError('HSX_E_INPUT_INVALID', cause instanceof Error ? cause.message : String(cause)),
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function duration(run) {
|
|
231
|
+
if (!run.finishedAt)
|
|
232
|
+
return undefined;
|
|
233
|
+
const ms = Date.parse(run.finishedAt) - Date.parse(run.startedAt);
|
|
234
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
235
|
+
return undefined;
|
|
236
|
+
return ms < 1_000 ? `${ms}ms` : `${(ms / 1_000).toFixed(ms < 10_000 ? 1 : 0)}s`;
|
|
237
|
+
}
|
|
238
|
+
function runDetail(run) {
|
|
239
|
+
const counts = [
|
|
240
|
+
run.delivered !== undefined ? `${run.delivered} delivered` : undefined,
|
|
241
|
+
run.quarantined !== undefined ? `${run.quarantined} quarantined` : undefined,
|
|
242
|
+
run.pages !== undefined ? `${run.pages} pages` : undefined,
|
|
243
|
+
duration(run),
|
|
244
|
+
].filter((value) => value !== undefined);
|
|
245
|
+
return [run.key.portalId, run.trigger, ...counts].join(' · ');
|
|
246
|
+
}
|
|
247
|
+
function runRows(runs) {
|
|
248
|
+
return runs.map((run) => ({
|
|
249
|
+
status: run.outcome === 'completed'
|
|
250
|
+
? 'ok'
|
|
251
|
+
: run.outcome === 'failed'
|
|
252
|
+
? 'fail'
|
|
253
|
+
: 'warn',
|
|
254
|
+
key: run.startedAt,
|
|
255
|
+
value: run.outcome,
|
|
256
|
+
detail: runDetail(run),
|
|
257
|
+
...(run.error ? { hint: run.error } : {}),
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
function poisonRows(rows) {
|
|
261
|
+
return rows.map((row) => ({
|
|
262
|
+
status: 'fail',
|
|
263
|
+
key: row.rowKey || '(missing row key)',
|
|
264
|
+
value: `${row.failureKind} · ${row.category}`,
|
|
265
|
+
detail: [
|
|
266
|
+
row.objectType,
|
|
267
|
+
`${row.attemptCount} attempt${row.attemptCount === 1 ? '' : 's'}`,
|
|
268
|
+
row.updatedAt,
|
|
269
|
+
].join(' · '),
|
|
270
|
+
hint: row.message,
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
function previewRows(rows) {
|
|
274
|
+
return rows.map((row) => ({
|
|
275
|
+
status: row.valid ? 'ok' : 'fail',
|
|
276
|
+
key: row.rowKey ?? '(missing row key)',
|
|
277
|
+
value: row.valid ? 'valid' : (row.category ?? 'invalid'),
|
|
278
|
+
...(row.valid && row.properties
|
|
279
|
+
? { detail: JSON.stringify(row.properties) }
|
|
280
|
+
: row.message
|
|
281
|
+
? { hint: row.message }
|
|
282
|
+
: {}),
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
function reportPreview(reporter, syncId, portalId, preview) {
|
|
286
|
+
return Effect.gen(function* () {
|
|
287
|
+
yield* reporter.header(`${syncId} · portal ${portalId} · preview`);
|
|
288
|
+
yield* reporter.rows([
|
|
289
|
+
{
|
|
290
|
+
status: preview.invalid > 0 ? 'warn' : 'ok',
|
|
291
|
+
key: 'sample',
|
|
292
|
+
value: `${preview.valid} valid · ${preview.invalid} invalid`,
|
|
293
|
+
detail: `${preview.fetched} fetched${preview.truncated ? ' · sample truncated' : ''}`,
|
|
294
|
+
},
|
|
295
|
+
{ key: 'target', value: preview.objectType, detail: `id property ${preview.idProperty}` },
|
|
296
|
+
]);
|
|
297
|
+
if (preview.rows.length > 0)
|
|
298
|
+
yield* reporter.rows(previewRows(preview.rows));
|
|
299
|
+
else
|
|
300
|
+
yield* reporter.info('The current source page is empty.');
|
|
301
|
+
yield* reporter.done('sync preview — no state or HubSpot writes');
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function statusRows(status) {
|
|
305
|
+
const latest = status.latestRun;
|
|
306
|
+
const state = status.state;
|
|
307
|
+
return [
|
|
308
|
+
...(state?.pausedAt
|
|
309
|
+
? [
|
|
310
|
+
{
|
|
311
|
+
status: 'warn',
|
|
312
|
+
key: 'control',
|
|
313
|
+
value: 'paused',
|
|
314
|
+
detail: `since ${state.pausedAt}`,
|
|
315
|
+
hint: 'Resume with `hs-x sync state resume`.',
|
|
316
|
+
},
|
|
317
|
+
]
|
|
318
|
+
: []),
|
|
319
|
+
{
|
|
320
|
+
status: state?.status === 'failed'
|
|
321
|
+
? 'fail'
|
|
322
|
+
: state?.status === 'running'
|
|
323
|
+
? 'warn'
|
|
324
|
+
: 'ok',
|
|
325
|
+
key: 'state',
|
|
326
|
+
value: state?.status ?? 'never run',
|
|
327
|
+
...(state?.lease ? { detail: `lease until ${state.lease.expiresAt}` } : {}),
|
|
328
|
+
...(state?.lastError ? { hint: state.lastError } : {}),
|
|
329
|
+
},
|
|
330
|
+
{ key: 'schedule', value: status.schedule === undefined ? 'manual' : String(status.schedule) },
|
|
331
|
+
{
|
|
332
|
+
key: 'cursor',
|
|
333
|
+
value: state?.cursorUpdatedAt ?? 'not established',
|
|
334
|
+
...(state?.cursor !== undefined ? { detail: JSON.stringify(state.cursor) } : {}),
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
status: status.poisonCount > 0 ? 'warn' : 'ok',
|
|
338
|
+
key: 'poison',
|
|
339
|
+
value: String(status.poisonCount),
|
|
340
|
+
...(status.poisonCount > 0
|
|
341
|
+
? { hint: 'Inspect quarantined rows with `hs-x sync poison list`.' }
|
|
342
|
+
: {}),
|
|
343
|
+
},
|
|
344
|
+
latest
|
|
345
|
+
? {
|
|
346
|
+
status: latest.outcome === 'completed'
|
|
347
|
+
? 'ok'
|
|
348
|
+
: latest.outcome === 'failed'
|
|
349
|
+
? 'fail'
|
|
350
|
+
: 'warn',
|
|
351
|
+
key: 'last run',
|
|
352
|
+
value: `${latest.startedAt} · ${latest.outcome}`,
|
|
353
|
+
detail: runDetail(latest),
|
|
354
|
+
...(latest.error ? { hint: latest.error } : {}),
|
|
355
|
+
}
|
|
356
|
+
: { key: 'last run', value: 'none' },
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
function statusBody(syncId, portal, opts) {
|
|
360
|
+
return Effect.gen(function* () {
|
|
361
|
+
const portalId = Option.getOrUndefined(portal);
|
|
362
|
+
if (!portalId) {
|
|
363
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', '`hs-x sync status` requires --portal <id>.'));
|
|
364
|
+
}
|
|
365
|
+
const { client } = yield* resolveSyncContext(opts);
|
|
366
|
+
const status = yield* Effect.tryPromise({
|
|
367
|
+
try: () => client.status({ capabilityId: syncId, portalId }),
|
|
368
|
+
catch: operationsError,
|
|
369
|
+
});
|
|
370
|
+
const reporter = yield* Reporter;
|
|
371
|
+
if (reporter.mode !== 'human') {
|
|
372
|
+
yield* reporter.data({ status });
|
|
373
|
+
yield* reporter.done();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
yield* reporter.header(`${syncId} · portal ${portalId}`);
|
|
377
|
+
yield* reporter.rows(statusRows(status));
|
|
378
|
+
yield* reporter.done('sync status');
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
function runsBody(syncId, portal, limit, opts) {
|
|
382
|
+
return Effect.gen(function* () {
|
|
383
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
|
|
384
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', '--limit must be an integer from 1 to 200.'));
|
|
385
|
+
}
|
|
386
|
+
const { client } = yield* resolveSyncContext(opts);
|
|
387
|
+
const portalId = Option.getOrUndefined(portal);
|
|
388
|
+
const runs = yield* Effect.tryPromise({
|
|
389
|
+
try: () => client.runs({
|
|
390
|
+
capabilityId: syncId,
|
|
391
|
+
...(portalId ? { portalId } : {}),
|
|
392
|
+
limit,
|
|
393
|
+
}),
|
|
394
|
+
catch: operationsError,
|
|
395
|
+
});
|
|
396
|
+
const reporter = yield* Reporter;
|
|
397
|
+
if (reporter.mode !== 'human') {
|
|
398
|
+
yield* reporter.data({ runs });
|
|
399
|
+
yield* reporter.done();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
yield* reporter.header(`${syncId}${portalId ? ` · portal ${portalId}` : ''}`);
|
|
403
|
+
if (runs.length === 0)
|
|
404
|
+
yield* reporter.info('No sync runs recorded.');
|
|
405
|
+
else
|
|
406
|
+
yield* reporter.rows(runRows(runs));
|
|
407
|
+
yield* reporter.done(`${runs.length} run${runs.length === 1 ? '' : 's'}`);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
function logEvents(run) {
|
|
411
|
+
return [
|
|
412
|
+
{
|
|
413
|
+
timestamp: run.startedAt,
|
|
414
|
+
level: 'info',
|
|
415
|
+
event: 'sync.run.started',
|
|
416
|
+
message: `${run.trigger} run started for portal ${run.key.portalId}`,
|
|
417
|
+
},
|
|
418
|
+
...(run.finishedAt
|
|
419
|
+
? [
|
|
420
|
+
{
|
|
421
|
+
timestamp: run.finishedAt,
|
|
422
|
+
level: run.outcome === 'failed' ? 'error' : 'info',
|
|
423
|
+
event: `sync.run.${run.outcome}`,
|
|
424
|
+
message: run.error ??
|
|
425
|
+
`${run.delivered ?? 0} delivered, ${run.quarantined ?? 0} quarantined, ${run.pages ?? 0} pages`,
|
|
426
|
+
},
|
|
427
|
+
]
|
|
428
|
+
: []),
|
|
429
|
+
];
|
|
430
|
+
}
|
|
431
|
+
function logsBody(syncId, runId, portal, opts) {
|
|
432
|
+
return Effect.gen(function* () {
|
|
433
|
+
const { client } = yield* resolveSyncContext(opts);
|
|
434
|
+
const portalId = Option.getOrUndefined(portal);
|
|
435
|
+
const run = yield* Effect.tryPromise({
|
|
436
|
+
try: () => client.run({
|
|
437
|
+
capabilityId: syncId,
|
|
438
|
+
runId,
|
|
439
|
+
...(portalId ? { portalId } : {}),
|
|
440
|
+
}),
|
|
441
|
+
catch: operationsError,
|
|
442
|
+
});
|
|
443
|
+
if (!run) {
|
|
444
|
+
return yield* Effect.fail(cliError('HSX_E_API_NOT_FOUND', `No visible sync run named '${runId}' was found.`));
|
|
445
|
+
}
|
|
446
|
+
const events = logEvents(run);
|
|
447
|
+
const reporter = yield* Reporter;
|
|
448
|
+
if (reporter.mode !== 'human') {
|
|
449
|
+
yield* reporter.data({ run, events });
|
|
450
|
+
yield* reporter.done();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
yield* reporter.header(`${syncId} · ${runId}`);
|
|
454
|
+
yield* reporter.rows(events.map((event) => ({
|
|
455
|
+
status: event.level === 'error' ? 'fail' : 'ok',
|
|
456
|
+
key: event.timestamp,
|
|
457
|
+
value: event.event,
|
|
458
|
+
detail: event.message,
|
|
459
|
+
})));
|
|
460
|
+
yield* reporter.done(`${events.length} durable lifecycle event${events.length === 1 ? '' : 's'}`);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function stateGetBody(syncId, portal, opts) {
|
|
464
|
+
return Effect.gen(function* () {
|
|
465
|
+
const portalId = Option.getOrUndefined(portal);
|
|
466
|
+
if (!portalId) {
|
|
467
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', '`hs-x sync state get` requires --portal <id>.'));
|
|
468
|
+
}
|
|
469
|
+
const { client } = yield* resolveSyncContext(opts);
|
|
470
|
+
const status = yield* Effect.tryPromise({
|
|
471
|
+
try: () => client.status({ capabilityId: syncId, portalId }),
|
|
472
|
+
catch: operationsError,
|
|
473
|
+
});
|
|
474
|
+
const reporter = yield* Reporter;
|
|
475
|
+
if (reporter.mode !== 'human') {
|
|
476
|
+
yield* reporter.data({
|
|
477
|
+
capability_id: syncId,
|
|
478
|
+
portal_id: portalId,
|
|
479
|
+
state: status.state ?? null,
|
|
480
|
+
checkpoints: status.checkpoints,
|
|
481
|
+
});
|
|
482
|
+
yield* reporter.done();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
yield* reporter.header(`${syncId} · portal ${portalId} · state`);
|
|
486
|
+
yield* reporter.rows(statusRows({ ...status, latestRun: undefined, poisonCount: 0 }).filter((row) => ['control', 'state', 'cursor'].includes(row.key)));
|
|
487
|
+
yield* reporter.rows([
|
|
488
|
+
{
|
|
489
|
+
key: 'checkpoints',
|
|
490
|
+
value: String(status.checkpoints.length),
|
|
491
|
+
...(status.checkpoints.length > 0
|
|
492
|
+
? { detail: status.checkpoints.map((checkpoint) => checkpoint.chunkId).join(', ') }
|
|
493
|
+
: {}),
|
|
494
|
+
},
|
|
495
|
+
]);
|
|
496
|
+
yield* reporter.done('sync state');
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
function stateResetBody(syncId, portal, yes, opts) {
|
|
500
|
+
return Effect.gen(function* () {
|
|
501
|
+
const portalId = Option.getOrUndefined(portal);
|
|
502
|
+
if (!portalId) {
|
|
503
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', '`hs-x sync state reset` requires --portal <id>.'));
|
|
504
|
+
}
|
|
505
|
+
if (!yes) {
|
|
506
|
+
if (opts.json || isMachineOutput() || !isInteractive()) {
|
|
507
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', 'Resetting sync state requires explicit consent outside an interactive terminal. Re-run with --yes.'));
|
|
508
|
+
}
|
|
509
|
+
const confirmed = yield* Effect.promise(() => promptConfirm({
|
|
510
|
+
message: `Reset cursor, checkpoints, attempts, and errors for ${syncId} in portal ${portalId}?`,
|
|
511
|
+
default: false,
|
|
512
|
+
}));
|
|
513
|
+
if (confirmed !== true) {
|
|
514
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', 'Sync state reset cancelled.'));
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const { client } = yield* resolveSyncContext(opts);
|
|
518
|
+
const result = yield* Effect.tryPromise({
|
|
519
|
+
try: () => client.resetState({ capabilityId: syncId, portalId }),
|
|
520
|
+
catch: operationsError,
|
|
521
|
+
});
|
|
522
|
+
if (!result.reset) {
|
|
523
|
+
return yield* Effect.fail(cliError('HSX_E_API_CONFLICT', `Sync state cannot be reset while ${result.ownerId} holds the lease until ${result.expiresAt}.`));
|
|
524
|
+
}
|
|
525
|
+
const reporter = yield* Reporter;
|
|
526
|
+
if (reporter.mode !== 'human') {
|
|
527
|
+
yield* reporter.data({ result });
|
|
528
|
+
yield* reporter.done();
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
yield* reporter.header(`${syncId} · portal ${portalId}`);
|
|
532
|
+
yield* reporter.rows([
|
|
533
|
+
{
|
|
534
|
+
status: 'ok',
|
|
535
|
+
key: 'state',
|
|
536
|
+
value: result.existed ? 'reset' : 'already empty',
|
|
537
|
+
detail: 'cursor, checkpoints, attempts, and last error cleared',
|
|
538
|
+
},
|
|
539
|
+
]);
|
|
540
|
+
yield* reporter.done('sync state reset');
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
function statePauseBody(syncId, portal, paused, yes, opts) {
|
|
544
|
+
const action = paused ? 'pause' : 'resume';
|
|
545
|
+
return Effect.gen(function* () {
|
|
546
|
+
const portalId = Option.getOrUndefined(portal);
|
|
547
|
+
if (!portalId) {
|
|
548
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', `\`hs-x sync state ${action}\` requires --portal <id>.`));
|
|
549
|
+
}
|
|
550
|
+
if (!yes) {
|
|
551
|
+
if (opts.json || isMachineOutput() || !isInteractive()) {
|
|
552
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', `${paused ? 'Pausing' : 'Resuming'} a sync requires explicit consent outside an interactive terminal. Re-run with --yes.`));
|
|
553
|
+
}
|
|
554
|
+
const confirmed = yield* Effect.promise(() => promptConfirm({
|
|
555
|
+
message: `${paused ? 'Pause' : 'Resume'} ${syncId} for portal ${portalId}?`,
|
|
556
|
+
default: false,
|
|
557
|
+
}));
|
|
558
|
+
if (confirmed !== true) {
|
|
559
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', `Sync ${action} cancelled.`));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const { client } = yield* resolveSyncContext(opts);
|
|
563
|
+
const result = yield* Effect.tryPromise({
|
|
564
|
+
try: () => client.setPaused({ capabilityId: syncId, portalId, paused }),
|
|
565
|
+
catch: operationsError,
|
|
566
|
+
});
|
|
567
|
+
const reporter = yield* Reporter;
|
|
568
|
+
if (reporter.mode !== 'human') {
|
|
569
|
+
yield* reporter.data({ result });
|
|
570
|
+
yield* reporter.done();
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
yield* reporter.header(`${syncId} · portal ${portalId}`);
|
|
574
|
+
yield* reporter.rows([
|
|
575
|
+
{
|
|
576
|
+
status: result.paused ? 'warn' : 'ok',
|
|
577
|
+
key: 'control',
|
|
578
|
+
value: result.paused ? 'paused' : 'running',
|
|
579
|
+
detail: result.changed ? 'state changed' : 'already in this state',
|
|
580
|
+
},
|
|
581
|
+
]);
|
|
582
|
+
yield* reporter.done(`sync ${action}`);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function poisonBody(syncId, portal, opts) {
|
|
586
|
+
return Effect.gen(function* () {
|
|
587
|
+
const portalId = Option.getOrUndefined(portal);
|
|
588
|
+
if (!portalId) {
|
|
589
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', '`hs-x sync poison list` requires --portal <id>.'));
|
|
590
|
+
}
|
|
591
|
+
const { client } = yield* resolveSyncContext(opts);
|
|
592
|
+
const rows = yield* Effect.tryPromise({
|
|
593
|
+
try: () => client.poison({ capabilityId: syncId, portalId }),
|
|
594
|
+
catch: operationsError,
|
|
595
|
+
});
|
|
596
|
+
const reporter = yield* Reporter;
|
|
597
|
+
if (reporter.mode !== 'human') {
|
|
598
|
+
yield* reporter.data({ rows });
|
|
599
|
+
yield* reporter.done();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
yield* reporter.header(`${syncId} · portal ${portalId} · poison rows`);
|
|
603
|
+
if (rows.length === 0)
|
|
604
|
+
yield* reporter.info('No quarantined rows.');
|
|
605
|
+
else
|
|
606
|
+
yield* reporter.rows(poisonRows(rows));
|
|
607
|
+
yield* reporter.done(`${rows.length} poison row${rows.length === 1 ? '' : 's'}`);
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
function poisonRetryBody(syncId, rowKey, portal, yes, opts) {
|
|
611
|
+
return Effect.gen(function* () {
|
|
612
|
+
const portalId = Option.getOrUndefined(portal);
|
|
613
|
+
if (!portalId) {
|
|
614
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', '`hs-x sync poison retry` requires --portal <id>.'));
|
|
615
|
+
}
|
|
616
|
+
if (!yes) {
|
|
617
|
+
if (opts.json || isMachineOutput() || !isInteractive()) {
|
|
618
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', 'Retrying a poison row performs a HubSpot write and requires explicit consent outside an interactive terminal. Re-run with --yes.'));
|
|
619
|
+
}
|
|
620
|
+
const confirmed = yield* Effect.promise(() => promptConfirm({
|
|
621
|
+
message: `Retry poison row ${rowKey} for ${syncId} in portal ${portalId}?`,
|
|
622
|
+
default: false,
|
|
623
|
+
}));
|
|
624
|
+
if (confirmed !== true) {
|
|
625
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', 'Poison retry cancelled.'));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const { client } = yield* resolveSyncContext(opts);
|
|
629
|
+
const result = yield* Effect.tryPromise({
|
|
630
|
+
try: () => client.retryPoison({ capabilityId: syncId, portalId, rowKey }),
|
|
631
|
+
catch: operationsError,
|
|
632
|
+
});
|
|
633
|
+
if (!result.retried) {
|
|
634
|
+
return yield* Effect.fail(cliError(result.reason === 'not_found' ? 'HSX_E_API_NOT_FOUND' : 'HSX_E_API_CONFLICT', result.message));
|
|
635
|
+
}
|
|
636
|
+
const reporter = yield* Reporter;
|
|
637
|
+
if (reporter.mode !== 'human') {
|
|
638
|
+
yield* reporter.data({ result });
|
|
639
|
+
yield* reporter.done();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
yield* reporter.header(`${syncId} · portal ${portalId}`);
|
|
643
|
+
yield* reporter.rows([
|
|
644
|
+
{
|
|
645
|
+
status: 'ok',
|
|
646
|
+
key: rowKey,
|
|
647
|
+
value: 'delivered',
|
|
648
|
+
detail: `${result.objectType} · poison row removed after confirmed upsert`,
|
|
649
|
+
},
|
|
650
|
+
]);
|
|
651
|
+
yield* reporter.done('poison retry');
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
function runBody(syncId, portal, preview, limit, opts) {
|
|
655
|
+
return Effect.gen(function* () {
|
|
656
|
+
const portalId = Option.getOrUndefined(portal);
|
|
657
|
+
if (!portalId) {
|
|
658
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', '`hs-x sync run` requires --portal <id>.'));
|
|
659
|
+
}
|
|
660
|
+
if (preview && (!Number.isInteger(limit) || limit < 1 || limit > 100)) {
|
|
661
|
+
return yield* Effect.fail(cliError('HSX_E_INPUT_INVALID', '--limit must be an integer from 1 to 100 for preview.'));
|
|
662
|
+
}
|
|
663
|
+
const context = yield* resolveSyncContext(opts);
|
|
664
|
+
if (preview) {
|
|
665
|
+
const result = yield* Effect.tryPromise({
|
|
666
|
+
try: () => context.client.preview({ capabilityId: syncId, portalId, limit }),
|
|
667
|
+
catch: operationsError,
|
|
668
|
+
});
|
|
669
|
+
const reporter = yield* Reporter;
|
|
670
|
+
if (reporter.mode !== 'human') {
|
|
671
|
+
yield* reporter.data({ preview: result });
|
|
672
|
+
yield* reporter.done();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
yield* reportPreview(reporter, syncId, portalId, result);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const payload = yield* Effect.tryPromise({
|
|
679
|
+
try: async () => {
|
|
680
|
+
const request = await createRuntimeSyncRunRequest({
|
|
681
|
+
runtimeBaseUrl: context.runtimeBaseUrl,
|
|
682
|
+
grantSecret: context.grantSecret,
|
|
683
|
+
scope: context.scope,
|
|
684
|
+
capabilityId: syncId,
|
|
685
|
+
trigger: 'manual',
|
|
686
|
+
body: { install: { portalId } },
|
|
687
|
+
});
|
|
688
|
+
const response = await fetch(request);
|
|
689
|
+
const body = (await response.json().catch(() => ({})));
|
|
690
|
+
if (!response.ok || body.ok !== true) {
|
|
691
|
+
const message = typeof body.message === 'string'
|
|
692
|
+
? body.message
|
|
693
|
+
: `Sync run request failed with status ${response.status}.`;
|
|
694
|
+
throw new RuntimeSyncOperationsError(response.status, message, typeof body.error === 'string' ? body.error : undefined);
|
|
695
|
+
}
|
|
696
|
+
return body;
|
|
697
|
+
},
|
|
698
|
+
catch: operationsError,
|
|
699
|
+
});
|
|
700
|
+
const reporter = yield* Reporter;
|
|
701
|
+
if (reporter.mode !== 'human') {
|
|
702
|
+
yield* reporter.data(payload);
|
|
703
|
+
yield* reporter.done();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const runId = typeof payload.runId === 'string' ? payload.runId : undefined;
|
|
707
|
+
const result = typeof payload.result === 'object' && payload.result !== null
|
|
708
|
+
? payload.result
|
|
709
|
+
: undefined;
|
|
710
|
+
yield* reporter.header(`${syncId} · portal ${portalId}`);
|
|
711
|
+
yield* reporter.rows([
|
|
712
|
+
{ status: 'ok', key: 'run', value: runId ?? 'completed' },
|
|
713
|
+
...(result
|
|
714
|
+
? [
|
|
715
|
+
{
|
|
716
|
+
key: 'delivery',
|
|
717
|
+
value: `${String(result.delivered ?? 0)} delivered`,
|
|
718
|
+
detail: `${String(result.quarantined ?? 0)} quarantined · ${String(result.pages ?? 0)} pages`,
|
|
719
|
+
},
|
|
720
|
+
]
|
|
721
|
+
: []),
|
|
722
|
+
]);
|
|
723
|
+
yield* reporter.done('sync run');
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
const syncStatusCmd = Command.make('status', { syncId: syncIdArg, portal: portalOption, ...identityOptions }, ({ syncId, portal, ...opts }) => runHandler('sync status', { json: opts.json }, statusBody(syncId, portal, opts)));
|
|
727
|
+
const syncRunsCmd = Command.make('runs', { syncId: syncIdArg, portal: portalOption, limit: limitOption, ...identityOptions }, ({ syncId, portal, limit, ...opts }) => runHandler('sync runs', { json: opts.json }, runsBody(syncId, portal, limit, opts)));
|
|
728
|
+
const syncLogsCmd = Command.make('logs', {
|
|
729
|
+
syncId: syncIdArg,
|
|
730
|
+
runId: runIdArg,
|
|
731
|
+
portal: portalOption,
|
|
732
|
+
...identityOptions,
|
|
733
|
+
}, ({ syncId, runId, portal, ...opts }) => runHandler('sync logs', { json: opts.json }, logsBody(syncId, runId, portal, opts)));
|
|
734
|
+
const syncStateGetCmd = Command.make('get', { syncId: syncIdArg, portal: portalOption, ...identityOptions }, ({ syncId, portal, ...opts }) => runHandler('sync state get', { json: opts.json }, stateGetBody(syncId, portal, opts)));
|
|
735
|
+
const syncStateResetCmd = Command.make('reset', { syncId: syncIdArg, portal: portalOption, yes: yesOption, ...identityOptions }, ({ syncId, portal, yes, ...opts }) => runHandler('sync state reset', { json: opts.json }, stateResetBody(syncId, portal, yes, opts)));
|
|
736
|
+
function makeSyncStatePauseCmd(name, paused) {
|
|
737
|
+
return Command.make(name, { syncId: syncIdArg, portal: portalOption, yes: yesOption, ...identityOptions }, ({ syncId, portal, yes, ...opts }) => runHandler(`sync state ${name}`, { json: opts.json }, statePauseBody(syncId, portal, paused, yes, opts)));
|
|
738
|
+
}
|
|
739
|
+
const syncStatePauseCmd = makeSyncStatePauseCmd('pause', true);
|
|
740
|
+
const syncStateResumeCmd = makeSyncStatePauseCmd('resume', false);
|
|
741
|
+
const syncStateCmd = Command.make('state').pipe(Command.withSubcommands([
|
|
742
|
+
syncStateGetCmd,
|
|
743
|
+
syncStateResetCmd,
|
|
744
|
+
syncStatePauseCmd,
|
|
745
|
+
syncStateResumeCmd,
|
|
746
|
+
]));
|
|
747
|
+
const syncPoisonRetryCmd = Command.make('retry', {
|
|
748
|
+
syncId: syncIdArg,
|
|
749
|
+
rowKey: poisonRowArg,
|
|
750
|
+
portal: portalOption,
|
|
751
|
+
yes: yesOption,
|
|
752
|
+
...identityOptions,
|
|
753
|
+
}, ({ syncId, rowKey, portal, yes, ...opts }) => runHandler('sync poison retry', { json: opts.json }, poisonRetryBody(syncId, rowKey, portal, yes, opts)));
|
|
754
|
+
const syncPoisonListCmd = Command.make('list', { syncId: syncIdArg, portal: portalOption, ...identityOptions }, ({ syncId, portal, ...opts }) => runHandler('sync poison list', { json: opts.json }, poisonBody(syncId, portal, opts)));
|
|
755
|
+
const syncPoisonCmd = Command.make('poison').pipe(Command.withSubcommands([syncPoisonListCmd, syncPoisonRetryCmd]));
|
|
756
|
+
const syncRunCmd = Command.make('run', {
|
|
757
|
+
syncId: syncIdArg,
|
|
758
|
+
portal: portalOption,
|
|
759
|
+
preview: previewOption,
|
|
760
|
+
limit: limitOption,
|
|
761
|
+
...identityOptions,
|
|
762
|
+
}, ({ syncId, portal, preview, limit, ...opts }) => runHandler('sync run', { json: opts.json }, runBody(syncId, portal, preview, limit, opts)));
|
|
763
|
+
export const syncCmd = Command.make('sync').pipe(Command.withSubcommands([
|
|
764
|
+
syncStatusCmd,
|
|
765
|
+
syncRunsCmd,
|
|
766
|
+
syncLogsCmd,
|
|
767
|
+
syncStateCmd,
|
|
768
|
+
syncPoisonCmd,
|
|
769
|
+
syncRunCmd,
|
|
770
|
+
]));
|
|
771
|
+
//# sourceMappingURL=sync.js.map
|