@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.
- package/README.md +88 -36
- package/client/github-mirror.css +277 -319
- package/client/github-mirror.d.ts +418 -0
- package/client/github-mirror.js +485 -0
- package/client/github-mirror.tsx +153 -357
- package/client/pulls-rest.ts +159 -0
- package/client/pulls-workspace.tsx +841 -0
- package/dist/client/github-mirror.bundle.js +239 -0
- package/dist/client/github-mirror.css +916 -0
- package/dist/client/github-mirror.d.ts +418 -0
- package/dist/client/github-mirror.js +485 -0
- package/dist/client/github-mirror.tsx +1315 -0
- package/dist/client/pulls-rest.d.ts +42 -0
- package/dist/client/pulls-rest.js +140 -0
- package/dist/client/pulls-rest.ts +159 -0
- package/dist/client/pulls-workspace.bundle.js +22 -0
- package/dist/client/pulls-workspace.d.ts +114 -0
- package/dist/client/pulls-workspace.js +418 -0
- package/dist/client/pulls-workspace.tsx +841 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +40 -0
- package/dist/src/generated/graphql-sdl.gen.json +1 -0
- package/dist/src/generated/graphql.gen.json +1 -0
- package/dist/src/generated/surface.gen.json +1 -0
- package/dist/src/generated/ui.gen.json +1 -0
- package/dist/src/github-budget.d.ts +69 -0
- package/dist/src/github-budget.js +172 -0
- package/dist/src/github-capabilities.d.ts +5 -0
- package/dist/src/github-capabilities.js +4468 -0
- package/dist/src/github-conformance.d.ts +43 -0
- package/dist/src/github-conformance.js +76 -0
- package/dist/src/github-connector.d.ts +307 -0
- package/dist/src/github-connector.js +1398 -0
- package/dist/src/github-events.d.ts +41 -0
- package/dist/src/github-events.js +232 -0
- package/dist/src/github-git-http.d.ts +49 -0
- package/dist/src/github-git-http.js +185 -0
- package/dist/src/github-git-plane.d.ts +114 -0
- package/dist/src/github-git-plane.js +407 -0
- package/dist/src/github-mirror-state.d.ts +2 -0
- package/dist/src/github-mirror-state.js +335 -0
- package/dist/src/github-mirror-ui.d.ts +20 -0
- package/dist/src/github-mirror-ui.js +101 -0
- package/dist/src/github-server.d.ts +14 -0
- package/dist/src/github-server.js +240 -0
- package/dist/src/github-shared.d.ts +12 -0
- package/dist/src/github-shared.js +21 -0
- package/dist/src/github-twin.d.ts +1411 -0
- package/dist/src/github-twin.js +4084 -0
- package/dist/src/github-ui-conformance.d.ts +4 -0
- package/dist/src/github-ui-conformance.js +105 -0
- package/dist/src/github-ui-structure.d.ts +18 -0
- package/dist/src/github-ui-structure.js +251 -0
- package/dist/src/graphql-wire.d.ts +22 -0
- package/dist/src/graphql-wire.js +89 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.js +104 -0
- package/dist/src/manifest.d.ts +8 -0
- package/dist/src/manifest.js +597 -0
- package/dist/src/npm-registry.d.ts +7 -0
- package/dist/src/npm-registry.js +47 -0
- package/dist/src/screens/app-installation.d.ts +3 -0
- package/dist/src/screens/app-installation.js +166 -0
- package/dist/src/screens/app-manifest.d.ts +3 -0
- package/dist/src/screens/app-manifest.js +81 -0
- package/dist/src/screens/oauth.d.ts +15 -0
- package/dist/src/screens/oauth.js +257 -0
- package/dist/src/screens/session.d.ts +8 -0
- package/dist/src/screens/session.js +170 -0
- package/dist/src/semantics/actions.d.ts +2 -0
- package/dist/src/semantics/actions.js +413 -0
- package/dist/src/semantics/activity.d.ts +4 -0
- package/dist/src/semantics/activity.js +161 -0
- package/dist/src/semantics/apps.d.ts +2 -0
- package/dist/src/semantics/apps.js +144 -0
- package/dist/src/semantics/branches.d.ts +2 -0
- package/dist/src/semantics/branches.js +136 -0
- package/dist/src/semantics/checks.d.ts +2 -0
- package/dist/src/semantics/checks.js +176 -0
- package/dist/src/semantics/code-scanning-upload.d.ts +2 -0
- package/dist/src/semantics/code-scanning-upload.js +97 -0
- package/dist/src/semantics/codespaces.d.ts +2 -0
- package/dist/src/semantics/codespaces.js +58 -0
- package/dist/src/semantics/commits.d.ts +2 -0
- package/dist/src/semantics/commits.js +109 -0
- package/dist/src/semantics/contents.d.ts +2 -0
- package/dist/src/semantics/contents.js +131 -0
- package/dist/src/semantics/deployments.d.ts +2 -0
- package/dist/src/semantics/deployments.js +127 -0
- package/dist/src/semantics/gists.d.ts +2 -0
- package/dist/src/semantics/gists.js +86 -0
- package/dist/src/semantics/git.d.ts +2 -0
- package/dist/src/semantics/git.js +235 -0
- package/dist/src/semantics/graphql.d.ts +7 -0
- package/dist/src/semantics/graphql.js +512 -0
- package/dist/src/semantics/index.d.ts +5 -0
- package/dist/src/semantics/index.js +62 -0
- package/dist/src/semantics/issues.d.ts +2 -0
- package/dist/src/semantics/issues.js +456 -0
- package/dist/src/semantics/keys.d.ts +2 -0
- package/dist/src/semantics/keys.js +66 -0
- package/dist/src/semantics/labels.d.ts +2 -0
- package/dist/src/semantics/labels.js +100 -0
- package/dist/src/semantics/meta.d.ts +12 -0
- package/dist/src/semantics/meta.js +144 -0
- package/dist/src/semantics/notifications.d.ts +4 -0
- package/dist/src/semantics/notifications.js +62 -0
- package/dist/src/semantics/orgs.d.ts +2 -0
- package/dist/src/semantics/orgs.js +420 -0
- package/dist/src/semantics/packages.d.ts +2 -0
- package/dist/src/semantics/packages.js +55 -0
- package/dist/src/semantics/pages.d.ts +2 -0
- package/dist/src/semantics/pages.js +176 -0
- package/dist/src/semantics/projects.d.ts +2 -0
- package/dist/src/semantics/projects.js +239 -0
- package/dist/src/semantics/pulls.d.ts +2 -0
- package/dist/src/semantics/pulls.js +462 -0
- package/dist/src/semantics/push-reactions.d.ts +101 -0
- package/dist/src/semantics/push-reactions.js +513 -0
- package/dist/src/semantics/releases.d.ts +19 -0
- package/dist/src/semantics/releases.js +231 -0
- package/dist/src/semantics/repo-invitations.d.ts +2 -0
- package/dist/src/semantics/repo-invitations.js +98 -0
- package/dist/src/semantics/repos.d.ts +17 -0
- package/dist/src/semantics/repos.js +390 -0
- package/dist/src/semantics/rulesets.d.ts +2 -0
- package/dist/src/semantics/rulesets.js +111 -0
- package/dist/src/semantics/search.d.ts +2 -0
- package/dist/src/semantics/search.js +84 -0
- package/dist/src/semantics/security.d.ts +2 -0
- package/dist/src/semantics/security.js +177 -0
- package/dist/src/semantics/shared.d.ts +61 -0
- package/dist/src/semantics/shared.js +151 -0
- package/dist/src/semantics/users.d.ts +2 -0
- package/dist/src/semantics/users.js +255 -0
- package/dist/src/semantics/webhooks.d.ts +2 -0
- package/dist/src/semantics/webhooks.js +107 -0
- package/dist/test-fixtures/github-a11y-reference.pr-list.SOURCE.md +56 -0
- package/dist/test-fixtures/github-a11y-reference.pr-list.json +1447 -0
- package/dist/test-fixtures/github-comment-schema.SOURCE.md +11 -0
- package/dist/test-fixtures/github-comment-schema.json +702 -0
- package/dist/test-fixtures/github-known-deviations.json +94 -0
- package/dist/test-fixtures/github-openapi-operations.SOURCE.md +76 -0
- package/dist/test-fixtures/github-openapi-operations.json +362 -0
- package/dist/test-fixtures/github-pull-schema.SOURCE.md +37 -0
- package/dist/test-fixtures/github-pull-schema.json +3601 -0
- package/dist/test-fixtures/github-review-schema.SOURCE.md +12 -0
- package/dist/test-fixtures/github-review-schema.json +236 -0
- package/package.json +20 -11
- package/src/cli.ts +7 -6
- package/src/generated/graphql-sdl.gen.json +1 -0
- package/src/generated/graphql.gen.json +1 -0
- package/src/generated/surface.gen.json +1 -0
- package/src/generated/ui.gen.json +1 -0
- package/src/github-a11y-snapshot.uitest.ts +5 -5
- package/src/github-budget.ts +4 -4
- package/src/github-capabilities.ts +1993 -556
- package/src/github-conformance.ts +12 -7
- package/src/github-connector.ts +108 -96
- package/src/github-events.ts +225 -95
- package/src/github-git-http.ts +48 -84
- package/src/github-git-plane.ts +247 -385
- package/src/github-journey.uitest.ts +28 -44
- package/src/github-mirror-state.ts +23 -61
- package/src/github-mirror-ui.ts +16 -10
- package/src/github-server.ts +158 -60
- package/src/github-shared.ts +1 -1
- package/src/github-twin.ts +1525 -4494
- package/src/github-ui-conformance.ts +7 -8
- package/src/github-ui-structure.ts +19 -18
- package/src/graphql-wire.ts +115 -0
- package/src/index.ts +50 -7
- package/src/manifest.ts +605 -0
- package/src/npm-registry.ts +43 -0
- package/src/screens/app-installation.tsx +217 -0
- package/src/screens/app-manifest.tsx +97 -0
- package/src/screens/oauth.tsx +258 -0
- package/src/screens/session.tsx +193 -0
- package/src/semantics/actions.ts +395 -0
- package/src/semantics/activity.ts +171 -0
- package/src/semantics/apps.ts +133 -0
- package/src/semantics/branches.ts +133 -0
- package/src/semantics/checks.ts +163 -0
- package/src/semantics/code-scanning-upload.ts +92 -0
- package/src/semantics/codespaces.ts +58 -0
- package/src/semantics/commits.ts +109 -0
- package/src/semantics/contents.ts +112 -0
- package/src/semantics/deployments.ts +116 -0
- package/src/semantics/gists.ts +85 -0
- package/src/semantics/git.ts +226 -0
- package/src/semantics/graphql.ts +505 -0
- package/src/semantics/index.ts +68 -0
- package/src/semantics/issues.ts +434 -0
- package/src/semantics/keys.ts +66 -0
- package/src/semantics/labels.ts +91 -0
- package/src/semantics/meta.ts +139 -0
- package/src/semantics/notifications.ts +58 -0
- package/src/semantics/orgs.ts +383 -0
- package/src/semantics/packages.ts +59 -0
- package/src/semantics/pages.ts +154 -0
- package/src/semantics/projects.ts +246 -0
- package/src/semantics/pulls.ts +421 -0
- package/src/semantics/push-reactions.ts +471 -0
- package/src/semantics/releases.ts +213 -0
- package/src/semantics/repo-invitations.ts +112 -0
- package/src/semantics/repos.ts +373 -0
- package/src/semantics/rulesets.ts +107 -0
- package/src/semantics/search.ts +83 -0
- package/src/semantics/security.ts +153 -0
- package/src/semantics/shared.ts +181 -0
- package/src/semantics/users.ts +252 -0
- package/src/semantics/webhooks.ts +101 -0
- package/test-fixtures/github-known-deviations.json +7 -8
- package/test-fixtures/github-openapi-operations.json +46 -227
- package/src/github-graphql.ts +0 -398
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// GitHub twin HTTP server — serve the real GitHub REST API over HTTP so the real
|
|
2
|
+
// `@octokit/rest` client (pointed at this baseUrl) works unmodified (R1). Reads
|
|
3
|
+
// fold observed evidence + action overlay; writes (simulator/fork) become local
|
|
4
|
+
// actions and fire GitHub webhooks (R17). Mirror mode rejects writes (R4).
|
|
5
|
+
// The git SMART-HTTP protocol (clone/fetch/push by an unmodified `git` CLI) is served
|
|
6
|
+
// FIRST — its routes (/:owner/:repo/info/refs, git-upload-pack, git-receive-pack) are
|
|
7
|
+
// binary pkt-line streams over the kernel's git library (github-git-http.ts); a push is
|
|
8
|
+
// announced as an API commit's is (semantics/shared.ts announce).
|
|
9
|
+
import { bindSemantics, compileSurface, coreFor, createDerivedFetch, crossCutting, matchOperation, semanticsContext, serveHttp, vendorError } from '@volter/world-core';
|
|
10
|
+
import surface from './generated/surface.gen.json' with { type: 'json' };
|
|
11
|
+
import { manifest, spanning } from "./manifest.js";
|
|
12
|
+
import { githubSemantics } from "./semantics/index.js";
|
|
13
|
+
import { githubGraphql } from "./semantics/graphql.js";
|
|
14
|
+
import { createGraphqlWire } from "./graphql-wire.js";
|
|
15
|
+
import { githubState, handleGithubDoor, revokedToken } from "./github-twin.js";
|
|
16
|
+
import { announce } from "./semantics/shared.js";
|
|
17
|
+
import { answerVendorErrors, RefusedWriteError } from '@volter/world-core';
|
|
18
|
+
import { worldNow, statefulTwinManifest } from '@volter/world-core';
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
20
|
+
import { githubOAuthFlow } from "./screens/oauth.js";
|
|
21
|
+
import { githubSessionFlow } from "./screens/session.js";
|
|
22
|
+
import { githubNpmRegistry } from "./npm-registry.js";
|
|
23
|
+
import { githubAppInstallationFlow } from "./screens/app-installation.js";
|
|
24
|
+
import { githubAppManifestFlow } from "./screens/app-manifest.js";
|
|
25
|
+
import { assetUploads } from "./semantics/releases.js";
|
|
26
|
+
import { handleGithubGitSmartHttp, parseGitSmartHttpPath } from "./github-git-http.js";
|
|
27
|
+
import { githubMirrorState } from "./github-mirror-state.js";
|
|
28
|
+
import { GITHUB_MIRROR_STORE } from "./github-shared.js";
|
|
29
|
+
// THE STORE DOOR (runtime contract R5c; mirror purity R3): named, deterministic projections
|
|
30
|
+
// over stored state, served at `GET /twin/store/<name>` and listed in the manifest as
|
|
31
|
+
// `stores` — the kernel adapter's door (`createTwinFetchFromHandler`), carried here by the
|
|
32
|
+
// pack's own fetch because github's wire exceeds the handler shape (the git smart-HTTP
|
|
33
|
+
// plane's binary pkt-line streams). GitHub's ONE store is `mirror`: everything the mirror
|
|
34
|
+
// console renders, projected from the folded state (`github-mirror-state.ts`), so the
|
|
35
|
+
// mirror reads twin state ONLY over this door — never by importing the handler. The
|
|
36
|
+
// door's shapes — the manifest's `stores` + `doors.store`, the 404 body for an unknown
|
|
37
|
+
// name — are the kernel door's, byte for byte.
|
|
38
|
+
const STORE_DOOR = '/twin/store/';
|
|
39
|
+
/**
|
|
40
|
+
* The whole GitHub serve path as a plain `(Request) => Response` — the `/twin` manifest, the
|
|
41
|
+
* git smart-HTTP bridge, REST reads and the write path with its webhook emission. NOTHING
|
|
42
|
+
* about it is port-bound, and `createGithubTwinServer` is one line of `Bun.serve` around it,
|
|
43
|
+
* so the standalone (R1) and any in-process lane execute the SAME bytes of serving code.
|
|
44
|
+
*
|
|
45
|
+
* The GIT PLANE is the KERNEL'S GIT LIBRARY (docs/contributing/architecture.md#the-engine-slot):
|
|
46
|
+
* one content-addressed object store per world over the blob seam, refs as world state, smart HTTP
|
|
47
|
+
* in pure JS — Bun and workerd alike, no `git` binary. Every plane touch on the REST read path is
|
|
48
|
+
* still existence-guarded (`hasBareRepo`), so a world with no repositories answers from the folded
|
|
49
|
+
* state alone, which is exactly what a pull-fed remote mirror serves (R14: `syncGithubFromRemote` folds PRs,
|
|
50
|
+
* reviews, comments and issues; none of that needs the plane).
|
|
51
|
+
*/
|
|
52
|
+
/** A release asset's upload: its raw bytes and media type go to the upload operation beside a request with no body,
|
|
53
|
+
* since the operation reads them as the file (semantics/releases.ts). */
|
|
54
|
+
async function uploadingAsset(request, inner) {
|
|
55
|
+
const bytes = new Uint8Array(await request.arrayBuffer());
|
|
56
|
+
const type = request.headers.get('content-type');
|
|
57
|
+
const headers = new Headers(request.headers);
|
|
58
|
+
headers.delete('content-type');
|
|
59
|
+
headers.delete('content-length');
|
|
60
|
+
return assetUploads.run({ bytes, type }, () => inner(new Request(request.url, { method: request.method, headers })));
|
|
61
|
+
}
|
|
62
|
+
/** GitHub's refusal of a request body that is not JSON. */
|
|
63
|
+
function malformedJson() {
|
|
64
|
+
return Response.json({ message: 'invalid JSON body' }, { status: 400 });
|
|
65
|
+
}
|
|
66
|
+
export function createGithubTwinFetch(options) {
|
|
67
|
+
const readOnly = options.readOnly ?? false;
|
|
68
|
+
const stores = { [GITHUB_MIRROR_STORE]: () => githubMirrorState(options.root) };
|
|
69
|
+
const storeNames = Object.keys(stores).sort();
|
|
70
|
+
const now = options.clock ?? worldNow;
|
|
71
|
+
const scope = { ...(options.root !== undefined ? { root: options.root } : {}), ...(options.clock ? { clock: options.clock } : {}) };
|
|
72
|
+
// The twin's doors, in front of GitHub's API: discovery, the store door, the git plane and the stand-ins for acts
|
|
73
|
+
// GitHub does in its web UI (github-twin.ts handleGithubDoor).
|
|
74
|
+
const doors = async (request) => {
|
|
75
|
+
const url = new URL(request.url);
|
|
76
|
+
const cleanPath = url.pathname.replace(/\/+$/, '') || '/';
|
|
77
|
+
// GET /twin — the discovery manifest (education inside the twin). It EDUCATES the
|
|
78
|
+
// kernel door's way: the store names + the door's path ride on the manifest.
|
|
79
|
+
if (request.method === 'GET' && cleanPath === '/twin') {
|
|
80
|
+
const manifest = statefulTwinManifest({ vendor: 'github', twinOf: 'the GitHub REST pull-request API', stores: 'repositories, pull requests, reviews and comments' });
|
|
81
|
+
const doors = manifest.doors !== null && typeof manifest.doors === 'object' ? manifest.doors : {};
|
|
82
|
+
return Response.json({ ...manifest, stores: storeNames, doors: { ...doors, store: 'GET /twin/store/<name>' } });
|
|
83
|
+
}
|
|
84
|
+
// GET /twin/store/<name> — the store door (see STORE_DOOR above).
|
|
85
|
+
if (request.method === 'GET' && cleanPath.startsWith(STORE_DOOR)) {
|
|
86
|
+
const name = cleanPath.slice(STORE_DOOR.length);
|
|
87
|
+
const store = stores[name];
|
|
88
|
+
if (store === undefined)
|
|
89
|
+
return Response.json({ error: 'unknown store', store: name, stores: storeNames }, { status: 404 });
|
|
90
|
+
return Response.json(store());
|
|
91
|
+
}
|
|
92
|
+
// git smart-HTTP (binary protocol, not JSON). Route-parse BEFORE touching the body so ordinary REST POSTs are
|
|
93
|
+
// never double-buffered.
|
|
94
|
+
if ((request.method === 'GET' || request.method === 'POST') && parseGitSmartHttpPath(request.method, url.pathname)) {
|
|
95
|
+
const occurredAt = now();
|
|
96
|
+
const git = await handleGithubGitSmartHttp({
|
|
97
|
+
method: request.method,
|
|
98
|
+
pathname: url.pathname,
|
|
99
|
+
query: url.searchParams,
|
|
100
|
+
...(request.method === 'POST' ? { body: new Uint8Array(await request.arrayBuffer()) } : {}),
|
|
101
|
+
...(options.root !== undefined ? { root: options.root } : {}),
|
|
102
|
+
readOnly,
|
|
103
|
+
contentEncoding: request.headers.get('content-encoding') ?? '',
|
|
104
|
+
gitProtocol: request.headers.get('git-protocol') ?? '',
|
|
105
|
+
occurredAt,
|
|
106
|
+
});
|
|
107
|
+
if (git) {
|
|
108
|
+
// a push moves branches as an API commit does: GitHub's own processes read it (workflows, secret scanning,
|
|
109
|
+
// Dependabot, rulesets) and its webhooks are delivered (semantics/shared.ts announce)
|
|
110
|
+
if (git.webhooks.length) {
|
|
111
|
+
const ctx = await semanticsContext(manifest, new Request(request.url, { headers: request.headers }), GIT_OPERATION, { ...scope, clock: () => occurredAt });
|
|
112
|
+
await announce(ctx, git.webhooks);
|
|
113
|
+
}
|
|
114
|
+
return new Response(git.body, { status: git.status, headers: { 'content-type': git.contentType, 'cache-control': 'no-cache' } });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return readOnly ? undefined : handleGithubDoor(request, { ...scope, now });
|
|
118
|
+
};
|
|
119
|
+
// An archived repository is read-only (docs.github.com/repositories/archiving-a-github-repository/archiving-repositories/
|
|
120
|
+
// about-archiving-repositories): GitHub refuses a write to it, except the repository's own edit (which unarchives it),
|
|
121
|
+
// deleting it, and a person's own watch of it.
|
|
122
|
+
const cross = crossCutting(manifest, { readOnly, ...scope });
|
|
123
|
+
const readOnlyWhenArchived = (request, next) => {
|
|
124
|
+
if (request.method === 'GET' || request.method === 'HEAD')
|
|
125
|
+
return next();
|
|
126
|
+
const seg = new URL(request.url).pathname.split('/').filter(Boolean);
|
|
127
|
+
if (seg[0] !== 'repos' || !seg[2] || seg.length === 3 || (seg.length === 4 && seg[3] === 'subscription'))
|
|
128
|
+
return next();
|
|
129
|
+
if (!githubState(scope.root).repos.find((r) => r.full_name === `${seg[1]}/${seg[2]}`)?.archived)
|
|
130
|
+
return next();
|
|
131
|
+
return Promise.resolve(Response.json({ message: 'Repository was archived so is read-only.', documentation_url: 'https://docs.github.com/articles/about-archiving-repositories' }, { status: 403 }));
|
|
132
|
+
};
|
|
133
|
+
// a refused or failed write at the head (kernel head.ts) answers in the vendor's own error shape
|
|
134
|
+
// The derived dispatch owns the REST wire: semantics handlers, then the derived core; an operation neither
|
|
135
|
+
// serves answers GitHub's Not Found.
|
|
136
|
+
const inner = createDerivedFetch({
|
|
137
|
+
surface: githubSurface,
|
|
138
|
+
handlers: bindSemantics(manifest, githubSemantics({ readOnly }), scope),
|
|
139
|
+
core: coreFor(manifest, scope),
|
|
140
|
+
around: (call, next) => readOnlyWhenArchived(call.request, () => cross(call, next)),
|
|
141
|
+
gap: () => vendorError(manifest, manifest.notFound),
|
|
142
|
+
});
|
|
143
|
+
// GraphQL is a second wire over the same state: the vendor's schema and the pack's resolvers, which get
|
|
144
|
+
// the handler interface for their request (semanticsContext). A query selecting a field nothing models
|
|
145
|
+
// answers GitHub's undefinedField error; a read-only twin refuses a mutation as it refuses any write.
|
|
146
|
+
const graphqlSemantics = githubGraphql();
|
|
147
|
+
const graphqlWire = createGraphqlWire({
|
|
148
|
+
loadSdl: async () => (await import('./generated/graphql-sdl.gen.json', { with: { type: 'json' } })).default.sdl,
|
|
149
|
+
resolvers: graphqlSemantics.resolvers,
|
|
150
|
+
stored: graphqlSemantics.stored,
|
|
151
|
+
context: async (request) => ({ ctx: await semanticsContext(manifest, request, GRAPHQL_OPERATION, { ...scope, clock: now }) }),
|
|
152
|
+
unmodeled: ({ type, field }) => Response.json({ errors: [{ message: `Field '${field}' doesn't exist on type '${type}' in the twin's modeled GraphQL subset`, type: 'undefinedField' }] }),
|
|
153
|
+
});
|
|
154
|
+
const graphql = async (request) => {
|
|
155
|
+
if (readOnly) {
|
|
156
|
+
const { query } = (await request.clone().json().catch(() => ({})));
|
|
157
|
+
if (typeof query === 'string' && /^\s*mutation\b/.test(query))
|
|
158
|
+
return Response.json({ message: 'twin is read-only (mirror mode); start in simulator/fork to write' }, { status: 403 });
|
|
159
|
+
}
|
|
160
|
+
return graphqlWire(request);
|
|
161
|
+
};
|
|
162
|
+
// GitHub reads a write's body as JSON whatever its content type says (manifest.body.json), and
|
|
163
|
+
// answers 400 to one that does not parse.
|
|
164
|
+
const routes = compileSurface(githubSurface);
|
|
165
|
+
const rest = async (request) => {
|
|
166
|
+
const url = new URL(request.url);
|
|
167
|
+
const matched = readOnly || request.method === 'GET' || request.method === 'HEAD' ? undefined : matchOperation(routes, request.method, url.pathname, url.searchParams);
|
|
168
|
+
if (!matched)
|
|
169
|
+
return inner(request);
|
|
170
|
+
// a release asset's body is the file itself, posted to the release's upload_url on uploads.github.com, not to the
|
|
171
|
+
// API host (docs.github.com/rest/releases/assets#upload-a-release-asset). Where the documentation stops and the
|
|
172
|
+
// twin decides: api.github.com answers the path as a route it does not serve, GitHub's 404; any other host is the
|
|
173
|
+
// twin's own address (a World's mount, a local port), where the two GitHub hosts cannot be told apart.
|
|
174
|
+
if (matched.operation.id === 'repos/upload-release-asset') {
|
|
175
|
+
if (url.hostname === 'api.github.com')
|
|
176
|
+
return Response.json({ message: 'Not Found', documentation_url: 'https://docs.github.com/rest', status: '404' }, { status: 404 });
|
|
177
|
+
return uploadingAsset(request, inner);
|
|
178
|
+
}
|
|
179
|
+
const text = await request.clone().text();
|
|
180
|
+
if (!text)
|
|
181
|
+
return inner(request);
|
|
182
|
+
try {
|
|
183
|
+
JSON.parse(text);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return malformedJson();
|
|
187
|
+
}
|
|
188
|
+
return inner(request);
|
|
189
|
+
};
|
|
190
|
+
// github.com's OAuth web flow (a hosted flow: the consent screen and the code exchange) sits beside the API
|
|
191
|
+
// the sign-in every page sends a visitor to who is not signed in (src/screens/session.tsx)
|
|
192
|
+
const session = githubSessionFlow({ ...(options.root !== undefined ? { root: options.root } : {}), now });
|
|
193
|
+
const oauth = githubOAuthFlow({ ...(options.root !== undefined ? { root: options.root } : {}), now });
|
|
194
|
+
// and a GitHub App's installation page (src/screens/app-installation.tsx)
|
|
195
|
+
const appInstall = githubAppInstallationFlow({ ...(options.root !== undefined ? { root: options.root } : {}), now });
|
|
196
|
+
// and the page that registers a GitHub App from a manifest (src/screens/app-manifest.tsx)
|
|
197
|
+
const appManifest = githubAppManifestFlow({ ...(options.root !== undefined ? { root: options.root } : {}), now });
|
|
198
|
+
// and GitHub Packages' npm registry, where `npm publish` puts a package (src/npm-registry.ts)
|
|
199
|
+
const npmRegistry = githubNpmRegistry({ ...(options.root !== undefined ? { root: options.root } : {}), now });
|
|
200
|
+
const wires = async (request) => {
|
|
201
|
+
const path = new URL(request.url).pathname.replace(/\/+$/, '');
|
|
202
|
+
const flow = readOnly ? undefined : (await session(request)) ?? (await oauth(request)) ?? (await appInstall(request)) ?? (await appManifest(request)) ?? (await npmRegistry(request));
|
|
203
|
+
if (flow)
|
|
204
|
+
return flow;
|
|
205
|
+
return request.method === 'POST' && (path === '/graphql' || path === '/api/graphql') ? graphql(request) : rest(request);
|
|
206
|
+
};
|
|
207
|
+
const served = async (request) => (await doors(request)) ?? wires(request);
|
|
208
|
+
const vended = answerVendorErrors(served, (error) => Response.json({ message: error instanceof RefusedWriteError ? `Validation Failed: ${error.message}` : error.message, errors: [{ resource: 'Write', code: error instanceof RefusedWriteError ? 'refused' : 'failed', message: error.message }], documentation_url: 'https://docs.github.com/rest' }, { status: 422 }));
|
|
209
|
+
// Every answer carries GitHub's own headers for when and which request it was: Date on the World's clock (a response
|
|
210
|
+
// dated by the host's clock contradicts the World's time) and an x-github-request-id in GitHub's colon-separated hex
|
|
211
|
+
// form, derived from the instant, the request and a counter so the same request twice gets two ids.
|
|
212
|
+
let answered = 0;
|
|
213
|
+
// which part serves each spec operation (the derived dispatch's owners), for the pack's report
|
|
214
|
+
return Object.assign(async (request) => {
|
|
215
|
+
const token = (request.headers.get('authorization') ?? '').replace(/^(?:bearer|token)\s+/i, '').trim() || undefined;
|
|
216
|
+
const res = revokedToken(token, options.root) ? vendorError(manifest, { status: 401, message: 'Bad credentials' }) : await vended(request);
|
|
217
|
+
const at = now();
|
|
218
|
+
const headers = new Headers(res.headers);
|
|
219
|
+
headers.set('date', new Date(at).toUTCString());
|
|
220
|
+
if (!headers.has('x-github-request-id')) {
|
|
221
|
+
const h = createHash('sha256').update(`${at}:${request.method}:${request.url}:${answered++}`).digest('hex').toUpperCase();
|
|
222
|
+
headers.set('x-github-request-id', `${h.slice(0, 4)}:${h.slice(4, 9)}:${h.slice(9, 15)}:${h.slice(15, 21)}:${h.slice(21, 29)}`);
|
|
223
|
+
}
|
|
224
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
225
|
+
}, { owners: inner.owners });
|
|
226
|
+
}
|
|
227
|
+
/** The GraphQL wire's requests, as the handler interface names them. */
|
|
228
|
+
const GRAPHQL_OPERATION = { id: 'graphql', method: 'POST', path: '/graphql', class: 'action' };
|
|
229
|
+
/** A git push, as the handler interface names it. */
|
|
230
|
+
const GIT_OPERATION = { id: 'git-receive-pack', method: 'POST', path: '/{owner}/{repo}.git/git-receive-pack', class: 'action' };
|
|
231
|
+
// The REST surface, with the path parameters GitHub lets hold slashes (manifest.ts `spanning`).
|
|
232
|
+
const githubSurface = { ...surface, spanning };
|
|
233
|
+
export async function createGithubTwinServer(options) {
|
|
234
|
+
const server = await serveHttp({
|
|
235
|
+
port: options.port ?? 0,
|
|
236
|
+
idleTimeout: 60,
|
|
237
|
+
fetch: createGithubTwinFetch(options),
|
|
238
|
+
});
|
|
239
|
+
return { port: server.port ?? options.port ?? 0, stop: () => server.stop(true) };
|
|
240
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The ONE named store the mirror renders from: `GET /twin/store/${GITHUB_MIRROR_STORE}`
|
|
2
|
+
* (runtime contract R5 — the store door — is the only way a mirror reads twin state). */
|
|
3
|
+
export declare const GITHUB_MIRROR_STORE = "mirror";
|
|
4
|
+
/** The store door's path, relative to the twin's base (the mirror host's origin). */
|
|
5
|
+
export declare const GITHUB_MIRROR_STORE_PATH = "/twin/store/mirror";
|
|
6
|
+
/** GitHub's web origin — the projection's `url` fields are real github.com links. */
|
|
7
|
+
export declare const GITHUB_WEB_ORIGIN = "https://github.com";
|
|
8
|
+
/** A pull request's web path (`/{owner}/{repo}/pull/{n}`) — the projection's `url` tail and
|
|
9
|
+
* the console's own route for an opened PR are the same grammar, real GitHub's. */
|
|
10
|
+
export declare function githubPullPath(repo: string, number: number | string): string;
|
|
11
|
+
/** An issue's web path (`/{owner}/{repo}/issues/{n}`), the same way. */
|
|
12
|
+
export declare function githubIssuePath(repo: string, number: number | string): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// GitHub SHARED helpers — pure shape functions both sides of the wire need: the twin's
|
|
2
|
+
// mirror-state projection (server side, `github-mirror-state.ts`) and the mirror's React
|
|
3
|
+
// client (`client/github-mirror.tsx`). Plain data in → plain data out; no state access,
|
|
4
|
+
// no `@volter/world-core`, no Bun/node imports — this module rides into the browser bundle
|
|
5
|
+
// unchanged.
|
|
6
|
+
/** The ONE named store the mirror renders from: `GET /twin/store/${GITHUB_MIRROR_STORE}`
|
|
7
|
+
* (runtime contract R5 — the store door — is the only way a mirror reads twin state). */
|
|
8
|
+
export const GITHUB_MIRROR_STORE = 'mirror';
|
|
9
|
+
/** The store door's path, relative to the twin's base (the mirror host's origin). */
|
|
10
|
+
export const GITHUB_MIRROR_STORE_PATH = `/twin/store/${GITHUB_MIRROR_STORE}`;
|
|
11
|
+
/** GitHub's web origin — the projection's `url` fields are real github.com links. */
|
|
12
|
+
export const GITHUB_WEB_ORIGIN = 'https://github.com';
|
|
13
|
+
/** A pull request's web path (`/{owner}/{repo}/pull/{n}`) — the projection's `url` tail and
|
|
14
|
+
* the console's own route for an opened PR are the same grammar, real GitHub's. */
|
|
15
|
+
export function githubPullPath(repo, number) {
|
|
16
|
+
return `/${repo}/pull/${number}`;
|
|
17
|
+
}
|
|
18
|
+
/** An issue's web path (`/{owner}/{repo}/issues/{n}`), the same way. */
|
|
19
|
+
export function githubIssuePath(repo, number) {
|
|
20
|
+
return `/${repo}/issues/${number}`;
|
|
21
|
+
}
|