@loopstack/github-oauth-example 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,17 +2,13 @@ import { z } from 'zod';
2
2
  import {
3
3
  BaseWorkflow,
4
4
  CallbackSchema,
5
- Final,
6
5
  Guard,
7
- Initial,
8
- InjectTool,
9
- InjectWorkflow,
10
6
  LinkDocument,
11
7
  MarkdownDocument,
12
- ToolResult,
13
8
  Transition,
14
9
  Workflow,
15
10
  } from '@loopstack/common';
11
+ import type { LoopstackContext } from '@loopstack/common';
16
12
  import {
17
13
  GitHubGetAuthenticatedUserTool,
18
14
  GitHubGetRepoTool,
@@ -26,17 +22,13 @@ import {
26
22
  } from '@loopstack/github-module';
27
23
  import { OAuthWorkflow } from '@loopstack/oauth-module';
28
24
 
29
- interface GitHubUserResult {
30
- error?: string;
25
+ interface GitHubReposOverviewState {
26
+ owner: string;
27
+ repo: string;
28
+ requiresAuthentication?: boolean;
31
29
  user?: { login: string; name: string | null; htmlUrl: string; publicRepos: number };
32
- }
33
-
34
- interface GitHubOrgsResult {
35
- orgs: Array<{ login: string; description: string | null }>;
36
- }
37
-
38
- interface GitHubRepoResult {
39
- repo: {
30
+ orgs?: Array<{ login: string; description: string | null }>;
31
+ repoDetails?: {
40
32
  fullName: string;
41
33
  description: string | null;
42
34
  language: string | null;
@@ -46,18 +38,9 @@ interface GitHubRepoResult {
46
38
  defaultBranch: string;
47
39
  htmlUrl: string;
48
40
  };
49
- }
50
-
51
- interface GitHubBranchesResult {
52
- branches: Array<{ name: string; protected: boolean }>;
53
- }
54
-
55
- interface GitHubIssuesResult {
56
- issues: Array<{ number: number; title: string; state: string; user: string; htmlUrl: string }>;
57
- }
58
-
59
- interface GitHubPullRequestsResult {
60
- pullRequests: Array<{
41
+ branches?: Array<{ name: string; protected: boolean }>;
42
+ issues?: Array<{ number: number; title: string; state: string; user: string; htmlUrl: string }>;
43
+ pullRequests?: Array<{
61
44
  number: number;
62
45
  title: string;
63
46
  state: string;
@@ -65,30 +48,22 @@ interface GitHubPullRequestsResult {
65
48
  draft: boolean;
66
49
  htmlUrl: string;
67
50
  }>;
68
- }
69
-
70
- interface GitHubDirectoryResult {
71
- entries: Array<{ name: string; type: string; path: string }>;
72
- }
73
-
74
- interface GitHubWorkflowRunsResult {
75
- totalCount: number;
76
- runs: Array<{
51
+ directoryEntries?: Array<{ name: string; type: string; path: string }>;
52
+ workflowRuns?: Array<{
77
53
  id: number;
78
54
  name: string;
79
55
  status: string;
80
56
  conclusion: string | null;
81
57
  htmlUrl: string;
82
58
  }>;
83
- }
84
-
85
- interface GitHubSearchCodeResult {
86
- totalCount: number;
87
- results: Array<{ name: string; path: string; repository: string }>;
59
+ searchResults?: Array<{ name: string; path: string; repository: string }>;
88
60
  }
89
61
 
90
62
  @Workflow({
91
- uiConfig: __dirname + '/github-repos-overview.ui.yaml',
63
+ title: 'GitHub Repository Overview',
64
+ description:
65
+ 'Comprehensive GitHub example that exercises every GitHub tool.\nFetches user info, repository details, issues, pull requests, branches,\ndirectory contents, workflow runs, and search results for a given repository.\nIf not authenticated, launches the OAuth sub-workflow and retries.',
66
+ name: 'github_repos_overview',
92
67
  schema: z
93
68
  .object({
94
69
  owner: z.string().default('octocat'),
@@ -96,75 +71,50 @@ interface GitHubSearchCodeResult {
96
71
  })
97
72
  .strict(),
98
73
  })
99
- export class GitHubReposOverviewWorkflow extends BaseWorkflow<{ owner: string; repo: string }> {
100
- // GitHub tools
101
- @InjectTool() private gitHubGetAuthenticatedUser: GitHubGetAuthenticatedUserTool;
102
- @InjectTool() private gitHubListUserOrgs: GitHubListUserOrgsTool;
103
- @InjectTool() private gitHubGetRepo: GitHubGetRepoTool;
104
- @InjectTool() private gitHubListBranches: GitHubListBranchesTool;
105
- @InjectTool() private gitHubListIssues: GitHubListIssuesTool;
106
- @InjectTool() private gitHubListPullRequests: GitHubListPullRequestsTool;
107
- @InjectTool() private gitHubListDirectory: GitHubListDirectoryTool;
108
- @InjectTool() private gitHubListWorkflowRuns: GitHubListWorkflowRunsTool;
109
- @InjectTool() private gitHubSearchCode: GitHubSearchCodeTool;
110
-
111
- @InjectWorkflow() oAuth: OAuthWorkflow;
74
+ export class GitHubReposOverviewWorkflow extends BaseWorkflow<
75
+ { owner: string; repo: string },
76
+ GitHubReposOverviewState
77
+ > {
78
+ constructor(
79
+ private readonly gitHubGetAuthenticatedUser: GitHubGetAuthenticatedUserTool,
80
+ private readonly gitHubListUserOrgs: GitHubListUserOrgsTool,
81
+ private readonly gitHubGetRepo: GitHubGetRepoTool,
82
+ private readonly gitHubListBranches: GitHubListBranchesTool,
83
+ private readonly gitHubListIssues: GitHubListIssuesTool,
84
+ private readonly gitHubListPullRequests: GitHubListPullRequestsTool,
85
+ private readonly gitHubListDirectory: GitHubListDirectoryTool,
86
+ private readonly gitHubListWorkflowRuns: GitHubListWorkflowRunsTool,
87
+ private readonly gitHubSearchCode: GitHubSearchCodeTool,
88
+ private readonly oAuthWorkflow: OAuthWorkflow,
89
+ ) {
90
+ super();
91
+ }
112
92
 
113
- owner!: string;
114
- repo!: string;
115
- requiresAuthentication?: boolean;
116
- user?: { login: string; name: string | null; htmlUrl: string; publicRepos: number };
117
- orgs?: Array<{ login: string; description: string | null }>;
118
- repoDetails?: {
119
- fullName: string;
120
- description: string | null;
121
- language: string | null;
122
- stars: number;
123
- forks: number;
124
- openIssues: number;
125
- defaultBranch: string;
126
- htmlUrl: string;
127
- };
128
- branches?: Array<{ name: string; protected: boolean }>;
129
- issues?: Array<{ number: number; title: string; state: string; user: string; htmlUrl: string }>;
130
- pullRequests?: Array<{
131
- number: number;
132
- title: string;
133
- state: string;
134
- user: string;
135
- draft: boolean;
136
- htmlUrl: string;
137
- }>;
138
- directoryEntries?: Array<{ name: string; type: string; path: string }>;
139
- workflowRuns?: Array<{
140
- id: number;
141
- name: string;
142
- status: string;
143
- conclusion: string | null;
144
- htmlUrl: string;
145
- }>;
146
- searchResults?: Array<{ name: string; path: string; repository: string }>;
147
93
  // --- Step 1: Fetch authenticated user ---
148
94
 
149
- @Initial({ to: 'user_fetched' })
150
- async fetchUser(args: { owner: string; repo: string }) {
151
- this.owner = args.owner;
152
- this.repo = args.repo;
153
- const result: ToolResult<GitHubUserResult> = await this.gitHubGetAuthenticatedUser.call();
154
- this.requiresAuthentication = result.data!.error === 'unauthorized';
155
- this.user = result.data!.user;
95
+ @Transition({ to: 'user_fetched' })
96
+ async fetchUser(state: GitHubReposOverviewState, ctx: LoopstackContext): Promise<GitHubReposOverviewState> {
97
+ const args = ctx.args as { owner: string; repo: string };
98
+ const result = await this.gitHubGetAuthenticatedUser.call();
99
+ return {
100
+ ...state,
101
+ owner: args.owner,
102
+ repo: args.repo,
103
+ requiresAuthentication: result.data!.error === 'unauthorized',
104
+ user: result.data!.user,
105
+ };
156
106
  }
157
107
 
158
108
  // If unauthorized -> launch OAuth
159
109
  @Transition({ from: 'user_fetched', to: 'awaiting_auth', priority: 10 })
160
110
  @Guard('needsAuth')
161
- async authRequired() {
162
- const result = await this.oAuth.run(
111
+ async authRequired(state: GitHubReposOverviewState): Promise<GitHubReposOverviewState> {
112
+ const result = await this.oAuthWorkflow.run(
163
113
  { provider: 'github', scopes: ['repo', 'read:org', 'workflow'] },
164
- { alias: 'oAuth', callback: { transition: 'authCompleted' } },
114
+ { callback: { transition: 'authCompleted' } },
165
115
  );
166
116
 
167
- await this.repository.save(
117
+ await this.documentStore.save(
168
118
  LinkDocument,
169
119
  {
170
120
  label: 'GitHub authentication required',
@@ -174,10 +124,11 @@ export class GitHubReposOverviewWorkflow extends BaseWorkflow<{ owner: string; r
174
124
  },
175
125
  { id: `link_${result.workflowId}` },
176
126
  );
127
+ return state;
177
128
  }
178
129
 
179
- needsAuth(): boolean {
180
- return !!this.requiresAuthentication;
130
+ needsAuth(state: GitHubReposOverviewState): boolean {
131
+ return !!state.requiresAuthentication;
181
132
  }
182
133
 
183
134
  // Auth completed -> retry from start
@@ -187,8 +138,11 @@ export class GitHubReposOverviewWorkflow extends BaseWorkflow<{ owner: string; r
187
138
  wait: true,
188
139
  schema: CallbackSchema,
189
140
  })
190
- async authCompleted(payload: { workflowId: string }) {
191
- await this.repository.save(
141
+ async authCompleted(
142
+ state: GitHubReposOverviewState,
143
+ payload: { workflowId: string },
144
+ ): Promise<GitHubReposOverviewState> {
145
+ await this.documentStore.save(
192
146
  LinkDocument,
193
147
  {
194
148
  status: 'success',
@@ -199,99 +153,98 @@ export class GitHubReposOverviewWorkflow extends BaseWorkflow<{ owner: string; r
199
153
  },
200
154
  { id: `link_${payload.workflowId}` },
201
155
  );
156
+ return state;
202
157
  }
203
158
 
204
159
  // --- Step 2: Fetch user orgs ---
205
160
 
206
161
  @Transition({ from: 'user_fetched', to: 'orgs_fetched' })
207
- async fetchOrgs() {
208
- const result: ToolResult<GitHubOrgsResult> = await this.gitHubListUserOrgs.call({ perPage: 10 });
209
- this.orgs = result.data!.orgs;
162
+ async fetchOrgs(state: GitHubReposOverviewState): Promise<GitHubReposOverviewState> {
163
+ const result = await this.gitHubListUserOrgs.call({ perPage: 10 });
164
+ return { ...state, orgs: result.data!.orgs };
210
165
  }
211
166
 
212
167
  // --- Step 3: Fetch repo details and branches ---
213
168
 
214
169
  @Transition({ from: 'orgs_fetched', to: 'repo_fetched' })
215
- async fetchRepoDetails() {
216
- const repoResult: ToolResult<GitHubRepoResult> = await this.gitHubGetRepo.call({
217
- owner: this.owner,
218
- repo: this.repo,
170
+ async fetchRepoDetails(state: GitHubReposOverviewState): Promise<GitHubReposOverviewState> {
171
+ const repoResult = await this.gitHubGetRepo.call({
172
+ owner: state.owner,
173
+ repo: state.repo,
219
174
  });
220
- this.repoDetails = repoResult.data!.repo;
221
175
 
222
- const branchesResult: ToolResult<GitHubBranchesResult> = await this.gitHubListBranches.call({
223
- owner: this.owner,
224
- repo: this.repo,
176
+ const branchesResult = await this.gitHubListBranches.call({
177
+ owner: state.owner,
178
+ repo: state.repo,
225
179
  });
226
- this.branches = branchesResult.data!.branches;
180
+ return { ...state, repoDetails: repoResult.data!.repo, branches: branchesResult.data!.branches };
227
181
  }
228
182
 
229
183
  // --- Step 4: Fetch issues and PRs ---
230
184
 
231
185
  @Transition({ from: 'repo_fetched', to: 'issues_prs_fetched' })
232
- async fetchIssuesPrs() {
233
- const issuesResult: ToolResult<GitHubIssuesResult> = await this.gitHubListIssues.call({
234
- owner: this.owner,
235
- repo: this.repo,
186
+ async fetchIssuesPrs(state: GitHubReposOverviewState): Promise<GitHubReposOverviewState> {
187
+ const issuesResult = await this.gitHubListIssues.call({
188
+ owner: state.owner,
189
+ repo: state.repo,
236
190
  state: 'open',
237
191
  perPage: 10,
238
192
  });
239
- this.issues = issuesResult.data!.issues;
240
193
 
241
- const prsResult: ToolResult<GitHubPullRequestsResult> = await this.gitHubListPullRequests.call({
242
- owner: this.owner,
243
- repo: this.repo,
194
+ const prsResult = await this.gitHubListPullRequests.call({
195
+ owner: state.owner,
196
+ repo: state.repo,
244
197
  state: 'open',
245
198
  perPage: 10,
246
199
  });
247
- this.pullRequests = prsResult.data!.pullRequests;
200
+ return { ...state, issues: issuesResult.data!.issues, pullRequests: prsResult.data!.pullRequests };
248
201
  }
249
202
 
250
203
  // --- Step 5: Fetch directory listing and workflow runs ---
251
204
 
252
205
  @Transition({ from: 'issues_prs_fetched', to: 'content_actions_fetched' })
253
- async fetchContentActions() {
254
- const dirResult: ToolResult<GitHubDirectoryResult> = await this.gitHubListDirectory.call({
255
- owner: this.owner,
256
- repo: this.repo,
206
+ async fetchContentActions(state: GitHubReposOverviewState): Promise<GitHubReposOverviewState> {
207
+ const dirResult = await this.gitHubListDirectory.call({
208
+ owner: state.owner,
209
+ repo: state.repo,
257
210
  });
258
- this.directoryEntries = dirResult.data!.entries;
259
211
 
260
- const runsResult: ToolResult<GitHubWorkflowRunsResult> = await this.gitHubListWorkflowRuns.call({
261
- owner: this.owner,
262
- repo: this.repo,
212
+ const runsResult = await this.gitHubListWorkflowRuns.call({
213
+ owner: state.owner,
214
+ repo: state.repo,
263
215
  perPage: 5,
264
216
  });
265
- this.workflowRuns = runsResult.data!.runs;
217
+ return { ...state, directoryEntries: dirResult.data!.entries, workflowRuns: runsResult.data!.runs };
266
218
  }
267
219
 
268
220
  // --- Step 6: Search code in the repo ---
269
221
 
270
222
  @Transition({ from: 'content_actions_fetched', to: 'search_done' })
271
- async fetchSearch() {
272
- const result: ToolResult<GitHubSearchCodeResult> = await this.gitHubSearchCode.call({
273
- query: `repo:${this.owner}/${this.repo}`,
223
+ async fetchSearch(state: GitHubReposOverviewState): Promise<GitHubReposOverviewState> {
224
+ const result = await this.gitHubSearchCode.call({
225
+ query: `repo:${state.owner}/${state.repo}`,
274
226
  perPage: 5,
275
227
  });
276
- this.searchResults = result.data!.results;
228
+ return { ...state, searchResults: result.data!.results };
277
229
  }
278
230
 
279
231
  // --- Display all results ---
280
232
 
281
- @Final({ from: 'search_done' })
282
- async displayResults() {
283
- await this.repository.save(MarkdownDocument, {
233
+ @Transition({ from: 'search_done', to: 'end' })
234
+ async displayResults(state: GitHubReposOverviewState): Promise<unknown> {
235
+ await this.documentStore.save(MarkdownDocument, {
284
236
  markdown: this.render(__dirname + '/templates/repoOverview.md', {
285
- user: this.user,
286
- orgs: this.orgs,
287
- repo: this.repoDetails,
288
- branches: this.branches,
289
- issues: this.issues,
290
- pullRequests: this.pullRequests,
291
- directoryEntries: this.directoryEntries,
292
- workflowRuns: this.workflowRuns,
293
- searchResults: this.searchResults,
237
+ user: state.user,
238
+ orgs: state.orgs,
239
+ repo: state.repoDetails,
240
+ branches: state.branches,
241
+ issues: state.issues,
242
+ pullRequests: state.pullRequests,
243
+ directoryEntries: state.directoryEntries,
244
+ workflowRuns: state.workflowRuns,
245
+ searchResults: state.searchResults,
294
246
  }),
295
247
  });
248
+ return {};
296
249
  }
297
250
  }
@@ -1,17 +0,0 @@
1
- title: 'GitHub Repository Overview'
2
-
3
- description: |
4
- Comprehensive GitHub example that exercises every GitHub tool.
5
- Fetches user info, repository details, issues, pull requests, branches,
6
- directory contents, workflow runs, and search results for a given repository.
7
- If not authenticated, launches the OAuth sub-workflow and retries.
8
-
9
- ui:
10
- form:
11
- properties:
12
- owner:
13
- title: 'Repository Owner'
14
- placeholder: 'octocat'
15
- repo:
16
- title: 'Repository Name'
17
- placeholder: 'Hello-World'
@@ -1,17 +0,0 @@
1
- title: 'GitHub Repository Overview'
2
-
3
- description: |
4
- Comprehensive GitHub example that exercises every GitHub tool.
5
- Fetches user info, repository details, issues, pull requests, branches,
6
- directory contents, workflow runs, and search results for a given repository.
7
- If not authenticated, launches the OAuth sub-workflow and retries.
8
-
9
- ui:
10
- form:
11
- properties:
12
- owner:
13
- title: 'Repository Owner'
14
- placeholder: 'octocat'
15
- repo:
16
- title: 'Repository Name'
17
- placeholder: 'Hello-World'