@volter/twin-github 0.1.2 → 2.0.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.
Files changed (215) hide show
  1. package/README.md +88 -36
  2. package/client/github-mirror.css +277 -319
  3. package/client/github-mirror.d.ts +418 -0
  4. package/client/github-mirror.js +485 -0
  5. package/client/github-mirror.tsx +153 -357
  6. package/client/pulls-rest.ts +159 -0
  7. package/client/pulls-workspace.tsx +841 -0
  8. package/dist/client/github-mirror.bundle.js +239 -0
  9. package/dist/client/github-mirror.css +916 -0
  10. package/dist/client/github-mirror.d.ts +418 -0
  11. package/dist/client/github-mirror.js +485 -0
  12. package/dist/client/github-mirror.tsx +1315 -0
  13. package/dist/client/pulls-rest.d.ts +42 -0
  14. package/dist/client/pulls-rest.js +140 -0
  15. package/dist/client/pulls-rest.ts +159 -0
  16. package/dist/client/pulls-workspace.bundle.js +22 -0
  17. package/dist/client/pulls-workspace.d.ts +114 -0
  18. package/dist/client/pulls-workspace.js +418 -0
  19. package/dist/client/pulls-workspace.tsx +841 -0
  20. package/dist/src/cli.d.ts +2 -0
  21. package/dist/src/cli.js +40 -0
  22. package/dist/src/generated/graphql-sdl.gen.json +1 -0
  23. package/dist/src/generated/graphql.gen.json +1 -0
  24. package/dist/src/generated/surface.gen.json +1 -0
  25. package/dist/src/generated/ui.gen.json +1 -0
  26. package/dist/src/github-budget.d.ts +69 -0
  27. package/dist/src/github-budget.js +172 -0
  28. package/dist/src/github-capabilities.d.ts +5 -0
  29. package/dist/src/github-capabilities.js +4468 -0
  30. package/dist/src/github-conformance.d.ts +43 -0
  31. package/dist/src/github-conformance.js +76 -0
  32. package/dist/src/github-connector.d.ts +307 -0
  33. package/dist/src/github-connector.js +1398 -0
  34. package/dist/src/github-events.d.ts +41 -0
  35. package/dist/src/github-events.js +232 -0
  36. package/dist/src/github-git-http.d.ts +49 -0
  37. package/dist/src/github-git-http.js +185 -0
  38. package/dist/src/github-git-plane.d.ts +114 -0
  39. package/dist/src/github-git-plane.js +407 -0
  40. package/dist/src/github-mirror-state.d.ts +2 -0
  41. package/dist/src/github-mirror-state.js +335 -0
  42. package/dist/src/github-mirror-ui.d.ts +20 -0
  43. package/dist/src/github-mirror-ui.js +101 -0
  44. package/dist/src/github-server.d.ts +14 -0
  45. package/dist/src/github-server.js +240 -0
  46. package/dist/src/github-shared.d.ts +12 -0
  47. package/dist/src/github-shared.js +21 -0
  48. package/dist/src/github-twin.d.ts +1411 -0
  49. package/dist/src/github-twin.js +4084 -0
  50. package/dist/src/github-ui-conformance.d.ts +4 -0
  51. package/dist/src/github-ui-conformance.js +105 -0
  52. package/dist/src/github-ui-structure.d.ts +18 -0
  53. package/dist/src/github-ui-structure.js +251 -0
  54. package/dist/src/graphql-wire.d.ts +22 -0
  55. package/dist/src/graphql-wire.js +89 -0
  56. package/dist/src/index.d.ts +17 -0
  57. package/dist/src/index.js +104 -0
  58. package/dist/src/manifest.d.ts +8 -0
  59. package/dist/src/manifest.js +597 -0
  60. package/dist/src/npm-registry.d.ts +7 -0
  61. package/dist/src/npm-registry.js +47 -0
  62. package/dist/src/screens/app-installation.d.ts +3 -0
  63. package/dist/src/screens/app-installation.js +166 -0
  64. package/dist/src/screens/app-manifest.d.ts +3 -0
  65. package/dist/src/screens/app-manifest.js +81 -0
  66. package/dist/src/screens/oauth.d.ts +15 -0
  67. package/dist/src/screens/oauth.js +257 -0
  68. package/dist/src/screens/session.d.ts +8 -0
  69. package/dist/src/screens/session.js +170 -0
  70. package/dist/src/semantics/actions.d.ts +2 -0
  71. package/dist/src/semantics/actions.js +413 -0
  72. package/dist/src/semantics/activity.d.ts +4 -0
  73. package/dist/src/semantics/activity.js +161 -0
  74. package/dist/src/semantics/apps.d.ts +2 -0
  75. package/dist/src/semantics/apps.js +144 -0
  76. package/dist/src/semantics/branches.d.ts +2 -0
  77. package/dist/src/semantics/branches.js +136 -0
  78. package/dist/src/semantics/checks.d.ts +2 -0
  79. package/dist/src/semantics/checks.js +176 -0
  80. package/dist/src/semantics/code-scanning-upload.d.ts +2 -0
  81. package/dist/src/semantics/code-scanning-upload.js +97 -0
  82. package/dist/src/semantics/codespaces.d.ts +2 -0
  83. package/dist/src/semantics/codespaces.js +58 -0
  84. package/dist/src/semantics/commits.d.ts +2 -0
  85. package/dist/src/semantics/commits.js +109 -0
  86. package/dist/src/semantics/contents.d.ts +2 -0
  87. package/dist/src/semantics/contents.js +131 -0
  88. package/dist/src/semantics/deployments.d.ts +2 -0
  89. package/dist/src/semantics/deployments.js +127 -0
  90. package/dist/src/semantics/gists.d.ts +2 -0
  91. package/dist/src/semantics/gists.js +86 -0
  92. package/dist/src/semantics/git.d.ts +2 -0
  93. package/dist/src/semantics/git.js +235 -0
  94. package/dist/src/semantics/graphql.d.ts +7 -0
  95. package/dist/src/semantics/graphql.js +512 -0
  96. package/dist/src/semantics/index.d.ts +5 -0
  97. package/dist/src/semantics/index.js +62 -0
  98. package/dist/src/semantics/issues.d.ts +2 -0
  99. package/dist/src/semantics/issues.js +456 -0
  100. package/dist/src/semantics/keys.d.ts +2 -0
  101. package/dist/src/semantics/keys.js +66 -0
  102. package/dist/src/semantics/labels.d.ts +2 -0
  103. package/dist/src/semantics/labels.js +100 -0
  104. package/dist/src/semantics/meta.d.ts +12 -0
  105. package/dist/src/semantics/meta.js +144 -0
  106. package/dist/src/semantics/notifications.d.ts +4 -0
  107. package/dist/src/semantics/notifications.js +62 -0
  108. package/dist/src/semantics/orgs.d.ts +2 -0
  109. package/dist/src/semantics/orgs.js +420 -0
  110. package/dist/src/semantics/packages.d.ts +2 -0
  111. package/dist/src/semantics/packages.js +55 -0
  112. package/dist/src/semantics/pages.d.ts +2 -0
  113. package/dist/src/semantics/pages.js +176 -0
  114. package/dist/src/semantics/projects.d.ts +2 -0
  115. package/dist/src/semantics/projects.js +239 -0
  116. package/dist/src/semantics/pulls.d.ts +2 -0
  117. package/dist/src/semantics/pulls.js +462 -0
  118. package/dist/src/semantics/push-reactions.d.ts +101 -0
  119. package/dist/src/semantics/push-reactions.js +513 -0
  120. package/dist/src/semantics/releases.d.ts +19 -0
  121. package/dist/src/semantics/releases.js +231 -0
  122. package/dist/src/semantics/repo-invitations.d.ts +2 -0
  123. package/dist/src/semantics/repo-invitations.js +98 -0
  124. package/dist/src/semantics/repos.d.ts +17 -0
  125. package/dist/src/semantics/repos.js +390 -0
  126. package/dist/src/semantics/rulesets.d.ts +2 -0
  127. package/dist/src/semantics/rulesets.js +111 -0
  128. package/dist/src/semantics/search.d.ts +2 -0
  129. package/dist/src/semantics/search.js +84 -0
  130. package/dist/src/semantics/security.d.ts +2 -0
  131. package/dist/src/semantics/security.js +177 -0
  132. package/dist/src/semantics/shared.d.ts +61 -0
  133. package/dist/src/semantics/shared.js +151 -0
  134. package/dist/src/semantics/users.d.ts +2 -0
  135. package/dist/src/semantics/users.js +255 -0
  136. package/dist/src/semantics/webhooks.d.ts +2 -0
  137. package/dist/src/semantics/webhooks.js +107 -0
  138. package/dist/test-fixtures/github-a11y-reference.pr-list.SOURCE.md +56 -0
  139. package/dist/test-fixtures/github-a11y-reference.pr-list.json +1447 -0
  140. package/dist/test-fixtures/github-comment-schema.SOURCE.md +11 -0
  141. package/dist/test-fixtures/github-comment-schema.json +702 -0
  142. package/dist/test-fixtures/github-known-deviations.json +94 -0
  143. package/dist/test-fixtures/github-openapi-operations.SOURCE.md +76 -0
  144. package/dist/test-fixtures/github-openapi-operations.json +362 -0
  145. package/dist/test-fixtures/github-pull-schema.SOURCE.md +37 -0
  146. package/dist/test-fixtures/github-pull-schema.json +3601 -0
  147. package/dist/test-fixtures/github-review-schema.SOURCE.md +12 -0
  148. package/dist/test-fixtures/github-review-schema.json +236 -0
  149. package/package.json +20 -11
  150. package/src/cli.ts +7 -6
  151. package/src/generated/graphql-sdl.gen.json +1 -0
  152. package/src/generated/graphql.gen.json +1 -0
  153. package/src/generated/surface.gen.json +1 -0
  154. package/src/generated/ui.gen.json +1 -0
  155. package/src/github-a11y-snapshot.uitest.ts +5 -5
  156. package/src/github-budget.ts +4 -4
  157. package/src/github-capabilities.ts +1993 -556
  158. package/src/github-conformance.ts +12 -7
  159. package/src/github-connector.ts +108 -96
  160. package/src/github-events.ts +225 -95
  161. package/src/github-git-http.ts +48 -84
  162. package/src/github-git-plane.ts +247 -385
  163. package/src/github-journey.uitest.ts +28 -44
  164. package/src/github-mirror-state.ts +23 -61
  165. package/src/github-mirror-ui.ts +16 -10
  166. package/src/github-server.ts +158 -60
  167. package/src/github-shared.ts +1 -1
  168. package/src/github-twin.ts +1525 -4494
  169. package/src/github-ui-conformance.ts +7 -8
  170. package/src/github-ui-structure.ts +19 -18
  171. package/src/graphql-wire.ts +115 -0
  172. package/src/index.ts +50 -7
  173. package/src/manifest.ts +605 -0
  174. package/src/npm-registry.ts +43 -0
  175. package/src/screens/app-installation.tsx +217 -0
  176. package/src/screens/app-manifest.tsx +97 -0
  177. package/src/screens/oauth.tsx +258 -0
  178. package/src/screens/session.tsx +193 -0
  179. package/src/semantics/actions.ts +395 -0
  180. package/src/semantics/activity.ts +171 -0
  181. package/src/semantics/apps.ts +133 -0
  182. package/src/semantics/branches.ts +133 -0
  183. package/src/semantics/checks.ts +163 -0
  184. package/src/semantics/code-scanning-upload.ts +92 -0
  185. package/src/semantics/codespaces.ts +58 -0
  186. package/src/semantics/commits.ts +109 -0
  187. package/src/semantics/contents.ts +112 -0
  188. package/src/semantics/deployments.ts +116 -0
  189. package/src/semantics/gists.ts +85 -0
  190. package/src/semantics/git.ts +226 -0
  191. package/src/semantics/graphql.ts +505 -0
  192. package/src/semantics/index.ts +68 -0
  193. package/src/semantics/issues.ts +434 -0
  194. package/src/semantics/keys.ts +66 -0
  195. package/src/semantics/labels.ts +91 -0
  196. package/src/semantics/meta.ts +139 -0
  197. package/src/semantics/notifications.ts +58 -0
  198. package/src/semantics/orgs.ts +383 -0
  199. package/src/semantics/packages.ts +59 -0
  200. package/src/semantics/pages.ts +154 -0
  201. package/src/semantics/projects.ts +246 -0
  202. package/src/semantics/pulls.ts +421 -0
  203. package/src/semantics/push-reactions.ts +471 -0
  204. package/src/semantics/releases.ts +213 -0
  205. package/src/semantics/repo-invitations.ts +112 -0
  206. package/src/semantics/repos.ts +373 -0
  207. package/src/semantics/rulesets.ts +107 -0
  208. package/src/semantics/search.ts +83 -0
  209. package/src/semantics/security.ts +153 -0
  210. package/src/semantics/shared.ts +181 -0
  211. package/src/semantics/users.ts +252 -0
  212. package/src/semantics/webhooks.ts +101 -0
  213. package/test-fixtures/github-known-deviations.json +7 -8
  214. package/test-fixtures/github-openapi-operations.json +46 -227
  215. package/src/github-graphql.ts +0 -398
@@ -0,0 +1,420 @@
1
+ import { GITHUB_WORLD_USER, githubNotFound, paginate, seedUser, toGithubIssueType, toGithubOrg, toGithubOrgMembership, toGithubOrgWebhook, toGithubRepo, toGithubTeam, toGithubTeamMembership, toGithubUser, twinUserId } from "../github-twin.js";
2
+ import { asView, gh, nextSeq, put, send } from "./shared.js";
3
+ const invalid = (ctx, resource, field, code, documentation_url) => ctx.reply({ message: 'Validation Failed', errors: [{ resource, field, code }], ...(documentation_url ? { documentation_url } : {}) }, 422);
4
+ // GitHub's team-name → slug derivation (lowercase, non-alphanum → '-', collapse/trim dashes).
5
+ function slugifyTeamName(name) {
6
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').replace(/-+/g, '-');
7
+ }
8
+ /** The team a path under /orgs/:org/teams/:slug names, or nothing. */
9
+ function teamOf(g) {
10
+ const org = decodeURIComponent(g.seg[1]);
11
+ const slug = decodeURIComponent(g.seg[3]);
12
+ return { org, slug, team: g.state().teams.find((t) => t.org === org && t.slug === slug) };
13
+ }
14
+ /** A read or write under a team: Not Found when the team does not exist. */
15
+ const underTeam = (serve) => async (ctx) => {
16
+ const g = gh(ctx);
17
+ const { org, slug, team } = teamOf(g);
18
+ return team ? serve(ctx, g, org, slug, team) : send(ctx, githubNotFound());
19
+ };
20
+ async function removeOrgMember(ctx) {
21
+ const { seg, state } = gh(ctx);
22
+ const org = decodeURIComponent(seg[1]);
23
+ const login = decodeURIComponent(seg[3]);
24
+ if (!state().orgMembers.some((m) => m.org === org && m.login === login))
25
+ return send(ctx, githubNotFound());
26
+ await put(ctx, { operation: 'org_membership.remove', subjectType: 'org_membership', subjectId: `orgmember:${org}#${login}`, fields: { deleted: true, org, login } });
27
+ return send(ctx, { status: 204, body: undefined });
28
+ }
29
+ /** Edit an issue type (GitHub's PUT; the hand-written route keeps the PATCH it also answered). */
30
+ async function updateIssueType(ctx) {
31
+ const { seg, body, occurredAt, state } = gh(ctx);
32
+ const org = decodeURIComponent(seg[1]);
33
+ const id = Number(decodeURIComponent(seg[3]));
34
+ const existing = state().issueTypes.find((t) => t.org === org && t.id === id);
35
+ if (!existing)
36
+ return send(ctx, githubNotFound());
37
+ // the update is a PUT: name and is_enabled are required, as on create (docs.github.com/rest/orgs/issue-types)
38
+ if (!body.name)
39
+ return invalid(ctx, 'IssueType', 'name', 'missing_field');
40
+ if (typeof body.is_enabled !== 'boolean')
41
+ return invalid(ctx, 'IssueType', 'is_enabled', 'missing_field');
42
+ const fields = { org, name: existing.name, description: existing.description, color: existing.color, is_enabled: existing.is_enabled, created_at: existing.created_at, updated_at: occurredAt };
43
+ for (const k of ['name', 'description', 'color'])
44
+ if (body[k] !== undefined)
45
+ fields[k] = body[k];
46
+ if (body.is_enabled !== undefined)
47
+ fields.is_enabled = body.is_enabled !== false;
48
+ await put(ctx, { operation: 'issue_type.update', subjectType: 'issue_type', subjectId: `issuetype:${id}`, fields });
49
+ return ctx.reply(toGithubIssueType(state().issueTypes.find((x) => x.org === org && x.id === id)));
50
+ }
51
+ /** An organization's outside collaborators: people with access to one of its repositories who are not its members,
52
+ * and members it converted (docs.github.com/organizations/managing-user-access-to-your-organizations-repositories). */
53
+ function outsideOf(st, org) {
54
+ const members = new Set(st.orgMembers.filter((m) => m.org === org && m.state === 'active').map((m) => m.login));
55
+ const logins = new Set([...st.outsideCollaborators.filter((c) => c.org === org).map((c) => c.login), ...st.collaborators.filter((c) => c.repository.startsWith(`${org}/`)).map((c) => c.login)]);
56
+ return [...logins].filter((l) => !members.has(l)).sort();
57
+ }
58
+ export function orgsSemantics() {
59
+ const orgsGet = async (ctx) => {
60
+ const { seg, caller, state } = gh(ctx);
61
+ const login = decodeURIComponent(seg[1]);
62
+ const st = state();
63
+ const o = st.orgs.find((x) => x.login === login);
64
+ if (!o)
65
+ return send(ctx, githubNotFound());
66
+ // GitHub reports the two-factor requirement only to the organization's owners.
67
+ const owner = !!caller && st.orgMembers.some((m) => m.org === login && m.login === caller && m.role === 'admin' && (m.state ?? 'active') === 'active');
68
+ return ctx.reply({ ...toGithubOrg(o, st), ...(owner ? { two_factor_requirement_enabled: o.two_factor_requirement_enabled === true } : {}) });
69
+ };
70
+ const orgsUpdate = async (ctx) => {
71
+ const { seg, body, occurredAt, state } = gh(ctx);
72
+ const login = decodeURIComponent(seg[1]);
73
+ const prev = state().orgs.find((o) => o.login === login);
74
+ // an organization is created on github.com (the twin's door, POST /_twin/orgs), never by an edit
75
+ if (!prev)
76
+ return send(ctx, githubNotFound());
77
+ const fields = { login, updated_at: occurredAt, created_at: prev.created_at ?? occurredAt };
78
+ for (const k of ['description', 'name', 'company', 'blog', 'location', 'email', 'billing_email'])
79
+ if (body[k] !== undefined)
80
+ fields[k] = body[k];
81
+ await put(ctx, { operation: 'organization.update', subjectType: 'organization', subjectId: `org:${login}`, fields });
82
+ return ctx.reply(toGithubOrg(state().orgs.find((x) => x.login === login), state()));
83
+ };
84
+ const orgsListMembers = async (ctx) => {
85
+ const { seg, query, state } = gh(ctx);
86
+ const org = decodeURIComponent(seg[1]);
87
+ const role = new URLSearchParams((query ?? '').replace(/^\?/, '')).get('role') ?? 'all';
88
+ if (!['all', 'admin', 'member'].includes(role))
89
+ return invalid(ctx, 'Organization', 'role', 'invalid', 'https://docs.github.com/rest/orgs/members#list-organization-members');
90
+ const members = state().orgMembers.filter((m) => m.org === org && m.state === 'active' && (role === 'all' || m.role === role));
91
+ return ctx.reply(paginate(members, query).map((m) => asView('simple-user', toGithubUser(seedUser(m.login)))));
92
+ };
93
+ const orgsCheckMembershipForUser = async (ctx) => {
94
+ const { seg, state } = gh(ctx);
95
+ const org = decodeURIComponent(seg[1]);
96
+ const m = state().orgMembers.find((x) => x.org === org && x.login === decodeURIComponent(seg[3]) && x.state === 'active');
97
+ return send(ctx, m ? { status: 204, body: undefined } : githubNotFound());
98
+ };
99
+ const orgsGetMembershipForUser = async (ctx) => {
100
+ const { seg, state } = gh(ctx);
101
+ const org = decodeURIComponent(seg[1]);
102
+ const login = decodeURIComponent(seg[3]);
103
+ const m = state().orgMembers.find((x) => x.org === org && x.login === login);
104
+ return send(ctx, m ? { status: 200, body: toGithubOrgMembership(m) } : githubNotFound());
105
+ };
106
+ const orgsListPendingInvitations = async (ctx) => {
107
+ const { seg, query, state } = gh(ctx);
108
+ const org = decodeURIComponent(seg[1]);
109
+ const pending = state().orgMembers.filter((m) => m.org === org && m.state === 'pending');
110
+ return ctx.reply(paginate(pending, query).map((m) => ({
111
+ id: twinUserId(`${org}#${m.login}`), node_id: null, login: m.login, email: null, role: m.role === 'admin' ? 'admin' : 'direct_member',
112
+ created_at: m.created_at ?? null, failed_at: null, failed_reason: null, inviter: toGithubUser(seedUser(GITHUB_WORLD_USER)), team_count: 0,
113
+ invitation_teams_url: `https://api.github.com/organizations/${org}/invitations/${m.login}/teams`, invitation_source: 'member',
114
+ })));
115
+ };
116
+ const orgsListMembershipsForAuthenticatedUser = async (ctx) => {
117
+ const { caller, query, state } = gh(ctx);
118
+ const me = caller ?? GITHUB_WORLD_USER;
119
+ const wanted = new URLSearchParams((query ?? '').replace(/^\?/, '')).get('state');
120
+ return ctx.reply(paginate(state().orgMembers.filter((m) => m.login === me && (!wanted || m.state === wanted)), query).map(toGithubOrgMembership));
121
+ };
122
+ const orgsGetMembershipForAuthenticatedUser = async (ctx) => {
123
+ const { caller, seg, state } = gh(ctx);
124
+ const org = decodeURIComponent(seg[3]);
125
+ const m = state().orgMembers.find((x) => x.org === org && x.login === (caller ?? GITHUB_WORLD_USER));
126
+ return send(ctx, m ? { status: 200, body: toGithubOrgMembership(m) } : githubNotFound());
127
+ };
128
+ const orgsListOutsideCollaborators = async (ctx) => {
129
+ const { seg, query, state } = gh(ctx);
130
+ const org = decodeURIComponent(seg[1]);
131
+ return ctx.reply(paginate(outsideOf(state(), org), query).map((login) => asView('simple-user', toGithubUser(seedUser(login)))));
132
+ };
133
+ const orgsConvertMemberToOutsideCollaborator = async (ctx) => {
134
+ const { seg } = gh(ctx);
135
+ const org = decodeURIComponent(seg[1]);
136
+ const login = decodeURIComponent(seg[3]);
137
+ await put(ctx, { operation: 'outside_collaborator.add', subjectType: 'outside_collaborator', subjectId: `outsidecollab:${org}#${login}`, fields: { org, login } });
138
+ // they are no longer a member, nor on its teams; the repositories they worked in stay theirs
139
+ const st = gh(ctx).state();
140
+ const membership = st.orgMembers.find((m) => m.org === org && m.login === login);
141
+ if (membership)
142
+ await put(ctx, { operation: 'org_membership.remove', subjectType: 'org_membership', subjectId: `orgmember:${org}#${login}`, fields: { ...membership, deleted: true } });
143
+ for (const t of st.teamMembers.filter((m) => m.org === org && m.login === login)) {
144
+ await put(ctx, { operation: 'team_membership.remove', subjectType: 'team_membership', subjectId: `teammember:${org}#${t.team_slug}#${login}`, fields: { ...t, deleted: true } });
145
+ }
146
+ return send(ctx, { status: 204, body: undefined });
147
+ };
148
+ const orgsRemoveOutsideCollaborator = async (ctx) => {
149
+ const { seg, state } = gh(ctx);
150
+ const org = decodeURIComponent(seg[1]);
151
+ const login = decodeURIComponent(seg[3]);
152
+ const st = state();
153
+ if (!outsideOf(st, org).includes(login))
154
+ return send(ctx, githubNotFound());
155
+ if (st.outsideCollaborators.some((c) => c.org === org && c.login === login))
156
+ await put(ctx, { operation: 'outside_collaborator.remove', subjectType: 'outside_collaborator', subjectId: `outsidecollab:${org}#${login}`, fields: { deleted: true, org, login } });
157
+ for (const c of st.collaborators.filter((x) => x.login === login && x.repository.startsWith(`${org}/`))) {
158
+ await put(ctx, { operation: 'collaborator.remove', subjectType: 'collaborator', subjectId: `collab:${c.repository}#${login}`, fields: { deleted: true, repository: c.repository, login } });
159
+ }
160
+ return send(ctx, { status: 204, body: undefined });
161
+ };
162
+ const orgsListWebhooks = async (ctx) => {
163
+ const { seg, query, state } = gh(ctx);
164
+ const org = decodeURIComponent(seg[1]);
165
+ return ctx.reply(paginate(state().orgWebhooks.filter((h) => h.org === org), query).map(toGithubOrgWebhook));
166
+ };
167
+ const orgsGetWebhook = async (ctx) => {
168
+ const { seg, state } = gh(ctx);
169
+ const org = decodeURIComponent(seg[1]);
170
+ const h = state().orgWebhooks.find((x) => x.org === org && x.id === Number(decodeURIComponent(seg[3])));
171
+ return send(ctx, h ? { status: 200, body: toGithubOrgWebhook(h) } : githubNotFound());
172
+ };
173
+ const orgsCreateWebhook = async (ctx) => {
174
+ const { seg, body, occurredAt, state } = gh(ctx);
175
+ const org = decodeURIComponent(seg[1]);
176
+ const cfg = (body.config ?? {});
177
+ if (!cfg.url)
178
+ return invalid(ctx, 'Hook', 'config', 'missing_field');
179
+ const seq = nextSeq(ctx, 'orghook');
180
+ const fields = { org, active: body.active !== false, events: Array.isArray(body.events) ? body.events : ['push'], url: String(cfg.url), content_type: cfg.content_type ?? 'form', created_at: occurredAt, updated_at: occurredAt };
181
+ await put(ctx, { operation: 'org_webhook.create', subjectType: 'org_webhook', subjectId: `orghook:${seq}`, fields });
182
+ return ctx.reply(toGithubOrgWebhook(state().orgWebhooks.find((x) => x.org === org && x.id === seq)), 201);
183
+ };
184
+ const orgsUpdateWebhook = async (ctx) => {
185
+ const { seg, body, occurredAt, state } = gh(ctx);
186
+ const org = decodeURIComponent(seg[1]);
187
+ const id = Number(decodeURIComponent(seg[3]));
188
+ const existing = state().orgWebhooks.find((h) => h.org === org && h.id === id);
189
+ if (!existing)
190
+ return send(ctx, githubNotFound());
191
+ const cfg = (body.config ?? {});
192
+ const fields = { org, created_at: existing.created_at, updated_at: occurredAt, url: existing.url, content_type: existing.content_type, events: existing.events, active: existing.active };
193
+ if (body.active !== undefined)
194
+ fields.active = body.active !== false;
195
+ if (Array.isArray(body.events))
196
+ fields.events = body.events;
197
+ if (cfg.url !== undefined)
198
+ fields.url = String(cfg.url);
199
+ if (cfg.content_type !== undefined)
200
+ fields.content_type = cfg.content_type;
201
+ await put(ctx, { operation: 'org_webhook.update', subjectType: 'org_webhook', subjectId: `orghook:${id}`, fields });
202
+ return ctx.reply(toGithubOrgWebhook(state().orgWebhooks.find((x) => x.org === org && x.id === id)));
203
+ };
204
+ const orgsDeleteWebhook = async (ctx) => {
205
+ const { seg, state } = gh(ctx);
206
+ const org = decodeURIComponent(seg[1]);
207
+ const id = Number(decodeURIComponent(seg[3]));
208
+ if (!state().orgWebhooks.find((h) => h.org === org && h.id === id))
209
+ return send(ctx, githubNotFound());
210
+ await put(ctx, { operation: 'org_webhook.delete', subjectType: 'org_webhook', subjectId: `orghook:${id}`, fields: { deleted: true, org } });
211
+ return send(ctx, { status: 204, body: undefined });
212
+ };
213
+ const orgsListIssueTypes = async (ctx) => {
214
+ const { seg, query, state } = gh(ctx);
215
+ const org = decodeURIComponent(seg[1]);
216
+ return ctx.reply(paginate(state().issueTypes.filter((t) => t.org === org), query).map(toGithubIssueType));
217
+ };
218
+ const orgsCreateIssueType = async (ctx) => {
219
+ const { seg, body, occurredAt, state } = gh(ctx);
220
+ const org = decodeURIComponent(seg[1]);
221
+ // name and is_enabled are required (docs.github.com/rest/orgs/issue-types#create-issue-type-for-an-organization)
222
+ if (!body.name)
223
+ return invalid(ctx, 'IssueType', 'name', 'missing_field');
224
+ if (typeof body.is_enabled !== 'boolean')
225
+ return invalid(ctx, 'IssueType', 'is_enabled', 'missing_field');
226
+ if (state().issueTypes.some((t) => t.org === org && t.name === String(body.name)))
227
+ return invalid(ctx, 'IssueType', 'name', 'already_exists');
228
+ const seq = nextSeq(ctx, 'issuetype');
229
+ const fields = { org, name: String(body.name), description: body.description ?? null, color: body.color ?? null, is_enabled: body.is_enabled, created_at: occurredAt, updated_at: occurredAt };
230
+ await put(ctx, { operation: 'issue_type.create', subjectType: 'issue_type', subjectId: `issuetype:${seq}`, fields });
231
+ return ctx.reply(toGithubIssueType(state().issueTypes.find((x) => x.org === org && x.name === String(body.name))));
232
+ };
233
+ const orgsDeleteIssueType = async (ctx) => {
234
+ const { seg, state } = gh(ctx);
235
+ const org = decodeURIComponent(seg[1]);
236
+ const id = Number(decodeURIComponent(seg[3]));
237
+ if (!state().issueTypes.find((t) => t.org === org && t.id === id))
238
+ return send(ctx, githubNotFound());
239
+ await put(ctx, { operation: 'issue_type.delete', subjectType: 'issue_type', subjectId: `issuetype:${id}`, fields: { deleted: true, org } });
240
+ return send(ctx, { status: 204, body: undefined });
241
+ };
242
+ const reposListForOrg = async (ctx) => {
243
+ const { seg, query, state } = gh(ctx);
244
+ const org = decodeURIComponent(seg[1]);
245
+ const params = new URLSearchParams((query ?? '').replace(/^\?/, ''));
246
+ let items = state().repos.filter((r) => r.owner === org);
247
+ const type = params.get('type');
248
+ if (type === 'public')
249
+ items = items.filter((r) => !(r.private ?? false));
250
+ else if (type === 'private')
251
+ items = items.filter((r) => r.private ?? false);
252
+ else if (type === 'forks')
253
+ items = items.filter((r) => r.fork ?? false);
254
+ else if (type === 'sources')
255
+ items = items.filter((r) => !(r.fork ?? false));
256
+ const sort = params.get('sort');
257
+ if (sort === 'full_name')
258
+ items = [...items].sort((a, b) => (a.full_name < b.full_name ? -1 : 1));
259
+ else if (sort === 'created')
260
+ items = [...items].sort((a, b) => ((a.created_at ?? '') < (b.created_at ?? '') ? 1 : -1));
261
+ else if (sort === 'updated')
262
+ items = [...items].sort((a, b) => ((a.updated_at ?? '') < (b.updated_at ?? '') ? 1 : -1));
263
+ return ctx.reply(paginate(items, query).map(toGithubRepo));
264
+ };
265
+ const teamsList = async (ctx) => {
266
+ const { seg, query, state } = gh(ctx);
267
+ const org = decodeURIComponent(seg[1]);
268
+ return ctx.reply(paginate(state().teams.filter((t) => t.org === org), query).map((t) => asView('team', toGithubTeam(t))));
269
+ };
270
+ const teamsUpdateInOrg = (ctx) => (underTeam(async (ctx, g, org, slug, existing) => {
271
+ const fields = { org, slug, name: existing.name, description: existing.description, privacy: existing.privacy, permission: existing.permission, created_at: existing.created_at, updated_at: g.occurredAt };
272
+ for (const k of ['name', 'description', 'privacy', 'permission'])
273
+ if (g.body[k] !== undefined)
274
+ fields[k] = g.body[k];
275
+ await put(ctx, { operation: 'team.update', subjectType: 'team', subjectId: `team:${existing.id}`, fields });
276
+ return ctx.reply(toGithubTeam(g.state().teams.find((x) => x.org === org && x.slug === slug), g.state()));
277
+ }))(ctx);
278
+ const teamsDeleteInOrg = (ctx) => (underTeam(async (ctx, _g, org, _slug, existing) => {
279
+ await put(ctx, { operation: 'team.delete', subjectType: 'team', subjectId: `team:${existing.id}`, fields: { deleted: true, org } });
280
+ return send(ctx, { status: 204, body: undefined });
281
+ }))(ctx);
282
+ const teamsListMembersInOrg = (ctx) => (underTeam(async (ctx, g, org, slug) => {
283
+ const role = new URLSearchParams((g.query ?? '').replace(/^\?/, '')).get('role') ?? 'all';
284
+ if (!['all', 'member', 'maintainer'].includes(role))
285
+ return invalid(ctx, 'Team', 'role', 'invalid', 'https://docs.github.com/rest/teams/members#list-team-members');
286
+ const members = g.state().teamMembers.filter((m) => m.org === org && m.team_slug === slug && (m.state ?? 'active') === 'active' && (role === 'all' || m.role === role));
287
+ return ctx.reply(paginate(members, g.query).map((m) => asView('team-member', toGithubUser(seedUser(m.login)))));
288
+ }))(ctx);
289
+ const teamsRemoveMembershipForUserInOrg = (ctx) => (underTeam(async (ctx, g, org, slug) => {
290
+ const login = decodeURIComponent(g.seg[5]);
291
+ if (!g.state().teamMembers.some((m) => m.org === org && m.team_slug === slug && m.login === login))
292
+ return send(ctx, githubNotFound());
293
+ await put(ctx, { operation: 'team_membership.remove', subjectType: 'team_membership', subjectId: `teammember:${org}#${slug}#${login}`, fields: { deleted: true, org, team_slug: slug, login } });
294
+ return send(ctx, { status: 204, body: undefined });
295
+ }))(ctx);
296
+ const teamsListReposInOrg = (ctx) => (underTeam(async (ctx, g, org, slug) => {
297
+ const st = g.state();
298
+ const items = st.teamRepos.filter((x) => x.org === org && x.team_slug === slug).map((t) => st.repos.find((r) => r.full_name === t.repository)).filter((r) => !!r);
299
+ return ctx.reply(paginate(items, g.query).map(toGithubRepo));
300
+ }))(ctx);
301
+ const teamsCheckPermissionsForRepoInOrg = (ctx) => (underTeam(async (ctx, g, org, slug) => {
302
+ const repo = `${decodeURIComponent(g.seg[5])}/${decodeURIComponent(g.seg[6])}`;
303
+ return send(ctx, g.state().teamRepos.find((x) => x.org === org && x.team_slug === slug && x.repository === repo) ? { status: 204, body: undefined } : githubNotFound());
304
+ }))(ctx);
305
+ const teamsRemoveRepoInOrg = (ctx) => (underTeam(async (ctx, g, org, slug) => {
306
+ const repo = `${decodeURIComponent(g.seg[5])}/${decodeURIComponent(g.seg[6])}`;
307
+ if (!g.state().teamRepos.some((x) => x.org === org && x.team_slug === slug && x.repository === repo))
308
+ return send(ctx, githubNotFound());
309
+ await put(ctx, { operation: 'team_repo.remove', subjectType: 'team_repo', subjectId: `teamrepo:${org}#${slug}#${repo}`, fields: { deleted: true, org, team_slug: slug, repository: repo } });
310
+ return send(ctx, { status: 204, body: undefined });
311
+ }))(ctx);
312
+ return {
313
+ 'orgs/get': orgsGet,
314
+ 'orgs/update': orgsUpdate,
315
+ // `role` filters as GitHub does: all (the default), admin or member; any other value is a validation error.
316
+ 'orgs/list-members': orgsListMembers,
317
+ // 204 member / 404 not
318
+ 'orgs/check-membership-for-user': orgsCheckMembershipForUser,
319
+ 'orgs/remove-member': removeOrgMember,
320
+ 'orgs/get-membership-for-user': orgsGetMembershipForUser,
321
+ // Invite or set a role (docs.github.com/rest/orgs/members#set-organization-membership-for-a-user): someone not
322
+ // yet a member is invited, pending until they accept (PATCH /user/memberships/orgs/{org}); a member's role
323
+ // changes in place.
324
+ 'orgs/set-membership-for-user': async (ctx) => {
325
+ const { seg, body, occurredAt, state } = gh(ctx);
326
+ const org = decodeURIComponent(seg[1]);
327
+ const login = decodeURIComponent(seg[3]);
328
+ if (body.role !== undefined && body.role !== 'admin' && body.role !== 'member')
329
+ return invalid(ctx, 'Membership', 'role', 'invalid', 'https://docs.github.com/rest/orgs/members#set-organization-membership-for-a-user');
330
+ const role = body.role === 'admin' ? 'admin' : 'member';
331
+ const prior = state().orgMembers.find((x) => x.org === org && x.login === login);
332
+ await put(ctx, { operation: 'org_membership.upsert', subjectType: 'org_membership', subjectId: `orgmember:${org}#${login}`, fields: { org, login, role, state: prior?.state ?? 'pending', created_at: prior?.created_at ?? occurredAt } });
333
+ return ctx.reply(toGithubOrgMembership(state().orgMembers.find((x) => x.org === org && x.login === login)));
334
+ },
335
+ // the invitations waiting on their invitee (docs.github.com/rest/orgs/members#list-pending-organization-invitations)
336
+ 'orgs/list-pending-invitations': orgsListPendingInvitations,
337
+ // the signed-in person's own memberships, and accepting an invitation: state 'active' is the only one accepted
338
+ // (docs.github.com/rest/orgs/members#update-an-organization-membership-for-the-authenticated-user)
339
+ 'orgs/list-memberships-for-authenticated-user': orgsListMembershipsForAuthenticatedUser,
340
+ 'orgs/get-membership-for-authenticated-user': orgsGetMembershipForAuthenticatedUser,
341
+ 'orgs/update-membership-for-authenticated-user': async (ctx) => {
342
+ const { caller, seg, body, state } = gh(ctx);
343
+ const org = decodeURIComponent(seg[3]);
344
+ const me = caller ?? GITHUB_WORLD_USER;
345
+ const m = state().orgMembers.find((x) => x.org === org && x.login === me);
346
+ if (!m)
347
+ return send(ctx, githubNotFound());
348
+ if (body.state !== 'active')
349
+ return invalid(ctx, 'Membership', 'state', 'invalid', 'https://docs.github.com/rest/orgs/members#update-an-organization-membership-for-the-authenticated-user');
350
+ await put(ctx, { operation: 'org_membership.upsert', subjectType: 'org_membership', subjectId: `orgmember:${org}#${me}`, fields: { ...m, state: 'active' } });
351
+ // the teams they were invited to take them in with the organization
352
+ for (const t of state().teamMembers.filter((x) => x.org === org && x.login === me && x.state === 'pending')) {
353
+ await put(ctx, { operation: 'team_membership.upsert', subjectType: 'team_membership', subjectId: `teammember:${org}#${t.team_slug}#${me}`, fields: { ...t, state: 'active' } });
354
+ }
355
+ return ctx.reply(toGithubOrgMembership(state().orgMembers.find((x) => x.org === org && x.login === me)));
356
+ },
357
+ 'orgs/remove-membership-for-user': removeOrgMember,
358
+ 'orgs/list-outside-collaborators': orgsListOutsideCollaborators,
359
+ 'orgs/convert-member-to-outside-collaborator': orgsConvertMemberToOutsideCollaborator,
360
+ // removing an outside collaborator takes away their access to every repository of the organization
361
+ 'orgs/remove-outside-collaborator': orgsRemoveOutsideCollaborator,
362
+ 'orgs/list-webhooks': orgsListWebhooks,
363
+ 'orgs/get-webhook': orgsGetWebhook,
364
+ 'orgs/create-webhook': orgsCreateWebhook,
365
+ 'orgs/update-webhook': orgsUpdateWebhook,
366
+ 'orgs/delete-webhook': orgsDeleteWebhook,
367
+ 'orgs/list-issue-types': orgsListIssueTypes,
368
+ 'orgs/create-issue-type': orgsCreateIssueType,
369
+ 'orgs/update-issue-type': updateIssueType,
370
+ 'orgs/delete-issue-type': orgsDeleteIssueType,
371
+ // repositories an org owns; ?type=(all|public|private|forks|sources) and ?sort=(created|updated|full_name)
372
+ 'repos/list-for-org': reposListForOrg,
373
+ // ── teams ──
374
+ 'teams/list': teamsList,
375
+ 'teams/get-by-name': underTeam(async (ctx, g, _org, _slug, team) => ctx.reply(toGithubTeam(team, g.state()))),
376
+ 'teams/create': async (ctx) => {
377
+ const { seg, body, occurredAt, state } = gh(ctx);
378
+ const org = decodeURIComponent(seg[1]);
379
+ if (!body.name)
380
+ return invalid(ctx, 'Team', 'name', 'missing_field');
381
+ const slug = slugifyTeamName(String(body.name));
382
+ if (state().teams.some((t) => t.org === org && t.slug === slug))
383
+ return invalid(ctx, 'Team', 'name', 'already_exists');
384
+ const seq = nextSeq(ctx, 'team');
385
+ const fields = { org, slug, name: String(body.name), description: body.description ?? null, privacy: body.privacy ?? 'secret', permission: body.permission ?? 'pull', created_at: occurredAt, updated_at: occurredAt };
386
+ await put(ctx, { operation: 'team.create', subjectType: 'team', subjectId: `team:${seq}`, fields });
387
+ return ctx.reply(toGithubTeam(state().teams.find((x) => x.org === org && x.slug === slug), state()), 201);
388
+ },
389
+ 'teams/update-in-org': teamsUpdateInOrg,
390
+ 'teams/delete-in-org': teamsDeleteInOrg,
391
+ // `role` filters as GitHub does: all (the default), member or maintainer; only active memberships are listed.
392
+ 'teams/list-members-in-org': teamsListMembersInOrg,
393
+ 'teams/get-membership-for-user-in-org': underTeam(async (ctx, g, org, slug) => {
394
+ const m = g.state().teamMembers.find((x) => x.org === org && x.team_slug === slug && x.login === decodeURIComponent(g.seg[5]));
395
+ return send(ctx, m ? { status: 200, body: toGithubTeamMembership(m, g.state()) } : githubNotFound());
396
+ }),
397
+ 'teams/add-or-update-membership-for-user-in-org': underTeam(async (ctx, g, org, slug) => {
398
+ const login = decodeURIComponent(g.seg[5]);
399
+ const role = g.body.role === 'maintainer' ? 'maintainer' : 'member';
400
+ // someone not yet in the organization is invited to it, and their team membership is pending until they accept
401
+ // (docs.github.com/rest/teams/members#add-or-update-team-membership-for-a-user)
402
+ const membership = g.state().orgMembers.find((m) => m.org === org && m.login === login);
403
+ if (!membership)
404
+ await put(ctx, { operation: 'org_membership.upsert', subjectType: 'org_membership', subjectId: `orgmember:${org}#${login}`, fields: { org, login, role: 'member', state: 'pending', created_at: g.occurredAt } });
405
+ const state = membership?.state === 'active' ? 'active' : 'pending';
406
+ await put(ctx, { operation: 'team_membership.upsert', subjectType: 'team_membership', subjectId: `teammember:${org}#${slug}#${login}`, fields: { org, team_slug: slug, login, role, state } });
407
+ return ctx.reply(toGithubTeamMembership(g.state().teamMembers.find((x) => x.org === org && x.team_slug === slug && x.login === login), g.state()));
408
+ }),
409
+ 'teams/remove-membership-for-user-in-org': teamsRemoveMembershipForUserInOrg,
410
+ 'teams/list-repos-in-org': teamsListReposInOrg,
411
+ // 204 when the team holds a grant on the repo, else 404
412
+ 'teams/check-permissions-for-repo-in-org': teamsCheckPermissionsForRepoInOrg,
413
+ 'teams/add-or-update-repo-permissions-in-org': underTeam(async (ctx, g, org, slug) => {
414
+ const repo = `${decodeURIComponent(g.seg[5])}/${decodeURIComponent(g.seg[6])}`;
415
+ await put(ctx, { operation: 'team_repo.grant', subjectType: 'team_repo', subjectId: `teamrepo:${org}#${slug}#${repo}`, fields: { org, team_slug: slug, repository: repo, permission: g.body.permission ?? 'pull' } });
416
+ return send(ctx, { status: 204, body: undefined });
417
+ }),
418
+ 'teams/remove-repo-in-org': teamsRemoveRepoInOrg,
419
+ };
420
+ }
@@ -0,0 +1,2 @@
1
+ import type { Semantics } from '@volter/world-core';
2
+ export declare function packagesSemantics(): Record<string, Semantics>;
@@ -0,0 +1,55 @@
1
+ import { githubNotFound, paginate, toGithubPackage, toGithubPackageVersion } from "../github-twin.js";
2
+ import { gh, put, send } from "./shared.js";
3
+ const versionCount = (st, p) => st.packageVersions.filter((v) => v.owner === p.owner && v.package_type === p.package_type && v.package_name === p.name).length;
4
+ /** A read or write under /user/packages/:type/:name: Not Found when the package does not exist. */
5
+ const underPackage = (serve) => async (ctx) => {
6
+ const { seg, state } = gh(ctx);
7
+ const type = decodeURIComponent(seg[2]);
8
+ const name = decodeURIComponent(seg[3]);
9
+ const st = state();
10
+ const pkg = st.packages.find((p) => p.owner === 'octocat' && p.package_type === type && p.name === name);
11
+ return pkg ? serve(ctx, st, pkg, type, name) : send(ctx, githubNotFound());
12
+ };
13
+ export function packagesSemantics() {
14
+ const packagesListPackagesForAuthenticatedUser = async (ctx) => {
15
+ const { query, state } = gh(ctx);
16
+ const st = state();
17
+ const pkgType = new URLSearchParams((query ?? '').replace(/^\?/, '')).get('package_type');
18
+ let items = st.packages.filter((p) => p.owner === 'octocat');
19
+ if (pkgType)
20
+ items = items.filter((p) => p.package_type === pkgType);
21
+ return ctx.reply(paginate(items, query).map((p) => toGithubPackage(p, versionCount(st, p))));
22
+ };
23
+ const packagesDeletePackageForAuthenticatedUser = (ctx) => (underPackage(async (ctx, _st, pkg, type, name) => {
24
+ await put(ctx, { operation: 'package.delete', subjectType: 'package', subjectId: `package:${pkg.id}`, fields: { deleted: true, name } });
25
+ for (const v of gh(ctx).state().packageVersions.filter((x) => x.owner === 'octocat' && x.package_type === type && x.package_name === name)) {
26
+ await put(ctx, { operation: 'package_version.delete', subjectType: 'package_version', subjectId: `packageversion:${v.id}`, fields: { deleted: true, package_name: name } });
27
+ }
28
+ return send(ctx, { status: 204, body: undefined });
29
+ }))(ctx);
30
+ const packagesDeletePackageVersionForAuthenticatedUser = async (ctx) => {
31
+ const { seg, state } = gh(ctx);
32
+ const type = decodeURIComponent(seg[2]);
33
+ const name = decodeURIComponent(seg[3]);
34
+ const vid = Number(decodeURIComponent(seg[5]));
35
+ if (!state().packageVersions.find((x) => x.owner === 'octocat' && x.package_type === type && x.package_name === name && x.id === vid))
36
+ return send(ctx, githubNotFound());
37
+ await put(ctx, { operation: 'package_version.delete', subjectType: 'package_version', subjectId: `packageversion:${vid}`, fields: { deleted: true, package_name: name } });
38
+ return send(ctx, { status: 204, body: undefined });
39
+ };
40
+ const packagesGetAllPackageVersionsForPackageOwnedByAuthenticatedUser = (ctx) => (underPackage(async (ctx, st, _pkg, type, name) => ctx.reply(paginate(st.packageVersions.filter((v) => v.owner === 'octocat' && v.package_type === type && v.package_name === name).sort((a, b) => String(b.created_at ?? '').localeCompare(String(a.created_at ?? '')) || b.id - a.id), gh(ctx).query).map(toGithubPackageVersion))))(ctx);
41
+ const packagesGetPackageVersionForAuthenticatedUser = (ctx) => (underPackage(async (ctx, st, _pkg, type, name) => {
42
+ const vid = Number(decodeURIComponent(gh(ctx).seg[5]));
43
+ const v = st.packageVersions.find((x) => x.owner === 'octocat' && x.package_type === type && x.package_name === name && x.id === vid);
44
+ return send(ctx, v ? { status: 200, body: toGithubPackageVersion(v) } : githubNotFound());
45
+ }))(ctx);
46
+ return {
47
+ 'packages/list-packages-for-authenticated-user': packagesListPackagesForAuthenticatedUser,
48
+ 'packages/get-package-for-authenticated-user': underPackage(async (ctx, st, pkg) => ctx.reply(toGithubPackage(pkg, versionCount(st, pkg)))),
49
+ 'packages/get-all-package-versions-for-package-owned-by-authenticated-user': packagesGetAllPackageVersionsForPackageOwnedByAuthenticatedUser,
50
+ 'packages/get-package-version-for-authenticated-user': packagesGetPackageVersionForAuthenticatedUser,
51
+ // deleting a package deletes its versions
52
+ 'packages/delete-package-for-authenticated-user': packagesDeletePackageForAuthenticatedUser,
53
+ 'packages/delete-package-version-for-authenticated-user': packagesDeletePackageVersionForAuthenticatedUser,
54
+ };
55
+ }
@@ -0,0 +1,2 @@
1
+ import type { Semantics } from '@volter/world-core';
2
+ export declare function pagesSemantics(): Record<string, Semantics>;