@funnelsgrove/cli 0.1.4 → 0.1.5
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 +12 -0
- package/dist/cli.js +169 -0
- package/dist/githubOutput.d.ts +65 -0
- package/dist/githubOutput.js +67 -0
- package/dist/githubSyncFlow.d.ts +27 -0
- package/dist/githubSyncFlow.js +75 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,18 @@ fgrove sync up --message 'Update funnel copy'
|
|
|
24
24
|
fgrove publish --env preview
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
GitHub sync workflow:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
fgrove github status --funnel claimbee-general
|
|
31
|
+
fgrove github connect --funnel claimbee-general --account The-Solid-Grove --repo claimbee-funnel
|
|
32
|
+
fgrove github push --funnel claimbee-general
|
|
33
|
+
fgrove github pull --funnel claimbee-general
|
|
34
|
+
fgrove publish --funnel claimbee-general --env preview
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The GitHub commands use the FunnelsGrove API only. When GitHub is connected, `fgrove sync up` pushes the resulting draft to GitHub before returning, and `fgrove publish` waits for the current draft to reach GitHub before publishing. Local `.env*` files remain CLI-local runtime material from `sync down`; they are not sent to GitHub sync.
|
|
38
|
+
|
|
27
39
|
The package also keeps the longer `funnelsgrove` command as a compatibility alias.
|
|
28
40
|
Use `--api-url` or `FUNNELSGROVE_API_URL` for non-production APIs.
|
|
29
41
|
Use `--config` or `FUNNELSGROVE_CONFIG` to keep test credentials separate from the default `~/.funnelsgrove/config.json`.
|
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,8 @@ import { Command } from 'commander';
|
|
|
8
8
|
import { callTrpcProcedure } from './apiClient.js';
|
|
9
9
|
import { clearActiveContext, getDefaultAuthConfigPath, loadActiveContext, loadAuthToken, saveActiveContext, saveAuthToken, } from './authStore.js';
|
|
10
10
|
import { buildSyncManifest, chunkChangedSourceFiles, collectChangedSourceFiles, collectSourceFiles, readSyncManifest, writeSourceFiles, writeSyncManifest, } from './localSync.js';
|
|
11
|
+
import { formatGitHubConnectRows, formatGitHubJobRows, formatGitHubStatusRows, } from './githubOutput.js';
|
|
12
|
+
import { syncGitHubDraftIfConnected } from './githubSyncFlow.js';
|
|
11
13
|
import { isKnownTemplate, KNOWN_TEMPLATE_SLUGS, reskinFunnel } from './reskin.js';
|
|
12
14
|
import { syncTemplateDocs, TEMPLATE_DOCS_DIR } from './templateDocs.js';
|
|
13
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -213,6 +215,34 @@ const printRows = (rows, columns) => {
|
|
|
213
215
|
console.log(columns.map((column) => row[column] || '').join('\t'));
|
|
214
216
|
}
|
|
215
217
|
};
|
|
218
|
+
const printValueRows = (rows) => {
|
|
219
|
+
for (const row of rows) {
|
|
220
|
+
console.log(row.join('\t'));
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const summarizeGitHubSyncResult = (result) => {
|
|
224
|
+
if (result.status === 'not_connected') {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
if (result.status === 'draft_missing') {
|
|
228
|
+
throw new Error('Cannot sync GitHub before publish because this funnel has no draft version.');
|
|
229
|
+
}
|
|
230
|
+
const remoteSha = result.remoteSha ? ` ${result.remoteSha}` : '';
|
|
231
|
+
return result.status === 'completed'
|
|
232
|
+
? `GitHub synced${remoteSha}`
|
|
233
|
+
: `GitHub already synced${remoteSha}`;
|
|
234
|
+
};
|
|
235
|
+
const syncGitHubDraftForCli = async (input) => {
|
|
236
|
+
const summary = summarizeGitHubSyncResult(await syncGitHubDraftIfConnected({
|
|
237
|
+
callApi,
|
|
238
|
+
token: input.token,
|
|
239
|
+
workspaceId: input.workspaceId,
|
|
240
|
+
funnelId: input.funnelId,
|
|
241
|
+
}));
|
|
242
|
+
if (summary) {
|
|
243
|
+
console.log(summary);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
216
246
|
const addExamples = (command, examples) => command.addHelpText('after', `\nExamples:\n${examples.map((example) => ` $ ${example}`).join('\n')}`);
|
|
217
247
|
const program = new Command();
|
|
218
248
|
program
|
|
@@ -452,6 +482,11 @@ addExamples(syncCommand
|
|
|
452
482
|
: null;
|
|
453
483
|
if (changes && changes.files.length === 0 && changes.deletedPaths.length === 0) {
|
|
454
484
|
console.log('No local changes to sync.');
|
|
485
|
+
await syncGitHubDraftForCli({
|
|
486
|
+
token,
|
|
487
|
+
workspaceId: target.workspaceId,
|
|
488
|
+
funnelId: target.funnelId,
|
|
489
|
+
});
|
|
455
490
|
return;
|
|
456
491
|
}
|
|
457
492
|
let result = null;
|
|
@@ -499,6 +534,11 @@ addExamples(syncCommand
|
|
|
499
534
|
funnelId: target.funnelId,
|
|
500
535
|
draftVersionId: result.versionId,
|
|
501
536
|
}));
|
|
537
|
+
await syncGitHubDraftForCli({
|
|
538
|
+
token,
|
|
539
|
+
workspaceId: target.workspaceId,
|
|
540
|
+
funnelId: target.funnelId,
|
|
541
|
+
});
|
|
502
542
|
const deletedSummary = deletedFileCount > 0 ? ` and removed ${deletedFileCount} files` : '';
|
|
503
543
|
console.log(`Synced ${syncedFileCount} files${deletedSummary} to draft v${result.versionSeq} (${result.versionId})`);
|
|
504
544
|
});
|
|
@@ -529,6 +569,11 @@ addExamples(program
|
|
|
529
569
|
if (publishEnv === 'production' && !options.domain?.trim()) {
|
|
530
570
|
throw new Error('--domain is required when publishing production');
|
|
531
571
|
}
|
|
572
|
+
await syncGitHubDraftForCli({
|
|
573
|
+
token,
|
|
574
|
+
workspaceId: target.workspaceId,
|
|
575
|
+
funnelId: target.funnelId,
|
|
576
|
+
});
|
|
532
577
|
const result = await callApi({
|
|
533
578
|
path: 'funnels.publish',
|
|
534
579
|
type: 'mutation',
|
|
@@ -542,6 +587,130 @@ addExamples(program
|
|
|
542
587
|
});
|
|
543
588
|
console.log(`${result.deploymentUrl}\tv${result.publishedVersionSeq}\t${result.publishedVersionId}`);
|
|
544
589
|
});
|
|
590
|
+
const githubCommand = addExamples(program.command('github').description('Manage GitHub funnel sync'), [
|
|
591
|
+
'fgrove github status --funnel claimbee-general',
|
|
592
|
+
'fgrove github connect --funnel claimbee-general --account The-Solid-Grove --repo claimbee-funnel',
|
|
593
|
+
'fgrove github push --funnel claimbee-general',
|
|
594
|
+
'fgrove github pull --funnel claimbee-general',
|
|
595
|
+
]);
|
|
596
|
+
addExamples(githubCommand
|
|
597
|
+
.command('status')
|
|
598
|
+
.description('Show GitHub sync status for a funnel')
|
|
599
|
+
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
600
|
+
.option('--funnel <id-or-slug>', 'Funnel id or slug')
|
|
601
|
+
.option('--dir <path>', 'Local source directory for reading sync manifest', '.'), [
|
|
602
|
+
'fgrove github status --funnel claimbee-general',
|
|
603
|
+
'fgrove github status --dir ./claimbee-funnel',
|
|
604
|
+
])
|
|
605
|
+
.action(async (options) => {
|
|
606
|
+
const token = await readAuthToken();
|
|
607
|
+
const target = await resolveSyncTarget({
|
|
608
|
+
token,
|
|
609
|
+
workspace: options.workspace,
|
|
610
|
+
funnel: options.funnel,
|
|
611
|
+
dir: options.dir,
|
|
612
|
+
});
|
|
613
|
+
const result = await callApi({
|
|
614
|
+
path: 'github.status',
|
|
615
|
+
type: 'query',
|
|
616
|
+
token,
|
|
617
|
+
data: {
|
|
618
|
+
workspaceId: target.workspaceId,
|
|
619
|
+
funnelId: target.funnelId,
|
|
620
|
+
},
|
|
621
|
+
});
|
|
622
|
+
printValueRows(formatGitHubStatusRows(result));
|
|
623
|
+
});
|
|
624
|
+
addExamples(githubCommand
|
|
625
|
+
.command('connect')
|
|
626
|
+
.description('Connect a funnel to a GitHub repository and queue the initial push')
|
|
627
|
+
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
628
|
+
.option('--funnel <id-or-slug>', 'Funnel id or slug')
|
|
629
|
+
.option('--dir <path>', 'Local source directory for reading sync manifest', '.')
|
|
630
|
+
.option('--installation <id>', 'Workspace GitHub installation row id')
|
|
631
|
+
.option('--account <login>', 'GitHub organization or user login')
|
|
632
|
+
.option('--repo <name>', 'GitHub repository name'), [
|
|
633
|
+
'fgrove github connect --funnel claimbee-general --account The-Solid-Grove --repo claimbee-funnel',
|
|
634
|
+
'fgrove github connect --dir ./claimbee-funnel --account The-Solid-Grove --repo claimbee-funnel',
|
|
635
|
+
])
|
|
636
|
+
.action(async (options) => {
|
|
637
|
+
const token = await readAuthToken();
|
|
638
|
+
const target = await resolveSyncTarget({
|
|
639
|
+
token,
|
|
640
|
+
workspace: options.workspace,
|
|
641
|
+
funnel: options.funnel,
|
|
642
|
+
dir: options.dir,
|
|
643
|
+
});
|
|
644
|
+
const result = await callApi({
|
|
645
|
+
path: 'github.connectFunnel',
|
|
646
|
+
type: 'mutation',
|
|
647
|
+
token,
|
|
648
|
+
data: {
|
|
649
|
+
workspaceId: target.workspaceId,
|
|
650
|
+
funnelId: target.funnelId,
|
|
651
|
+
installationId: options.installation,
|
|
652
|
+
accountLogin: options.account,
|
|
653
|
+
repoName: options.repo,
|
|
654
|
+
},
|
|
655
|
+
});
|
|
656
|
+
printValueRows(formatGitHubConnectRows(result));
|
|
657
|
+
});
|
|
658
|
+
addExamples(githubCommand
|
|
659
|
+
.command('push')
|
|
660
|
+
.description('Queue a push of the current FunnelsGrove draft to GitHub')
|
|
661
|
+
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
662
|
+
.option('--funnel <id-or-slug>', 'Funnel id or slug')
|
|
663
|
+
.option('--dir <path>', 'Local source directory for reading sync manifest', '.'), [
|
|
664
|
+
'fgrove github push --funnel claimbee-general',
|
|
665
|
+
'fgrove github push --dir ./claimbee-funnel',
|
|
666
|
+
])
|
|
667
|
+
.action(async (options) => {
|
|
668
|
+
const token = await readAuthToken();
|
|
669
|
+
const target = await resolveSyncTarget({
|
|
670
|
+
token,
|
|
671
|
+
workspace: options.workspace,
|
|
672
|
+
funnel: options.funnel,
|
|
673
|
+
dir: options.dir,
|
|
674
|
+
});
|
|
675
|
+
const result = await callApi({
|
|
676
|
+
path: 'github.pushNow',
|
|
677
|
+
type: 'mutation',
|
|
678
|
+
token,
|
|
679
|
+
data: {
|
|
680
|
+
workspaceId: target.workspaceId,
|
|
681
|
+
funnelId: target.funnelId,
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
printValueRows(formatGitHubJobRows(result));
|
|
685
|
+
});
|
|
686
|
+
addExamples(githubCommand
|
|
687
|
+
.command('pull')
|
|
688
|
+
.description('Queue a pull from GitHub into the FunnelsGrove draft')
|
|
689
|
+
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
690
|
+
.option('--funnel <id-or-slug>', 'Funnel id or slug')
|
|
691
|
+
.option('--dir <path>', 'Local source directory for reading sync manifest', '.'), [
|
|
692
|
+
'fgrove github pull --funnel claimbee-general',
|
|
693
|
+
'fgrove github pull --dir ./claimbee-funnel',
|
|
694
|
+
])
|
|
695
|
+
.action(async (options) => {
|
|
696
|
+
const token = await readAuthToken();
|
|
697
|
+
const target = await resolveSyncTarget({
|
|
698
|
+
token,
|
|
699
|
+
workspace: options.workspace,
|
|
700
|
+
funnel: options.funnel,
|
|
701
|
+
dir: options.dir,
|
|
702
|
+
});
|
|
703
|
+
const result = await callApi({
|
|
704
|
+
path: 'github.pullLatest',
|
|
705
|
+
type: 'mutation',
|
|
706
|
+
token,
|
|
707
|
+
data: {
|
|
708
|
+
workspaceId: target.workspaceId,
|
|
709
|
+
funnelId: target.funnelId,
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
printValueRows(formatGitHubJobRows(result));
|
|
713
|
+
});
|
|
545
714
|
addExamples(program
|
|
546
715
|
.command('docs')
|
|
547
716
|
.description('Install or refresh local funnel editing docs in a funnel directory')
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
type Row = string[];
|
|
2
|
+
export type GitHubStatusResponse = {
|
|
3
|
+
connected: boolean;
|
|
4
|
+
installUrl: string | null;
|
|
5
|
+
account: {
|
|
6
|
+
id: string;
|
|
7
|
+
accountLogin: string;
|
|
8
|
+
accountType: string;
|
|
9
|
+
} | null;
|
|
10
|
+
installations: Array<{
|
|
11
|
+
id: string;
|
|
12
|
+
githubInstallationId: number;
|
|
13
|
+
accountLogin: string;
|
|
14
|
+
accountType: string;
|
|
15
|
+
}>;
|
|
16
|
+
connection: {
|
|
17
|
+
id: string;
|
|
18
|
+
repoOwner: string;
|
|
19
|
+
repoName: string;
|
|
20
|
+
repoUrl: string;
|
|
21
|
+
defaultBranch: string;
|
|
22
|
+
status: string;
|
|
23
|
+
lastSyncedRemoteSha: string | null;
|
|
24
|
+
lastSyncedVersionId: string | null;
|
|
25
|
+
lastSyncedVersionSeq: number | null;
|
|
26
|
+
} | null;
|
|
27
|
+
draftVersionId: string | null;
|
|
28
|
+
draftVersionSeq: number | null;
|
|
29
|
+
publishedVersionId: string | null;
|
|
30
|
+
publishedVersionSeq: number | null;
|
|
31
|
+
syncJobs: Array<{
|
|
32
|
+
id: string;
|
|
33
|
+
direction: string;
|
|
34
|
+
status: string;
|
|
35
|
+
triggerSource: string;
|
|
36
|
+
errorMessage: string | null;
|
|
37
|
+
targetRemoteSha: string | null;
|
|
38
|
+
appliedVersionId: string | null;
|
|
39
|
+
appliedVersionSeq: number | null;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
completedAt: string | null;
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
export type GitHubConnectResponse = {
|
|
45
|
+
connection: {
|
|
46
|
+
repo_owner: string;
|
|
47
|
+
repo_name: string;
|
|
48
|
+
repo_url: string;
|
|
49
|
+
default_branch: string;
|
|
50
|
+
status: string;
|
|
51
|
+
};
|
|
52
|
+
syncJob: {
|
|
53
|
+
id: string;
|
|
54
|
+
direction: string;
|
|
55
|
+
status: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export type GitHubJobResponse = {
|
|
59
|
+
status: string;
|
|
60
|
+
jobId: string | null;
|
|
61
|
+
};
|
|
62
|
+
export declare const formatGitHubStatusRows: (status: GitHubStatusResponse) => Row[];
|
|
63
|
+
export declare const formatGitHubConnectRows: (result: GitHubConnectResponse) => Row[];
|
|
64
|
+
export declare const formatGitHubJobRows: (result: GitHubJobResponse) => Row[];
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const formatVersion = (seq, id) => {
|
|
2
|
+
if (!id) {
|
|
3
|
+
return ['Not published'];
|
|
4
|
+
}
|
|
5
|
+
return [seq ? `v${seq}` : 'version', id];
|
|
6
|
+
};
|
|
7
|
+
export const formatGitHubStatusRows = (status) => {
|
|
8
|
+
const rows = [['connected', String(status.connected)]];
|
|
9
|
+
if (status.account) {
|
|
10
|
+
rows.push(['account', status.account.accountLogin, status.account.accountType]);
|
|
11
|
+
}
|
|
12
|
+
if (status.connection) {
|
|
13
|
+
rows.push([
|
|
14
|
+
'repo',
|
|
15
|
+
`${status.connection.repoOwner}/${status.connection.repoName}`,
|
|
16
|
+
status.connection.repoUrl,
|
|
17
|
+
]);
|
|
18
|
+
rows.push(['branch', status.connection.defaultBranch]);
|
|
19
|
+
rows.push(['status', status.connection.status]);
|
|
20
|
+
if (status.connection.lastSyncedRemoteSha) {
|
|
21
|
+
rows.push(['lastRemoteSha', status.connection.lastSyncedRemoteSha]);
|
|
22
|
+
}
|
|
23
|
+
if (status.connection.lastSyncedVersionId) {
|
|
24
|
+
rows.push([
|
|
25
|
+
'lastVersion',
|
|
26
|
+
...formatVersion(status.connection.lastSyncedVersionSeq, status.connection.lastSyncedVersionId),
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (status.installUrl) {
|
|
31
|
+
rows.push(['installUrl', status.installUrl]);
|
|
32
|
+
}
|
|
33
|
+
if (status.draftVersionId) {
|
|
34
|
+
rows.push(['draft', ...formatVersion(status.draftVersionSeq, status.draftVersionId)]);
|
|
35
|
+
}
|
|
36
|
+
rows.push(['published', ...formatVersion(status.publishedVersionSeq, status.publishedVersionId)]);
|
|
37
|
+
for (const job of status.syncJobs) {
|
|
38
|
+
rows.push([
|
|
39
|
+
'job',
|
|
40
|
+
job.id,
|
|
41
|
+
job.direction,
|
|
42
|
+
job.status,
|
|
43
|
+
job.triggerSource,
|
|
44
|
+
job.targetRemoteSha || '',
|
|
45
|
+
job.appliedVersionSeq ? `v${job.appliedVersionSeq}` : '',
|
|
46
|
+
job.errorMessage || '',
|
|
47
|
+
].filter((value, index, values) => value || index < values.length - 1));
|
|
48
|
+
}
|
|
49
|
+
return rows;
|
|
50
|
+
};
|
|
51
|
+
export const formatGitHubConnectRows = (result) => [
|
|
52
|
+
[
|
|
53
|
+
'connected',
|
|
54
|
+
`${result.connection.repo_owner}/${result.connection.repo_name}`,
|
|
55
|
+
result.connection.repo_url,
|
|
56
|
+
],
|
|
57
|
+
['branch', result.connection.default_branch],
|
|
58
|
+
['status', result.connection.status],
|
|
59
|
+
['syncJob', result.syncJob.id, result.syncJob.direction, result.syncJob.status],
|
|
60
|
+
];
|
|
61
|
+
export const formatGitHubJobRows = (result) => {
|
|
62
|
+
const rows = [['status', result.status]];
|
|
63
|
+
if (result.jobId) {
|
|
64
|
+
rows.push(['job', result.jobId]);
|
|
65
|
+
}
|
|
66
|
+
return rows;
|
|
67
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type ApiCallInput = {
|
|
2
|
+
path: string;
|
|
3
|
+
type: 'query' | 'mutation';
|
|
4
|
+
data?: unknown;
|
|
5
|
+
token?: string | null;
|
|
6
|
+
};
|
|
7
|
+
export type GitHubFlowCallApi = <T>(input: ApiCallInput) => Promise<T>;
|
|
8
|
+
export type GitHubSyncFlowResult = {
|
|
9
|
+
status: 'not_connected';
|
|
10
|
+
} | {
|
|
11
|
+
status: 'draft_missing';
|
|
12
|
+
} | {
|
|
13
|
+
status: 'completed' | 'skipped';
|
|
14
|
+
jobId: string;
|
|
15
|
+
remoteSha: string | null;
|
|
16
|
+
versionId: string | null;
|
|
17
|
+
};
|
|
18
|
+
export declare const syncGitHubDraftIfConnected: (input: {
|
|
19
|
+
callApi: GitHubFlowCallApi;
|
|
20
|
+
token: string;
|
|
21
|
+
workspaceId: string;
|
|
22
|
+
funnelId: string;
|
|
23
|
+
pollIntervalMs?: number;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
sleep?: (ms: number) => Promise<void>;
|
|
26
|
+
}) => Promise<GitHubSyncFlowResult>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const TERMINAL_JOB_STATUSES = new Set(['completed', 'skipped', 'failed']);
|
|
2
|
+
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
4
|
+
const sleepDefault = async (ms) => {
|
|
5
|
+
await new Promise((resolve) => {
|
|
6
|
+
setTimeout(resolve, ms);
|
|
7
|
+
});
|
|
8
|
+
};
|
|
9
|
+
const findJob = (status, jobId) => {
|
|
10
|
+
return status.syncJobs.find((job) => job.id === jobId) || null;
|
|
11
|
+
};
|
|
12
|
+
const assertSuccessfulTerminalJob = (job) => {
|
|
13
|
+
if (job.status === 'failed' || (job.status === 'skipped' && job.errorMessage)) {
|
|
14
|
+
throw new Error(job.errorMessage || 'GitHub sync failed.');
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export const syncGitHubDraftIfConnected = async (input) => {
|
|
18
|
+
const status = await input.callApi({
|
|
19
|
+
path: 'github.status',
|
|
20
|
+
type: 'query',
|
|
21
|
+
token: input.token,
|
|
22
|
+
data: {
|
|
23
|
+
workspaceId: input.workspaceId,
|
|
24
|
+
funnelId: input.funnelId,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
if (!status.connection || status.connection.status === 'disconnected') {
|
|
28
|
+
return { status: 'not_connected' };
|
|
29
|
+
}
|
|
30
|
+
const pushResult = await input.callApi({
|
|
31
|
+
path: 'github.pushNow',
|
|
32
|
+
type: 'mutation',
|
|
33
|
+
token: input.token,
|
|
34
|
+
data: {
|
|
35
|
+
workspaceId: input.workspaceId,
|
|
36
|
+
funnelId: input.funnelId,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
if (pushResult.status === 'not_connected') {
|
|
40
|
+
return { status: 'not_connected' };
|
|
41
|
+
}
|
|
42
|
+
if (pushResult.status === 'draft_missing') {
|
|
43
|
+
return { status: 'draft_missing' };
|
|
44
|
+
}
|
|
45
|
+
if (!pushResult.jobId) {
|
|
46
|
+
throw new Error(`GitHub sync did not return a job id (status: ${pushResult.status}).`);
|
|
47
|
+
}
|
|
48
|
+
const pollIntervalMs = input.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
49
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
50
|
+
const sleep = input.sleep || sleepDefault;
|
|
51
|
+
const deadline = Date.now() + timeoutMs;
|
|
52
|
+
while (Date.now() <= deadline) {
|
|
53
|
+
const nextStatus = await input.callApi({
|
|
54
|
+
path: 'github.status',
|
|
55
|
+
type: 'query',
|
|
56
|
+
token: input.token,
|
|
57
|
+
data: {
|
|
58
|
+
workspaceId: input.workspaceId,
|
|
59
|
+
funnelId: input.funnelId,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const job = findJob(nextStatus, pushResult.jobId);
|
|
63
|
+
if (job && TERMINAL_JOB_STATUSES.has(job.status)) {
|
|
64
|
+
assertSuccessfulTerminalJob(job);
|
|
65
|
+
return {
|
|
66
|
+
status: job.status,
|
|
67
|
+
jobId: job.id,
|
|
68
|
+
remoteSha: job.targetRemoteSha,
|
|
69
|
+
versionId: job.appliedVersionId,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
await sleep(pollIntervalMs);
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`Timed out waiting for GitHub sync job ${pushResult.jobId}.`);
|
|
75
|
+
};
|