Monorepo for Tangled tangled.org

spindle/engine: store workflow logs in s3 #1216

open opened by jobala.tngl.sh targeting master from jobala.tngl.sh/tangled: upload-workflow-logs

Overview#

This pr implements the first part of this proposal and introduces one new environment variable

  • LogBucket, when set logs will be uploaded to the specified bucket.
Labels

None yet.

assignee

None yet.

Participants 3
AT URI
at://did:plc:qcqdzn5ohjxyp2ilrunon6kn/sh.tangled.repo.pull/3mhq5g7a6ra22
+2361 -2668
Diff #1
-34
api/tangled/knotsubscribeRepos.go
··· 1 - // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 - 3 - package tangled 4 - 5 - // schema: sh.tangled.knot.subscribeRepos 6 - 7 - const ( 8 - KnotSubscribeReposNSID = "sh.tangled.knot.subscribeRepos" 9 - ) 10 - 11 - // KnotSubscribeRepos_GitSync1 is a "gitSync1" in the sh.tangled.knot.subscribeRepos schema. 12 - type KnotSubscribeRepos_GitSync1 struct { 13 - // did: Repository DID identifier 14 - Did string `json:"did" cborgen:"did"` 15 - // seq: The stream sequence number of this message. 16 - Seq int64 `json:"seq" cborgen:"seq"` 17 - } 18 - 19 - // KnotSubscribeRepos_GitSync2 is a "gitSync2" in the sh.tangled.knot.subscribeRepos schema. 20 - type KnotSubscribeRepos_GitSync2 struct { 21 - // did: Repository AT-URI identifier 22 - Did *string `json:"did,omitempty" cborgen:"did,omitempty"` 23 - // seq: The stream sequence number of this message. 24 - Seq int64 `json:"seq" cborgen:"seq"` 25 - } 26 - 27 - // KnotSubscribeRepos_Identity is a "identity" in the sh.tangled.knot.subscribeRepos schema. 28 - type KnotSubscribeRepos_Identity struct { 29 - // did: Repository DID identifier 30 - Did string `json:"did" cborgen:"did"` 31 - // seq: The stream sequence number of this message. 32 - Seq int64 `json:"seq" cborgen:"seq"` 33 - Time string `json:"time" cborgen:"time"` 34 - }
-32
api/tangled/syncrequestCrawl.go
··· 1 - // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 - 3 - package tangled 4 - 5 - // schema: sh.tangled.sync.requestCrawl 6 - 7 - import ( 8 - "context" 9 - 10 - "github.com/bluesky-social/indigo/lex/util" 11 - ) 12 - 13 - const ( 14 - SyncRequestCrawlNSID = "sh.tangled.sync.requestCrawl" 15 - ) 16 - 17 - // SyncRequestCrawl_Input is the input argument to a sh.tangled.sync.requestCrawl call. 18 - type SyncRequestCrawl_Input struct { 19 - // ensureRepo: specific repository to ensure crawling 20 - EnsureRepo *string `json:"ensureRepo,omitempty" cborgen:"ensureRepo,omitempty"` 21 - // hostname: Hostname of the current service (eg, Knot) that is requesting to be crawled. 22 - Hostname string `json:"hostname" cborgen:"hostname"` 23 - } 24 - 25 - // SyncRequestCrawl calls the XRPC method "sh.tangled.sync.requestCrawl". 26 - func SyncRequestCrawl(ctx context.Context, c util.LexClient, input *SyncRequestCrawl_Input) error { 27 - if err := c.LexDo(ctx, util.Procedure, "application/json", "sh.tangled.sync.requestCrawl", nil, input, nil); err != nil { 28 - return err 29 - } 30 - 31 - return nil 32 - }
+3 -3
appview/config/config.go
··· 139 139 UpdateInterval time.Duration `env:"UPDATE_INTERVAL, default=1h"` 140 140 } 141 141 142 - type OgreConfig struct { 143 - Host string `env:"HOST, default=https://ogre.tangled.network"` 142 + type OgcardConfig struct { 143 + Host string `env:"HOST, default=https://og.tangled.org"` 144 144 } 145 145 146 146 func (cfg RedisConfig) ToURL() string { ··· 175 175 Bluesky BlueskyConfig `env:",prefix=TANGLED_BLUESKY_"` 176 176 Sites SitesConfig `env:",prefix=TANGLED_SITES_"` 177 177 KnotMirror KnotMirrorConfig `env:",prefix=TANGLED_KNOTMIRROR_"` 178 - Ogre OgreConfig `env:",prefix=TANGLED_OGRE_"` 178 + Ogcard OgcardConfig `env:",prefix=TANGLED_OGCARD_"` 179 179 } 180 180 181 181 func LoadConfig(ctx context.Context) (*Config, error) {
+3 -3
appview/issues/issues.go
··· 23 23 "tangled.org/core/appview/models" 24 24 "tangled.org/core/appview/notify" 25 25 "tangled.org/core/appview/oauth" 26 - "tangled.org/core/ogre" 26 + "tangled.org/core/appview/ogcard" 27 27 "tangled.org/core/appview/pages" 28 28 "tangled.org/core/appview/pages/repoinfo" 29 29 "tangled.org/core/appview/pagination" ··· 49 49 logger *slog.Logger 50 50 validator *validator.Validator 51 51 indexer *issues_indexer.Indexer 52 - ogreClient *ogre.Client 52 + ogcardClient *ogcard.Client 53 53 } 54 54 55 55 func New( ··· 79 79 logger: logger, 80 80 validator: validator, 81 81 indexer: indexer, 82 - ogreClient: ogre.NewClient(config.Ogre.Host), 82 + ogcardClient: ogcard.NewClient(config.Ogcard.Host), 83 83 } 84 84 } 85 85
+5 -5
appview/issues/opengraph.go
··· 7 7 "time" 8 8 9 9 "tangled.org/core/appview/models" 10 - "tangled.org/core/ogre" 10 + "tangled.org/core/appview/ogcard" 11 11 ) 12 12 13 13 func (rp *Issues) IssueOpenGraphSummary(w http.ResponseWriter, r *http.Request) { ··· 29 29 if err != nil { 30 30 ownerHandle = f.Did 31 31 } else { 32 - ownerHandle = owner.Handle.String() 32 + ownerHandle = "@" + owner.Handle.String() 33 33 } 34 34 35 35 var authorHandle string ··· 49 49 50 50 commentCount := len(issue.Comments) 51 51 52 - payload := ogre.IssueCardPayload{ 52 + payload := ogcard.IssueCardPayload{ 53 53 Type: "issue", 54 54 RepoName: f.Name, 55 55 OwnerHandle: ownerHandle, ··· 57 57 Title: issue.Title, 58 58 IssueNumber: issue.IssueId, 59 59 Status: status, 60 - Labels: []ogre.LabelData{}, 60 + Labels: []ogcard.LabelData{}, 61 61 CommentCount: commentCount, 62 62 ReactionCount: 0, 63 63 CreatedAt: issue.Created.Format(time.RFC3339), 64 64 } 65 65 66 - imageBytes, err := rp.ogreClient.RenderIssueCard(r.Context(), payload) 66 + imageBytes, err := rp.ogcardClient.RenderIssueCard(r.Context(), payload) 67 67 if err != nil { 68 68 log.Println("failed to render issue card", err) 69 69 http.Error(w, "failed to render issue card", http.StatusInternalServerError)
+4
appview/ogcard/.gitignore
··· 1 + node_modules/ 2 + output/ 3 + .wrangler/ 4 + .DS_Store
+492
appview/ogcard/bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "@tangled/ogcard-worker", 7 + "dependencies": { 8 + "@fontsource/inter": "^5.2.8", 9 + "@resvg/resvg-wasm": "^2.6.2", 10 + "@tangled/ogcard-runtime": "*", 11 + "lucide-static": "^0.577.0", 12 + "preact": "^10.29.0", 13 + "satori": "0.25.0", 14 + "zod": "^4.3.6", 15 + }, 16 + "devDependencies": { 17 + "@cloudflare/workers-types": "^4.20260317.1", 18 + "@types/bun": "^1.3.11", 19 + "@types/node": "^25.5.0", 20 + "knip": "^6.0.1", 21 + "tsx": "^4.21.0", 22 + "typescript": "^5.9.3", 23 + "wrangler": "^4.75.0", 24 + }, 25 + }, 26 + "packages/runtime": { 27 + "name": "@tangled/ogcard-runtime", 28 + "version": "1.0.0", 29 + }, 30 + }, 31 + "packages": { 32 + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="], 33 + 34 + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.15.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-EGYmJaGZKWl+X8tXxcnx4v2bOZSjQeNI5dWFeXivgX9+YCT69AkzHHwlNbVpqtEUTbew8eQurpyOpeN8fg00nw=="], 35 + 36 + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260317.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g=="], 37 + 38 + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260317.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg=="], 39 + 40 + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260317.1", "", { "os": "linux", "cpu": "x64" }, "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug=="], 41 + 42 + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260317.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw=="], 43 + 44 + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260317.1", "", { "os": "win32", "cpu": "x64" }, "sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ=="], 45 + 46 + "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260317.1", "", {}, "sha512-+G4eVwyCpm8Au1ex8vQBCuA9wnwqetz4tPNRoB/53qvktERWBRMQnrtvC1k584yRE3emMThtuY0gWshvSJ++PQ=="], 47 + 48 + "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], 49 + 50 + "@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], 51 + 52 + "@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="], 53 + 54 + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], 55 + 56 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], 57 + 58 + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], 59 + 60 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], 61 + 62 + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], 63 + 64 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], 65 + 66 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], 67 + 68 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], 69 + 70 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], 71 + 72 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], 73 + 74 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], 75 + 76 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], 77 + 78 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], 79 + 80 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], 81 + 82 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], 83 + 84 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], 85 + 86 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], 87 + 88 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], 89 + 90 + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], 91 + 92 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], 93 + 94 + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], 95 + 96 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], 97 + 98 + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], 99 + 100 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], 101 + 102 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], 103 + 104 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], 105 + 106 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], 107 + 108 + "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], 109 + 110 + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], 111 + 112 + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], 113 + 114 + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], 115 + 116 + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], 117 + 118 + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], 119 + 120 + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], 121 + 122 + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], 123 + 124 + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], 125 + 126 + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], 127 + 128 + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], 129 + 130 + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], 131 + 132 + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], 133 + 134 + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], 135 + 136 + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], 137 + 138 + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], 139 + 140 + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], 141 + 142 + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], 143 + 144 + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], 145 + 146 + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], 147 + 148 + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], 149 + 150 + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], 151 + 152 + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], 153 + 154 + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], 155 + 156 + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], 157 + 158 + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], 159 + 160 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 161 + 162 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 163 + 164 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], 165 + 166 + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], 167 + 168 + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 169 + 170 + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 171 + 172 + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 173 + 174 + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.120.0", "", { "os": "android", "cpu": "arm" }, "sha512-WU3qtINx802wOl8RxAF1v0VvmC2O4D9M8Sv486nLeQ7iPHVmncYZrtBhB4SYyX+XZxj2PNnCcN+PW21jHgiOxg=="], 175 + 176 + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.120.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SEf80EHdhlbjZEgzeWm0ZA/br4GKMenDW3QB/gtyeTV1gStvvZeFi40ioHDZvds2m4Z9J1bUAUL8yn1/+A6iGg=="], 177 + 178 + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.120.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xVrrbCai8R8CUIBu3CjryutQnEYhZqs1maIqDvtUCFZb8vY33H7uh9mHpL3a0JBIKoBUKjPH8+rzyAeXnS2d6A=="], 179 + 180 + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.120.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-xyHBbnJ6mydnQUH7MAcafOkkrNzQC6T+LXgDH/3InEq2BWl/g424IMRiJVSpVqGjB+p2bd0h0WRR8iIwzjU7rw=="], 181 + 182 + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.120.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMnVRllquXUYTeNfFKmxTTEdZ/ix1nLl0ducDzMSREoWYGVIHnOOxoKMWlCOvRr9Wk/HZqo2rh1jeumbPGPV9A=="], 183 + 184 + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.120.0", "", { "os": "linux", "cpu": "arm" }, "sha512-tkvn2CQ7QdcsMnpfiX3fd3wA3EFsWKYlcQzq9cFw/xc89Al7W6Y4O0FgLVkVQpo0Tnq/qtE1XfkJOnRRA9S/NA=="], 185 + 186 + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.120.0", "", { "os": "linux", "cpu": "arm" }, "sha512-WN5y135Ic42gQDk9grbwY9++fDhqf8knN6fnP+0WALlAUh4odY/BDK1nfTJRSfpJD9P3r1BwU0m3pW2DU89whQ=="], 187 + 188 + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.120.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1GgQBCcXvFMw99EPdMy+4NZ3aYyXsxjf9kbUUg8HuAy3ZBXzOry5KfFEzT9nqmgZI1cuetvApkiJBZLAPo8uaw=="], 189 + 190 + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.120.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gmMQ70gsPdDBgpcErvJEoWNBr7bJooSLlvOBVBSGfOzlP5NvJ3bFvnUeZZ9d+dPrqSngtonf7nyzWUTUj/U+lw=="], 191 + 192 + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.120.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-T/kZuU0ajop0xhzVMwH5r3srC9Nqup5HaIo+3uFjIN5uPxa0LvSxC1ZqP4aQGJVW5G0z8/nCkjIfSMS91P/wzw=="], 193 + 194 + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.120.0", "", { "os": "linux", "cpu": "none" }, "sha512-vn21KXLAXzaI3N5CZWlBr1iWeXLl9QFIMor7S1hUjUGTeUuWCoE6JZB040/ZNDwf+JXPX8Ao9KbmJq9FMC2iGw=="], 195 + 196 + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.120.0", "", { "os": "linux", "cpu": "none" }, "sha512-SUbUxlar007LTGmSLGIC5x/WJvwhdX+PwNzFJ9f/nOzZOrCFbOT4ikt7pJIRg1tXVsEfzk5mWpGO1NFiSs4PIw=="], 197 + 198 + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.120.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hYiPJTxyfJY2+lMBFk3p2bo0R9GN+TtpPFlRqVchL1qvLG+pznstramHNvJlw9AjaoRUHwp9IKR7UZQnRPGjgQ=="], 199 + 200 + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.120.0", "", { "os": "linux", "cpu": "x64" }, "sha512-q+5jSVZkprJCIy3dzJpApat0InJaoxQLsJuD6DkX8hrUS61z2lHQ1Fe9L2+TYbKHXCLWbL0zXe7ovkIdopBGMQ=="], 201 + 202 + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.120.0", "", { "os": "linux", "cpu": "x64" }, "sha512-D9QDDZNnH24e7X4ftSa6ar/2hCavETfW3uk0zgcMIrZNy459O5deTbWrjGzZiVrSWigGtlQwzs2McBP0QsfV1w=="], 203 + 204 + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.120.0", "", { "os": "none", "cpu": "arm64" }, "sha512-TBU8ZwOUWAOUWVfmI16CYWbvh4uQb9zHnGBHsw5Cp2JUVG044OIY1CSHODLifqzQIMTXvDvLzcL89GGdUIqNrA=="], 205 + 206 + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.120.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-WG/FOZgDJCpJnuF3ToG/K28rcOmSY7FmFmfBKYb2fmLyhDzPpUldFGV7/Fz4ru0Iz/v4KPmf8xVgO8N3lO4KHA=="], 207 + 208 + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.120.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1T0HKGcsz/BKo77t7+89L8Qvu4f9DoleKWHp3C5sJEcbCjDOLx3m9m722bWZTY+hANlUEs+yjlK+lBFsA+vrVQ=="], 209 + 210 + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.120.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-L7vfLzbOXsjBXV0rv/6Y3Jd9BRjPeCivINZAqrSyAOZN3moCopDN+Psq9ZrGNZtJzP8946MtlRFZ0Als0wBCOw=="], 211 + 212 + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.120.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ys+upfqNtSu58huAhJMBKl3XCkGzyVFBlMlGPzHeFKgpFF/OdgNs1MMf8oaJIbgMH8ZxgGF7qfue39eJohmKIg=="], 213 + 214 + "@oxc-project/types": ["@oxc-project/types@0.120.0", "", {}, "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg=="], 215 + 216 + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.19.1", "", { "os": "android", "cpu": "arm" }, "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg=="], 217 + 218 + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.19.1", "", { "os": "android", "cpu": "arm64" }, "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA=="], 219 + 220 + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.19.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ=="], 221 + 222 + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.19.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ=="], 223 + 224 + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.19.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw=="], 225 + 226 + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A=="], 227 + 228 + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ=="], 229 + 230 + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig=="], 231 + 232 + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew=="], 233 + 234 + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.19.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ=="], 235 + 236 + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w=="], 237 + 238 + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw=="], 239 + 240 + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.19.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA=="], 241 + 242 + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ=="], 243 + 244 + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw=="], 245 + 246 + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.19.1", "", { "os": "none", "cpu": "arm64" }, "sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA=="], 247 + 248 + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.19.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg=="], 249 + 250 + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.19.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ=="], 251 + 252 + "@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.19.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA=="], 253 + 254 + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.19.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw=="], 255 + 256 + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], 257 + 258 + "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], 259 + 260 + "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], 261 + 262 + "@resvg/resvg-wasm": ["@resvg/resvg-wasm@2.6.2", "", {}, "sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw=="], 263 + 264 + "@shuding/opentype.js": ["@shuding/opentype.js@1.4.0-beta.0", "", { "dependencies": { "fflate": "^0.7.3", "string.prototype.codepointat": "^0.2.1" }, "bin": { "ot": "bin/ot" } }, "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA=="], 265 + 266 + "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], 267 + 268 + "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="], 269 + 270 + "@tangled/ogcard-runtime": ["@tangled/ogcard-runtime@workspace:packages/runtime"], 271 + 272 + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 273 + 274 + "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], 275 + 276 + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], 277 + 278 + "base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="], 279 + 280 + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], 281 + 282 + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 283 + 284 + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], 285 + 286 + "camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="], 287 + 288 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 289 + 290 + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], 291 + 292 + "css-background-parser": ["css-background-parser@0.1.0", "", {}, "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="], 293 + 294 + "css-box-shadow": ["css-box-shadow@1.0.0-3", "", {}, "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="], 295 + 296 + "css-color-keywords": ["css-color-keywords@1.0.0", "", {}, "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="], 297 + 298 + "css-gradient-parser": ["css-gradient-parser@0.0.17", "", {}, "sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg=="], 299 + 300 + "css-to-react-native": ["css-to-react-native@3.2.0", "", { "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", "postcss-value-parser": "^4.0.2" } }, "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ=="], 301 + 302 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 303 + 304 + "emoji-regex-xs": ["emoji-regex-xs@2.0.1", "", {}, "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g=="], 305 + 306 + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], 307 + 308 + "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], 309 + 310 + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], 311 + 312 + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], 313 + 314 + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], 315 + 316 + "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="], 317 + 318 + "fflate": ["fflate@0.7.4", "", {}, "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="], 319 + 320 + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 321 + 322 + "formatly": ["formatly@0.3.0", "", { "dependencies": { "fd-package-json": "^2.0.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w=="], 323 + 324 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 325 + 326 + "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], 327 + 328 + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 329 + 330 + "hex-rgb": ["hex-rgb@4.3.0", "", {}, "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw=="], 331 + 332 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 333 + 334 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 335 + 336 + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 337 + 338 + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], 339 + 340 + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], 341 + 342 + "knip": ["knip@6.0.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "get-tsconfig": "4.13.6", "jiti": "^2.6.0", "minimist": "^1.2.8", "oxc-parser": "^0.120.0", "oxc-resolver": "^11.19.1", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "unbash": "^2.2.0", "yaml": "^2.8.2", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-qk5m+w6IYEqfRG5546DXZJYl5AXsgFfDD6ULaDvkubqNtLye79sokBg3usURrWFjASMeQtvX19TfldU3jHkMNA=="], 343 + 344 + "linebreak": ["linebreak@1.1.0", "", { "dependencies": { "base64-js": "0.0.8", "unicode-trie": "^2.0.0" } }, "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ=="], 345 + 346 + "lucide-static": ["lucide-static@0.577.0", "", {}, "sha512-hx39J5Tq4JWF2ALY+5YRg+SxQLpeAmLJDXNcqiBJH/UuVwp43it9fyki/onZO7AVFgG5ZbB+fWwZR9mwGHE2XQ=="], 347 + 348 + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], 349 + 350 + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 351 + 352 + "miniflare": ["miniflare@4.20260317.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260317.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-xuwk5Kjv+shi5iUBAdCrRl9IaWSGnTU8WuTQzsUS2GlSDIMCJuu8DiF/d9ExjMXYiQG5ml+k9SVKnMj8cRkq0w=="], 353 + 354 + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 355 + 356 + "oxc-parser": ["oxc-parser@0.120.0", "", { "dependencies": { "@oxc-project/types": "^0.120.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.120.0", "@oxc-parser/binding-android-arm64": "0.120.0", "@oxc-parser/binding-darwin-arm64": "0.120.0", "@oxc-parser/binding-darwin-x64": "0.120.0", "@oxc-parser/binding-freebsd-x64": "0.120.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.120.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.120.0", "@oxc-parser/binding-linux-arm64-gnu": "0.120.0", "@oxc-parser/binding-linux-arm64-musl": "0.120.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-musl": "0.120.0", "@oxc-parser/binding-linux-s390x-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-musl": "0.120.0", "@oxc-parser/binding-openharmony-arm64": "0.120.0", "@oxc-parser/binding-wasm32-wasi": "0.120.0", "@oxc-parser/binding-win32-arm64-msvc": "0.120.0", "@oxc-parser/binding-win32-ia32-msvc": "0.120.0", "@oxc-parser/binding-win32-x64-msvc": "0.120.0" } }, "sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w=="], 357 + 358 + "oxc-resolver": ["oxc-resolver@11.19.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.19.1", "@oxc-resolver/binding-android-arm64": "11.19.1", "@oxc-resolver/binding-darwin-arm64": "11.19.1", "@oxc-resolver/binding-darwin-x64": "11.19.1", "@oxc-resolver/binding-freebsd-x64": "11.19.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-musl": "11.19.1", "@oxc-resolver/binding-openharmony-arm64": "11.19.1", "@oxc-resolver/binding-wasm32-wasi": "11.19.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" } }, "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg=="], 359 + 360 + "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], 361 + 362 + "parse-css-color": ["parse-css-color@0.2.1", "", { "dependencies": { "color-name": "^1.1.4", "hex-rgb": "^4.1.0" } }, "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg=="], 363 + 364 + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], 365 + 366 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 367 + 368 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 369 + 370 + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 371 + 372 + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], 373 + 374 + "preact": ["preact@10.29.0", "", {}, "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg=="], 375 + 376 + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 377 + 378 + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 379 + 380 + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 381 + 382 + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 383 + 384 + "satori": ["satori@0.25.0", "", { "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", "css-box-shadow": "1.0.0-3", "css-gradient-parser": "^0.0.17", "css-to-react-native": "^3.0.0", "emoji-regex-xs": "^2.0.1", "escape-html": "^1.0.3", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", "yoga-layout": "^3.2.1" } }, "sha512-utINfLxrYrmSnLvxFT4ZwgwWa8KOjrz7ans32V5wItgHVmzESl/9i33nE38uG0miycab8hUqQtDlOpqrIpB/iw=="], 385 + 386 + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 387 + 388 + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], 389 + 390 + "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="], 391 + 392 + "string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="], 393 + 394 + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], 395 + 396 + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], 397 + 398 + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], 399 + 400 + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 401 + 402 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 403 + 404 + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], 405 + 406 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 407 + 408 + "unbash": ["unbash@2.2.0", "", {}, "sha512-X2wH19RAPZE3+ldGicOkoj/SIA83OIxcJ6Cuaw23hf8Xc6fQpvZXY0SftE2JgS0QhYLUG4uwodSI3R53keyh7w=="], 409 + 410 + "undici": ["undici@7.24.4", "", {}, "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w=="], 411 + 412 + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], 413 + 414 + "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], 415 + 416 + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], 417 + 418 + "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], 419 + 420 + "workerd": ["workerd@1.20260317.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260317.1", "@cloudflare/workerd-darwin-arm64": "1.20260317.1", "@cloudflare/workerd-linux-64": "1.20260317.1", "@cloudflare/workerd-linux-arm64": "1.20260317.1", "@cloudflare/workerd-windows-64": "1.20260317.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g=="], 421 + 422 + "wrangler": ["wrangler@4.75.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.15.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260317.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260317.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260317.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-Efk1tcnm4eduBYpH1sSjMYydXMnIFPns/qABI3+fsbDrUk5GksNYX8nYGVP4sFygvGPO7kJc36YJKB5ooA7JAg=="], 423 + 424 + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], 425 + 426 + "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], 427 + 428 + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], 429 + 430 + "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], 431 + 432 + "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], 433 + 434 + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], 435 + 436 + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 437 + 438 + "wrangler/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], 439 + 440 + "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], 441 + 442 + "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], 443 + 444 + "wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], 445 + 446 + "wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], 447 + 448 + "wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], 449 + 450 + "wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], 451 + 452 + "wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], 453 + 454 + "wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], 455 + 456 + "wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], 457 + 458 + "wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], 459 + 460 + "wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], 461 + 462 + "wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], 463 + 464 + "wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], 465 + 466 + "wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], 467 + 468 + "wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], 469 + 470 + "wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], 471 + 472 + "wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], 473 + 474 + "wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], 475 + 476 + "wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], 477 + 478 + "wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], 479 + 480 + "wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], 481 + 482 + "wrangler/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], 483 + 484 + "wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], 485 + 486 + "wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], 487 + 488 + "wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], 489 + 490 + "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], 491 + } 492 + }
+117
appview/ogcard/client.go
··· 1 + package ogcard 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "encoding/json" 7 + "fmt" 8 + "io" 9 + "net/http" 10 + "time" 11 + ) 12 + 13 + type Client struct { 14 + host string 15 + client *http.Client 16 + } 17 + 18 + func NewClient(host string) *Client { 19 + return &Client{ 20 + host: host, 21 + client: &http.Client{ 22 + Timeout: 10 * time.Second, 23 + }, 24 + } 25 + } 26 + 27 + type LabelData struct { 28 + Name string `json:"name"` 29 + Color string `json:"color"` 30 + } 31 + 32 + type LanguageData struct { 33 + Color string `json:"color"` 34 + Percentage float32 `json:"percentage"` 35 + } 36 + 37 + type RepositoryCardPayload struct { 38 + Type string `json:"type"` 39 + RepoName string `json:"repoName"` 40 + OwnerHandle string `json:"ownerHandle"` 41 + Stars int `json:"stars"` 42 + Pulls int `json:"pulls"` 43 + Issues int `json:"issues"` 44 + CreatedAt string `json:"createdAt"` 45 + AvatarUrl string `json:"avatarUrl"` 46 + Languages []LanguageData `json:"languages"` 47 + } 48 + 49 + type IssueCardPayload struct { 50 + Type string `json:"type"` 51 + RepoName string `json:"repoName"` 52 + OwnerHandle string `json:"ownerHandle"` 53 + AvatarUrl string `json:"avatarUrl"` 54 + Title string `json:"title"` 55 + IssueNumber int `json:"issueNumber"` 56 + Status string `json:"status"` 57 + Labels []LabelData `json:"labels"` 58 + CommentCount int `json:"commentCount"` 59 + ReactionCount int `json:"reactionCount"` 60 + CreatedAt string `json:"createdAt"` 61 + } 62 + 63 + type PullRequestCardPayload struct { 64 + Type string `json:"type"` 65 + RepoName string `json:"repoName"` 66 + OwnerHandle string `json:"ownerHandle"` 67 + AvatarUrl string `json:"avatarUrl"` 68 + Title string `json:"title"` 69 + PullRequestNumber int `json:"pullRequestNumber"` 70 + Status string `json:"status"` 71 + FilesChanged int `json:"filesChanged"` 72 + Additions int `json:"additions"` 73 + Deletions int `json:"deletions"` 74 + Rounds int `json:"rounds"` 75 + CommentCount int `json:"commentCount"` 76 + ReactionCount int `json:"reactionCount"` 77 + CreatedAt string `json:"createdAt"` 78 + } 79 + 80 + func (c *Client) doRequest(ctx context.Context, path string, payload any) ([]byte, error) { 81 + body, err := json.Marshal(payload) 82 + if err != nil { 83 + return nil, fmt.Errorf("marshal payload: %w", err) 84 + } 85 + 86 + url := fmt.Sprintf("%s/%s", c.host, path) 87 + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) 88 + if err != nil { 89 + return nil, fmt.Errorf("create request: %w", err) 90 + } 91 + req.Header.Set("Content-Type", "application/json") 92 + 93 + resp, err := c.client.Do(req) 94 + if err != nil { 95 + return nil, fmt.Errorf("do request: %w", err) 96 + } 97 + defer resp.Body.Close() 98 + 99 + if resp.StatusCode != http.StatusOK { 100 + respBody, _ := io.ReadAll(resp.Body) 101 + return nil, fmt.Errorf("unexpected status: %d, body: %s", resp.StatusCode, string(respBody)) 102 + } 103 + 104 + return io.ReadAll(resp.Body) 105 + } 106 + 107 + func (c *Client) RenderRepositoryCard(ctx context.Context, payload RepositoryCardPayload) ([]byte, error) { 108 + return c.doRequest(ctx, "repository", payload) 109 + } 110 + 111 + func (c *Client) RenderIssueCard(ctx context.Context, payload IssueCardPayload) ([]byte, error) { 112 + return c.doRequest(ctx, "issue", payload) 113 + } 114 + 115 + func (c *Client) RenderPullRequestCard(ctx context.Context, payload PullRequestCardPayload) ([]byte, error) { 116 + return c.doRequest(ctx, "pullRequest", payload) 117 + }
+4
appview/ogcard/knip.json
··· 1 + { 2 + "$schema": "https://unpkg.com/knip@5/schema.json", 3 + "tags": ["-lintignore"] 4 + }
+34
appview/ogcard/package.json
··· 1 + { 2 + "name": "@tangled/ogcard-worker", 3 + "version": "1.0.0", 4 + "private": true, 5 + "type": "module", 6 + "workspaces": [ 7 + "packages/runtime" 8 + ], 9 + "scripts": { 10 + "dev": "wrangler dev", 11 + "deploy": "wrangler deploy", 12 + "typecheck": "tsc --noEmit", 13 + "test": "bun test", 14 + "knip": "knip" 15 + }, 16 + "dependencies": { 17 + "@fontsource/inter": "^5.2.8", 18 + "@resvg/resvg-wasm": "^2.6.2", 19 + "@tangled/ogcard-runtime": "*", 20 + "lucide-static": "^0.577.0", 21 + "preact": "^10.29.0", 22 + "satori": "0.25.0", 23 + "zod": "^4.3.6" 24 + }, 25 + "devDependencies": { 26 + "@cloudflare/workers-types": "^4.20260317.1", 27 + "@types/bun": "^1.3.11", 28 + "@types/node": "^25.5.0", 29 + "knip": "^6.0.1", 30 + "tsx": "^4.21.0", 31 + "typescript": "^5.9.3", 32 + "wrangler": "^4.75.0" 33 + } 34 + }
+88
appview/ogcard/packages/runtime/index.ts
··· 1 + /** 2 + * Bun/Node.js runtime implementation 3 + * Uses filesystem APIs to load WASM and fonts 4 + */ 5 + import { readFile } from "node:fs/promises"; 6 + import { createRequire } from "node:module"; 7 + import type { FontData, SatoriFn, ResvgClass } from "./types"; 8 + 9 + const require = createRequire(import.meta.url); 10 + 11 + let satoriFn: SatoriFn | null = null; 12 + let resvgInitialized = false; 13 + let Resvg: ResvgClass | null = null; 14 + 15 + export async function initSatori(): Promise<SatoriFn> { 16 + if (satoriFn) return satoriFn; 17 + 18 + const { default: satori } = await import("satori"); 19 + satoriFn = satori; 20 + 21 + return satoriFn; 22 + } 23 + 24 + export async function initResvg(): Promise<ResvgClass> { 25 + if (resvgInitialized) return Resvg!; 26 + 27 + const { Resvg: ResvgClass, initWasm } = await import("@resvg/resvg-wasm"); 28 + const wasmPath = require.resolve("@resvg/resvg-wasm/index_bg.wasm"); 29 + const wasmBuffer = await readFile(wasmPath); 30 + await initWasm(wasmBuffer); 31 + 32 + Resvg = ResvgClass; 33 + resvgInitialized = true; 34 + return Resvg; 35 + } 36 + 37 + export async function loadFonts(): Promise<FontData[]> { 38 + // In Bun, .woff imports return a Module object with `default` being the file path 39 + const inter400Module = await import( 40 + "@fontsource/inter/files/inter-latin-400-normal.woff" 41 + ); 42 + const inter500Module = await import( 43 + "@fontsource/inter/files/inter-latin-500-normal.woff" 44 + ); 45 + const inter600Module = await import( 46 + "@fontsource/inter/files/inter-latin-600-normal.woff" 47 + ); 48 + 49 + const inter400Path = (inter400Module as { default: string }).default; 50 + const inter500Path = (inter500Module as { default: string }).default; 51 + const inter600Path = (inter600Module as { default: string }).default; 52 + 53 + const [buf400, buf500, buf600] = await Promise.all([ 54 + readFile(inter400Path), 55 + readFile(inter500Path), 56 + readFile(inter600Path), 57 + ]); 58 + 59 + return [ 60 + { 61 + name: "Inter", 62 + data: buf400.buffer.slice( 63 + buf400.byteOffset, 64 + buf400.byteOffset + buf400.byteLength, 65 + ), 66 + weight: 400, 67 + style: "normal", 68 + }, 69 + { 70 + name: "Inter", 71 + data: buf500.buffer.slice( 72 + buf500.byteOffset, 73 + buf500.byteOffset + buf500.byteLength, 74 + ), 75 + weight: 500, 76 + style: "normal", 77 + }, 78 + { 79 + name: "Inter", 80 + data: buf600.buffer.slice( 81 + buf600.byteOffset, 82 + buf600.byteOffset + buf600.byteLength, 83 + ), 84 + weight: 600, 85 + style: "normal", 86 + }, 87 + ]; 88 + }
+12
appview/ogcard/packages/runtime/package.json
··· 1 + { 2 + "name": "@tangled/ogcard-runtime", 3 + "version": "1.0.0", 4 + "private": true, 5 + "type": "module", 6 + "exports": { 7 + "workerd": "./workerd.ts", 8 + "bun": "./index.ts", 9 + "default": "./index.ts" 10 + }, 11 + "types": "./types.ts" 12 + }
+10
appview/ogcard/packages/runtime/types.ts
··· 1 + export interface FontData { 2 + name: string; 3 + data: ArrayBuffer; 4 + weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; 5 + style: "normal" | "italic"; 6 + } 7 + 8 + export type SatoriFn = typeof import("satori").default; 9 + 10 + export type ResvgClass = typeof import("@resvg/resvg-wasm").Resvg;
+60
appview/ogcard/packages/runtime/workerd.ts
··· 1 + /** 2 + * Cloudflare Workers runtime implementation 3 + * Uses ?module suffix for WASM imports as required by Wrangler 4 + */ 5 + import type { FontData, SatoriFn, ResvgClass } from "./types"; 6 + 7 + import inter400 from "@fontsource/inter/files/inter-latin-400-normal.woff"; 8 + import inter500 from "@fontsource/inter/files/inter-latin-500-normal.woff"; 9 + import inter600 from "@fontsource/inter/files/inter-latin-600-normal.woff"; 10 + 11 + let satoriFn: SatoriFn | null = null; 12 + let resvgInitialized = false; 13 + let Resvg: ResvgClass | null = null; 14 + 15 + export async function initSatori(): Promise<SatoriFn> { 16 + if (satoriFn) return satoriFn; 17 + 18 + const { default: satori, init } = await import("satori/standalone"); 19 + const wasmModule = (await import("satori/yoga.wasm?module")).default; 20 + await init(wasmModule); 21 + satoriFn = satori; 22 + 23 + return satoriFn; 24 + } 25 + 26 + export async function initResvg(): Promise<ResvgClass> { 27 + if (resvgInitialized) return Resvg!; 28 + 29 + const { Resvg: ResvgClass, initWasm } = await import("@resvg/resvg-wasm"); 30 + const wasmModule = (await import("@resvg/resvg-wasm/index_bg.wasm?module")) 31 + .default; 32 + await initWasm(wasmModule); 33 + 34 + Resvg = ResvgClass; 35 + resvgInitialized = true; 36 + return Resvg; 37 + } 38 + 39 + export async function loadFonts(): Promise<FontData[]> { 40 + return [ 41 + { 42 + name: "Inter", 43 + data: inter400 as ArrayBuffer, 44 + weight: 400, 45 + style: "normal", 46 + }, 47 + { 48 + name: "Inter", 49 + data: inter500 as ArrayBuffer, 50 + weight: 500, 51 + style: "normal", 52 + }, 53 + { 54 + name: "Inter", 55 + data: inter600 as ArrayBuffer, 56 + weight: 600, 57 + style: "normal", 58 + }, 59 + ]; 60 + }
appview/ogcard/src/__tests__/assets/avatar.jpg

This is a binary file and will not be displayed.

+87
appview/ogcard/src/__tests__/fixtures.ts
··· 1 + import type { 2 + RepositoryCardData, 3 + IssueCardData, 4 + PullRequestCardData, 5 + } from "../validation"; 6 + 7 + const LONG_TITLE = 8 + "fix critical memory leak in WebSocket connection handler that causes server crashes under high load conditions in production environments"; 9 + 10 + export const createRepoData = (avatarUrl: string): RepositoryCardData => ({ 11 + type: "repository", 12 + repoName: "core", 13 + ownerHandle: "tangled.org", 14 + stars: 746, 15 + pulls: 82, 16 + issues: 176, 17 + createdAt: "2026-01-29T00:00:00Z", 18 + avatarUrl, 19 + languages: [ 20 + { color: "#00ADD8", percentage: 50 }, 21 + { color: "#e34c26", percentage: 30 }, 22 + { color: "#7e7eff", percentage: 10 }, 23 + { color: "#663399", percentage: 5 }, 24 + { color: "#f1e05a", percentage: 5 }, 25 + ], 26 + }); 27 + 28 + export const createIssueData = ( 29 + avatarUrl: string, 30 + overrides?: Partial<IssueCardData>, 31 + ): IssueCardData => ({ 32 + type: "issue", 33 + repoName: "core", 34 + ownerHandle: "tangled.org", 35 + avatarUrl, 36 + title: "feature request: sync fork button", 37 + issueNumber: 8, 38 + status: "open", 39 + labels: [ 40 + { name: "feature", color: "#4639d6" }, 41 + { name: "help-wanted", color: "#008672" }, 42 + { name: "enhancement", color: "#0052cc" }, 43 + ], 44 + commentCount: 12, 45 + reactionCount: 5, 46 + createdAt: "2026-01-29T00:00:00Z", 47 + ...overrides, 48 + }); 49 + 50 + export const createPullRequestData = ( 51 + avatarUrl: string, 52 + overrides?: Partial<PullRequestCardData>, 53 + ): PullRequestCardData => ({ 54 + type: "pullRequest", 55 + repoName: "core", 56 + ownerHandle: "tangled.org", 57 + avatarUrl, 58 + title: "add author description to README.md", 59 + pullRequestNumber: 1, 60 + status: "open", 61 + filesChanged: 2, 62 + additions: 116, 63 + deletions: 59, 64 + rounds: 3, 65 + commentCount: 12, 66 + reactionCount: 31, 67 + createdAt: "2026-01-29T00:00:00Z", 68 + ...overrides, 69 + }); 70 + 71 + export const createLongTitleIssueData = ( 72 + avatarUrl: string, 73 + overrides?: Partial<IssueCardData>, 74 + ): IssueCardData => ({ 75 + ...createIssueData(avatarUrl), 76 + title: LONG_TITLE, 77 + ...overrides, 78 + }); 79 + 80 + export const createLongTitlePullRequestData = ( 81 + avatarUrl: string, 82 + overrides?: Partial<PullRequestCardData>, 83 + ): PullRequestCardData => ({ 84 + ...createPullRequestData(avatarUrl), 85 + title: LONG_TITLE, 86 + ...overrides, 87 + });
+132
appview/ogcard/src/__tests__/render.test.ts
··· 1 + import { test, describe, beforeAll } from "bun:test"; 2 + import { writeFileSync, mkdirSync, readFileSync } from "fs"; 3 + import { join } from "path"; 4 + import { h, type VNode } from "preact"; 5 + import { renderCard } from "../lib/render"; 6 + import { RepositoryCard } from "../components/cards/repository"; 7 + import { IssueCard } from "../components/cards/issue"; 8 + import { PullRequestCard } from "../components/cards/pull-request"; 9 + import { 10 + repositoryCardSchema, 11 + issueCardSchema, 12 + pullRequestCardSchema, 13 + } from "../validation"; 14 + import { 15 + createRepoData, 16 + createIssueData, 17 + createPullRequestData, 18 + createLongTitleIssueData, 19 + createLongTitlePullRequestData, 20 + } from "./fixtures"; 21 + 22 + const outputDir = join(process.cwd(), "output"); 23 + let avatarDataUri: string; 24 + 25 + const loadAvatar = (): string => { 26 + const avatarPath = join( 27 + process.cwd(), 28 + "src", 29 + "__tests__", 30 + "assets", 31 + "avatar.jpg", 32 + ); 33 + const avatarBase64 = readFileSync(avatarPath).toString("base64"); 34 + return `data:image/jpeg;base64,${avatarBase64}`; 35 + }; 36 + 37 + beforeAll(() => { 38 + mkdirSync(outputDir, { recursive: true }); 39 + avatarDataUri = loadAvatar(); 40 + }); 41 + 42 + const savePng = (filename: string, buffer: Uint8Array) => { 43 + writeFileSync(join(outputDir, filename), buffer); 44 + }; 45 + 46 + const renderAndSave = async <P>(component: VNode<P>, filename: string) => { 47 + const { png } = await renderCard(component as VNode); 48 + savePng(filename, png); 49 + }; 50 + 51 + describe("repository card", () => { 52 + test("renders repository card", async () => { 53 + const data = createRepoData(avatarDataUri); 54 + const validated = repositoryCardSchema.parse(data); 55 + await renderAndSave(h(RepositoryCard, validated), "repository-card.png"); 56 + }); 57 + }); 58 + 59 + describe("issue cards", () => { 60 + test("renders open issue", async () => { 61 + const data = createIssueData(avatarDataUri); 62 + const validated = issueCardSchema.parse(data); 63 + await renderAndSave(h(IssueCard, validated), "issue-card.png"); 64 + }); 65 + 66 + test("renders closed issue", async () => { 67 + const data = createIssueData(avatarDataUri, { 68 + issueNumber: 5, 69 + status: "closed", 70 + labels: [{ name: "wontfix", color: "#6a737d" }], 71 + reactionCount: 2, 72 + }); 73 + const validated = issueCardSchema.parse(data); 74 + await renderAndSave(h(IssueCard, validated), "issue-card-closed.png"); 75 + }); 76 + 77 + test("renders issue with long title", async () => { 78 + const data = createLongTitleIssueData(avatarDataUri, { 79 + issueNumber: 42, 80 + }); 81 + const validated = issueCardSchema.parse(data); 82 + await renderAndSave(h(IssueCard, validated), "issue-card-long-title.png"); 83 + }); 84 + }); 85 + 86 + describe("pull request cards", () => { 87 + test("renders open pull request", async () => { 88 + const data = createPullRequestData(avatarDataUri); 89 + const validated = pullRequestCardSchema.parse(data); 90 + await renderAndSave(h(PullRequestCard, validated), "pull-request-card.png"); 91 + }); 92 + 93 + test("renders merged pull request", async () => { 94 + const data = createPullRequestData(avatarDataUri, { 95 + pullRequestNumber: 2, 96 + status: "merged", 97 + title: "Implement OAuth2 authentication flow", 98 + filesChanged: 5, 99 + additions: 342, 100 + deletions: 28, 101 + }); 102 + const validated = pullRequestCardSchema.parse(data); 103 + await renderAndSave( 104 + h(PullRequestCard, validated), 105 + "pull-request-card-merged.png", 106 + ); 107 + }); 108 + 109 + test("renders closed pull request", async () => { 110 + const data = createPullRequestData(avatarDataUri, { 111 + pullRequestNumber: 3, 112 + status: "closed", 113 + title: "WIP: Experimental feature", 114 + }); 115 + const validated = pullRequestCardSchema.parse(data); 116 + await renderAndSave( 117 + h(PullRequestCard, validated), 118 + "pull-request-card-closed.png", 119 + ); 120 + }); 121 + 122 + test("renders pull request with long title", async () => { 123 + const data = createLongTitlePullRequestData(avatarDataUri, { 124 + pullRequestNumber: 42, 125 + }); 126 + const validated = pullRequestCardSchema.parse(data); 127 + await renderAndSave( 128 + h(PullRequestCard, validated), 129 + "pull-request-card-long-title.png", 130 + ); 131 + }); 132 + });
+52
appview/ogcard/src/components/cards/issue.tsx
··· 1 + import { Card, Row, Col } from "../shared/layout"; 2 + import { TangledLogo } from "../shared/logo"; 3 + import { IssueStatusBadge } from "../shared/status-badge"; 4 + import { CardHeader } from "../shared/card-header"; 5 + import { LabelList } from "../shared/label-pill"; 6 + import { FooterStats } from "../shared/footer-stats"; 7 + import { TYPOGRAPHY } from "../shared/constants"; 8 + import type { IssueCardData } from "../../validation"; 9 + 10 + export function IssueCard(data: IssueCardData) { 11 + return ( 12 + <Card style={{ justifyContent: "space-between" }}> 13 + <Col style={{ gap: 48 }}> 14 + <Col style={{ gap: 32 }}> 15 + <Row style={{ justifyContent: "space-between" }}> 16 + <CardHeader 17 + avatarUrl={data.avatarUrl} 18 + ownerHandle={data.ownerHandle} 19 + repoName={data.repoName} 20 + /> 21 + <IssueStatusBadge status={data.status} /> 22 + </Row> 23 + 24 + <div 25 + style={{ 26 + ...TYPOGRAPHY.title, 27 + color: "#000000", 28 + display: "block", 29 + lineClamp: `2 "... #${data.issueNumber}"`, 30 + }}> 31 + {data.title} 32 + </div> 33 + </Col> 34 + 35 + <LabelList labels={data.labels} /> 36 + </Col> 37 + 38 + <Row 39 + style={{ 40 + alignItems: "flex-end", 41 + justifyContent: "space-between", 42 + }}> 43 + <FooterStats 44 + createdAt={data.createdAt} 45 + reactionCount={data.reactionCount} 46 + commentCount={data.commentCount} 47 + /> 48 + <TangledLogo /> 49 + </Row> 50 + </Card> 51 + ); 52 + }
+137
appview/ogcard/src/components/cards/pull-request.tsx
··· 1 + import { Card, Row, Col } from "../shared/layout"; 2 + import { TangledLogo } from "../shared/logo"; 3 + import { StatusBadge } from "../shared/status-badge"; 4 + import { CardHeader } from "../shared/card-header"; 5 + import { FooterStats } from "../shared/footer-stats"; 6 + import { FileDiff, RefreshCw } from "../../icons/lucide"; 7 + import { COLORS, TYPOGRAPHY } from "../shared/constants"; 8 + import type { PullRequestCardData } from "../../validation"; 9 + 10 + interface FilesChangedPillProps { 11 + filesChanged: number; 12 + additions: number; 13 + deletions: number; 14 + } 15 + 16 + function FilesChangedPill({ 17 + filesChanged, 18 + additions, 19 + deletions, 20 + }: FilesChangedPillProps) { 21 + return ( 22 + <Row 23 + style={{ 24 + overflow: "hidden", 25 + borderRadius: 18, 26 + backgroundColor: "#fff", 27 + border: `4px solid ${COLORS.label.border}`, 28 + }}> 29 + <Row 30 + style={{ 31 + gap: 16, 32 + padding: "16px 28px", 33 + }}> 34 + <FileDiff size={34} color="#202020" /> 35 + <span style={{ ...TYPOGRAPHY.body, color: "#202020" }}> 36 + {filesChanged} files 37 + </span> 38 + </Row> 39 + <Row style={{ gap: 0 }}> 40 + <Row 41 + style={{ 42 + padding: "16px 10px 16px 11px", 43 + backgroundColor: COLORS.diff.additions.bg, 44 + }}> 45 + <span 46 + style={{ ...TYPOGRAPHY.body, color: COLORS.diff.additions.text }}> 47 + +{additions} 48 + </span> 49 + </Row> 50 + <Row 51 + style={{ 52 + padding: "16px 16px 16px 11px", 53 + backgroundColor: COLORS.diff.deletions.bg, 54 + }}> 55 + <span 56 + style={{ ...TYPOGRAPHY.body, color: COLORS.diff.deletions.text }}> 57 + -{deletions} 58 + </span> 59 + </Row> 60 + </Row> 61 + </Row> 62 + ); 63 + } 64 + 65 + interface MetricPillProps { 66 + value: number; 67 + label: string; 68 + } 69 + 70 + function RoundsPill({ value, label }: MetricPillProps) { 71 + return ( 72 + <Row 73 + style={{ 74 + gap: 16, 75 + padding: "16px 28px", 76 + borderRadius: 18, 77 + backgroundColor: "#fff", 78 + border: `4px solid ${COLORS.label.border}`, 79 + }}> 80 + <RefreshCw size={36} color="#202020" /> 81 + <span style={{ ...TYPOGRAPHY.body, color: "#202020" }}> 82 + {value} {label} 83 + </span> 84 + </Row> 85 + ); 86 + } 87 + 88 + export function PullRequestCard(data: PullRequestCardData) { 89 + return ( 90 + <Card style={{ justifyContent: "space-between" }}> 91 + <Col style={{ gap: 48 }}> 92 + <Col style={{ gap: 32 }}> 93 + <Row style={{ justifyContent: "space-between" }}> 94 + <CardHeader 95 + avatarUrl={data.avatarUrl} 96 + ownerHandle={data.ownerHandle} 97 + repoName={data.repoName} 98 + /> 99 + <StatusBadge status={data.status} /> 100 + </Row> 101 + 102 + <span 103 + style={{ 104 + ...TYPOGRAPHY.title, 105 + color: "#000000", 106 + display: "block", 107 + lineClamp: `2 "... #${data.pullRequestNumber}"`, 108 + }}> 109 + {data.title} 110 + </span> 111 + </Col> 112 + 113 + <Row style={{ gap: 16 }}> 114 + <FilesChangedPill 115 + filesChanged={data.filesChanged} 116 + additions={data.additions} 117 + deletions={data.deletions} 118 + /> 119 + <RoundsPill value={data.rounds} label="rounds" /> 120 + </Row> 121 + </Col> 122 + 123 + <Row 124 + style={{ 125 + alignItems: "flex-end", 126 + justifyContent: "space-between", 127 + }}> 128 + <FooterStats 129 + createdAt={data.createdAt} 130 + reactionCount={data.reactionCount} 131 + commentCount={data.commentCount} 132 + /> 133 + <TangledLogo /> 134 + </Row> 135 + </Card> 136 + ); 137 + }
+44
appview/ogcard/src/components/cards/repository.tsx
··· 1 + import { Card, Row, Col } from "../shared/layout"; 2 + import { Avatar } from "../shared/avatar"; 3 + import { LanguageCircles } from "../shared/language-circles"; 4 + import { Metrics } from "../shared/metrics"; 5 + import { TangledLogo } from "../shared/logo"; 6 + import { FooterStats } from "../shared/footer-stats"; 7 + import { TYPOGRAPHY } from "../shared/constants"; 8 + import type { RepositoryCardData } from "../../validation"; 9 + 10 + export function RepositoryCard(data: RepositoryCardData) { 11 + return ( 12 + <Card> 13 + <LanguageCircles languages={data.languages} /> 14 + 15 + <Col style={{ gap: 64 }}> 16 + <Col style={{ gap: 24 }}> 17 + <span style={{ ...TYPOGRAPHY.repoName, color: "#000000" }}> 18 + {data.repoName} 19 + </span> 20 + 21 + <Row style={{ gap: 16 }}> 22 + <Avatar src={data.avatarUrl} size={64} /> 23 + <span style={{ ...TYPOGRAPHY.ownerHandle, color: "#000000" }}> 24 + {data.ownerHandle} 25 + </span> 26 + </Row> 27 + </Col> 28 + 29 + <Metrics stars={data.stars} pulls={data.pulls} issues={data.issues} /> 30 + </Col> 31 + 32 + <Row 33 + style={{ 34 + alignItems: "flex-end", 35 + justifyContent: "space-between", 36 + flexGrow: 1, 37 + }}> 38 + <FooterStats createdAt={data.createdAt} /> 39 + 40 + <TangledLogo /> 41 + </Row> 42 + </Card> 43 + ); 44 + }
+31
appview/ogcard/src/components/shared/avatar.tsx
··· 1 + interface AvatarProps { 2 + src: string; 3 + size?: number; 4 + } 5 + 6 + export function Avatar({ src, size = 64 }: AvatarProps) { 7 + const avatarSrc = 8 + src.includes("avatar.tangled.sh") && !src.includes("format=") 9 + ? `${src}${src.includes("?") ? "&" : "?"}format=jpeg` 10 + : src; 11 + 12 + return ( 13 + <div 14 + style={{ 15 + width: size, 16 + height: size, 17 + borderRadius: size / 2, 18 + overflow: "hidden", 19 + display: "flex", 20 + alignItems: "center", 21 + justifyContent: "center", 22 + }}> 23 + <img 24 + src={avatarSrc} 25 + width={size} 26 + height={size} 27 + style={{ objectFit: "cover" }} 28 + /> 29 + </div> 30 + ); 31 + }
+24
appview/ogcard/src/components/shared/card-header.tsx
··· 1 + import { Row } from "./layout"; 2 + import { Avatar } from "./avatar"; 3 + import { TYPOGRAPHY } from "./constants"; 4 + 5 + interface CardHeaderProps { 6 + avatarUrl: string; 7 + ownerHandle: string; 8 + repoName: string; 9 + } 10 + 11 + export function CardHeader({ 12 + avatarUrl, 13 + ownerHandle, 14 + repoName, 15 + }: CardHeaderProps) { 16 + return ( 17 + <Row style={{ gap: 16 }}> 18 + <Avatar src={avatarUrl} size={64} /> 19 + <span style={{ ...TYPOGRAPHY.cardHeader, color: "#000000" }}> 20 + {ownerHandle} / {repoName} 21 + </span> 22 + </Row> 23 + ); 24 + }
+30
appview/ogcard/src/components/shared/constants.ts
··· 1 + export const COLORS = { 2 + text: "#000000", 3 + textSecondary: "#7D7D7D", 4 + icon: "#404040", 5 + status: { 6 + open: { bg: "#16A34A", text: "#ffffff" }, 7 + closed: { bg: "#1f2937", text: "#ffffff" }, 8 + merged: { bg: "#7C3AED", text: "#ffffff" }, 9 + }, 10 + label: { 11 + text: "#202020", 12 + border: "#E6E6E6", 13 + }, 14 + diff: { 15 + additions: { bg: "#dcfce7", text: "#15803d" }, 16 + deletions: { bg: "#fee2e2", text: "#b91c1c" }, 17 + }, 18 + } as const; 19 + 20 + export const TYPOGRAPHY = { 21 + title: { fontFamily: "Inter", fontSize: 64, fontWeight: 600 }, 22 + repoName: { fontFamily: "Inter", fontSize: 144, fontWeight: 600 }, 23 + ownerHandle: { fontFamily: "Inter", fontSize: 48, fontWeight: 500 }, 24 + cardHeader: { fontFamily: "Inter", fontSize: 48, fontWeight: 500 }, 25 + status: { fontFamily: "Inter", fontSize: 48, fontWeight: 500 }, 26 + metricValue: { fontFamily: "Inter", fontSize: 48, fontWeight: 500 }, 27 + body: { fontFamily: "Inter", fontSize: 36, fontWeight: 400 }, 28 + meta: { fontFamily: "Inter", fontSize: 32, fontWeight: 400 }, 29 + label: { fontFamily: "Inter", fontSize: 24, fontWeight: 400 }, 30 + } as const;
+33
appview/ogcard/src/components/shared/footer-stats.tsx
··· 1 + import { Row } from "./layout"; 2 + import { Calendar, MessageSquare, SmilePlus } from "../../icons/lucide"; 3 + import { StatItem } from "./stat-item"; 4 + 5 + interface FooterStatsProps { 6 + createdAt: string; 7 + reactionCount?: number; 8 + commentCount?: number; 9 + } 10 + 11 + export function FooterStats({ 12 + createdAt, 13 + reactionCount, 14 + commentCount, 15 + }: FooterStatsProps) { 16 + const formattedDate = new Intl.DateTimeFormat("en-GB", { 17 + day: "numeric", 18 + month: "short", 19 + year: "numeric", 20 + }).format(new Date(createdAt)); 21 + 22 + return ( 23 + <Row style={{ gap: 64 }}> 24 + <StatItem Icon={Calendar} value={formattedDate} /> 25 + {reactionCount ? ( 26 + <StatItem Icon={SmilePlus} value={reactionCount} /> 27 + ) : null} 28 + {commentCount ? ( 29 + <StatItem Icon={MessageSquare} value={commentCount} /> 30 + ) : null} 31 + </Row> 32 + ); 33 + }
+49
appview/ogcard/src/components/shared/label-pill.tsx
··· 1 + import { Row } from "./layout"; 2 + import { COLORS, TYPOGRAPHY } from "./constants"; 3 + 4 + interface LabelPillProps { 5 + name: string; 6 + color: string; 7 + } 8 + 9 + function LabelPill({ name, color }: LabelPillProps) { 10 + return ( 11 + <Row 12 + style={{ 13 + gap: 16, 14 + padding: "16px 28px", 15 + borderRadius: 18, 16 + backgroundColor: "#fff", 17 + border: `4px solid ${COLORS.label.border}`, 18 + }}> 19 + <div 20 + style={{ 21 + width: 24, 22 + height: 24, 23 + borderRadius: "50%", 24 + backgroundColor: color, 25 + }} 26 + /> 27 + <span style={{ ...TYPOGRAPHY.body, color: COLORS.label.text }}> 28 + {name} 29 + </span> 30 + </Row> 31 + ); 32 + } 33 + 34 + interface LabelListProps { 35 + labels: Array<{ name: string; color: string }>; 36 + max?: number; 37 + } 38 + 39 + export function LabelList({ labels, max = 5 }: LabelListProps) { 40 + if (labels.length === 0) return null; 41 + 42 + return ( 43 + <Row style={{ gap: 12 }}> 44 + {labels.slice(0, max).map((label, i) => ( 45 + <LabelPill key={i} name={label.name} color={label.color} /> 46 + ))} 47 + </Row> 48 + ); 49 + }
+56
appview/ogcard/src/components/shared/language-circles.tsx
··· 1 + import type { Language } from "../../validation"; 2 + 3 + interface LanguageCirclesProps { 4 + languages: Language[]; 5 + } 6 + 7 + const MAX_RADIUS = 380; 8 + 9 + function percentageToThickness(percentage: number): number { 10 + return (percentage / 100) * MAX_RADIUS; 11 + } 12 + 13 + export function LanguageCircles({ languages }: LanguageCirclesProps) { 14 + const sortedLanguages = [...languages] 15 + .sort((a, b) => b.percentage - a.percentage) 16 + .slice(0, 5) 17 + .reverse(); 18 + 19 + let cumulativeRadius = 0; 20 + 21 + return ( 22 + <div 23 + style={{ 24 + position: "absolute", 25 + right: -MAX_RADIUS, 26 + top: -MAX_RADIUS, 27 + width: MAX_RADIUS * 2, 28 + height: MAX_RADIUS * 2, 29 + display: "flex", 30 + }}> 31 + {sortedLanguages.map((lang, i) => { 32 + const thickness = percentageToThickness(lang.percentage); 33 + const contentSize = cumulativeRadius * 2; 34 + 35 + cumulativeRadius += thickness; 36 + 37 + return ( 38 + <div 39 + key={i} 40 + style={{ 41 + position: "absolute", 42 + left: "50%", 43 + top: "50%", 44 + transform: "translate(-50%, -50%)", 45 + width: contentSize, 46 + height: contentSize, 47 + borderRadius: "50%", 48 + border: `${thickness}px solid ${lang.color}`, 49 + boxSizing: "content-box", 50 + }} 51 + /> 52 + ); 53 + })} 54 + </div> 55 + ); 56 + }
+45
appview/ogcard/src/components/shared/layout.tsx
··· 1 + import type { ComponentChildren } from "preact"; 2 + 3 + interface StyleProps { 4 + style?: Record<string, string | number>; 5 + children?: ComponentChildren; 6 + } 7 + 8 + export function Card({ children, style }: StyleProps) { 9 + return ( 10 + <div 11 + style={{ 12 + width: 1200, 13 + height: 630, 14 + background: "white", 15 + display: "flex", 16 + flexDirection: "column", 17 + padding: 48, 18 + ...style, 19 + }}> 20 + {children} 21 + </div> 22 + ); 23 + } 24 + 25 + export function Row({ children, style }: StyleProps) { 26 + return ( 27 + <div 28 + style={{ 29 + display: "flex", 30 + flexDirection: "row", 31 + alignItems: "center", 32 + ...style, 33 + }}> 34 + {children} 35 + </div> 36 + ); 37 + } 38 + 39 + export function Col({ children, style }: StyleProps) { 40 + return ( 41 + <div style={{ display: "flex", flexDirection: "column", ...style }}> 42 + {children} 43 + </div> 44 + ); 45 + }
+68
appview/ogcard/src/components/shared/logo.tsx
··· 1 + export function TangledLogo() { 2 + return ( 3 + <div 4 + style={{ 5 + width: 256, 6 + height: 70, 7 + display: "contents", 8 + }}> 9 + <svg 10 + width="256" 11 + height="70" 12 + viewBox="0 0 256 70" 13 + fill="none" 14 + xmlns="http://www.w3.org/2000/svg"> 15 + <path 16 + d="M38.6562 30.0449L39.168 30.2402L39.6807 30.4346L40.0234 30.8076L40.3672 31.1807L40.4922 31.7363L40.6162 32.292L39.9473 35.0566L39.7441 36.6553L39.5049 41.5752L39.3906 42.0312L39.2773 42.4863L38.9443 42.8193L38.6104 43.1514L37.9229 43.4463L37.3574 43.4414L36.793 43.4375L36.4531 43.2549L36.1143 43.0723L35.7949 42.6689L35.4756 42.2666L35.3438 41.7139L35.2109 41.1621L35.3564 37.832L35.5674 35.2646L36.002 33.0186L36.165 32.3838L36.3271 31.748L36.7324 30.9541L37.5293 30.2979L38.0928 30.1719L38.6562 30.0449Z" 17 + fill="black" 18 + /> 19 + <path 20 + d="M30.5889 31.1201L30.8682 31.4277L31.1484 31.7354L31.2695 32.0986L31.3916 32.4619V33.3789L31.1904 33.9082L30.8477 35.5654L30.8486 38.1523L31.1074 40.5127L30.5547 41.7197L30.1074 42.0156L29.6611 42.3115L28.4502 42.3838L28.0098 42.1787L27.5693 41.9727L27.04 41.2539L26.9248 40.9336L26.8086 40.6123L26.6738 39.8008L26.4814 36.9756L26.6992 34.3018L26.8467 33.6553L26.9932 33.0078L27.4502 31.7852L28.0137 31.1162L28.8779 30.7236H29.7334L30.5889 31.1201Z" 21 + fill="black" 22 + /> 23 + <path 24 + fill-rule="evenodd" 25 + clip-rule="evenodd" 26 + d="M45.4551 0L48.0215 0.143555L50.1611 0.571289L51.8721 1.12793L53.5869 1.91602L55.0186 2.78613L56.5781 3.96191L58.3262 5.74609L59.2383 6.91309L59.6455 7.55957L60.0518 8.20605L60.5176 9.16797L60.9824 10.1309L61.2656 10.9355L61.5479 11.7402L61.9658 13.7363L61.999 13.7607L62.0332 13.7852L64.707 15.0918L66.5674 16.3906L68.4297 18.1523L69.5566 19.5645L70.5576 21.1123L71.4766 23.0723L72.0156 24.6768L72.5146 27.0293L72.5107 30.9873L72.1279 32.8848L71.8564 33.7539L71.584 34.623L70.8457 36.3145L69.9648 37.832L68.8193 39.3867L67.2871 41L65.5625 42.3418L63.6367 43.4707L63.5068 43.5762L63.376 43.6816L63.5449 44.0723L63.7148 44.4629L63.9775 45.1045L64.2393 45.7461L65.3809 50.1318L65.5762 51.041L65.7725 51.9492L65.7617 56.0137L65.2803 58.2598L64.7393 59.8643L63.5947 62.2168L62.2529 64.1426L61.3398 65.0898L60.4277 66.0381L58.7158 67.3643L58.0547 67.7627L57.3926 68.1602L56.6641 68.4814L55.9355 68.8018L55.668 68.9443L55.4004 69.0859L53.9033 69.5225L51.9668 69.8672L50.2666 69.8496L49.8389 69.8301L48.7695 69.8105L48.3955 69.8877L48.0205 69.9639L47.8271 69.8506L47.6328 69.7373L46.418 69.6748L45.5889 69.5967L44.7607 69.5176V69.3584L44.1455 69.2383L43.5303 69.1172L43.0264 68.876L42.5225 68.6338L42.5146 68.3857L40.9121 67.5215L39.252 66.2988L37.6768 64.7842L36.3486 63.0723L35.3242 61.3613L35.2842 61.3184L35.2441 61.2744L34.3906 62.2441L33.0488 63.5322L31.9307 64.4111L31.1709 64.9014L30.4121 65.3926L28.915 66.0859L28.9307 66.1836L28.9473 66.2812L28.4307 66.4189L27.915 66.5576L25.8828 67.165L24.8135 67.4365L22.46 67.5928L20.3213 67.3818L20.209 67.29L20.0967 67.1992L19.6338 67.3203L19.4824 67.168L19.3301 67.0166L18.7031 66.8779L18.0752 66.7383L16.2891 66.1172L14.332 65.1357L12.3652 63.7656L10.5332 62.0312L9.18457 60.292L8.41992 59.0078L7.78125 57.7246L7.02539 55.6924L6.52441 53.3398L6.52734 49.1689L6.91406 47.251L7.56738 45.2109L8.51465 43.292L8.72559 42.9502L8.93652 42.6094L8.34277 42.2012L7.80762 41.8965L7.27246 41.5928L5.66895 40.416L4.09082 38.9014L2.95703 37.4893L2.47852 36.748L1.99902 36.0078L1.61035 35.208L1.2207 34.4092L0.859375 33.3926L0.49707 32.377L0 30.0244L0.0126953 25.8525L0.610352 23.291L1.40625 21.2383L1.9082 20.3164L2.41113 19.3955L3.06738 18.5059L3.72461 17.6172L5.24121 16.04L5.93555 15.4834L6.63086 14.9277L8.02148 14.0195L9.95117 13.0859L11.0166 12.6934L12.0859 10.665L13.3633 8.84766L15.252 6.89062L16.0215 6.30273L16.792 5.71582L18.6221 4.63867L20.5225 3.85156L21.7842 3.44922L24.1719 2.97266L27.7012 2.99023L30.375 3.5459L32.1934 4.21094L33.1846 4.70605L34.1768 5.2002L35.6152 3.80762L36.5781 3.05176L37.0596 2.74512L37.541 2.4375L37.9688 2.19043L38.3965 1.94238L39.0918 1.61523L39.7861 1.28711L41.3906 0.693359L43.3164 0.251953L45.4551 0ZM39.8984 21.0156L39.7324 21.3662L38.7773 22.457L37.7549 23.1094L37.0898 23.3311L36.4248 23.5537H34.7402L33.5654 23.1494L33.2246 22.9727L32.8848 22.7969L32.1631 22.1328L31.4424 21.4678L31.1543 21.2139L30.8672 20.959L29.5518 22.1494L29.0537 22.3848L28.5566 22.6211L28.0225 22.7646L27.4873 22.9092L26.8281 22.9102L26.1699 22.9121L25.4941 22.7012L24.8184 22.4912L24.5127 22.7812L24.207 23.0723L23.8438 23.5645L23.4814 24.0576L22.9707 24.248L22.4609 24.4375L21.7676 24.9531L21.4385 25.2959L21.1104 25.6387L18.9746 28.8174L16.3428 34.3018L15.4658 36.334L15.1318 37.6572L15.084 38.2793L15.0371 38.9014L15.1396 39.6318L15.2432 40.3613L15.5322 40.9531L15.8223 41.5439L16.2539 41.9209L16.6855 42.2969L17.8115 42.8223L19.4658 42.834L20.1611 42.54L20.8564 42.2471L21.498 41.7891L23.71 40.0234L23.791 40.0732L23.8711 40.123L24.0127 42.9297L24.5439 46.0146L25.0791 48.2061L25.8701 50.0244L26.1865 50.5049L26.502 50.9863L27.167 51.7002L28.8779 52.9375L29.7461 53.377L31.0381 53.7109L32.5146 53.8965L33.1289 53.8584L33.7441 53.8213L34.3896 53.7686L35.0361 53.7168L36.2783 53.3086L37.0127 52.9541L37.7471 52.6006L39.3975 51.3564L40.7764 49.8105L42.0938 48.0986L43.2275 46.3877L44.5176 44.1416L45.3223 42.2656L45.4082 42.2129L45.4941 42.1592L46.6807 43.7354L47.6641 44.6426L48.2715 44.9209L48.8779 45.2002L49.7871 45.2432L50.6963 45.2852L51.9795 44.8027L53.1855 43.5723L53.4639 42.7344L53.7412 41.8955L54.0879 40.0781L53.9785 37.5107L53.5176 34.9492L53.001 33.0186L52.1523 30.5996L51.1514 28.7402L50.4463 27.7783L50.0723 27.418L49.6982 27.0586L49.8115 25.96L49.4424 24.4629L48.6377 22.751L47.5391 21.5381L46.4971 20.7734L45.8828 21.1426L45.2686 21.5127L43.6631 21.8701L43.0449 21.8047L42.4268 21.7402L41.0137 21.209L40.6572 20.9375L40.3018 20.666H40.0645L39.8984 21.0156Z" 27 + fill="black" 28 + /> 29 + <path 30 + fill-rule="evenodd" 31 + clip-rule="evenodd" 32 + d="M171.79 22.4316C173.259 22.4316 174.489 22.6826 175.479 23.1836C176.47 23.6732 177.268 24.2882 177.871 25.0283C178.486 25.7568 178.958 26.474 179.288 27.1797H179.562V22.7734H186.787V49.2646C186.787 51.4507 186.24 53.2782 185.147 54.7471C184.054 56.2273 182.539 57.3432 180.604 58.0947C178.679 58.8462 176.465 59.2226 173.96 59.2227C171.603 59.2227 169.582 58.9032 167.896 58.2656C166.223 57.628 164.89 56.7683 163.899 55.6865C162.909 54.6049 162.266 53.4037 161.97 52.083L168.699 51.1777C168.904 51.6559 169.229 52.1061 169.673 52.5273C170.117 52.9598 170.703 53.3127 171.432 53.5859C172.172 53.8592 173.071 53.9961 174.13 53.9961C175.713 53.9961 177.017 53.6197 178.042 52.8682C179.078 52.1166 179.596 50.8813 179.596 49.1621V44.3623H179.288C178.969 45.0911 178.491 45.7806 177.854 46.4297C177.216 47.0785 176.396 47.6077 175.395 48.0176C174.393 48.4275 173.197 48.6328 171.808 48.6328C169.838 48.6328 168.044 48.1775 166.427 47.2666C164.821 46.3443 163.54 44.9379 162.584 43.0479C161.639 41.1463 161.167 38.7434 161.167 35.8398C161.167 32.8679 161.65 30.3853 162.618 28.3926C163.586 26.3999 164.873 24.9086 166.479 23.918C168.095 22.9274 169.866 22.4317 171.79 22.4316ZM174.113 28.2217C172.918 28.2217 171.91 28.5463 171.09 29.1953C170.27 29.833 169.65 30.7217 169.229 31.8604C168.807 32.999 168.597 34.3141 168.597 35.8057C168.597 37.32 168.807 38.6299 169.229 39.7344C169.661 40.8273 170.281 41.6759 171.09 42.2793C171.91 42.8714 172.918 43.167 174.113 43.167C175.286 43.167 176.277 42.8765 177.085 42.2959C177.905 41.7039 178.531 40.8615 178.964 39.7686C179.408 38.6641 179.63 37.3428 179.63 35.8057C179.63 34.2685 179.414 32.9359 178.981 31.8086C178.549 30.67 177.922 29.7874 177.103 29.1611C176.283 28.5349 175.286 28.2217 174.113 28.2217Z" 33 + fill="black" 34 + /> 35 + <path 36 + fill-rule="evenodd" 37 + clip-rule="evenodd" 38 + d="M215.798 22.4316C217.528 22.4317 219.139 22.7107 220.631 23.2686C222.134 23.8151 223.444 24.6407 224.56 25.7451C225.687 26.8496 226.564 28.2392 227.19 29.9131C227.817 31.5754 228.13 33.5224 228.13 35.7539V37.7529H210.264V37.7695C210.264 39.0676 210.503 40.1897 210.981 41.1348C211.471 42.0796 212.16 42.808 213.048 43.3203C213.936 43.8327 214.99 44.0889 216.208 44.0889C217.016 44.0888 217.756 43.9757 218.428 43.748C219.1 43.5203 219.675 43.1781 220.153 42.7227C220.631 42.2672 220.996 41.7091 221.246 41.0488L227.976 41.4932C227.634 43.1101 226.934 44.5225 225.875 45.7295C224.827 46.925 223.472 47.8585 221.81 48.5303C220.159 49.1906 218.251 49.5205 216.088 49.5205C213.389 49.5205 211.066 48.974 209.119 47.8809C207.184 46.7764 205.692 45.2165 204.645 43.2012C203.597 41.1744 203.073 38.7776 203.073 36.0107C203.073 33.3121 203.597 30.9435 204.645 28.9053C205.692 26.867 207.167 25.2783 209.068 24.1396C210.981 23.0011 213.225 22.4316 215.798 22.4316ZM215.917 27.8633C214.813 27.8633 213.833 28.1195 212.979 28.6318C212.137 29.1328 211.476 29.8102 210.998 30.6641C210.557 31.4413 210.317 32.3013 210.273 33.2432H221.28C221.28 32.1957 221.052 31.2674 220.597 30.459C220.141 29.6508 219.509 29.0189 218.701 28.5635C217.904 28.0967 216.976 27.8633 215.917 27.8633Z" 39 + fill="black" 40 + /> 41 + <path 42 + fill-rule="evenodd" 43 + clip-rule="evenodd" 44 + d="M118.389 22.4316C119.846 22.4316 121.241 22.6028 122.573 22.9443C123.917 23.2859 125.107 23.8149 126.144 24.5322C127.191 25.2496 128.017 26.1725 128.62 27.2998C129.224 28.4157 129.525 29.7536 129.525 31.3135V49.0088H122.625V45.3701H122.42C121.999 46.1898 121.435 46.9129 120.729 47.5391C120.024 48.1539 119.175 48.6382 118.185 48.9912C117.194 49.3328 116.049 49.5039 114.751 49.5039C113.077 49.5039 111.586 49.2134 110.276 48.6328C108.967 48.0407 107.93 47.1696 107.167 46.0195C106.416 44.8581 106.04 43.4114 106.04 41.6807C106.04 40.2233 106.308 38.9993 106.843 38.0088C107.378 37.0181 108.107 36.2207 109.029 35.6172C109.952 35.0138 110.999 34.5584 112.172 34.251C113.356 33.9435 114.597 33.7278 115.896 33.6025C117.421 33.4431 118.651 33.2948 119.585 33.1582C120.519 33.0102 121.196 32.7934 121.617 32.5088C122.038 32.2241 122.249 31.803 122.249 31.2451V31.1426C122.249 30.0609 121.908 29.2239 121.225 28.6318C120.553 28.0397 119.596 27.7441 118.354 27.7441C117.045 27.7442 116.004 28.0346 115.229 28.6152C114.455 29.1845 113.943 29.9014 113.692 30.7666L106.963 30.2207C107.304 28.6266 107.976 27.2483 108.978 26.0869C109.98 24.9141 111.273 24.0149 112.855 23.3887C114.449 22.7511 116.294 22.4317 118.389 22.4316ZM122.301 36.8477C122.073 36.9956 121.76 37.1316 121.361 37.2568C120.974 37.3707 120.536 37.4796 120.046 37.582C119.556 37.6731 119.067 37.7582 118.577 37.8379C118.088 37.9062 117.644 37.9694 117.245 38.0264C116.391 38.1516 115.644 38.3507 115.007 38.624C114.369 38.8973 113.874 39.2676 113.521 39.7344C113.169 40.1898 112.992 40.7593 112.992 41.4424C112.992 42.4327 113.35 43.1902 114.067 43.7139C114.796 44.2263 115.719 44.4824 116.835 44.4824C117.905 44.4824 118.85 44.2718 119.67 43.8506C120.49 43.4179 121.134 42.8371 121.601 42.1084C122.067 41.3798 122.301 40.554 122.301 39.6318V36.8477Z" 45 + fill="black" 46 + /> 47 + <path 48 + fill-rule="evenodd" 49 + clip-rule="evenodd" 50 + d="M256 14.0283V49.0088H248.826V44.8066H248.52C248.178 45.5353 247.694 46.2583 247.067 46.9756C246.453 47.6815 245.65 48.2685 244.659 48.7354C243.68 49.2022 242.484 49.4355 241.072 49.4355C239.08 49.4355 237.275 48.9231 235.658 47.8984C234.053 46.8623 232.777 45.3419 231.832 43.3379C230.898 41.3224 230.432 38.8512 230.432 35.9248C230.432 32.9188 230.915 30.4194 231.883 28.4268C232.851 26.4227 234.138 24.9252 235.743 23.9346C237.36 22.9326 239.131 22.4317 241.055 22.4316C242.524 22.4316 243.748 22.6826 244.728 23.1836C245.718 23.6732 246.515 24.2883 247.118 25.0283C247.733 25.757 248.201 26.4738 248.52 27.1797H248.741V14.0283H256ZM243.378 28.2217C242.182 28.2217 241.174 28.5463 240.354 29.1953C239.535 29.8444 238.914 30.7445 238.493 31.8945C238.072 33.0445 237.861 34.3764 237.861 35.8906C237.861 37.4163 238.072 38.7657 238.493 39.9385C238.926 41.0999 239.546 42.0114 240.354 42.6719C241.174 43.3209 242.182 43.6455 243.378 43.6455C244.551 43.6455 245.541 43.3261 246.35 42.6885C247.169 42.0394 247.796 41.1341 248.229 39.9727C248.673 38.8113 248.895 37.4505 248.895 35.8906C248.895 34.3309 248.679 32.9761 248.246 31.8262C247.813 30.6761 247.187 29.7874 246.367 29.1611C245.547 28.5349 244.551 28.2217 243.378 28.2217Z" 51 + fill="black" 52 + /> 53 + <path 54 + d="M99.0752 16.4883V22.7734H104.012V28.2393H99.0752V40.9463C99.0752 41.6179 99.178 42.1418 99.3828 42.5176C99.5878 42.882 99.8729 43.1381 100.237 43.2861C100.613 43.4341 101.046 43.5088 101.535 43.5088C101.877 43.5088 102.218 43.4798 102.56 43.4229C102.901 43.3545 103.164 43.3037 103.346 43.2695L104.49 48.6836C104.126 48.7974 103.613 48.9292 102.953 49.0771C102.293 49.2366 101.489 49.333 100.544 49.3672C98.7906 49.4354 97.2534 49.2021 95.9326 48.667C94.6232 48.1318 93.6037 47.3001 92.875 46.1729C92.1464 45.0457 91.7885 43.6224 91.7998 41.9033V28.2393H88.2129V22.7734H91.7998V16.4883H99.0752Z" 55 + fill="black" 56 + /> 57 + <path 58 + d="M198.397 41.1514C198.409 41.9824 198.556 42.5861 198.841 42.9619C199.137 43.3263 199.639 43.5088 200.345 43.5088C200.709 43.4974 200.993 43.4745 201.198 43.4404C201.403 43.4063 201.574 43.3607 201.711 43.3037L202.872 48.5986C202.496 48.7125 202.035 48.8318 201.488 48.957C200.953 49.0709 200.23 49.1446 199.319 49.1787C196.53 49.2812 194.469 48.7575 193.137 47.6074C191.804 46.446 191.132 44.6187 191.121 42.125V14.0283H198.397V41.1514Z" 59 + fill="black" 60 + /> 61 + <path 62 + d="M148.839 22.4316C150.661 22.4316 152.249 22.8299 153.604 23.627C154.959 24.4239 156.012 25.5629 156.764 27.043C157.515 28.5118 157.892 30.2656 157.892 32.3037V49.0088H150.615V33.6025C150.627 31.9972 150.217 30.7443 149.386 29.8447C148.555 28.9338 147.41 28.4785 145.952 28.4785C144.973 28.4785 144.108 28.6891 143.356 29.1104C142.616 29.5317 142.036 30.1466 141.614 30.9551C141.204 31.752 140.994 32.7138 140.982 33.8408V49.0088H133.706V22.7734H140.641V27.4023H140.948C141.529 25.8766 142.502 24.6694 143.868 23.7812C145.235 22.8817 146.892 22.4316 148.839 22.4316Z" 63 + fill="black" 64 + /> 65 + </svg> 66 + </div> 67 + ); 68 + }
+43
appview/ogcard/src/components/shared/metrics.tsx
··· 1 + import { Row, Col } from "./layout"; 2 + import { TYPOGRAPHY } from "./constants"; 3 + import { 4 + Star, 5 + GitPullRequest, 6 + CircleDot, 7 + type LucideIcon, 8 + } from "../../icons/lucide"; 9 + 10 + interface MetricsProps { 11 + stars: number; 12 + pulls: number; 13 + issues: number; 14 + } 15 + 16 + // Display stars, pulls, issues with Lucide icons 17 + export function Metrics({ stars, pulls, issues }: MetricsProps) { 18 + return ( 19 + <Row style={{ gap: 56, alignItems: "flex-start" }}> 20 + <MetricItem value={stars} label="stars" Icon={Star} /> 21 + <MetricItem value={pulls} label="pulls" Icon={GitPullRequest} /> 22 + <MetricItem value={issues} label="issues" Icon={CircleDot} /> 23 + </Row> 24 + ); 25 + } 26 + 27 + interface MetricItemProps { 28 + value: number; 29 + label: string; 30 + Icon: LucideIcon; 31 + } 32 + 33 + function MetricItem({ value, label, Icon }: MetricItemProps) { 34 + return ( 35 + <Col style={{ gap: 12 }}> 36 + <Row style={{ gap: 12, alignItems: "center" }}> 37 + <span style={TYPOGRAPHY.metricValue}>{value}</span> 38 + <Icon size={48} /> 39 + </Row> 40 + <span style={{ ...TYPOGRAPHY.label, opacity: 0.75 }}>{label}</span> 41 + </Col> 42 + ); 43 + }
+17
appview/ogcard/src/components/shared/stat-item.tsx
··· 1 + import { Row } from "./layout"; 2 + import { TYPOGRAPHY } from "./constants"; 3 + import type { LucideIcon } from "../../icons/lucide"; 4 + 5 + interface StatItemProps { 6 + Icon: LucideIcon; 7 + value: string | number; 8 + } 9 + 10 + export function StatItem({ Icon, value }: StatItemProps) { 11 + return ( 12 + <Row style={{ gap: 16 }}> 13 + <Icon size={36} color="#404040" /> 14 + <span style={{ ...TYPOGRAPHY.body, color: "#404040" }}>{value}</span> 15 + </Row> 16 + ); 17 + }
+73
appview/ogcard/src/components/shared/status-badge.tsx
··· 1 + import { Row } from "./layout"; 2 + import { 3 + CircleDot, 4 + Ban, 5 + GitPullRequest, 6 + GitPullRequestClosed, 7 + GitMerge, 8 + } from "../../icons/lucide"; 9 + import { COLORS, TYPOGRAPHY } from "./constants"; 10 + 11 + const STATUS_CONFIG = { 12 + open: { 13 + Icon: CircleDot, 14 + bg: COLORS.status.open.bg, 15 + text: COLORS.status.open.text, 16 + }, 17 + closed: { 18 + Icon: Ban, 19 + bg: COLORS.status.closed.bg, 20 + text: COLORS.status.closed.text, 21 + }, 22 + merged: { 23 + Icon: GitMerge, 24 + bg: COLORS.status.merged.bg, 25 + text: COLORS.status.merged.text, 26 + }, 27 + } as const; 28 + 29 + interface StatusBadgeProps { 30 + status: "open" | "closed" | "merged"; 31 + } 32 + 33 + export function StatusBadge({ status }: StatusBadgeProps) { 34 + const config = 35 + status === "merged" 36 + ? STATUS_CONFIG.merged 37 + : status === "closed" 38 + ? STATUS_CONFIG.closed 39 + : STATUS_CONFIG.open; 40 + const Icon = config.Icon; 41 + 42 + return ( 43 + <Row 44 + style={{ 45 + gap: 12, 46 + padding: "14px 26px 14px 24px", 47 + borderRadius: 18, 48 + backgroundColor: config.bg, 49 + }}> 50 + <Icon size={48} color={config.text} /> 51 + <span style={{ ...TYPOGRAPHY.status, color: config.text }}>{status}</span> 52 + </Row> 53 + ); 54 + } 55 + 56 + export function IssueStatusBadge({ status }: { status: "open" | "closed" }) { 57 + const config = 58 + status === "closed" ? STATUS_CONFIG.closed : STATUS_CONFIG.open; 59 + const Icon = config.Icon; 60 + 61 + return ( 62 + <Row 63 + style={{ 64 + gap: 12, 65 + padding: "14px 26px 14px 24px", 66 + borderRadius: 18, 67 + backgroundColor: config.bg, 68 + }}> 69 + <Icon size={48} color={config.text} /> 70 + <span style={{ ...TYPOGRAPHY.status, color: config.text }}>{status}</span> 71 + </Row> 72 + ); 73 + }
+52
appview/ogcard/src/icons/lucide.tsx
··· 1 + import { h } from "preact"; 2 + import iconNodes from "lucide-static/icon-nodes.json"; 3 + 4 + interface IconProps { 5 + size?: number; 6 + color?: string; 7 + strokeWidth?: number; 8 + } 9 + 10 + type IconNodeEntry = [string, Record<string, string | number>]; 11 + 12 + function createIcon(name: string) { 13 + const nodes = (iconNodes as unknown as Record<string, IconNodeEntry[]>)[name]; 14 + if (!nodes) throw new Error(`Icon "${name}" not found`); 15 + 16 + return function Icon({ 17 + size = 24, 18 + color = "currentColor", 19 + strokeWidth = 2, 20 + }: IconProps = {}) { 21 + return h( 22 + "svg", 23 + { 24 + xmlns: "http://www.w3.org/2000/svg", 25 + width: size, 26 + height: size, 27 + viewBox: "0 0 24 24", 28 + fill: "none", 29 + stroke: color, 30 + strokeWidth, 31 + strokeLinecap: "round" as const, 32 + strokeLinejoin: "round" as const, 33 + }, 34 + nodes.map(([tag, attrs], i) => h(tag, { key: i, ...attrs })), 35 + ); 36 + }; 37 + } 38 + 39 + export const Star = createIcon("star"); 40 + export const GitPullRequest = createIcon("git-pull-request"); 41 + export const GitPullRequestClosed = createIcon("git-pull-request-closed"); 42 + export const GitMerge = createIcon("git-merge"); 43 + export const CircleDot = createIcon("circle-dot"); 44 + export const Calendar = createIcon("calendar"); 45 + export const MessageSquare = createIcon("message-square"); 46 + export const MessageSquareCode = createIcon("message-square-code"); 47 + export const Ban = createIcon("ban"); 48 + export const SmilePlus = createIcon("smile-plus"); 49 + export const FileDiff = createIcon("file-diff"); 50 + export const RefreshCw = createIcon("refresh-cw"); 51 + 52 + export type LucideIcon = typeof Star;
+96
appview/ogcard/src/index.tsx
··· 1 + import { cardPayloadSchema } from "./validation"; 2 + import { renderCard } from "./lib/render"; 3 + import { RepositoryCard } from "./components/cards/repository"; 4 + import { IssueCard } from "./components/cards/issue"; 5 + import { PullRequestCard } from "./components/cards/pull-request"; 6 + import { z } from "zod"; 7 + 8 + declare global { 9 + interface CacheStorage { 10 + default: Cache; 11 + } 12 + } 13 + 14 + interface Env { 15 + ENVIRONMENT: string; 16 + } 17 + 18 + export default { 19 + async fetch(request: Request, env: Env): Promise<Response> { 20 + if (request.method !== "POST") { 21 + return new Response("Method not allowed", { status: 405 }); 22 + } 23 + 24 + const url = new URL(request.url); 25 + const cardType = url.pathname.split("/").pop(); 26 + 27 + try { 28 + const body = await request.json(); 29 + const payload = cardPayloadSchema.parse(body); 30 + 31 + let component; 32 + switch (payload.type) { 33 + case "repository": 34 + component = <RepositoryCard {...payload} />; 35 + break; 36 + case "issue": 37 + component = <IssueCard {...payload} />; 38 + break; 39 + case "pullRequest": 40 + component = <PullRequestCard {...payload} />; 41 + break; 42 + default: 43 + return new Response("Unknown card type", { status: 400 }); 44 + } 45 + 46 + const cacheKeyUrl = new URL(request.url); 47 + cacheKeyUrl.searchParams.set("payload", JSON.stringify(payload)); 48 + const cacheKey = new Request(cacheKeyUrl.toString(), { method: "GET" }); 49 + const cache = caches.default; 50 + const cached = await cache.match(cacheKey); 51 + 52 + if (cached) { 53 + return cached; 54 + } 55 + 56 + const { png: pngBuffer } = await renderCard(component); 57 + 58 + const response = new Response(pngBuffer as any, { 59 + headers: { 60 + "Content-Type": "image/png", 61 + "Cache-Control": "public, max-age=3600", 62 + }, 63 + }); 64 + 65 + await cache.put(cacheKey, response.clone()); 66 + 67 + return response; 68 + } catch (error) { 69 + if (error instanceof z.ZodError) { 70 + return new Response( 71 + JSON.stringify({ errors: (error as z.ZodError).issues }), 72 + { 73 + status: 400, 74 + headers: { "Content-Type": "application/json" }, 75 + }, 76 + ); 77 + } 78 + 79 + console.error("Error generating card:", error); 80 + const errorMessage = 81 + error instanceof Error ? error.message : String(error); 82 + const errorStack = error instanceof Error ? error.stack : ""; 83 + console.error("Error stack:", errorStack); 84 + return new Response( 85 + JSON.stringify({ 86 + error: errorMessage, 87 + stack: errorStack, 88 + }), 89 + { 90 + status: 500, 91 + headers: { "Content-Type": "application/json" }, 92 + }, 93 + ); 94 + } 95 + }, 96 + };
+46
appview/ogcard/src/lib/render.ts
··· 1 + import type { VNode } from "preact"; 2 + import { initSatori, initResvg, loadFonts } from "@tangled/ogcard-runtime"; 3 + import type { ResvgClass } from "@tangled/ogcard-runtime/types"; 4 + 5 + let satoriFn: typeof import("satori").default | null = null; 6 + let Resvg: ResvgClass | null = null; 7 + let fontsLoaded = false; 8 + let cachedFonts: Awaited<ReturnType<typeof loadFonts>> | null = null; 9 + 10 + export interface RenderResult { 11 + svg: string; 12 + png: Uint8Array; 13 + } 14 + 15 + export async function renderCard(component: VNode): Promise<RenderResult> { 16 + if (!satoriFn) { 17 + satoriFn = await initSatori(); 18 + } 19 + 20 + if (!Resvg) { 21 + Resvg = await initResvg(); 22 + } 23 + 24 + if (!fontsLoaded) { 25 + cachedFonts = await loadFonts(); 26 + fontsLoaded = true; 27 + } 28 + 29 + const svg = await satoriFn(component as any, { 30 + width: 1200, 31 + height: 630, 32 + fonts: cachedFonts!, 33 + embedFont: true, 34 + }); 35 + 36 + const resvg = new Resvg!(svg, { 37 + fitTo: { mode: "width", value: 1200 }, 38 + }); 39 + 40 + const pngData = resvg.render(); 41 + 42 + return { 43 + svg, 44 + png: pngData.asPng(), 45 + }; 46 + }
+9
appview/ogcard/src/types.d.ts
··· 1 + declare module "*.wasm?module" { 2 + const value: WebAssembly.Module; 3 + export default value; 4 + } 5 + 6 + declare module "*.woff" { 7 + const value: ArrayBuffer; 8 + export default value; 9 + }
+70
appview/ogcard/src/validation.ts
··· 1 + import { z } from "zod"; 2 + 3 + const hexColor = /^#[0-9A-Fa-f]{6}$/; 4 + 5 + const languageSchema = z.object({ 6 + color: z.string().regex(hexColor), 7 + percentage: z.number().min(0).max(100), 8 + }); 9 + 10 + export const repositoryCardSchema = z.object({ 11 + type: z.literal("repository"), 12 + repoName: z.string().min(1).max(100), 13 + ownerHandle: z.string().min(1).max(100), 14 + stars: z.number().int().min(0).max(1000000), 15 + pulls: z.number().int().min(0).max(100000), 16 + issues: z.number().int().min(0).max(100000), 17 + createdAt: z.string().max(100), 18 + avatarUrl: z.string().url(), 19 + languages: z.array(languageSchema).max(5), 20 + }); 21 + 22 + export const issueCardSchema = z.object({ 23 + type: z.literal("issue"), 24 + repoName: z.string().min(1).max(100), 25 + ownerHandle: z.string().min(1).max(100), 26 + avatarUrl: z.string().url(), 27 + title: z.string().min(1).max(500), 28 + issueNumber: z.number().int().positive(), 29 + status: z.enum(["open", "closed"]), 30 + labels: z 31 + .array( 32 + z.object({ 33 + name: z.string().max(50), 34 + color: z.string().regex(hexColor), 35 + }), 36 + ) 37 + .max(10), 38 + commentCount: z.number().int().min(0), 39 + reactionCount: z.number().int().min(0), 40 + createdAt: z.string(), 41 + }); 42 + 43 + export const pullRequestCardSchema = z.object({ 44 + type: z.literal("pullRequest"), 45 + repoName: z.string().min(1).max(100), 46 + ownerHandle: z.string().min(1).max(100), 47 + avatarUrl: z.string().url(), 48 + title: z.string().min(1).max(500), 49 + pullRequestNumber: z.number().int().positive(), 50 + status: z.enum(["open", "closed", "merged"]), 51 + filesChanged: z.number().int().min(0), 52 + additions: z.number().int().min(0), 53 + deletions: z.number().int().min(0), 54 + rounds: z.number().int().min(1), 55 + // reviews: z.number().int().min(0), // TODO: implement review tracking 56 + commentCount: z.number().int().min(0), 57 + reactionCount: z.number().int().min(0), 58 + createdAt: z.string(), 59 + }); 60 + 61 + export const cardPayloadSchema = z.discriminatedUnion("type", [ 62 + repositoryCardSchema, 63 + issueCardSchema, 64 + pullRequestCardSchema, 65 + ]); 66 + 67 + export type Language = z.infer<typeof languageSchema>; 68 + export type RepositoryCardData = z.infer<typeof repositoryCardSchema>; 69 + export type IssueCardData = z.infer<typeof issueCardSchema>; 70 + export type PullRequestCardData = z.infer<typeof pullRequestCardSchema>;
+19
appview/ogcard/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ES2022", 5 + "lib": ["ES2022"], 6 + "moduleResolution": "bundler", 7 + "types": ["@cloudflare/workers-types", "node", "bun"], 8 + "jsx": "react-jsx", 9 + "jsxImportSource": "preact", 10 + "strict": true, 11 + "esModuleInterop": true, 12 + "skipLibCheck": true, 13 + "forceConsistentCasingInFileNames": true, 14 + "resolveJsonModule": true, 15 + "noEmit": true 16 + }, 17 + "include": ["src/**/*"], 18 + "exclude": ["node_modules"] 19 + }
+25
appview/ogcard/wrangler.jsonc
··· 1 + { 2 + "$schema": "node_modules/wrangler/config-schema.json", 3 + "name": "tangled-ogcard-worker", 4 + "main": "src/index.tsx", 5 + "compatibility_date": "2026-03-07", 6 + "observability": { 7 + "enabled": true, 8 + }, 9 + "routes": [ 10 + { 11 + "pattern": "og.tangled.org/*", 12 + "zone_name": "tangled.org", 13 + }, 14 + ], 15 + "vars": { 16 + "ENVIRONMENT": "production", 17 + }, 18 + "rules": [ 19 + { 20 + "type": "Data", 21 + "globs": ["**/*.woff"], 22 + "fallthrough": true, 23 + }, 24 + ], 25 + }
+4 -4
appview/pulls/opengraph.go
··· 7 7 "time" 8 8 9 9 "tangled.org/core/appview/models" 10 - "tangled.org/core/ogre" 10 + "tangled.org/core/appview/ogcard" 11 11 "tangled.org/core/patchutil" 12 12 ) 13 13 ··· 30 30 if err != nil { 31 31 ownerHandle = f.Did 32 32 } else { 33 - ownerHandle = owner.Handle.String() 33 + ownerHandle = "@" + owner.Handle.String() 34 34 } 35 35 36 36 var authorHandle string ··· 71 71 rounds = 1 72 72 } 73 73 74 - payload := ogre.PullRequestCardPayload{ 74 + payload := ogcard.PullRequestCardPayload{ 75 75 Type: "pullRequest", 76 76 RepoName: f.Name, 77 77 OwnerHandle: ownerHandle, ··· 88 88 CreatedAt: pull.Created.Format(time.RFC3339), 89 89 } 90 90 91 - imageBytes, err := s.ogreClient.RenderPullRequestCard(r.Context(), payload) 91 + imageBytes, err := s.ogcardClient.RenderPullRequestCard(r.Context(), payload) 92 92 if err != nil { 93 93 log.Println("failed to render pull request card", err) 94 94 http.Error(w, "failed to render pull request card", http.StatusInternalServerError)
+3 -3
appview/pulls/pulls.go
··· 26 26 "tangled.org/core/appview/models" 27 27 "tangled.org/core/appview/notify" 28 28 "tangled.org/core/appview/oauth" 29 - "tangled.org/core/ogre" 29 + "tangled.org/core/appview/ogcard" 30 30 "tangled.org/core/appview/pages" 31 31 "tangled.org/core/appview/pages/markup" 32 32 "tangled.org/core/appview/pages/repoinfo" ··· 66 66 logger *slog.Logger 67 67 validator *validator.Validator 68 68 indexer *pulls_indexer.Indexer 69 - ogreClient *ogre.Client 69 + ogcardClient *ogcard.Client 70 70 } 71 71 72 72 func New( ··· 96 96 logger: logger, 97 97 validator: validator, 98 98 indexer: indexer, 99 - ogreClient: ogre.NewClient(config.Ogre.Host), 99 + ogcardClient: ogcard.NewClient(config.Ogcard.Host), 100 100 } 101 101 } 102 102
+6 -6
appview/repo/opengraph.go
··· 9 9 10 10 "github.com/go-enry/go-enry/v2" 11 11 "tangled.org/core/appview/db" 12 - "tangled.org/core/ogre" 12 + "tangled.org/core/appview/ogcard" 13 13 "tangled.org/core/orm" 14 14 "tangled.org/core/types" 15 15 ) ··· 26 26 if err != nil { 27 27 ownerHandle = f.Did 28 28 } else { 29 - ownerHandle = owner.Handle.String() 29 + ownerHandle = "@" + owner.Handle.String() 30 30 } 31 31 32 32 avatarUrl := rp.pages.AvatarUrl(ownerHandle, "256") ··· 69 69 }) 70 70 } 71 71 72 - ogLanguages := []ogre.LanguageData{} 72 + var ogLanguages []ogcard.LanguageData 73 73 for _, lang := range languageStats { 74 74 if len(ogLanguages) >= 5 { 75 75 break 76 76 } 77 - ogLanguages = append(ogLanguages, ogre.LanguageData{ 77 + ogLanguages = append(ogLanguages, ogcard.LanguageData{ 78 78 Color: lang.Color, 79 79 Percentage: lang.Percentage, 80 80 }) 81 81 } 82 82 83 - payload := ogre.RepositoryCardPayload{ 83 + payload := ogcard.RepositoryCardPayload{ 84 84 Type: "repository", 85 85 RepoName: f.Name, 86 86 OwnerHandle: ownerHandle, ··· 92 92 Languages: ogLanguages, 93 93 } 94 94 95 - imageBytes, err := rp.ogreClient.RenderRepositoryCard(r.Context(), payload) 95 + imageBytes, err := rp.ogcardClient.RenderRepositoryCard(r.Context(), payload) 96 96 if err != nil { 97 97 log.Println("failed to render repository card", err) 98 98 http.Error(w, "failed to render repository card", http.StatusInternalServerError)
+3 -3
appview/repo/repo.go
··· 20 20 "tangled.org/core/appview/models" 21 21 "tangled.org/core/appview/notify" 22 22 "tangled.org/core/appview/oauth" 23 - "tangled.org/core/ogre" 23 + "tangled.org/core/appview/ogcard" 24 24 "tangled.org/core/appview/pages" 25 25 "tangled.org/core/appview/reporesolver" 26 26 "tangled.org/core/appview/validator" ··· 54 54 serviceAuth *serviceauth.ServiceAuth 55 55 validator *validator.Validator 56 56 cfClient *cloudflare.Client 57 - ogreClient *ogre.Client 57 + ogcardClient *ogcard.Client 58 58 } 59 59 60 60 func New( ··· 84 84 logger: logger, 85 85 validator: validator, 86 86 cfClient: cfClient, 87 - ogreClient: ogre.NewClient(config.Ogre.Host), 87 + ogcardClient: ogcard.NewClient(config.Ogcard.Host), 88 88 } 89 89 } 90 90
+1 -2
flake.nix
··· 107 107 knot = self.callPackage ./nix/pkgs/knot.nix {}; 108 108 dolly = self.callPackage ./nix/pkgs/dolly.nix {}; 109 109 tap = self.callPackage ./nix/pkgs/tap.nix {}; 110 - knotmirror = self.callPackage ./nix/pkgs/knotmirror.nix {}; 110 + knotmirror = self.callPackage ./nix/pkgs/knot-mirror.nix {}; 111 111 }); 112 112 in { 113 113 overlays.default = final: prev: { ··· 133 133 docs 134 134 dolly 135 135 tap 136 - knotmirror 137 136 ; 138 137 139 138 pkgsStatic-appview = staticPackages.appview;
+19 -12
go.mod
··· 7 7 github.com/alecthomas/assert/v2 v2.11.0 8 8 github.com/alecthomas/chroma/v2 v2.23.1 9 9 github.com/avast/retry-go/v4 v4.6.1 10 - github.com/aws/aws-sdk-go-v2 v1.41.1 11 - github.com/aws/aws-sdk-go-v2/credentials v1.19.9 12 - github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 10 + github.com/aws/aws-sdk-go-v2 v1.41.4 11 + github.com/aws/aws-sdk-go-v2/credentials v1.19.12 12 + github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1 13 13 github.com/blevesearch/bleve/v2 v2.5.3 14 14 github.com/bluekeyes/go-gitdiff v0.8.1 15 15 github.com/bluesky-social/indigo v0.0.0-20260220055544-bf41e2ee75ab ··· 70 70 github.com/adrg/frontmatter v0.2.0 // indirect 71 71 github.com/alecthomas/repr v0.5.2 // indirect 72 72 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect 73 - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect 74 - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect 75 - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect 76 - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 // indirect 77 - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect 78 - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 // indirect 79 - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect 80 - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect 81 - github.com/aws/smithy-go v1.24.0 // indirect 73 + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect 74 + github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect 75 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect 76 + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect 77 + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect 78 + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect 79 + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 // indirect 80 + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect 81 + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 // indirect 82 + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect 83 + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 // indirect 84 + github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect 85 + github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect 86 + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect 87 + github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect 88 + github.com/aws/smithy-go v1.24.2 // indirect 82 89 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 83 90 github.com/aymerick/douceur v0.2.0 // indirect 84 91 github.com/beorn7/perks v1.0.1 // indirect
+38
go.sum
··· 27 27 github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA= 28 28 github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= 29 29 github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= 30 + github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= 31 + github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= 30 32 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= 31 33 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= 34 + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/E37XNTaWJ8W6vrbYV9lJEkCnhuY= 35 + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= 36 + github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= 37 + github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= 32 38 github.com/aws/aws-sdk-go-v2/credentials v1.19.9 h1:sWvTKsyrMlJGEuj/WgrwilpoJ6Xa1+KhIpGdzw7mMU8= 33 39 github.com/aws/aws-sdk-go-v2/credentials v1.19.9/go.mod h1:+J44MBhmfVY/lETFiKI+klz0Vym2aCmIjqgClMmW82w= 40 + github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= 41 + github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= 42 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= 43 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= 34 44 github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= 35 45 github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= 46 + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= 47 + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= 36 48 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= 37 49 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= 50 + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= 51 + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= 52 + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= 53 + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= 38 54 github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 h1:JqcdRG//czea7Ppjb+g/n4o8i/R50aTBHkA7vu0lK+k= 39 55 github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17/go.mod h1:CO+WeGmIdj/MlPel2KwID9Gt7CNq4M65HUfBW97liM0= 56 + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 h1:SwGMTMLIlvDNyhMteQ6r8IJSBPlRdXX5d4idhIGbkXA= 57 + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21/go.mod h1:UUxgWxofmOdAMuqEsSppbDtGKLfR04HGsD0HXzvhI1k= 40 58 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= 41 59 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= 60 + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= 61 + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= 42 62 github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 h1:Z5EiPIzXKewUQK0QTMkutjiaPVeVYXX7KIqhXu/0fXs= 43 63 github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8/go.mod h1:FsTpJtvC4U1fyDXk7c71XoDv3HlRm8V3NiYLeYLh5YE= 64 + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 h1:qtJZ70afD3ISKWnoX3xB0J2otEqu3LqicRcDBqsj0hQ= 65 + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12/go.mod h1:v2pNpJbRNl4vEUWEh5ytQok0zACAKfdmKS51Hotc3pQ= 44 66 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= 45 67 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= 68 + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= 69 + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= 46 70 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 h1:bGeHBsGZx0Dvu/eJC0Lh9adJa3M1xREcndxLNZlve2U= 47 71 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17/go.mod h1:dcW24lbU0CzHusTE8LLHhRLI42ejmINN8Lcr22bwh/g= 72 + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 h1:siU1A6xjUZ2N8zjTHSXFhB9L/2OY8Dqs0xXiLjF30jA= 73 + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20/go.mod h1:4TLZCmVJDM3FOu5P5TJP0zOlu9zWgDWU7aUxWbr+rcw= 48 74 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 h1:oeu8VPlOre74lBA/PMhxa5vewaMIMmILM+RraSyB8KA= 49 75 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0/go.mod h1:5jggDlZ2CLQhwJBiZJb4vfk4f0GxWdEDruWKEJ1xOdo= 76 + github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1 h1:csi9NLpFZXb9fxY7rS1xVzgPRGMt7MSNWeQ6eo247kE= 77 + github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1/go.mod h1:qXVal5H0ChqXP63t6jze5LmFalc7+ZE7wOdLtZ0LCP0= 78 + github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= 79 + github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= 80 + github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= 81 + github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= 82 + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= 83 + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= 84 + github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= 85 + github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= 50 86 github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= 51 87 github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= 88 + github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= 89 + github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= 52 90 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 53 91 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 54 92 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
-56
knotmirror/hostutil/hostutil.go
··· 1 - package hostutil 2 - 3 - import ( 4 - "fmt" 5 - "net/url" 6 - "strings" 7 - 8 - "github.com/bluesky-social/indigo/atproto/syntax" 9 - ) 10 - 11 - func ParseHostname(raw string) (hostname string, noSSL bool, err error) { 12 - // handle case of bare hostname 13 - if !strings.Contains(raw, "://") { 14 - if strings.HasPrefix(raw, "localhost:") { 15 - raw = "http://" + raw 16 - } else { 17 - raw = "https://" + raw 18 - } 19 - } 20 - 21 - u, err := url.Parse(raw) 22 - if err != nil { 23 - return "", false, fmt.Errorf("not a valid host URL: %w", err) 24 - } 25 - 26 - switch u.Scheme { 27 - case "https", "wss": 28 - noSSL = false 29 - case "http", "ws": 30 - noSSL = true 31 - default: 32 - return "", false, fmt.Errorf("unsupported URL scheme: %s", u.Scheme) 33 - } 34 - 35 - // 'localhost' (exact string) is allowed *with* a required port number; SSL is optional 36 - if u.Hostname() == "localhost" { 37 - if u.Port() == "" || !strings.HasPrefix(u.Host, "localhost:") { 38 - return "", false, fmt.Errorf("port number is required for localhost") 39 - } 40 - return u.Host, noSSL, nil 41 - } 42 - 43 - // port numbers not allowed otherwise 44 - if u.Port() != "" { 45 - return "", false, fmt.Errorf("port number not allowed for non-local names") 46 - } 47 - 48 - // check it is a real hostname (eg, not IP address or single-word alias) 49 - h, err := syntax.ParseHandle(u.Host) 50 - if err != nil { 51 - return "", false, fmt.Errorf("not a public hostname") 52 - } 53 - 54 - // lower-case in response 55 - return h.Normalize().String(), noSSL, nil 56 - }
+1 -1
knotmirror/knotmirror.go
··· 49 49 } 50 50 logger.Info(fmt.Sprintf("clearing resyning states: %d records updated", rows)) 51 51 52 + xrpc := xrpc.New(logger, cfg, db, resolver) 52 53 knotstream := knotstream.NewKnotStream(logger, db, cfg) 53 54 crawler := NewCrawler(logger, db) 54 55 resyncer := NewResyncer(logger, db, gitm, cfg) 55 56 adminpage := NewAdminServer(logger, db, resyncer) 56 - xrpc := xrpc.New(logger, cfg, db, resolver, knotstream) 57 57 58 58 // maintain repository list with tap 59 59 // NOTE: this can be removed once we introduce did-for-repo because then we can just listen to KnotStream for #identity events.
+4 -6
knotmirror/knotstream/slurper.go
··· 263 263 return fmt.Errorf("unmarshaling message: %w", err) 264 264 } 265 265 266 - if err := s.ProcessLegacyGitRefUpdate(ctx, task.key, &legacyMessage); err != nil { 266 + if err := s.ProcessLegacyGitRefUpdate(ctx, &legacyMessage); err != nil { 267 267 return fmt.Errorf("processing gitRefUpdate: %w", err) 268 268 } 269 269 return nil 270 270 } 271 271 272 - func (s *KnotSlurper) ProcessLegacyGitRefUpdate(ctx context.Context, source string, evt *LegacyGitEvent) error { 272 + func (s *KnotSlurper) ProcessLegacyGitRefUpdate(ctx context.Context, evt *LegacyGitEvent) error { 273 273 knotstreamEventsReceived.Inc() 274 274 275 - l := s.logger.With("src", source) 276 - 277 275 curr, err := db.GetRepoByName(ctx, s.db, syntax.DID(evt.Event.RepoDid), evt.Event.RepoName) 278 276 if err != nil { 279 277 return fmt.Errorf("failed to get repo '%s': %w", evt.Event.RepoDid+"/"+evt.Event.RepoName, err) ··· 286 284 // But we want to store that in did/rkey in knot-mirror. 287 285 // Therefore, we should ignore when the repository is unknown. 288 286 // Hopefully crawler will sync it later. 289 - l.Warn("skipping event from unknown repo", "did/name", evt.Event.RepoDid+"/"+evt.Event.RepoName) 287 + s.logger.Warn("skipping event from unknown repo", "did/repo", evt.Event.RepoDid+"/"+evt.Event.RepoName) 290 288 knotstreamEventsSkipped.Inc() 291 289 return nil 292 290 } 293 - l = l.With("repoAt", curr.AtUri()) 291 + l := s.logger.With("repoAt", curr.AtUri()) 294 292 295 293 // TODO: should plan resync to resyncBuffer on RepoStateResyncing 296 294 if curr.State != models.RepoStateActive {
+5 -17
knotmirror/models/models.go
··· 85 85 HostStatusBanned, 86 86 } 87 87 88 - func (h *Host) URL() string { 89 - if h.NoSSL { 90 - return fmt.Sprintf("http://%s", h.Hostname) 91 - } else { 92 - return fmt.Sprintf("https://%s", h.Hostname) 93 - } 94 - } 95 - 96 - func (h *Host) WsURL() string { 97 - if h.NoSSL { 98 - return fmt.Sprintf("ws://%s", h.Hostname) 99 - } else { 100 - return fmt.Sprintf("wss://%s", h.Hostname) 101 - } 102 - } 103 - 104 88 // func (h *Host) SubscribeGitRefsURL(cursor int64) string { 105 89 // scheme := "wss" 106 90 // if h.NoSSL { ··· 114 98 // } 115 99 116 100 func (h *Host) LegacyEventsURL(cursor int64) string { 117 - u := fmt.Sprintf("%s/events", h.WsURL()) 101 + scheme := "wss" 102 + if h.NoSSL { 103 + scheme = "ws" 104 + } 105 + u := fmt.Sprintf("%s://%s/events", scheme, h.Hostname) 118 106 if cursor > 0 { 119 107 u = fmt.Sprintf("%s?cursor=%d", u, cursor) 120 108 }
+1 -5
knotmirror/resyncer.go
··· 24 24 logger *slog.Logger 25 25 db *sql.DB 26 26 gitm GitMirrorManager 27 - cfg *config.Config 28 27 29 28 claimJobMu sync.Mutex 30 29 ··· 44 43 logger: log.SubLogger(l, "resyncer"), 45 44 db: db, 46 45 gitm: gitm, 47 - cfg: cfg, 48 46 49 47 runningJobs: make(map[syntax.ATURI]context.CancelFunc), 50 48 ··· 274 272 275 273 // checkKnotReachability checks if Knot is reachable and is valid git remote server 276 274 func (r *Resyncer) checkKnotReachability(ctx context.Context, repo *models.Repo) error { 277 - repoUrl, err := makeRepoRemoteUrl(repo.KnotDomain, repo.DidSlashRepo(), r.cfg.KnotUseSSL) 275 + repoUrl, err := makeRepoRemoteUrl(repo.KnotDomain, repo.DidSlashRepo(), true) 278 276 if err != nil { 279 277 return err 280 278 } 281 279 282 280 repoUrl += "/info/refs?service=git-upload-pack" 283 - 284 - r.logger.Debug("checking knot reachability", "url", repoUrl) 285 281 286 282 client := http.Client{ 287 283 Timeout: 30 * time.Second,
+2 -17
knotmirror/tapclient.go
··· 8 8 "log/slog" 9 9 "net/netip" 10 10 "net/url" 11 - "strings" 12 11 "time" 13 12 14 13 "tangled.org/core/api/tangled" ··· 79 78 return fmt.Errorf("parsing record: %w", err) 80 79 } 81 80 82 - knotUrl := record.Knot 83 - if !strings.Contains(record.Knot, "://") { 84 - if host, _ := db.GetHost(ctx, t.db, record.Knot); host != nil { 85 - knotUrl = host.URL() 86 - } else { 87 - t.logger.Warn("repo is from unknown knot") 88 - if t.cfg.KnotUseSSL { 89 - knotUrl = "https://" + knotUrl 90 - } else { 91 - knotUrl = "http://" + knotUrl 92 - } 93 - } 94 - } 95 - 96 81 status := models.RepoStatePending 97 82 errMsg := "" 98 - u, err := url.Parse(knotUrl) 83 + u, err := url.Parse("http://" + record.Knot) // parsing with fake scheme 99 84 if err != nil { 100 85 status = models.RepoStateSuspended 101 86 errMsg = "failed to parse knot url" ··· 109 94 Rkey: evt.Rkey, 110 95 Cid: evt.CID, 111 96 Name: record.Name, 112 - KnotDomain: knotUrl, 97 + KnotDomain: record.Knot, 113 98 State: status, 114 99 ErrorMsg: errMsg, 115 100 RetryAfter: 0, // clear retry info
-104
knotmirror/xrpc/sync_requestCrawl.go
··· 1 - package xrpc 2 - 3 - import ( 4 - "encoding/json" 5 - "fmt" 6 - "net/http" 7 - "strings" 8 - 9 - "github.com/bluesky-social/indigo/api/atproto" 10 - "github.com/bluesky-social/indigo/atproto/atclient" 11 - "github.com/bluesky-social/indigo/atproto/syntax" 12 - "github.com/bluesky-social/indigo/xrpc" 13 - "tangled.org/core/api/tangled" 14 - "tangled.org/core/knotmirror/db" 15 - "tangled.org/core/knotmirror/hostutil" 16 - "tangled.org/core/knotmirror/models" 17 - ) 18 - 19 - func (x *Xrpc) RequestCrawl(w http.ResponseWriter, r *http.Request) { 20 - var input tangled.SyncRequestCrawl_Input 21 - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { 22 - writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "failed to decode json body"}) 23 - return 24 - } 25 - 26 - ctx := r.Context() 27 - 28 - l := x.logger.With("input", input) 29 - 30 - hostname, noSSL, err := hostutil.ParseHostname(input.Hostname) 31 - if err != nil { 32 - l.Error("invalid hostname", "err", err) 33 - writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("hostname field empty or invalid: %s", input.Hostname)}) 34 - return 35 - } 36 - 37 - // TODO: check if host is Knot with knot.describeServer 38 - 39 - // store given repoAt to db 40 - // this will allow knotmirror to ingest repo creation event bypassing tap. 41 - // this step won't be needed once we introduce did-for-repo 42 - // TODO(boltless): remove this section 43 - if input.EnsureRepo != nil { 44 - repoAt, err := syntax.ParseATURI(*input.EnsureRepo) 45 - if err != nil { 46 - l.Error("invalid repo at-uri", "err", err) 47 - writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", *input.EnsureRepo)}) 48 - return 49 - } 50 - owner, err := x.resolver.ResolveIdent(ctx, repoAt.Authority().String()) 51 - if err != nil || owner.Handle.IsInvalidHandle() { 52 - l.Error("failed to resolve ident", "err", err, "owner", repoAt.Authority().String()) 53 - writeErr(w, fmt.Errorf("failed to resolve repo owner")) 54 - return 55 - } 56 - xrpcc := xrpc.Client{Host: owner.PDSEndpoint()} 57 - out, err := atproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 58 - if err != nil { 59 - l.Error("failed to get repo record", "err", err, "repo", repoAt) 60 - writeErr(w, fmt.Errorf("failed to get repo record")) 61 - return 62 - } 63 - record := out.Value.Val.(*tangled.Repo) 64 - 65 - knotUrl := record.Knot 66 - if !strings.Contains(record.Knot, "://") { 67 - if noSSL { 68 - knotUrl = "http://" + knotUrl 69 - } else { 70 - knotUrl = "https://" + knotUrl 71 - } 72 - } 73 - 74 - repo := &models.Repo{ 75 - Did: owner.DID, 76 - Rkey: repoAt.RecordKey(), 77 - Cid: (*syntax.CID)(out.Cid), 78 - Name: record.Name, 79 - KnotDomain: knotUrl, 80 - State: models.RepoStatePending, 81 - ErrorMsg: "", 82 - RetryAfter: 0, 83 - RetryCount: 0, 84 - } 85 - 86 - if err := db.UpsertRepo(ctx, x.db, repo); err != nil { 87 - l.Error("failed to upsert repo", "err", err) 88 - writeErr(w, err) 89 - return 90 - } 91 - } 92 - 93 - // subscribe to requested host 94 - if !x.ks.CheckIfSubscribed(hostname) { 95 - if err := x.ks.SubscribeHost(ctx, hostname, noSSL); err != nil { 96 - // TODO(boltless): return HostBanned on banned hosts 97 - l.Error("failed to subscribe host", "err", err) 98 - writeErr(w, err) 99 - return 100 - } 101 - } 102 - 103 - w.WriteHeader(http.StatusOK) 104 - }
+1 -5
knotmirror/xrpc/xrpc.go
··· 12 12 "tangled.org/core/api/tangled" 13 13 "tangled.org/core/idresolver" 14 14 "tangled.org/core/knotmirror/config" 15 - "tangled.org/core/knotmirror/knotstream" 16 15 "tangled.org/core/log" 17 16 ) 18 17 ··· 20 19 cfg *config.Config 21 20 db *sql.DB 22 21 resolver *idresolver.Resolver 23 - ks *knotstream.KnotStream 24 22 logger *slog.Logger 25 23 } 26 24 27 - func New(logger *slog.Logger, cfg *config.Config, db *sql.DB, resolver *idresolver.Resolver, ks *knotstream.KnotStream) *Xrpc { 25 + func New(logger *slog.Logger, cfg *config.Config, db *sql.DB, resolver *idresolver.Resolver) *Xrpc { 28 26 return &Xrpc{ 29 27 cfg, 30 28 db, 31 29 resolver, 32 - ks, 33 30 log.SubLogger(logger, "xrpc"), 34 31 } 35 32 } ··· 50 47 r.Get("/"+tangled.GitTempListCommitsNSID, x.ListCommits) 51 48 r.Get("/"+tangled.GitTempListLanguagesNSID, x.ListLanguages) 52 49 r.Get("/"+tangled.GitTempListTagsNSID, x.ListTags) 53 - r.Post("/"+tangled.SyncRequestCrawlNSID, x.RequestCrawl) 54 50 55 51 return r 56 52 }
+4 -5
knotserver/config/config.go
··· 39 39 } 40 40 41 41 type Config struct { 42 - Repo Repo `env:",prefix=KNOT_REPO_"` 43 - Server Server `env:",prefix=KNOT_SERVER_"` 44 - Git Git `env:",prefix=KNOT_GIT_"` 45 - AppViewEndpoint string `env:"APPVIEW_ENDPOINT, default=https://tangled.org"` 46 - KnotMirrors []string `env:"KNOT_MIRRORS, default=https://mirror.tangled.network"` 42 + Repo Repo `env:",prefix=KNOT_REPO_"` 43 + Server Server `env:",prefix=KNOT_SERVER_"` 44 + Git Git `env:",prefix=KNOT_GIT_"` 45 + AppViewEndpoint string `env:"APPVIEW_ENDPOINT, default=https://tangled.org"` 47 46 } 48 47 49 48 func Load(ctx context.Context) (*Config, error) {
-29
knotserver/events.go
··· 7 7 "strconv" 8 8 "time" 9 9 10 - "github.com/bluesky-social/indigo/xrpc" 11 10 "github.com/gorilla/websocket" 12 - "tangled.org/core/api/tangled" 13 11 "tangled.org/core/log" 14 12 ) 15 13 ··· 63 61 return 64 62 } 65 63 66 - // try request crawl when connection closed 67 - defer func() { 68 - go func() { 69 - retryCtx, retryCancel := context.WithTimeout(context.Background(), 10*time.Second) 70 - defer retryCancel() 71 - if err := h.requestCrawl(retryCtx); err != nil { 72 - l.Error("error requesting crawls", "err", err) 73 - } 74 - }() 75 - }() 76 - 77 64 for { 78 65 // wait for new data or timeout 79 66 select { ··· 131 118 132 119 return nil 133 120 } 134 - 135 - func (h *Knot) requestCrawl(ctx context.Context) error { 136 - h.l.Info("requesting crawl", "mirrors", h.c.KnotMirrors) 137 - input := &tangled.SyncRequestCrawl_Input{ 138 - Hostname: h.c.Server.Hostname, 139 - } 140 - for _, knotmirror := range h.c.KnotMirrors { 141 - xrpcc := xrpc.Client{Host: knotmirror} 142 - if err := tangled.SyncRequestCrawl(ctx, &xrpcc, input); err != nil { 143 - h.l.Error("error requesting crawl", "err", err) 144 - } else { 145 - h.l.Info("crawl requested successfully") 146 - } 147 - } 148 - return nil 149 - }
-16
knotserver/server.go
··· 5 5 "fmt" 6 6 "net/http" 7 7 8 - "github.com/bluesky-social/indigo/xrpc" 9 8 "github.com/urfave/cli/v3" 10 9 "tangled.org/core/api/tangled" 11 10 "tangled.org/core/hook" ··· 98 97 99 98 logger.Info("starting internal server", "address", c.Server.InternalListenAddr) 100 99 go http.ListenAndServe(c.Server.InternalListenAddr, imux) 101 - 102 - // TODO(boltless): too lazy here. should clear this up 103 - go func() { 104 - input := &tangled.SyncRequestCrawl_Input{ 105 - Hostname: c.Server.Hostname, 106 - } 107 - for _, knotmirror := range c.KnotMirrors { 108 - xrpcc := xrpc.Client{Host: knotmirror} 109 - if err := tangled.SyncRequestCrawl(ctx, &xrpcc, input); err != nil { 110 - logger.Error("error requesting crawl", "err", err) 111 - } else { 112 - logger.Info("crawl requested successfully") 113 - } 114 - } 115 - }() 116 100 117 101 logger.Info("starting main server", "address", c.Server.ListenAddr) 118 102 logger.Error("server error", "error", http.ListenAndServe(c.Server.ListenAddr, mux))
-30
knotserver/xrpc/create_repo.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "context" 5 4 "encoding/json" 6 5 "errors" 7 6 "fmt" 8 7 "net/http" 9 8 "path/filepath" 10 9 "strings" 11 - "time" 12 10 13 11 comatproto "github.com/bluesky-social/indigo/api/atproto" 14 12 "github.com/bluesky-social/indigo/atproto/syntax" ··· 122 120 repoPath, 123 121 ) 124 122 125 - // HACK: request crawl for this repository 126 - // Users won't want to sync entire network from their local knotmirror. 127 - // Therefore, to bypass the local tap, requestCrawl directly to the knotmirror. 128 - go func() { 129 - if h.Config.Server.Dev { 130 - repoAt := fmt.Sprintf("at://%s/%s/%s", actorDid, tangled.RepoNSID, rkey) 131 - rCtx, rCancel := context.WithTimeout(context.Background(), 10*time.Second) 132 - defer rCancel() 133 - h.requestCrawl(rCtx, &tangled.SyncRequestCrawl_Input{ 134 - Hostname: h.Config.Server.Hostname, 135 - EnsureRepo: &repoAt, 136 - }) 137 - } 138 - }() 139 - 140 123 w.WriteHeader(http.StatusOK) 141 - } 142 - 143 - func (h *Xrpc) requestCrawl(ctx context.Context, input *tangled.SyncRequestCrawl_Input) error { 144 - h.Logger.Info("requesting crawl", "mirrors", h.Config.KnotMirrors) 145 - for _, knotmirror := range h.Config.KnotMirrors { 146 - xrpcc := xrpc.Client{Host: knotmirror} 147 - if err := tangled.SyncRequestCrawl(ctx, &xrpcc, input); err != nil { 148 - h.Logger.Error("error requesting crawl", "err", err) 149 - } else { 150 - h.Logger.Info("crawl requested successfully") 151 - } 152 - } 153 - return nil 154 124 } 155 125 156 126 func validateRepoName(name string) error {
-57
lexicons/knot/subscribeRepos.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.tangled.knot.subscribeRepos", 4 - "defs": { 5 - "main": { 6 - "type": "subscription", 7 - "description": "Repository event stream, aka Firehose endpoint. Outputs repo commits with diff data, and identity update events, for all repositories on the current server. See the atproto specifications for details around stream sequencing, repo versioning, CAR diff format, and more. Public and does not require auth; implemented by PDS and Relay.", 8 - "parameters": { 9 - "type": "params", 10 - "properties": { 11 - "cursor": { 12 - "type": "integer", 13 - "description": "The last known event seq number to backfill from." 14 - } 15 - } 16 - }, 17 - "message": { 18 - "schema": { 19 - "type": "union", 20 - "refs": ["#identity", "gitRefUpdate"] 21 - } 22 - }, 23 - "errors": [ 24 - { "name": "FutureCursor" }, 25 - { 26 - "name": "ConsumerTooSlow", 27 - "description": "If the consumer of the stream can not keep up with events, and a backlog gets too large, the server will drop the connection." 28 - } 29 - ] 30 - }, 31 - "identity": { 32 - "type": "object", 33 - "required": ["seq", "did", "time"], 34 - "properties": { 35 - "seq": { "type": "integer", "description": "The stream sequence number of this message." }, 36 - "did": { "type": "string", "format": "did", "description": "Repository DID identifier" }, 37 - "time": { "type": "string", "format": "datetime" } 38 - } 39 - }, 40 - "gitSync1": { 41 - "type": "object", 42 - "required": ["seq", "did"], 43 - "properties": { 44 - "seq": { "type": "integer", "description": "The stream sequence number of this message." }, 45 - "did": { "type": "string", "format": "did", "description": "Repository DID identifier" } 46 - } 47 - }, 48 - "gitSync2": { 49 - "type": "object", 50 - "required": ["seq", "repo"], 51 - "properties": { 52 - "seq": { "type": "integer", "description": "The stream sequence number of this message." }, 53 - "did": { "type": "string", "format": "at-uri", "description": "Repository AT-URI identifier" } 54 - } 55 - } 56 - } 57 - }
-29
lexicons/sync/requestCrawl.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.tangled.sync.requestCrawl", 4 - "defs": { 5 - "main": { 6 - "type": "procedure", 7 - "description": "Request a service to persistently crawl hosted repos. Does not require auth.", 8 - "input": { 9 - "encoding": "application/json", 10 - "schema": { 11 - "type": "object", 12 - "required": ["hostname"], 13 - "properties": { 14 - "hostname": { 15 - "type": "string", 16 - "description": "Hostname of the current service (eg, Knot) that is requesting to be crawled." 17 - }, 18 - "ensureRepo": { 19 - "type": "string", 20 - "format": "at-uri", 21 - "description": "specific repository to ensure crawling" 22 - } 23 - } 24 - } 25 - }, 26 - "errors": [{ "name": "HostBanned" }] 27 - } 28 - } 29 - }
+54 -24
nix/gomod2nix.toml
··· 7 7 [mod."github.com/Blank-Xu/sql-adapter"] 8 8 version = "v1.1.1" 9 9 hash = "sha256-9AiQhXoNPCiViV+p5aa3qGFkYU4rJNbADvNdYGq4GA4=" 10 + [mod."github.com/BurntSushi/toml"] 11 + version = "v0.3.1" 12 + hash = "sha256-Rqak1dE/Aj/+Kx1/pl3Hifgt+Q3OzuZ5fJR+/x3nTbo=" 10 13 [mod."github.com/Microsoft/go-winio"] 11 14 version = "v0.6.2" 12 15 hash = "sha256-tVNWDUMILZbJvarcl/E7tpSnkn7urqgSHa2Eaka5vSU=" ··· 16 19 [mod."github.com/RoaringBitmap/roaring/v2"] 17 20 version = "v2.4.5" 18 21 hash = "sha256-igWY0S1PTolQkfctYcmVJioJyV1pk2V81X6o6BA1XQA=" 22 + [mod."github.com/adrg/frontmatter"] 23 + version = "v0.2.0" 24 + hash = "sha256-WJsVcdCpkIkjqUz5fJOFStZYwQlrcFzQ6+mZatZiimo=" 19 25 [mod."github.com/alecthomas/assert/v2"] 20 26 version = "v2.11.0" 21 27 hash = "sha256-tDJCDKZ0R4qNA7hgMKWrpDyogt1802LCJDBCExxdqaU=" ··· 33 39 version = "v4.6.1" 34 40 hash = "sha256-PeZc8k4rDV64+k8nZt/oy1YNVbLevltXP3ZD1jf6Z6k=" 35 41 [mod."github.com/aws/aws-sdk-go-v2"] 36 - version = "v1.41.1" 37 - hash = "sha256-umafTZB+cuy8+Kzpl2WrlygJO3tR3D2WkTC0eTY5G/g=" 42 + version = "v1.41.4" 43 + hash = "sha256-k9xv4f8YPSzZ1yR3/zuyNDGenZKk0DD4lceL713yXtc=" 38 44 [mod."github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream"] 39 - version = "v1.7.4" 40 - hash = "sha256-ZY/Jn1p0IgDe8MONhp0RFHZmRgTBZZ5ddqXlNWEo7Ys=" 45 + version = "v1.7.7" 46 + hash = "sha256-5hw1g8s21Gx1Q3JtiaVM6q8qGt7DW1YO9U1dEEqRXfE=" 47 + [mod."github.com/aws/aws-sdk-go-v2/config"] 48 + version = "v1.32.12" 49 + hash = "sha256-aTkdSRe8KPmVZdsunU8j/hZQLhGw1ckKpLN/ryRBZM0=" 41 50 [mod."github.com/aws/aws-sdk-go-v2/credentials"] 42 - version = "v1.19.9" 43 - hash = "sha256-eqM5BmetQ/MrxTwoUCqRpkm5frFAspuq+QWod+5wzrU=" 51 + version = "v1.19.12" 52 + hash = "sha256-xEIT1ARA9RYrQtLZIus71E6niNHIOVM1J7mUnA5AhJQ=" 53 + [mod."github.com/aws/aws-sdk-go-v2/feature/ec2/imds"] 54 + version = "v1.18.20" 55 + hash = "sha256-dCTpdKZheVCSt+R+NnFOnlS0bCt4gPavlDh15Kl/sMQ=" 44 56 [mod."github.com/aws/aws-sdk-go-v2/internal/configsources"] 45 - version = "v1.4.17" 46 - hash = "sha256-W+d0WDYBrVJxdfRSvoe3cvZYgIgUSVyXsVdQlMcIZvc=" 57 + version = "v1.4.20" 58 + hash = "sha256-aATIk4oLd7aaV66ereBdjINLMDwmIHxu+NNsgKWH1t4=" 47 59 [mod."github.com/aws/aws-sdk-go-v2/internal/endpoints/v2"] 48 - version = "v2.7.17" 49 - hash = "sha256-dKJx0+K1DKi2LJKQNwnTofZH4GWLDgY6/ZI2XR2oCGI=" 60 + version = "v2.7.20" 61 + hash = "sha256-G6266uj64sgfDTJ9V1UY1sQs3UmryB0CFgxzmbjjChY=" 62 + [mod."github.com/aws/aws-sdk-go-v2/internal/ini"] 63 + version = "v1.8.6" 64 + hash = "sha256-oIRPqu99vnGINAWKnCEytpv7N0gRWO7S72tb1r8oxvk=" 50 65 [mod."github.com/aws/aws-sdk-go-v2/internal/v4a"] 51 - version = "v1.4.17" 52 - hash = "sha256-PKwNn9nFf+7TqocgQprxP9r1Fs4IxwGLGd05MrsxmLg=" 66 + version = "v1.4.21" 67 + hash = "sha256-Sq4kRaiIFkPqwYnKv1dsxplyX4duipUYGFFT5gzJtJM=" 53 68 [mod."github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding"] 54 - version = "v1.13.4" 55 - hash = "sha256-Rm6czqOnOULP080D97WQQSqkBhmN6ei1qZaTa51SRj8=" 69 + version = "v1.13.7" 70 + hash = "sha256-AfYJdpmnW01Bk/jfHATlNU6lddjqcigFkHw/zcT9WO4=" 56 71 [mod."github.com/aws/aws-sdk-go-v2/service/internal/checksum"] 57 - version = "v1.9.8" 58 - hash = "sha256-DZhR0aqHrgAFBGSlnsSQ6XeAJ/q504RG/LBWhtQqRVg=" 72 + version = "v1.9.12" 73 + hash = "sha256-gze5CLp6Cp3X1jdqawWlXG7LToH+L6L/79b8F/AgWIY=" 59 74 [mod."github.com/aws/aws-sdk-go-v2/service/internal/presigned-url"] 60 - version = "v1.13.17" 61 - hash = "sha256-qS7Db9S/KQ24kvJdL8qH4gnoN116J1ezwOnbovCiFEI=" 75 + version = "v1.13.20" 76 + hash = "sha256-a5TifKunIoqKd2uAceYh6F1LvMHMyEQcWvJf0sxKhPM=" 62 77 [mod."github.com/aws/aws-sdk-go-v2/service/internal/s3shared"] 63 - version = "v1.19.17" 64 - hash = "sha256-x/Cb4j3HFlg1+U21YCAIhBnfpf1yehJU2Ss/PxamEMI=" 78 + version = "v1.19.20" 79 + hash = "sha256-T+pXMFELor69MTIo67TIkcWWyK+wubweg0gS5I9Dp8A=" 65 80 [mod."github.com/aws/aws-sdk-go-v2/service/s3"] 66 - version = "v1.96.0" 67 - hash = "sha256-lzAn2KHIkd742mDwEZqGEaIXlEfvltL6HdP+sEQ/8YA=" 81 + version = "v1.97.1" 82 + hash = "sha256-J51tttUIPFVEnHddftY4CJXKe2WLLIQDhJ8eYM8jgts=" 83 + [mod."github.com/aws/aws-sdk-go-v2/service/signin"] 84 + version = "v1.0.8" 85 + hash = "sha256-o4pWg3yMZHxdI94x5Z6qbiRg7gpmzbpJnJWsR1BOc44=" 86 + [mod."github.com/aws/aws-sdk-go-v2/service/sso"] 87 + version = "v1.30.13" 88 + hash = "sha256-V277a0ikm/H0paIeDLtPGEyav2a69Kdb9d5bh+JLAeY=" 89 + [mod."github.com/aws/aws-sdk-go-v2/service/ssooidc"] 90 + version = "v1.35.17" 91 + hash = "sha256-r5V5DoCIR4yzN1Ttg+dIA85GVkWMPgeD6Zu0rWGqNJE=" 92 + [mod."github.com/aws/aws-sdk-go-v2/service/sts"] 93 + version = "v1.41.9" 94 + hash = "sha256-I15uxeoKxDURsZrEVDzCRtVIu/HE756M1Rt7PPpdZ7c=" 68 95 [mod."github.com/aws/smithy-go"] 69 - version = "v1.24.0" 70 - hash = "sha256-ZPFhf2Yv3BQpUn3cN4wSnoO7uBki8oCisZxL6F09nnE=" 96 + version = "v1.24.2" 97 + hash = "sha256-v0y+Lir61fgdCwdVoca5mK+FcGh9OD3cTEwHIfLytcI=" 71 98 [mod."github.com/aymanbagabas/go-osc52/v2"] 72 99 version = "v2.0.1" 73 100 hash = "sha256-6Bp0jBZ6npvsYcKZGHHIUSVSTAMEyieweAX2YAKDjjg=" ··· 696 723 [mod."gopkg.in/warnings.v0"] 697 724 version = "v0.1.2" 698 725 hash = "sha256-ATVL9yEmgYbkJ1DkltDGRn/auGAjqGOfjQyBYyUo8s8=" 726 + [mod."gopkg.in/yaml.v2"] 727 + version = "v2.4.0" 728 + hash = "sha256-uVEGglIedjOIGZzHW4YwN1VoRSTK8o0eGZqzd+TNdd0=" 699 729 [mod."gopkg.in/yaml.v3"] 700 730 version = "v3.0.1" 701 731 hash = "sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU="
-9
nix/modules/knot.nix
··· 115 115 ''; 116 116 }; 117 117 118 - knotmirrors = mkOption { 119 - type = types.listOf types.str; 120 - default = [ 121 - "https://mirror.tangled.network" 122 - ]; 123 - description = "List of knotmirror hosts to request crawl"; 124 - }; 125 - 126 118 server = { 127 119 listenAddr = mkOption { 128 120 type = types.str; ··· 271 263 "KNOT_SERVER_PLC_URL=${cfg.server.plcUrl}" 272 264 "KNOT_SERVER_JETSTREAM_ENDPOINT=${cfg.server.jetstreamEndpoint}" 273 265 "KNOT_SERVER_OWNER=${cfg.server.owner}" 274 - "KNOT_MIRRORS=${concatStringsSep "," cfg.knotmirrors}" 275 266 "KNOT_SERVER_LOG_DIDS=${ 276 267 if cfg.server.logDids 277 268 then "true"
+2 -14
nix/modules/knotmirror.nix
··· 66 66 description = "Whether to automatically mirror from entire network"; 67 67 }; 68 68 69 - knotUseSSL = mkOption { 70 - type = types.bool; 71 - default = true; 72 - description = "Use SSL for knot connection"; 73 - }; 74 - 75 - knotSSRF = mkOption { 76 - type = types.bool; 77 - default = true; 78 - description = "enable SSRF protection for knots"; 79 - }; 80 - 81 69 tap = { 82 70 port = mkOption { 83 71 type = types.port; ··· 140 128 "MIRROR_TAP_URL=http://localhost:${toString cfg.tap.port}" 141 129 "MIRROR_DB_URL=${cfg.dbUrl}" 142 130 "MIRROR_GIT_BASEPATH=/var/lib/knotmirror/repos" 143 - "MIRROR_KNOT_USE_SSL=${boolToString cfg.knotUseSSL}" 144 - "MIRROR_KNOT_SSRF=${boolToString cfg.knotSSRF}" 131 + "MIRROR_KNOT_USE_SSL=true" 132 + "MIRROR_KNOT_SSRF=true" 145 133 "MIRROR_RESYNC_PARALLELISM=12" 146 134 "MIRROR_METRICS_LISTEN=127.0.0.1:7100" 147 135 "MIRROR_ADMIN_LISTEN=${cfg.adminListenAddr}"
+23
nix/modules/spindle.nix
··· 109 109 default = "5m"; 110 110 description = "Timeout for each step of a pipeline"; 111 111 }; 112 + 113 + logBucket = mkOption { 114 + type = types.str; 115 + default = "tangled-logs"; 116 + description = "S3 bucket for workflow logs"; 117 + }; 118 + }; 119 + 120 + environmentFile = mkOption { 121 + type = with types; nullOr path; 122 + default = null; 123 + example = "/etc/spindle.env"; 124 + description = '' 125 + Additional environment file as defined in {manpage}`systemd.exec(5)`. 126 + 127 + Sensitive secrets such as {env}`AWS_SECRET_ACCESS_KEY`, 128 + {env}`AWS_ACCESS_KEY_ID`, {env}`AWS_REGION` 129 + may be passed to the service 130 + without making them world readable in the nix store. 131 + ''; 112 132 }; 113 133 }; 114 134 }; ··· 123 143 serviceConfig = { 124 144 LogsDirectory = "spindle"; 125 145 StateDirectory = "spindle"; 146 + EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; 147 + 126 148 Environment = [ 127 149 "SPINDLE_SERVER_LISTEN_ADDR=${cfg.server.listenAddr}" 128 150 "SPINDLE_SERVER_DB_PATH=${cfg.server.dbPath}" ··· 138 160 "SPINDLE_SERVER_SECRETS_OPENBAO_MOUNT=${cfg.server.secrets.openbao.mount}" 139 161 "SPINDLE_NIXERY_PIPELINES_NIXERY=${cfg.pipelines.nixery}" 140 162 "SPINDLE_NIXERY_PIPELINES_WORKFLOW_TIMEOUT=${cfg.pipelines.workflowTimeout}" 163 + "SPINDLE_S3_LOG_BUCKET=${cfg.pipelines.logBucket}" 141 164 ]; 142 165 ExecStart = "${cfg.package}/bin/spindle"; 143 166 Restart = "always";
+18
nix/pkgs/knot-mirror.nix
··· 1 + { 2 + buildGoApplication, 3 + modules, 4 + src, 5 + }: 6 + buildGoApplication { 7 + pname = "knotmirror"; 8 + version = "0.1.0"; 9 + inherit src modules; 10 + 11 + doCheck = false; 12 + 13 + subPackages = ["cmd/knotmirror"]; 14 + 15 + meta = { 16 + mainProgram = "knotmirror"; 17 + }; 18 + }
-18
nix/pkgs/knotmirror.nix
··· 1 - { 2 - buildGoApplication, 3 - modules, 4 - src, 5 - }: 6 - buildGoApplication { 7 - pname = "knotmirror"; 8 - version = "0.1.0"; 9 - inherit src modules; 10 - 11 - doCheck = false; 12 - 13 - subPackages = ["cmd/knotmirror"]; 14 - 15 - meta = { 16 - mainProgram = "knotmirror"; 17 - }; 18 - }
+6 -47
nix/vm.nix
··· 25 25 modules = [ 26 26 self.nixosModules.knot 27 27 self.nixosModules.spindle 28 - self.nixosModules.knotmirror 29 28 ({ 30 29 lib, 31 30 config, ··· 58 57 host.port = 6555; 59 58 guest.port = 6555; 60 59 } 61 - # knotmirror 62 - { 63 - from = "host"; 64 - host.port = 7007; # 7000 is deserved in macos for Airplay 65 - guest.port = 7000; 66 - } 67 - # knotmirror-tap 68 - { 69 - from = "host"; 70 - host.port = 7480; 71 - guest.port = 7480; 72 - } 73 - # knotmirror-admin 74 - { 75 - from = "host"; 76 - host.port = 7200; 77 - guest.port = 7200; 78 - } 79 60 ]; 80 61 sharedDirectories = { 81 62 # We can't use the 9p mounts directly for most of these ··· 100 81 networking.firewall.enable = false; 101 82 time.timeZone = "Europe/London"; 102 83 services.getty.autologinUser = "root"; 103 - environment.systemPackages = with pkgs; [curl vim git sqlite litecli postgresql_14]; 84 + environment.systemPackages = with pkgs; [curl vim git sqlite litecli]; 104 85 services.tangled.knot = { 105 86 enable = true; 106 87 motd = "Welcome to the development knot!\n"; ··· 110 91 plcUrl = plcUrl; 111 92 jetstreamEndpoint = jetstream; 112 93 listenAddr = "0.0.0.0:6444"; 113 - dev = true; 114 94 }; 115 - knotmirrors = [ 116 - "http://localhost:7000" 117 - ]; 118 95 }; 119 96 services.tangled.spindle = { 120 97 enable = true; 98 + environmentFile = "/var/lib/spindle/.env"; 121 99 server = { 122 100 owner = envVar "TANGLED_VM_SPINDLE_OWNER"; 123 101 hostname = envVarOr "TANGLED_VM_SPINDLE_HOST" "localhost:6555"; ··· 131 109 provider = "sqlite"; 132 110 }; 133 111 }; 134 - }; 135 - services.postgresql = { 136 - enable = true; 137 - package = pkgs.postgresql_14; 138 - ensureDatabases = ["mirror" "tap"]; 139 - ensureUsers = [ 140 - {name = "tnglr";} 141 - ]; 142 - authentication = '' 143 - local all tnglr trust 144 - host all tnglr 127.0.0.1/32 trust 145 - ''; 146 - }; 147 - services.tangled.knotmirror = { 148 - enable = true; 149 - listenAddr = "0.0.0.0:7000"; 150 - adminListenAddr = "0.0.0.0:7200"; 151 - hostname = "localhost:7000"; 152 - dbUrl = "postgresql://tnglr@127.0.0.1:5432/mirror"; 153 - fullNetwork = false; 154 - tap.dbUrl = "postgresql://tnglr@127.0.0.1:5432/tap"; 112 + 113 + pipelines = { 114 + logBucket = envVarOr "SPINDLE_S3_LOG_BUCKET" ""; 115 + }; 155 116 }; 156 117 users = { 157 118 # So we don't have to deal with permission clashing between ··· 179 140 in { 180 141 knot = mkDataSyncScripts "/mnt/knot-data" config.services.tangled.knot.stateDir; 181 142 spindle = mkDataSyncScripts "/mnt/spindle-data" (builtins.dirOf config.services.tangled.spindle.server.dbPath); 182 - knotmirror.after = ["postgresql.target"]; 183 - tap-knotmirror.after = ["postgresql.target"]; 184 143 }; 185 144 }) 186 145 ];
-4
ogre/.gitignore
··· 1 - node_modules/ 2 - output/ 3 - .wrangler/ 4 - .DS_Store
-492
ogre/bun.lock
··· 1 - { 2 - "lockfileVersion": 1, 3 - "configVersion": 1, 4 - "workspaces": { 5 - "": { 6 - "name": "@tangled/ogre-worker", 7 - "dependencies": { 8 - "@fontsource/inter": "^5.2.8", 9 - "@resvg/resvg-wasm": "^2.6.2", 10 - "@tangled/ogre-runtime": "*", 11 - "lucide-static": "^0.577.0", 12 - "preact": "^10.29.0", 13 - "satori": "0.25.0", 14 - "zod": "^4.3.6", 15 - }, 16 - "devDependencies": { 17 - "@cloudflare/workers-types": "^4.20260317.1", 18 - "@types/bun": "^1.3.11", 19 - "@types/node": "^25.5.0", 20 - "knip": "^6.0.1", 21 - "tsx": "^4.21.0", 22 - "typescript": "^5.9.3", 23 - "wrangler": "^4.75.0", 24 - }, 25 - }, 26 - "packages/runtime": { 27 - "name": "@tangled/ogre-runtime", 28 - "version": "1.0.0", 29 - }, 30 - }, 31 - "packages": { 32 - "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="], 33 - 34 - "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.15.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-EGYmJaGZKWl+X8tXxcnx4v2bOZSjQeNI5dWFeXivgX9+YCT69AkzHHwlNbVpqtEUTbew8eQurpyOpeN8fg00nw=="], 35 - 36 - "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260317.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g=="], 37 - 38 - "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260317.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg=="], 39 - 40 - "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260317.1", "", { "os": "linux", "cpu": "x64" }, "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug=="], 41 - 42 - "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260317.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw=="], 43 - 44 - "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260317.1", "", { "os": "win32", "cpu": "x64" }, "sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ=="], 45 - 46 - "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260317.1", "", {}, "sha512-+G4eVwyCpm8Au1ex8vQBCuA9wnwqetz4tPNRoB/53qvktERWBRMQnrtvC1k584yRE3emMThtuY0gWshvSJ++PQ=="], 47 - 48 - "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], 49 - 50 - "@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], 51 - 52 - "@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="], 53 - 54 - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], 55 - 56 - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], 57 - 58 - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], 59 - 60 - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], 61 - 62 - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], 63 - 64 - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], 65 - 66 - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], 67 - 68 - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], 69 - 70 - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], 71 - 72 - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], 73 - 74 - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], 75 - 76 - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], 77 - 78 - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], 79 - 80 - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], 81 - 82 - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], 83 - 84 - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], 85 - 86 - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], 87 - 88 - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], 89 - 90 - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], 91 - 92 - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], 93 - 94 - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], 95 - 96 - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], 97 - 98 - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], 99 - 100 - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], 101 - 102 - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], 103 - 104 - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], 105 - 106 - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], 107 - 108 - "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], 109 - 110 - "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], 111 - 112 - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], 113 - 114 - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], 115 - 116 - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], 117 - 118 - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], 119 - 120 - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], 121 - 122 - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], 123 - 124 - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], 125 - 126 - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], 127 - 128 - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], 129 - 130 - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], 131 - 132 - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], 133 - 134 - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], 135 - 136 - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], 137 - 138 - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], 139 - 140 - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], 141 - 142 - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], 143 - 144 - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], 145 - 146 - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], 147 - 148 - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], 149 - 150 - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], 151 - 152 - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], 153 - 154 - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], 155 - 156 - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], 157 - 158 - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], 159 - 160 - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 161 - 162 - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 163 - 164 - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], 165 - 166 - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], 167 - 168 - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 169 - 170 - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 171 - 172 - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 173 - 174 - "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.120.0", "", { "os": "android", "cpu": "arm" }, "sha512-WU3qtINx802wOl8RxAF1v0VvmC2O4D9M8Sv486nLeQ7iPHVmncYZrtBhB4SYyX+XZxj2PNnCcN+PW21jHgiOxg=="], 175 - 176 - "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.120.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SEf80EHdhlbjZEgzeWm0ZA/br4GKMenDW3QB/gtyeTV1gStvvZeFi40ioHDZvds2m4Z9J1bUAUL8yn1/+A6iGg=="], 177 - 178 - "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.120.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xVrrbCai8R8CUIBu3CjryutQnEYhZqs1maIqDvtUCFZb8vY33H7uh9mHpL3a0JBIKoBUKjPH8+rzyAeXnS2d6A=="], 179 - 180 - "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.120.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-xyHBbnJ6mydnQUH7MAcafOkkrNzQC6T+LXgDH/3InEq2BWl/g424IMRiJVSpVqGjB+p2bd0h0WRR8iIwzjU7rw=="], 181 - 182 - "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.120.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMnVRllquXUYTeNfFKmxTTEdZ/ix1nLl0ducDzMSREoWYGVIHnOOxoKMWlCOvRr9Wk/HZqo2rh1jeumbPGPV9A=="], 183 - 184 - "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.120.0", "", { "os": "linux", "cpu": "arm" }, "sha512-tkvn2CQ7QdcsMnpfiX3fd3wA3EFsWKYlcQzq9cFw/xc89Al7W6Y4O0FgLVkVQpo0Tnq/qtE1XfkJOnRRA9S/NA=="], 185 - 186 - "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.120.0", "", { "os": "linux", "cpu": "arm" }, "sha512-WN5y135Ic42gQDk9grbwY9++fDhqf8knN6fnP+0WALlAUh4odY/BDK1nfTJRSfpJD9P3r1BwU0m3pW2DU89whQ=="], 187 - 188 - "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.120.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1GgQBCcXvFMw99EPdMy+4NZ3aYyXsxjf9kbUUg8HuAy3ZBXzOry5KfFEzT9nqmgZI1cuetvApkiJBZLAPo8uaw=="], 189 - 190 - "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.120.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gmMQ70gsPdDBgpcErvJEoWNBr7bJooSLlvOBVBSGfOzlP5NvJ3bFvnUeZZ9d+dPrqSngtonf7nyzWUTUj/U+lw=="], 191 - 192 - "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.120.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-T/kZuU0ajop0xhzVMwH5r3srC9Nqup5HaIo+3uFjIN5uPxa0LvSxC1ZqP4aQGJVW5G0z8/nCkjIfSMS91P/wzw=="], 193 - 194 - "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.120.0", "", { "os": "linux", "cpu": "none" }, "sha512-vn21KXLAXzaI3N5CZWlBr1iWeXLl9QFIMor7S1hUjUGTeUuWCoE6JZB040/ZNDwf+JXPX8Ao9KbmJq9FMC2iGw=="], 195 - 196 - "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.120.0", "", { "os": "linux", "cpu": "none" }, "sha512-SUbUxlar007LTGmSLGIC5x/WJvwhdX+PwNzFJ9f/nOzZOrCFbOT4ikt7pJIRg1tXVsEfzk5mWpGO1NFiSs4PIw=="], 197 - 198 - "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.120.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hYiPJTxyfJY2+lMBFk3p2bo0R9GN+TtpPFlRqVchL1qvLG+pznstramHNvJlw9AjaoRUHwp9IKR7UZQnRPGjgQ=="], 199 - 200 - "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.120.0", "", { "os": "linux", "cpu": "x64" }, "sha512-q+5jSVZkprJCIy3dzJpApat0InJaoxQLsJuD6DkX8hrUS61z2lHQ1Fe9L2+TYbKHXCLWbL0zXe7ovkIdopBGMQ=="], 201 - 202 - "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.120.0", "", { "os": "linux", "cpu": "x64" }, "sha512-D9QDDZNnH24e7X4ftSa6ar/2hCavETfW3uk0zgcMIrZNy459O5deTbWrjGzZiVrSWigGtlQwzs2McBP0QsfV1w=="], 203 - 204 - "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.120.0", "", { "os": "none", "cpu": "arm64" }, "sha512-TBU8ZwOUWAOUWVfmI16CYWbvh4uQb9zHnGBHsw5Cp2JUVG044OIY1CSHODLifqzQIMTXvDvLzcL89GGdUIqNrA=="], 205 - 206 - "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.120.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-WG/FOZgDJCpJnuF3ToG/K28rcOmSY7FmFmfBKYb2fmLyhDzPpUldFGV7/Fz4ru0Iz/v4KPmf8xVgO8N3lO4KHA=="], 207 - 208 - "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.120.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1T0HKGcsz/BKo77t7+89L8Qvu4f9DoleKWHp3C5sJEcbCjDOLx3m9m722bWZTY+hANlUEs+yjlK+lBFsA+vrVQ=="], 209 - 210 - "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.120.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-L7vfLzbOXsjBXV0rv/6Y3Jd9BRjPeCivINZAqrSyAOZN3moCopDN+Psq9ZrGNZtJzP8946MtlRFZ0Als0wBCOw=="], 211 - 212 - "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.120.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ys+upfqNtSu58huAhJMBKl3XCkGzyVFBlMlGPzHeFKgpFF/OdgNs1MMf8oaJIbgMH8ZxgGF7qfue39eJohmKIg=="], 213 - 214 - "@oxc-project/types": ["@oxc-project/types@0.120.0", "", {}, "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg=="], 215 - 216 - "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.19.1", "", { "os": "android", "cpu": "arm" }, "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg=="], 217 - 218 - "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.19.1", "", { "os": "android", "cpu": "arm64" }, "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA=="], 219 - 220 - "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.19.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ=="], 221 - 222 - "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.19.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ=="], 223 - 224 - "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.19.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw=="], 225 - 226 - "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A=="], 227 - 228 - "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ=="], 229 - 230 - "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig=="], 231 - 232 - "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew=="], 233 - 234 - "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.19.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ=="], 235 - 236 - "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w=="], 237 - 238 - "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw=="], 239 - 240 - "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.19.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA=="], 241 - 242 - "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ=="], 243 - 244 - "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw=="], 245 - 246 - "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.19.1", "", { "os": "none", "cpu": "arm64" }, "sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA=="], 247 - 248 - "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.19.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg=="], 249 - 250 - "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.19.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ=="], 251 - 252 - "@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.19.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA=="], 253 - 254 - "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.19.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw=="], 255 - 256 - "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], 257 - 258 - "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], 259 - 260 - "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], 261 - 262 - "@resvg/resvg-wasm": ["@resvg/resvg-wasm@2.6.2", "", {}, "sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw=="], 263 - 264 - "@shuding/opentype.js": ["@shuding/opentype.js@1.4.0-beta.0", "", { "dependencies": { "fflate": "^0.7.3", "string.prototype.codepointat": "^0.2.1" }, "bin": { "ot": "bin/ot" } }, "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA=="], 265 - 266 - "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], 267 - 268 - "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="], 269 - 270 - "@tangled/ogre-runtime": ["@tangled/ogre-runtime@workspace:packages/runtime"], 271 - 272 - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 273 - 274 - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], 275 - 276 - "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], 277 - 278 - "base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="], 279 - 280 - "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], 281 - 282 - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 283 - 284 - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], 285 - 286 - "camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="], 287 - 288 - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 289 - 290 - "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], 291 - 292 - "css-background-parser": ["css-background-parser@0.1.0", "", {}, "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="], 293 - 294 - "css-box-shadow": ["css-box-shadow@1.0.0-3", "", {}, "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="], 295 - 296 - "css-color-keywords": ["css-color-keywords@1.0.0", "", {}, "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="], 297 - 298 - "css-gradient-parser": ["css-gradient-parser@0.0.17", "", {}, "sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg=="], 299 - 300 - "css-to-react-native": ["css-to-react-native@3.2.0", "", { "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", "postcss-value-parser": "^4.0.2" } }, "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ=="], 301 - 302 - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 303 - 304 - "emoji-regex-xs": ["emoji-regex-xs@2.0.1", "", {}, "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g=="], 305 - 306 - "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], 307 - 308 - "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], 309 - 310 - "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], 311 - 312 - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], 313 - 314 - "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], 315 - 316 - "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="], 317 - 318 - "fflate": ["fflate@0.7.4", "", {}, "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="], 319 - 320 - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 321 - 322 - "formatly": ["formatly@0.3.0", "", { "dependencies": { "fd-package-json": "^2.0.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w=="], 323 - 324 - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 325 - 326 - "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], 327 - 328 - "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 329 - 330 - "hex-rgb": ["hex-rgb@4.3.0", "", {}, "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw=="], 331 - 332 - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 333 - 334 - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 335 - 336 - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 337 - 338 - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], 339 - 340 - "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], 341 - 342 - "knip": ["knip@6.0.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "get-tsconfig": "4.13.6", "jiti": "^2.6.0", "minimist": "^1.2.8", "oxc-parser": "^0.120.0", "oxc-resolver": "^11.19.1", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "unbash": "^2.2.0", "yaml": "^2.8.2", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-qk5m+w6IYEqfRG5546DXZJYl5AXsgFfDD6ULaDvkubqNtLye79sokBg3usURrWFjASMeQtvX19TfldU3jHkMNA=="], 343 - 344 - "linebreak": ["linebreak@1.1.0", "", { "dependencies": { "base64-js": "0.0.8", "unicode-trie": "^2.0.0" } }, "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ=="], 345 - 346 - "lucide-static": ["lucide-static@0.577.0", "", {}, "sha512-hx39J5Tq4JWF2ALY+5YRg+SxQLpeAmLJDXNcqiBJH/UuVwp43it9fyki/onZO7AVFgG5ZbB+fWwZR9mwGHE2XQ=="], 347 - 348 - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], 349 - 350 - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 351 - 352 - "miniflare": ["miniflare@4.20260317.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260317.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-xuwk5Kjv+shi5iUBAdCrRl9IaWSGnTU8WuTQzsUS2GlSDIMCJuu8DiF/d9ExjMXYiQG5ml+k9SVKnMj8cRkq0w=="], 353 - 354 - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 355 - 356 - "oxc-parser": ["oxc-parser@0.120.0", "", { "dependencies": { "@oxc-project/types": "^0.120.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.120.0", "@oxc-parser/binding-android-arm64": "0.120.0", "@oxc-parser/binding-darwin-arm64": "0.120.0", "@oxc-parser/binding-darwin-x64": "0.120.0", "@oxc-parser/binding-freebsd-x64": "0.120.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.120.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.120.0", "@oxc-parser/binding-linux-arm64-gnu": "0.120.0", "@oxc-parser/binding-linux-arm64-musl": "0.120.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-musl": "0.120.0", "@oxc-parser/binding-linux-s390x-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-musl": "0.120.0", "@oxc-parser/binding-openharmony-arm64": "0.120.0", "@oxc-parser/binding-wasm32-wasi": "0.120.0", "@oxc-parser/binding-win32-arm64-msvc": "0.120.0", "@oxc-parser/binding-win32-ia32-msvc": "0.120.0", "@oxc-parser/binding-win32-x64-msvc": "0.120.0" } }, "sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w=="], 357 - 358 - "oxc-resolver": ["oxc-resolver@11.19.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.19.1", "@oxc-resolver/binding-android-arm64": "11.19.1", "@oxc-resolver/binding-darwin-arm64": "11.19.1", "@oxc-resolver/binding-darwin-x64": "11.19.1", "@oxc-resolver/binding-freebsd-x64": "11.19.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-musl": "11.19.1", "@oxc-resolver/binding-openharmony-arm64": "11.19.1", "@oxc-resolver/binding-wasm32-wasi": "11.19.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" } }, "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg=="], 359 - 360 - "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], 361 - 362 - "parse-css-color": ["parse-css-color@0.2.1", "", { "dependencies": { "color-name": "^1.1.4", "hex-rgb": "^4.1.0" } }, "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg=="], 363 - 364 - "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], 365 - 366 - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 367 - 368 - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 369 - 370 - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 371 - 372 - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], 373 - 374 - "preact": ["preact@10.29.0", "", {}, "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg=="], 375 - 376 - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 377 - 378 - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 379 - 380 - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 381 - 382 - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 383 - 384 - "satori": ["satori@0.25.0", "", { "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", "css-box-shadow": "1.0.0-3", "css-gradient-parser": "^0.0.17", "css-to-react-native": "^3.0.0", "emoji-regex-xs": "^2.0.1", "escape-html": "^1.0.3", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", "yoga-layout": "^3.2.1" } }, "sha512-utINfLxrYrmSnLvxFT4ZwgwWa8KOjrz7ans32V5wItgHVmzESl/9i33nE38uG0miycab8hUqQtDlOpqrIpB/iw=="], 385 - 386 - "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 387 - 388 - "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], 389 - 390 - "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="], 391 - 392 - "string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="], 393 - 394 - "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], 395 - 396 - "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], 397 - 398 - "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], 399 - 400 - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 401 - 402 - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 403 - 404 - "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], 405 - 406 - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 407 - 408 - "unbash": ["unbash@2.2.0", "", {}, "sha512-X2wH19RAPZE3+ldGicOkoj/SIA83OIxcJ6Cuaw23hf8Xc6fQpvZXY0SftE2JgS0QhYLUG4uwodSI3R53keyh7w=="], 409 - 410 - "undici": ["undici@7.24.4", "", {}, "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w=="], 411 - 412 - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], 413 - 414 - "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], 415 - 416 - "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], 417 - 418 - "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], 419 - 420 - "workerd": ["workerd@1.20260317.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260317.1", "@cloudflare/workerd-darwin-arm64": "1.20260317.1", "@cloudflare/workerd-linux-64": "1.20260317.1", "@cloudflare/workerd-linux-arm64": "1.20260317.1", "@cloudflare/workerd-windows-64": "1.20260317.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g=="], 421 - 422 - "wrangler": ["wrangler@4.75.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.15.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260317.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260317.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260317.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-Efk1tcnm4eduBYpH1sSjMYydXMnIFPns/qABI3+fsbDrUk5GksNYX8nYGVP4sFygvGPO7kJc36YJKB5ooA7JAg=="], 423 - 424 - "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], 425 - 426 - "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], 427 - 428 - "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], 429 - 430 - "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], 431 - 432 - "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], 433 - 434 - "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], 435 - 436 - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 437 - 438 - "wrangler/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], 439 - 440 - "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], 441 - 442 - "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], 443 - 444 - "wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], 445 - 446 - "wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], 447 - 448 - "wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], 449 - 450 - "wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], 451 - 452 - "wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], 453 - 454 - "wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], 455 - 456 - "wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], 457 - 458 - "wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], 459 - 460 - "wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], 461 - 462 - "wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], 463 - 464 - "wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], 465 - 466 - "wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], 467 - 468 - "wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], 469 - 470 - "wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], 471 - 472 - "wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], 473 - 474 - "wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], 475 - 476 - "wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], 477 - 478 - "wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], 479 - 480 - "wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], 481 - 482 - "wrangler/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], 483 - 484 - "wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], 485 - 486 - "wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], 487 - 488 - "wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], 489 - 490 - "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], 491 - } 492 - }
-117
ogre/client.go
··· 1 - package ogre 2 - 3 - import ( 4 - "bytes" 5 - "context" 6 - "encoding/json" 7 - "fmt" 8 - "io" 9 - "net/http" 10 - "time" 11 - ) 12 - 13 - type Client struct { 14 - host string 15 - client *http.Client 16 - } 17 - 18 - func NewClient(host string) *Client { 19 - return &Client{ 20 - host: host, 21 - client: &http.Client{ 22 - Timeout: 10 * time.Second, 23 - }, 24 - } 25 - } 26 - 27 - type LabelData struct { 28 - Name string `json:"name"` 29 - Color string `json:"color"` 30 - } 31 - 32 - type LanguageData struct { 33 - Color string `json:"color"` 34 - Percentage float32 `json:"percentage"` 35 - } 36 - 37 - type RepositoryCardPayload struct { 38 - Type string `json:"type"` 39 - RepoName string `json:"repoName"` 40 - OwnerHandle string `json:"ownerHandle"` 41 - Stars int `json:"stars"` 42 - Pulls int `json:"pulls"` 43 - Issues int `json:"issues"` 44 - CreatedAt string `json:"createdAt"` 45 - AvatarUrl string `json:"avatarUrl"` 46 - Languages []LanguageData `json:"languages"` 47 - } 48 - 49 - type IssueCardPayload struct { 50 - Type string `json:"type"` 51 - RepoName string `json:"repoName"` 52 - OwnerHandle string `json:"ownerHandle"` 53 - AvatarUrl string `json:"avatarUrl"` 54 - Title string `json:"title"` 55 - IssueNumber int `json:"issueNumber"` 56 - Status string `json:"status"` 57 - Labels []LabelData `json:"labels"` 58 - CommentCount int `json:"commentCount"` 59 - ReactionCount int `json:"reactionCount"` 60 - CreatedAt string `json:"createdAt"` 61 - } 62 - 63 - type PullRequestCardPayload struct { 64 - Type string `json:"type"` 65 - RepoName string `json:"repoName"` 66 - OwnerHandle string `json:"ownerHandle"` 67 - AvatarUrl string `json:"avatarUrl"` 68 - Title string `json:"title"` 69 - PullRequestNumber int `json:"pullRequestNumber"` 70 - Status string `json:"status"` 71 - FilesChanged int `json:"filesChanged"` 72 - Additions int `json:"additions"` 73 - Deletions int `json:"deletions"` 74 - Rounds int `json:"rounds"` 75 - CommentCount int `json:"commentCount"` 76 - ReactionCount int `json:"reactionCount"` 77 - CreatedAt string `json:"createdAt"` 78 - } 79 - 80 - func (c *Client) doRequest(ctx context.Context, path string, payload any) ([]byte, error) { 81 - body, err := json.Marshal(payload) 82 - if err != nil { 83 - return nil, fmt.Errorf("marshal payload: %w", err) 84 - } 85 - 86 - url := fmt.Sprintf("%s/%s", c.host, path) 87 - req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) 88 - if err != nil { 89 - return nil, fmt.Errorf("create request: %w", err) 90 - } 91 - req.Header.Set("Content-Type", "application/json") 92 - 93 - resp, err := c.client.Do(req) 94 - if err != nil { 95 - return nil, fmt.Errorf("do request: %w", err) 96 - } 97 - defer resp.Body.Close() 98 - 99 - if resp.StatusCode != http.StatusOK { 100 - respBody, _ := io.ReadAll(resp.Body) 101 - return nil, fmt.Errorf("unexpected status: %d, body: %s", resp.StatusCode, string(respBody)) 102 - } 103 - 104 - return io.ReadAll(resp.Body) 105 - } 106 - 107 - func (c *Client) RenderRepositoryCard(ctx context.Context, payload RepositoryCardPayload) ([]byte, error) { 108 - return c.doRequest(ctx, "repository", payload) 109 - } 110 - 111 - func (c *Client) RenderIssueCard(ctx context.Context, payload IssueCardPayload) ([]byte, error) { 112 - return c.doRequest(ctx, "issue", payload) 113 - } 114 - 115 - func (c *Client) RenderPullRequestCard(ctx context.Context, payload PullRequestCardPayload) ([]byte, error) { 116 - return c.doRequest(ctx, "pullRequest", payload) 117 - }
-4
ogre/knip.json
··· 1 - { 2 - "$schema": "https://unpkg.com/knip@5/schema.json", 3 - "tags": ["-lintignore"] 4 - }
-34
ogre/package.json
··· 1 - { 2 - "name": "@tangled/ogre-worker", 3 - "version": "1.0.0", 4 - "private": true, 5 - "type": "module", 6 - "workspaces": [ 7 - "packages/runtime" 8 - ], 9 - "scripts": { 10 - "dev": "wrangler dev", 11 - "deploy": "wrangler deploy", 12 - "typecheck": "tsc --noEmit", 13 - "test": "bun test", 14 - "knip": "knip" 15 - }, 16 - "dependencies": { 17 - "@fontsource/inter": "^5.2.8", 18 - "@resvg/resvg-wasm": "^2.6.2", 19 - "@tangled/ogre-runtime": "*", 20 - "lucide-static": "^0.577.0", 21 - "preact": "^10.29.0", 22 - "satori": "0.25.0", 23 - "zod": "^4.3.6" 24 - }, 25 - "devDependencies": { 26 - "@cloudflare/workers-types": "^4.20260317.1", 27 - "@types/bun": "^1.3.11", 28 - "@types/node": "^25.5.0", 29 - "knip": "^6.0.1", 30 - "tsx": "^4.21.0", 31 - "typescript": "^5.9.3", 32 - "wrangler": "^4.75.0" 33 - } 34 - }
-88
ogre/packages/runtime/index.ts
··· 1 - /** 2 - * Bun/Node.js runtime implementation 3 - * Uses filesystem APIs to load WASM and fonts 4 - */ 5 - import { readFile } from "node:fs/promises"; 6 - import { createRequire } from "node:module"; 7 - import type { FontData, SatoriFn, ResvgClass } from "./types"; 8 - 9 - const require = createRequire(import.meta.url); 10 - 11 - let satoriFn: SatoriFn | null = null; 12 - let resvgInitialized = false; 13 - let Resvg: ResvgClass | null = null; 14 - 15 - export async function initSatori(): Promise<SatoriFn> { 16 - if (satoriFn) return satoriFn; 17 - 18 - const { default: satori } = await import("satori"); 19 - satoriFn = satori; 20 - 21 - return satoriFn; 22 - } 23 - 24 - export async function initResvg(): Promise<ResvgClass> { 25 - if (resvgInitialized) return Resvg!; 26 - 27 - const { Resvg: ResvgClass, initWasm } = await import("@resvg/resvg-wasm"); 28 - const wasmPath = require.resolve("@resvg/resvg-wasm/index_bg.wasm"); 29 - const wasmBuffer = await readFile(wasmPath); 30 - await initWasm(wasmBuffer); 31 - 32 - Resvg = ResvgClass; 33 - resvgInitialized = true; 34 - return Resvg; 35 - } 36 - 37 - export async function loadFonts(): Promise<FontData[]> { 38 - // In Bun, .woff imports return a Module object with `default` being the file path 39 - const inter400Module = await import( 40 - "@fontsource/inter/files/inter-latin-400-normal.woff" 41 - ); 42 - const inter500Module = await import( 43 - "@fontsource/inter/files/inter-latin-500-normal.woff" 44 - ); 45 - const inter600Module = await import( 46 - "@fontsource/inter/files/inter-latin-600-normal.woff" 47 - ); 48 - 49 - const inter400Path = (inter400Module as { default: string }).default; 50 - const inter500Path = (inter500Module as { default: string }).default; 51 - const inter600Path = (inter600Module as { default: string }).default; 52 - 53 - const [buf400, buf500, buf600] = await Promise.all([ 54 - readFile(inter400Path), 55 - readFile(inter500Path), 56 - readFile(inter600Path), 57 - ]); 58 - 59 - return [ 60 - { 61 - name: "Inter", 62 - data: buf400.buffer.slice( 63 - buf400.byteOffset, 64 - buf400.byteOffset + buf400.byteLength, 65 - ), 66 - weight: 400, 67 - style: "normal", 68 - }, 69 - { 70 - name: "Inter", 71 - data: buf500.buffer.slice( 72 - buf500.byteOffset, 73 - buf500.byteOffset + buf500.byteLength, 74 - ), 75 - weight: 500, 76 - style: "normal", 77 - }, 78 - { 79 - name: "Inter", 80 - data: buf600.buffer.slice( 81 - buf600.byteOffset, 82 - buf600.byteOffset + buf600.byteLength, 83 - ), 84 - weight: 600, 85 - style: "normal", 86 - }, 87 - ]; 88 - }
-12
ogre/packages/runtime/package.json
··· 1 - { 2 - "name": "@tangled/ogre-runtime", 3 - "version": "1.0.0", 4 - "private": true, 5 - "type": "module", 6 - "exports": { 7 - "workerd": "./workerd.ts", 8 - "bun": "./index.ts", 9 - "default": "./index.ts" 10 - }, 11 - "types": "./types.ts" 12 - }
-10
ogre/packages/runtime/types.ts
··· 1 - export interface FontData { 2 - name: string; 3 - data: ArrayBuffer; 4 - weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; 5 - style: "normal" | "italic"; 6 - } 7 - 8 - export type SatoriFn = typeof import("satori").default; 9 - 10 - export type ResvgClass = typeof import("@resvg/resvg-wasm").Resvg;
-60
ogre/packages/runtime/workerd.ts
··· 1 - /** 2 - * Cloudflare Workers runtime implementation 3 - * Uses ?module suffix for WASM imports as required by Wrangler 4 - */ 5 - import type { FontData, SatoriFn, ResvgClass } from "./types"; 6 - 7 - import inter400 from "@fontsource/inter/files/inter-latin-400-normal.woff"; 8 - import inter500 from "@fontsource/inter/files/inter-latin-500-normal.woff"; 9 - import inter600 from "@fontsource/inter/files/inter-latin-600-normal.woff"; 10 - 11 - let satoriFn: SatoriFn | null = null; 12 - let resvgInitialized = false; 13 - let Resvg: ResvgClass | null = null; 14 - 15 - export async function initSatori(): Promise<SatoriFn> { 16 - if (satoriFn) return satoriFn; 17 - 18 - const { default: satori, init } = await import("satori/standalone"); 19 - const wasmModule = (await import("satori/yoga.wasm?module")).default; 20 - await init(wasmModule); 21 - satoriFn = satori; 22 - 23 - return satoriFn; 24 - } 25 - 26 - export async function initResvg(): Promise<ResvgClass> { 27 - if (resvgInitialized) return Resvg!; 28 - 29 - const { Resvg: ResvgClass, initWasm } = await import("@resvg/resvg-wasm"); 30 - const wasmModule = (await import("@resvg/resvg-wasm/index_bg.wasm?module")) 31 - .default; 32 - await initWasm(wasmModule); 33 - 34 - Resvg = ResvgClass; 35 - resvgInitialized = true; 36 - return Resvg; 37 - } 38 - 39 - export async function loadFonts(): Promise<FontData[]> { 40 - return [ 41 - { 42 - name: "Inter", 43 - data: inter400 as ArrayBuffer, 44 - weight: 400, 45 - style: "normal", 46 - }, 47 - { 48 - name: "Inter", 49 - data: inter500 as ArrayBuffer, 50 - weight: 500, 51 - style: "normal", 52 - }, 53 - { 54 - name: "Inter", 55 - data: inter600 as ArrayBuffer, 56 - weight: 600, 57 - style: "normal", 58 - }, 59 - ]; 60 - }
-3
ogre/readme.md
··· 1 - # ogre: open graph rendering engine 2 - 3 - πŸ‘Ή
ogre/src/__tests__/assets/avatar.jpg

This is a binary file and will not be displayed.

-87
ogre/src/__tests__/fixtures.ts
··· 1 - import type { 2 - RepositoryCardData, 3 - IssueCardData, 4 - PullRequestCardData, 5 - } from "../validation"; 6 - 7 - const LONG_TITLE = 8 - "fix critical memory leak in WebSocket connection handler that causes server crashes under high load conditions in production environments"; 9 - 10 - export const createRepoData = (avatarUrl: string): RepositoryCardData => ({ 11 - type: "repository", 12 - repoName: "core", 13 - ownerHandle: "tangled.org", 14 - stars: 746, 15 - pulls: 82, 16 - issues: 176, 17 - createdAt: "2026-01-29T00:00:00Z", 18 - avatarUrl, 19 - languages: [ 20 - { color: "#00ADD8", percentage: 50 }, 21 - { color: "#e34c26", percentage: 30 }, 22 - { color: "#7e7eff", percentage: 10 }, 23 - { color: "#663399", percentage: 5 }, 24 - { color: "#f1e05a", percentage: 5 }, 25 - ], 26 - }); 27 - 28 - export const createIssueData = ( 29 - avatarUrl: string, 30 - overrides?: Partial<IssueCardData>, 31 - ): IssueCardData => ({ 32 - type: "issue", 33 - repoName: "core", 34 - ownerHandle: "tangled.org", 35 - avatarUrl, 36 - title: "feature request: sync fork button", 37 - issueNumber: 8, 38 - status: "open", 39 - labels: [ 40 - { name: "feature", color: "#4639d6" }, 41 - { name: "help-wanted", color: "#008672" }, 42 - { name: "enhancement", color: "#0052cc" }, 43 - ], 44 - commentCount: 12, 45 - reactionCount: 5, 46 - createdAt: "2026-01-29T00:00:00Z", 47 - ...overrides, 48 - }); 49 - 50 - export const createPullRequestData = ( 51 - avatarUrl: string, 52 - overrides?: Partial<PullRequestCardData>, 53 - ): PullRequestCardData => ({ 54 - type: "pullRequest", 55 - repoName: "core", 56 - ownerHandle: "tangled.org", 57 - avatarUrl, 58 - title: "add author description to README.md", 59 - pullRequestNumber: 1, 60 - status: "open", 61 - filesChanged: 2, 62 - additions: 116, 63 - deletions: 59, 64 - rounds: 3, 65 - commentCount: 12, 66 - reactionCount: 31, 67 - createdAt: "2026-01-29T00:00:00Z", 68 - ...overrides, 69 - }); 70 - 71 - export const createLongTitleIssueData = ( 72 - avatarUrl: string, 73 - overrides?: Partial<IssueCardData>, 74 - ): IssueCardData => ({ 75 - ...createIssueData(avatarUrl), 76 - title: LONG_TITLE, 77 - ...overrides, 78 - }); 79 - 80 - export const createLongTitlePullRequestData = ( 81 - avatarUrl: string, 82 - overrides?: Partial<PullRequestCardData>, 83 - ): PullRequestCardData => ({ 84 - ...createPullRequestData(avatarUrl), 85 - title: LONG_TITLE, 86 - ...overrides, 87 - });
-132
ogre/src/__tests__/render.test.ts
··· 1 - import { test, describe, beforeAll } from "bun:test"; 2 - import { writeFileSync, mkdirSync, readFileSync } from "fs"; 3 - import { join } from "path"; 4 - import { h, type VNode } from "preact"; 5 - import { renderCard } from "../lib/render"; 6 - import { RepositoryCard } from "../components/cards/repository"; 7 - import { IssueCard } from "../components/cards/issue"; 8 - import { PullRequestCard } from "../components/cards/pull-request"; 9 - import { 10 - repositoryCardSchema, 11 - issueCardSchema, 12 - pullRequestCardSchema, 13 - } from "../validation"; 14 - import { 15 - createRepoData, 16 - createIssueData, 17 - createPullRequestData, 18 - createLongTitleIssueData, 19 - createLongTitlePullRequestData, 20 - } from "./fixtures"; 21 - 22 - const outputDir = join(process.cwd(), "output"); 23 - let avatarDataUri: string; 24 - 25 - const loadAvatar = (): string => { 26 - const avatarPath = join( 27 - process.cwd(), 28 - "src", 29 - "__tests__", 30 - "assets", 31 - "avatar.jpg", 32 - ); 33 - const avatarBase64 = readFileSync(avatarPath).toString("base64"); 34 - return `data:image/jpeg;base64,${avatarBase64}`; 35 - }; 36 - 37 - beforeAll(() => { 38 - mkdirSync(outputDir, { recursive: true }); 39 - avatarDataUri = loadAvatar(); 40 - }); 41 - 42 - const savePng = (filename: string, buffer: Uint8Array) => { 43 - writeFileSync(join(outputDir, filename), buffer); 44 - }; 45 - 46 - const renderAndSave = async <P>(component: VNode<P>, filename: string) => { 47 - const { png } = await renderCard(component as VNode); 48 - savePng(filename, png); 49 - }; 50 - 51 - describe("repository card", () => { 52 - test("renders repository card", async () => { 53 - const data = createRepoData(avatarDataUri); 54 - const validated = repositoryCardSchema.parse(data); 55 - await renderAndSave(h(RepositoryCard, validated), "repository-card.png"); 56 - }); 57 - }); 58 - 59 - describe("issue cards", () => { 60 - test("renders open issue", async () => { 61 - const data = createIssueData(avatarDataUri); 62 - const validated = issueCardSchema.parse(data); 63 - await renderAndSave(h(IssueCard, validated), "issue-card.png"); 64 - }); 65 - 66 - test("renders closed issue", async () => { 67 - const data = createIssueData(avatarDataUri, { 68 - issueNumber: 5, 69 - status: "closed", 70 - labels: [{ name: "wontfix", color: "#6a737d" }], 71 - reactionCount: 2, 72 - }); 73 - const validated = issueCardSchema.parse(data); 74 - await renderAndSave(h(IssueCard, validated), "issue-card-closed.png"); 75 - }); 76 - 77 - test("renders issue with long title", async () => { 78 - const data = createLongTitleIssueData(avatarDataUri, { 79 - issueNumber: 42, 80 - }); 81 - const validated = issueCardSchema.parse(data); 82 - await renderAndSave(h(IssueCard, validated), "issue-card-long-title.png"); 83 - }); 84 - }); 85 - 86 - describe("pull request cards", () => { 87 - test("renders open pull request", async () => { 88 - const data = createPullRequestData(avatarDataUri); 89 - const validated = pullRequestCardSchema.parse(data); 90 - await renderAndSave(h(PullRequestCard, validated), "pull-request-card.png"); 91 - }); 92 - 93 - test("renders merged pull request", async () => { 94 - const data = createPullRequestData(avatarDataUri, { 95 - pullRequestNumber: 2, 96 - status: "merged", 97 - title: "Implement OAuth2 authentication flow", 98 - filesChanged: 5, 99 - additions: 342, 100 - deletions: 28, 101 - }); 102 - const validated = pullRequestCardSchema.parse(data); 103 - await renderAndSave( 104 - h(PullRequestCard, validated), 105 - "pull-request-card-merged.png", 106 - ); 107 - }); 108 - 109 - test("renders closed pull request", async () => { 110 - const data = createPullRequestData(avatarDataUri, { 111 - pullRequestNumber: 3, 112 - status: "closed", 113 - title: "WIP: Experimental feature", 114 - }); 115 - const validated = pullRequestCardSchema.parse(data); 116 - await renderAndSave( 117 - h(PullRequestCard, validated), 118 - "pull-request-card-closed.png", 119 - ); 120 - }); 121 - 122 - test("renders pull request with long title", async () => { 123 - const data = createLongTitlePullRequestData(avatarDataUri, { 124 - pullRequestNumber: 42, 125 - }); 126 - const validated = pullRequestCardSchema.parse(data); 127 - await renderAndSave( 128 - h(PullRequestCard, validated), 129 - "pull-request-card-long-title.png", 130 - ); 131 - }); 132 - });
-52
ogre/src/components/cards/issue.tsx
··· 1 - import { Card, Row, Col } from "../shared/layout"; 2 - import { TangledLogo } from "../shared/logo"; 3 - import { IssueStatusBadge } from "../shared/status-badge"; 4 - import { CardHeader } from "../shared/card-header"; 5 - import { LabelList } from "../shared/label-pill"; 6 - import { FooterStats } from "../shared/footer-stats"; 7 - import { TYPOGRAPHY } from "../shared/constants"; 8 - import type { IssueCardData } from "../../validation"; 9 - 10 - export function IssueCard(data: IssueCardData) { 11 - return ( 12 - <Card style={{ justifyContent: "space-between" }}> 13 - <Col style={{ gap: 48 }}> 14 - <Col style={{ gap: 32 }}> 15 - <Row style={{ justifyContent: "space-between" }}> 16 - <CardHeader 17 - avatarUrl={data.avatarUrl} 18 - ownerHandle={data.ownerHandle} 19 - repoName={data.repoName} 20 - /> 21 - <IssueStatusBadge status={data.status} /> 22 - </Row> 23 - 24 - <div 25 - style={{ 26 - ...TYPOGRAPHY.title, 27 - color: "#000000", 28 - display: "block", 29 - lineClamp: `2 "... #${data.issueNumber}"`, 30 - }}> 31 - {data.title} 32 - </div> 33 - </Col> 34 - 35 - <LabelList labels={data.labels} /> 36 - </Col> 37 - 38 - <Row 39 - style={{ 40 - alignItems: "flex-end", 41 - justifyContent: "space-between", 42 - }}> 43 - <FooterStats 44 - createdAt={data.createdAt} 45 - reactionCount={data.reactionCount} 46 - commentCount={data.commentCount} 47 - /> 48 - <TangledLogo /> 49 - </Row> 50 - </Card> 51 - ); 52 - }
-137
ogre/src/components/cards/pull-request.tsx
··· 1 - import { Card, Row, Col } from "../shared/layout"; 2 - import { TangledLogo } from "../shared/logo"; 3 - import { StatusBadge } from "../shared/status-badge"; 4 - import { CardHeader } from "../shared/card-header"; 5 - import { FooterStats } from "../shared/footer-stats"; 6 - import { FileDiff, RefreshCw } from "../../icons/lucide"; 7 - import { COLORS, TYPOGRAPHY } from "../shared/constants"; 8 - import type { PullRequestCardData } from "../../validation"; 9 - 10 - interface FilesChangedPillProps { 11 - filesChanged: number; 12 - additions: number; 13 - deletions: number; 14 - } 15 - 16 - function FilesChangedPill({ 17 - filesChanged, 18 - additions, 19 - deletions, 20 - }: FilesChangedPillProps) { 21 - return ( 22 - <Row 23 - style={{ 24 - overflow: "hidden", 25 - borderRadius: 18, 26 - backgroundColor: "#fff", 27 - border: `4px solid ${COLORS.label.border}`, 28 - }}> 29 - <Row 30 - style={{ 31 - gap: 16, 32 - padding: "16px 28px", 33 - }}> 34 - <FileDiff size={34} color="#202020" /> 35 - <span style={{ ...TYPOGRAPHY.body, color: "#202020" }}> 36 - {filesChanged} files 37 - </span> 38 - </Row> 39 - <Row style={{ gap: 0 }}> 40 - <Row 41 - style={{ 42 - padding: "16px 10px 16px 11px", 43 - backgroundColor: COLORS.diff.additions.bg, 44 - }}> 45 - <span 46 - style={{ ...TYPOGRAPHY.body, color: COLORS.diff.additions.text }}> 47 - +{additions} 48 - </span> 49 - </Row> 50 - <Row 51 - style={{ 52 - padding: "16px 16px 16px 11px", 53 - backgroundColor: COLORS.diff.deletions.bg, 54 - }}> 55 - <span 56 - style={{ ...TYPOGRAPHY.body, color: COLORS.diff.deletions.text }}> 57 - -{deletions} 58 - </span> 59 - </Row> 60 - </Row> 61 - </Row> 62 - ); 63 - } 64 - 65 - interface MetricPillProps { 66 - value: number; 67 - label: string; 68 - } 69 - 70 - function RoundsPill({ value, label }: MetricPillProps) { 71 - return ( 72 - <Row 73 - style={{ 74 - gap: 16, 75 - padding: "16px 28px", 76 - borderRadius: 18, 77 - backgroundColor: "#fff", 78 - border: `4px solid ${COLORS.label.border}`, 79 - }}> 80 - <RefreshCw size={36} color="#202020" /> 81 - <span style={{ ...TYPOGRAPHY.body, color: "#202020" }}> 82 - {value} {label} 83 - </span> 84 - </Row> 85 - ); 86 - } 87 - 88 - export function PullRequestCard(data: PullRequestCardData) { 89 - return ( 90 - <Card style={{ justifyContent: "space-between" }}> 91 - <Col style={{ gap: 48 }}> 92 - <Col style={{ gap: 32 }}> 93 - <Row style={{ justifyContent: "space-between" }}> 94 - <CardHeader 95 - avatarUrl={data.avatarUrl} 96 - ownerHandle={data.ownerHandle} 97 - repoName={data.repoName} 98 - /> 99 - <StatusBadge status={data.status} /> 100 - </Row> 101 - 102 - <span 103 - style={{ 104 - ...TYPOGRAPHY.title, 105 - color: "#000000", 106 - display: "block", 107 - lineClamp: `2 "... #${data.pullRequestNumber}"`, 108 - }}> 109 - {data.title} 110 - </span> 111 - </Col> 112 - 113 - <Row style={{ gap: 16 }}> 114 - <FilesChangedPill 115 - filesChanged={data.filesChanged} 116 - additions={data.additions} 117 - deletions={data.deletions} 118 - /> 119 - <RoundsPill value={data.rounds} label="rounds" /> 120 - </Row> 121 - </Col> 122 - 123 - <Row 124 - style={{ 125 - alignItems: "flex-end", 126 - justifyContent: "space-between", 127 - }}> 128 - <FooterStats 129 - createdAt={data.createdAt} 130 - reactionCount={data.reactionCount} 131 - commentCount={data.commentCount} 132 - /> 133 - <TangledLogo /> 134 - </Row> 135 - </Card> 136 - ); 137 - }
-53
ogre/src/components/cards/repository.tsx
··· 1 - import { Card, Row, Col } from "../shared/layout"; 2 - import { Avatar } from "../shared/avatar"; 3 - import { LanguageCircles } from "../shared/language-circles"; 4 - import { Metrics } from "../shared/metrics"; 5 - import { TangledLogo } from "../shared/logo"; 6 - import { FooterStats } from "../shared/footer-stats"; 7 - import { TYPOGRAPHY } from "../shared/constants"; 8 - import type { RepositoryCardData } from "../../validation"; 9 - 10 - function repoNameFontSize(name: string): number { 11 - // Available width ~1000px (1104px card content minus language circles area). 12 - // Inter 600 average char width is ~0.58Γ— the font size. 13 - const maxSize = TYPOGRAPHY.repoName.fontSize; 14 - const fitted = Math.floor(1000 / (name.length * 0.58)); 15 - return Math.min(maxSize, Math.max(fitted, 48)); 16 - } 17 - 18 - export function RepositoryCard(data: RepositoryCardData) { 19 - const fontSize = repoNameFontSize(data.repoName); 20 - return ( 21 - <Card> 22 - <LanguageCircles languages={data.languages} /> 23 - 24 - <Col style={{ gap: 64 }}> 25 - <Col style={{ gap: 24 }}> 26 - <span style={{ ...TYPOGRAPHY.repoName, fontSize, color: "#000000" }}> 27 - {data.repoName} 28 - </span> 29 - 30 - <Row style={{ gap: 16 }}> 31 - <Avatar src={data.avatarUrl} size={64} /> 32 - <span style={{ ...TYPOGRAPHY.ownerHandle, color: "#000000" }}> 33 - {data.ownerHandle} 34 - </span> 35 - </Row> 36 - </Col> 37 - 38 - <Metrics stars={data.stars} pulls={data.pulls} issues={data.issues} /> 39 - </Col> 40 - 41 - <Row 42 - style={{ 43 - alignItems: "flex-end", 44 - justifyContent: "space-between", 45 - flexGrow: 1, 46 - }}> 47 - <FooterStats createdAt={data.createdAt} /> 48 - 49 - <TangledLogo /> 50 - </Row> 51 - </Card> 52 - ); 53 - }
-31
ogre/src/components/shared/avatar.tsx
··· 1 - interface AvatarProps { 2 - src: string; 3 - size?: number; 4 - } 5 - 6 - export function Avatar({ src, size = 64 }: AvatarProps) { 7 - const avatarSrc = src.includes("avatar.tangled.sh") 8 - ? src.replace(/[?&]format=\w+/, "").replace(/[?&]$/, "") + 9 - (src.includes("?") ? "&" : "?") + "format=jpeg" 10 - : src; 11 - 12 - return ( 13 - <div 14 - style={{ 15 - width: size, 16 - height: size, 17 - borderRadius: size / 2, 18 - overflow: "hidden", 19 - display: "flex", 20 - alignItems: "center", 21 - justifyContent: "center", 22 - }}> 23 - <img 24 - src={avatarSrc} 25 - width={size} 26 - height={size} 27 - style={{ objectFit: "cover" }} 28 - /> 29 - </div> 30 - ); 31 - }
-24
ogre/src/components/shared/card-header.tsx
··· 1 - import { Row } from "./layout"; 2 - import { Avatar } from "./avatar"; 3 - import { TYPOGRAPHY } from "./constants"; 4 - 5 - interface CardHeaderProps { 6 - avatarUrl: string; 7 - ownerHandle: string; 8 - repoName: string; 9 - } 10 - 11 - export function CardHeader({ 12 - avatarUrl, 13 - ownerHandle, 14 - repoName, 15 - }: CardHeaderProps) { 16 - return ( 17 - <Row style={{ gap: 16 }}> 18 - <Avatar src={avatarUrl} size={64} /> 19 - <span style={{ ...TYPOGRAPHY.cardHeader, color: "#000000" }}> 20 - {ownerHandle} / {repoName} 21 - </span> 22 - </Row> 23 - ); 24 - }
-30
ogre/src/components/shared/constants.ts
··· 1 - export const COLORS = { 2 - text: "#000000", 3 - textSecondary: "#7D7D7D", 4 - icon: "#404040", 5 - status: { 6 - open: { bg: "#16A34A", text: "#ffffff" }, 7 - closed: { bg: "#1f2937", text: "#ffffff" }, 8 - merged: { bg: "#7C3AED", text: "#ffffff" }, 9 - }, 10 - label: { 11 - text: "#202020", 12 - border: "#E6E6E6", 13 - }, 14 - diff: { 15 - additions: { bg: "#dcfce7", text: "#15803d" }, 16 - deletions: { bg: "#fee2e2", text: "#b91c1c" }, 17 - }, 18 - } as const; 19 - 20 - export const TYPOGRAPHY = { 21 - title: { fontFamily: "Inter", fontSize: 64, fontWeight: 600 }, 22 - repoName: { fontFamily: "Inter", fontSize: 144, fontWeight: 600 }, 23 - ownerHandle: { fontFamily: "Inter", fontSize: 48, fontWeight: 500 }, 24 - cardHeader: { fontFamily: "Inter", fontSize: 48, fontWeight: 500 }, 25 - status: { fontFamily: "Inter", fontSize: 48, fontWeight: 500 }, 26 - metricValue: { fontFamily: "Inter", fontSize: 48, fontWeight: 500 }, 27 - body: { fontFamily: "Inter", fontSize: 36, fontWeight: 400 }, 28 - meta: { fontFamily: "Inter", fontSize: 32, fontWeight: 400 }, 29 - label: { fontFamily: "Inter", fontSize: 24, fontWeight: 400 }, 30 - } as const;
-33
ogre/src/components/shared/footer-stats.tsx
··· 1 - import { Row } from "./layout"; 2 - import { Calendar, MessageSquare, SmilePlus } from "../../icons/lucide"; 3 - import { StatItem } from "./stat-item"; 4 - 5 - interface FooterStatsProps { 6 - createdAt: string; 7 - reactionCount?: number; 8 - commentCount?: number; 9 - } 10 - 11 - export function FooterStats({ 12 - createdAt, 13 - reactionCount, 14 - commentCount, 15 - }: FooterStatsProps) { 16 - const formattedDate = new Intl.DateTimeFormat("en-GB", { 17 - day: "numeric", 18 - month: "short", 19 - year: "numeric", 20 - }).format(new Date(createdAt)); 21 - 22 - return ( 23 - <Row style={{ gap: 64 }}> 24 - <StatItem Icon={Calendar} value={formattedDate} /> 25 - {reactionCount ? ( 26 - <StatItem Icon={SmilePlus} value={reactionCount} /> 27 - ) : null} 28 - {commentCount ? ( 29 - <StatItem Icon={MessageSquare} value={commentCount} /> 30 - ) : null} 31 - </Row> 32 - ); 33 - }
-49
ogre/src/components/shared/label-pill.tsx
··· 1 - import { Row } from "./layout"; 2 - import { COLORS, TYPOGRAPHY } from "./constants"; 3 - 4 - interface LabelPillProps { 5 - name: string; 6 - color: string; 7 - } 8 - 9 - function LabelPill({ name, color }: LabelPillProps) { 10 - return ( 11 - <Row 12 - style={{ 13 - gap: 16, 14 - padding: "16px 28px", 15 - borderRadius: 18, 16 - backgroundColor: "#fff", 17 - border: `4px solid ${COLORS.label.border}`, 18 - }}> 19 - <div 20 - style={{ 21 - width: 24, 22 - height: 24, 23 - borderRadius: "50%", 24 - backgroundColor: color, 25 - }} 26 - /> 27 - <span style={{ ...TYPOGRAPHY.body, color: COLORS.label.text }}> 28 - {name} 29 - </span> 30 - </Row> 31 - ); 32 - } 33 - 34 - interface LabelListProps { 35 - labels: Array<{ name: string; color: string }>; 36 - max?: number; 37 - } 38 - 39 - export function LabelList({ labels, max = 5 }: LabelListProps) { 40 - if (labels.length === 0) return null; 41 - 42 - return ( 43 - <Row style={{ gap: 12 }}> 44 - {labels.slice(0, max).map((label, i) => ( 45 - <LabelPill key={i} name={label.name} color={label.color} /> 46 - ))} 47 - </Row> 48 - ); 49 - }
-56
ogre/src/components/shared/language-circles.tsx
··· 1 - import type { Language } from "../../validation"; 2 - 3 - interface LanguageCirclesProps { 4 - languages: Language[]; 5 - } 6 - 7 - const MAX_RADIUS = 380; 8 - 9 - function percentageToThickness(percentage: number): number { 10 - return (percentage / 100) * MAX_RADIUS; 11 - } 12 - 13 - export function LanguageCircles({ languages }: LanguageCirclesProps) { 14 - const sortedLanguages = [...languages] 15 - .sort((a, b) => b.percentage - a.percentage) 16 - .slice(0, 5) 17 - .reverse(); 18 - 19 - let cumulativeRadius = 0; 20 - 21 - return ( 22 - <div 23 - style={{ 24 - position: "absolute", 25 - right: -MAX_RADIUS, 26 - top: -MAX_RADIUS, 27 - width: MAX_RADIUS * 2, 28 - height: MAX_RADIUS * 2, 29 - display: "flex", 30 - }}> 31 - {sortedLanguages.map((lang, i) => { 32 - const thickness = percentageToThickness(lang.percentage); 33 - const contentSize = cumulativeRadius * 2; 34 - 35 - cumulativeRadius += thickness; 36 - 37 - return ( 38 - <div 39 - key={i} 40 - style={{ 41 - position: "absolute", 42 - left: "50%", 43 - top: "50%", 44 - transform: "translate(-50%, -50%)", 45 - width: contentSize, 46 - height: contentSize, 47 - borderRadius: "50%", 48 - border: `${thickness}px solid ${lang.color}`, 49 - boxSizing: "content-box", 50 - }} 51 - /> 52 - ); 53 - })} 54 - </div> 55 - ); 56 - }
-45
ogre/src/components/shared/layout.tsx
··· 1 - import type { ComponentChildren } from "preact"; 2 - 3 - interface StyleProps { 4 - style?: Record<string, string | number>; 5 - children?: ComponentChildren; 6 - } 7 - 8 - export function Card({ children, style }: StyleProps) { 9 - return ( 10 - <div 11 - style={{ 12 - width: 1200, 13 - height: 630, 14 - background: "white", 15 - display: "flex", 16 - flexDirection: "column", 17 - padding: 48, 18 - ...style, 19 - }}> 20 - {children} 21 - </div> 22 - ); 23 - } 24 - 25 - export function Row({ children, style }: StyleProps) { 26 - return ( 27 - <div 28 - style={{ 29 - display: "flex", 30 - flexDirection: "row", 31 - alignItems: "center", 32 - ...style, 33 - }}> 34 - {children} 35 - </div> 36 - ); 37 - } 38 - 39 - export function Col({ children, style }: StyleProps) { 40 - return ( 41 - <div style={{ display: "flex", flexDirection: "column", ...style }}> 42 - {children} 43 - </div> 44 - ); 45 - }
-68
ogre/src/components/shared/logo.tsx
··· 1 - export function TangledLogo() { 2 - return ( 3 - <div 4 - style={{ 5 - width: 256, 6 - height: 70, 7 - display: "contents", 8 - }}> 9 - <svg 10 - width="256" 11 - height="70" 12 - viewBox="0 0 256 70" 13 - fill="none" 14 - xmlns="http://www.w3.org/2000/svg"> 15 - <path 16 - d="M38.6562 30.0449L39.168 30.2402L39.6807 30.4346L40.0234 30.8076L40.3672 31.1807L40.4922 31.7363L40.6162 32.292L39.9473 35.0566L39.7441 36.6553L39.5049 41.5752L39.3906 42.0312L39.2773 42.4863L38.9443 42.8193L38.6104 43.1514L37.9229 43.4463L37.3574 43.4414L36.793 43.4375L36.4531 43.2549L36.1143 43.0723L35.7949 42.6689L35.4756 42.2666L35.3438 41.7139L35.2109 41.1621L35.3564 37.832L35.5674 35.2646L36.002 33.0186L36.165 32.3838L36.3271 31.748L36.7324 30.9541L37.5293 30.2979L38.0928 30.1719L38.6562 30.0449Z" 17 - fill="black" 18 - /> 19 - <path 20 - d="M30.5889 31.1201L30.8682 31.4277L31.1484 31.7354L31.2695 32.0986L31.3916 32.4619V33.3789L31.1904 33.9082L30.8477 35.5654L30.8486 38.1523L31.1074 40.5127L30.5547 41.7197L30.1074 42.0156L29.6611 42.3115L28.4502 42.3838L28.0098 42.1787L27.5693 41.9727L27.04 41.2539L26.9248 40.9336L26.8086 40.6123L26.6738 39.8008L26.4814 36.9756L26.6992 34.3018L26.8467 33.6553L26.9932 33.0078L27.4502 31.7852L28.0137 31.1162L28.8779 30.7236H29.7334L30.5889 31.1201Z" 21 - fill="black" 22 - /> 23 - <path 24 - fill-rule="evenodd" 25 - clip-rule="evenodd" 26 - d="M45.4551 0L48.0215 0.143555L50.1611 0.571289L51.8721 1.12793L53.5869 1.91602L55.0186 2.78613L56.5781 3.96191L58.3262 5.74609L59.2383 6.91309L59.6455 7.55957L60.0518 8.20605L60.5176 9.16797L60.9824 10.1309L61.2656 10.9355L61.5479 11.7402L61.9658 13.7363L61.999 13.7607L62.0332 13.7852L64.707 15.0918L66.5674 16.3906L68.4297 18.1523L69.5566 19.5645L70.5576 21.1123L71.4766 23.0723L72.0156 24.6768L72.5146 27.0293L72.5107 30.9873L72.1279 32.8848L71.8564 33.7539L71.584 34.623L70.8457 36.3145L69.9648 37.832L68.8193 39.3867L67.2871 41L65.5625 42.3418L63.6367 43.4707L63.5068 43.5762L63.376 43.6816L63.5449 44.0723L63.7148 44.4629L63.9775 45.1045L64.2393 45.7461L65.3809 50.1318L65.5762 51.041L65.7725 51.9492L65.7617 56.0137L65.2803 58.2598L64.7393 59.8643L63.5947 62.2168L62.2529 64.1426L61.3398 65.0898L60.4277 66.0381L58.7158 67.3643L58.0547 67.7627L57.3926 68.1602L56.6641 68.4814L55.9355 68.8018L55.668 68.9443L55.4004 69.0859L53.9033 69.5225L51.9668 69.8672L50.2666 69.8496L49.8389 69.8301L48.7695 69.8105L48.3955 69.8877L48.0205 69.9639L47.8271 69.8506L47.6328 69.7373L46.418 69.6748L45.5889 69.5967L44.7607 69.5176V69.3584L44.1455 69.2383L43.5303 69.1172L43.0264 68.876L42.5225 68.6338L42.5146 68.3857L40.9121 67.5215L39.252 66.2988L37.6768 64.7842L36.3486 63.0723L35.3242 61.3613L35.2842 61.3184L35.2441 61.2744L34.3906 62.2441L33.0488 63.5322L31.9307 64.4111L31.1709 64.9014L30.4121 65.3926L28.915 66.0859L28.9307 66.1836L28.9473 66.2812L28.4307 66.4189L27.915 66.5576L25.8828 67.165L24.8135 67.4365L22.46 67.5928L20.3213 67.3818L20.209 67.29L20.0967 67.1992L19.6338 67.3203L19.4824 67.168L19.3301 67.0166L18.7031 66.8779L18.0752 66.7383L16.2891 66.1172L14.332 65.1357L12.3652 63.7656L10.5332 62.0312L9.18457 60.292L8.41992 59.0078L7.78125 57.7246L7.02539 55.6924L6.52441 53.3398L6.52734 49.1689L6.91406 47.251L7.56738 45.2109L8.51465 43.292L8.72559 42.9502L8.93652 42.6094L8.34277 42.2012L7.80762 41.8965L7.27246 41.5928L5.66895 40.416L4.09082 38.9014L2.95703 37.4893L2.47852 36.748L1.99902 36.0078L1.61035 35.208L1.2207 34.4092L0.859375 33.3926L0.49707 32.377L0 30.0244L0.0126953 25.8525L0.610352 23.291L1.40625 21.2383L1.9082 20.3164L2.41113 19.3955L3.06738 18.5059L3.72461 17.6172L5.24121 16.04L5.93555 15.4834L6.63086 14.9277L8.02148 14.0195L9.95117 13.0859L11.0166 12.6934L12.0859 10.665L13.3633 8.84766L15.252 6.89062L16.0215 6.30273L16.792 5.71582L18.6221 4.63867L20.5225 3.85156L21.7842 3.44922L24.1719 2.97266L27.7012 2.99023L30.375 3.5459L32.1934 4.21094L33.1846 4.70605L34.1768 5.2002L35.6152 3.80762L36.5781 3.05176L37.0596 2.74512L37.541 2.4375L37.9688 2.19043L38.3965 1.94238L39.0918 1.61523L39.7861 1.28711L41.3906 0.693359L43.3164 0.251953L45.4551 0ZM39.8984 21.0156L39.7324 21.3662L38.7773 22.457L37.7549 23.1094L37.0898 23.3311L36.4248 23.5537H34.7402L33.5654 23.1494L33.2246 22.9727L32.8848 22.7969L32.1631 22.1328L31.4424 21.4678L31.1543 21.2139L30.8672 20.959L29.5518 22.1494L29.0537 22.3848L28.5566 22.6211L28.0225 22.7646L27.4873 22.9092L26.8281 22.9102L26.1699 22.9121L25.4941 22.7012L24.8184 22.4912L24.5127 22.7812L24.207 23.0723L23.8438 23.5645L23.4814 24.0576L22.9707 24.248L22.4609 24.4375L21.7676 24.9531L21.4385 25.2959L21.1104 25.6387L18.9746 28.8174L16.3428 34.3018L15.4658 36.334L15.1318 37.6572L15.084 38.2793L15.0371 38.9014L15.1396 39.6318L15.2432 40.3613L15.5322 40.9531L15.8223 41.5439L16.2539 41.9209L16.6855 42.2969L17.8115 42.8223L19.4658 42.834L20.1611 42.54L20.8564 42.2471L21.498 41.7891L23.71 40.0234L23.791 40.0732L23.8711 40.123L24.0127 42.9297L24.5439 46.0146L25.0791 48.2061L25.8701 50.0244L26.1865 50.5049L26.502 50.9863L27.167 51.7002L28.8779 52.9375L29.7461 53.377L31.0381 53.7109L32.5146 53.8965L33.1289 53.8584L33.7441 53.8213L34.3896 53.7686L35.0361 53.7168L36.2783 53.3086L37.0127 52.9541L37.7471 52.6006L39.3975 51.3564L40.7764 49.8105L42.0938 48.0986L43.2275 46.3877L44.5176 44.1416L45.3223 42.2656L45.4082 42.2129L45.4941 42.1592L46.6807 43.7354L47.6641 44.6426L48.2715 44.9209L48.8779 45.2002L49.7871 45.2432L50.6963 45.2852L51.9795 44.8027L53.1855 43.5723L53.4639 42.7344L53.7412 41.8955L54.0879 40.0781L53.9785 37.5107L53.5176 34.9492L53.001 33.0186L52.1523 30.5996L51.1514 28.7402L50.4463 27.7783L50.0723 27.418L49.6982 27.0586L49.8115 25.96L49.4424 24.4629L48.6377 22.751L47.5391 21.5381L46.4971 20.7734L45.8828 21.1426L45.2686 21.5127L43.6631 21.8701L43.0449 21.8047L42.4268 21.7402L41.0137 21.209L40.6572 20.9375L40.3018 20.666H40.0645L39.8984 21.0156Z" 27 - fill="black" 28 - /> 29 - <path 30 - fill-rule="evenodd" 31 - clip-rule="evenodd" 32 - d="M171.79 22.4316C173.259 22.4316 174.489 22.6826 175.479 23.1836C176.47 23.6732 177.268 24.2882 177.871 25.0283C178.486 25.7568 178.958 26.474 179.288 27.1797H179.562V22.7734H186.787V49.2646C186.787 51.4507 186.24 53.2782 185.147 54.7471C184.054 56.2273 182.539 57.3432 180.604 58.0947C178.679 58.8462 176.465 59.2226 173.96 59.2227C171.603 59.2227 169.582 58.9032 167.896 58.2656C166.223 57.628 164.89 56.7683 163.899 55.6865C162.909 54.6049 162.266 53.4037 161.97 52.083L168.699 51.1777C168.904 51.6559 169.229 52.1061 169.673 52.5273C170.117 52.9598 170.703 53.3127 171.432 53.5859C172.172 53.8592 173.071 53.9961 174.13 53.9961C175.713 53.9961 177.017 53.6197 178.042 52.8682C179.078 52.1166 179.596 50.8813 179.596 49.1621V44.3623H179.288C178.969 45.0911 178.491 45.7806 177.854 46.4297C177.216 47.0785 176.396 47.6077 175.395 48.0176C174.393 48.4275 173.197 48.6328 171.808 48.6328C169.838 48.6328 168.044 48.1775 166.427 47.2666C164.821 46.3443 163.54 44.9379 162.584 43.0479C161.639 41.1463 161.167 38.7434 161.167 35.8398C161.167 32.8679 161.65 30.3853 162.618 28.3926C163.586 26.3999 164.873 24.9086 166.479 23.918C168.095 22.9274 169.866 22.4317 171.79 22.4316ZM174.113 28.2217C172.918 28.2217 171.91 28.5463 171.09 29.1953C170.27 29.833 169.65 30.7217 169.229 31.8604C168.807 32.999 168.597 34.3141 168.597 35.8057C168.597 37.32 168.807 38.6299 169.229 39.7344C169.661 40.8273 170.281 41.6759 171.09 42.2793C171.91 42.8714 172.918 43.167 174.113 43.167C175.286 43.167 176.277 42.8765 177.085 42.2959C177.905 41.7039 178.531 40.8615 178.964 39.7686C179.408 38.6641 179.63 37.3428 179.63 35.8057C179.63 34.2685 179.414 32.9359 178.981 31.8086C178.549 30.67 177.922 29.7874 177.103 29.1611C176.283 28.5349 175.286 28.2217 174.113 28.2217Z" 33 - fill="black" 34 - /> 35 - <path 36 - fill-rule="evenodd" 37 - clip-rule="evenodd" 38 - d="M215.798 22.4316C217.528 22.4317 219.139 22.7107 220.631 23.2686C222.134 23.8151 223.444 24.6407 224.56 25.7451C225.687 26.8496 226.564 28.2392 227.19 29.9131C227.817 31.5754 228.13 33.5224 228.13 35.7539V37.7529H210.264V37.7695C210.264 39.0676 210.503 40.1897 210.981 41.1348C211.471 42.0796 212.16 42.808 213.048 43.3203C213.936 43.8327 214.99 44.0889 216.208 44.0889C217.016 44.0888 217.756 43.9757 218.428 43.748C219.1 43.5203 219.675 43.1781 220.153 42.7227C220.631 42.2672 220.996 41.7091 221.246 41.0488L227.976 41.4932C227.634 43.1101 226.934 44.5225 225.875 45.7295C224.827 46.925 223.472 47.8585 221.81 48.5303C220.159 49.1906 218.251 49.5205 216.088 49.5205C213.389 49.5205 211.066 48.974 209.119 47.8809C207.184 46.7764 205.692 45.2165 204.645 43.2012C203.597 41.1744 203.073 38.7776 203.073 36.0107C203.073 33.3121 203.597 30.9435 204.645 28.9053C205.692 26.867 207.167 25.2783 209.068 24.1396C210.981 23.0011 213.225 22.4316 215.798 22.4316ZM215.917 27.8633C214.813 27.8633 213.833 28.1195 212.979 28.6318C212.137 29.1328 211.476 29.8102 210.998 30.6641C210.557 31.4413 210.317 32.3013 210.273 33.2432H221.28C221.28 32.1957 221.052 31.2674 220.597 30.459C220.141 29.6508 219.509 29.0189 218.701 28.5635C217.904 28.0967 216.976 27.8633 215.917 27.8633Z" 39 - fill="black" 40 - /> 41 - <path 42 - fill-rule="evenodd" 43 - clip-rule="evenodd" 44 - d="M118.389 22.4316C119.846 22.4316 121.241 22.6028 122.573 22.9443C123.917 23.2859 125.107 23.8149 126.144 24.5322C127.191 25.2496 128.017 26.1725 128.62 27.2998C129.224 28.4157 129.525 29.7536 129.525 31.3135V49.0088H122.625V45.3701H122.42C121.999 46.1898 121.435 46.9129 120.729 47.5391C120.024 48.1539 119.175 48.6382 118.185 48.9912C117.194 49.3328 116.049 49.5039 114.751 49.5039C113.077 49.5039 111.586 49.2134 110.276 48.6328C108.967 48.0407 107.93 47.1696 107.167 46.0195C106.416 44.8581 106.04 43.4114 106.04 41.6807C106.04 40.2233 106.308 38.9993 106.843 38.0088C107.378 37.0181 108.107 36.2207 109.029 35.6172C109.952 35.0138 110.999 34.5584 112.172 34.251C113.356 33.9435 114.597 33.7278 115.896 33.6025C117.421 33.4431 118.651 33.2948 119.585 33.1582C120.519 33.0102 121.196 32.7934 121.617 32.5088C122.038 32.2241 122.249 31.803 122.249 31.2451V31.1426C122.249 30.0609 121.908 29.2239 121.225 28.6318C120.553 28.0397 119.596 27.7441 118.354 27.7441C117.045 27.7442 116.004 28.0346 115.229 28.6152C114.455 29.1845 113.943 29.9014 113.692 30.7666L106.963 30.2207C107.304 28.6266 107.976 27.2483 108.978 26.0869C109.98 24.9141 111.273 24.0149 112.855 23.3887C114.449 22.7511 116.294 22.4317 118.389 22.4316ZM122.301 36.8477C122.073 36.9956 121.76 37.1316 121.361 37.2568C120.974 37.3707 120.536 37.4796 120.046 37.582C119.556 37.6731 119.067 37.7582 118.577 37.8379C118.088 37.9062 117.644 37.9694 117.245 38.0264C116.391 38.1516 115.644 38.3507 115.007 38.624C114.369 38.8973 113.874 39.2676 113.521 39.7344C113.169 40.1898 112.992 40.7593 112.992 41.4424C112.992 42.4327 113.35 43.1902 114.067 43.7139C114.796 44.2263 115.719 44.4824 116.835 44.4824C117.905 44.4824 118.85 44.2718 119.67 43.8506C120.49 43.4179 121.134 42.8371 121.601 42.1084C122.067 41.3798 122.301 40.554 122.301 39.6318V36.8477Z" 45 - fill="black" 46 - /> 47 - <path 48 - fill-rule="evenodd" 49 - clip-rule="evenodd" 50 - d="M256 14.0283V49.0088H248.826V44.8066H248.52C248.178 45.5353 247.694 46.2583 247.067 46.9756C246.453 47.6815 245.65 48.2685 244.659 48.7354C243.68 49.2022 242.484 49.4355 241.072 49.4355C239.08 49.4355 237.275 48.9231 235.658 47.8984C234.053 46.8623 232.777 45.3419 231.832 43.3379C230.898 41.3224 230.432 38.8512 230.432 35.9248C230.432 32.9188 230.915 30.4194 231.883 28.4268C232.851 26.4227 234.138 24.9252 235.743 23.9346C237.36 22.9326 239.131 22.4317 241.055 22.4316C242.524 22.4316 243.748 22.6826 244.728 23.1836C245.718 23.6732 246.515 24.2883 247.118 25.0283C247.733 25.757 248.201 26.4738 248.52 27.1797H248.741V14.0283H256ZM243.378 28.2217C242.182 28.2217 241.174 28.5463 240.354 29.1953C239.535 29.8444 238.914 30.7445 238.493 31.8945C238.072 33.0445 237.861 34.3764 237.861 35.8906C237.861 37.4163 238.072 38.7657 238.493 39.9385C238.926 41.0999 239.546 42.0114 240.354 42.6719C241.174 43.3209 242.182 43.6455 243.378 43.6455C244.551 43.6455 245.541 43.3261 246.35 42.6885C247.169 42.0394 247.796 41.1341 248.229 39.9727C248.673 38.8113 248.895 37.4505 248.895 35.8906C248.895 34.3309 248.679 32.9761 248.246 31.8262C247.813 30.6761 247.187 29.7874 246.367 29.1611C245.547 28.5349 244.551 28.2217 243.378 28.2217Z" 51 - fill="black" 52 - /> 53 - <path 54 - d="M99.0752 16.4883V22.7734H104.012V28.2393H99.0752V40.9463C99.0752 41.6179 99.178 42.1418 99.3828 42.5176C99.5878 42.882 99.8729 43.1381 100.237 43.2861C100.613 43.4341 101.046 43.5088 101.535 43.5088C101.877 43.5088 102.218 43.4798 102.56 43.4229C102.901 43.3545 103.164 43.3037 103.346 43.2695L104.49 48.6836C104.126 48.7974 103.613 48.9292 102.953 49.0771C102.293 49.2366 101.489 49.333 100.544 49.3672C98.7906 49.4354 97.2534 49.2021 95.9326 48.667C94.6232 48.1318 93.6037 47.3001 92.875 46.1729C92.1464 45.0457 91.7885 43.6224 91.7998 41.9033V28.2393H88.2129V22.7734H91.7998V16.4883H99.0752Z" 55 - fill="black" 56 - /> 57 - <path 58 - d="M198.397 41.1514C198.409 41.9824 198.556 42.5861 198.841 42.9619C199.137 43.3263 199.639 43.5088 200.345 43.5088C200.709 43.4974 200.993 43.4745 201.198 43.4404C201.403 43.4063 201.574 43.3607 201.711 43.3037L202.872 48.5986C202.496 48.7125 202.035 48.8318 201.488 48.957C200.953 49.0709 200.23 49.1446 199.319 49.1787C196.53 49.2812 194.469 48.7575 193.137 47.6074C191.804 46.446 191.132 44.6187 191.121 42.125V14.0283H198.397V41.1514Z" 59 - fill="black" 60 - /> 61 - <path 62 - d="M148.839 22.4316C150.661 22.4316 152.249 22.8299 153.604 23.627C154.959 24.4239 156.012 25.5629 156.764 27.043C157.515 28.5118 157.892 30.2656 157.892 32.3037V49.0088H150.615V33.6025C150.627 31.9972 150.217 30.7443 149.386 29.8447C148.555 28.9338 147.41 28.4785 145.952 28.4785C144.973 28.4785 144.108 28.6891 143.356 29.1104C142.616 29.5317 142.036 30.1466 141.614 30.9551C141.204 31.752 140.994 32.7138 140.982 33.8408V49.0088H133.706V22.7734H140.641V27.4023H140.948C141.529 25.8766 142.502 24.6694 143.868 23.7812C145.235 22.8817 146.892 22.4316 148.839 22.4316Z" 63 - fill="black" 64 - /> 65 - </svg> 66 - </div> 67 - ); 68 - }
-43
ogre/src/components/shared/metrics.tsx
··· 1 - import { Row, Col } from "./layout"; 2 - import { TYPOGRAPHY } from "./constants"; 3 - import { 4 - Star, 5 - GitPullRequest, 6 - CircleDot, 7 - type LucideIcon, 8 - } from "../../icons/lucide"; 9 - 10 - interface MetricsProps { 11 - stars: number; 12 - pulls: number; 13 - issues: number; 14 - } 15 - 16 - // Display stars, pulls, issues with Lucide icons 17 - export function Metrics({ stars, pulls, issues }: MetricsProps) { 18 - return ( 19 - <Row style={{ gap: 56, alignItems: "flex-start" }}> 20 - <MetricItem value={stars} label="stars" Icon={Star} /> 21 - <MetricItem value={pulls} label="pulls" Icon={GitPullRequest} /> 22 - <MetricItem value={issues} label="issues" Icon={CircleDot} /> 23 - </Row> 24 - ); 25 - } 26 - 27 - interface MetricItemProps { 28 - value: number; 29 - label: string; 30 - Icon: LucideIcon; 31 - } 32 - 33 - function MetricItem({ value, label, Icon }: MetricItemProps) { 34 - return ( 35 - <Col style={{ gap: 12 }}> 36 - <Row style={{ gap: 12, alignItems: "center" }}> 37 - <span style={TYPOGRAPHY.metricValue}>{value}</span> 38 - <Icon size={48} /> 39 - </Row> 40 - <span style={{ ...TYPOGRAPHY.label, opacity: 0.75 }}>{label}</span> 41 - </Col> 42 - ); 43 - }
-17
ogre/src/components/shared/stat-item.tsx
··· 1 - import { Row } from "./layout"; 2 - import { TYPOGRAPHY } from "./constants"; 3 - import type { LucideIcon } from "../../icons/lucide"; 4 - 5 - interface StatItemProps { 6 - Icon: LucideIcon; 7 - value: string | number; 8 - } 9 - 10 - export function StatItem({ Icon, value }: StatItemProps) { 11 - return ( 12 - <Row style={{ gap: 16 }}> 13 - <Icon size={36} color="#404040" /> 14 - <span style={{ ...TYPOGRAPHY.body, color: "#404040" }}>{value}</span> 15 - </Row> 16 - ); 17 - }
-73
ogre/src/components/shared/status-badge.tsx
··· 1 - import { Row } from "./layout"; 2 - import { 3 - CircleDot, 4 - Ban, 5 - GitPullRequest, 6 - GitPullRequestClosed, 7 - GitMerge, 8 - } from "../../icons/lucide"; 9 - import { COLORS, TYPOGRAPHY } from "./constants"; 10 - 11 - const STATUS_CONFIG = { 12 - open: { 13 - Icon: CircleDot, 14 - bg: COLORS.status.open.bg, 15 - text: COLORS.status.open.text, 16 - }, 17 - closed: { 18 - Icon: Ban, 19 - bg: COLORS.status.closed.bg, 20 - text: COLORS.status.closed.text, 21 - }, 22 - merged: { 23 - Icon: GitMerge, 24 - bg: COLORS.status.merged.bg, 25 - text: COLORS.status.merged.text, 26 - }, 27 - } as const; 28 - 29 - interface StatusBadgeProps { 30 - status: "open" | "closed" | "merged"; 31 - } 32 - 33 - export function StatusBadge({ status }: StatusBadgeProps) { 34 - const config = 35 - status === "merged" 36 - ? STATUS_CONFIG.merged 37 - : status === "closed" 38 - ? STATUS_CONFIG.closed 39 - : STATUS_CONFIG.open; 40 - const Icon = config.Icon; 41 - 42 - return ( 43 - <Row 44 - style={{ 45 - gap: 12, 46 - padding: "14px 26px 14px 24px", 47 - borderRadius: 18, 48 - backgroundColor: config.bg, 49 - }}> 50 - <Icon size={48} color={config.text} /> 51 - <span style={{ ...TYPOGRAPHY.status, color: config.text }}>{status}</span> 52 - </Row> 53 - ); 54 - } 55 - 56 - export function IssueStatusBadge({ status }: { status: "open" | "closed" }) { 57 - const config = 58 - status === "closed" ? STATUS_CONFIG.closed : STATUS_CONFIG.open; 59 - const Icon = config.Icon; 60 - 61 - return ( 62 - <Row 63 - style={{ 64 - gap: 12, 65 - padding: "14px 26px 14px 24px", 66 - borderRadius: 18, 67 - backgroundColor: config.bg, 68 - }}> 69 - <Icon size={48} color={config.text} /> 70 - <span style={{ ...TYPOGRAPHY.status, color: config.text }}>{status}</span> 71 - </Row> 72 - ); 73 - }
-52
ogre/src/icons/lucide.tsx
··· 1 - import { h } from "preact"; 2 - import iconNodes from "lucide-static/icon-nodes.json"; 3 - 4 - interface IconProps { 5 - size?: number; 6 - color?: string; 7 - strokeWidth?: number; 8 - } 9 - 10 - type IconNodeEntry = [string, Record<string, string | number>]; 11 - 12 - function createIcon(name: string) { 13 - const nodes = (iconNodes as unknown as Record<string, IconNodeEntry[]>)[name]; 14 - if (!nodes) throw new Error(`Icon "${name}" not found`); 15 - 16 - return function Icon({ 17 - size = 24, 18 - color = "currentColor", 19 - strokeWidth = 2, 20 - }: IconProps = {}) { 21 - return h( 22 - "svg", 23 - { 24 - xmlns: "http://www.w3.org/2000/svg", 25 - width: size, 26 - height: size, 27 - viewBox: "0 0 24 24", 28 - fill: "none", 29 - stroke: color, 30 - strokeWidth, 31 - strokeLinecap: "round" as const, 32 - strokeLinejoin: "round" as const, 33 - }, 34 - nodes.map(([tag, attrs], i) => h(tag, { key: i, ...attrs })), 35 - ); 36 - }; 37 - } 38 - 39 - export const Star = createIcon("star"); 40 - export const GitPullRequest = createIcon("git-pull-request"); 41 - export const GitPullRequestClosed = createIcon("git-pull-request-closed"); 42 - export const GitMerge = createIcon("git-merge"); 43 - export const CircleDot = createIcon("circle-dot"); 44 - export const Calendar = createIcon("calendar"); 45 - export const MessageSquare = createIcon("message-square"); 46 - export const MessageSquareCode = createIcon("message-square-code"); 47 - export const Ban = createIcon("ban"); 48 - export const SmilePlus = createIcon("smile-plus"); 49 - export const FileDiff = createIcon("file-diff"); 50 - export const RefreshCw = createIcon("refresh-cw"); 51 - 52 - export type LucideIcon = typeof Star;
-96
ogre/src/index.tsx
··· 1 - import { cardPayloadSchema } from "./validation"; 2 - import { renderCard } from "./lib/render"; 3 - import { RepositoryCard } from "./components/cards/repository"; 4 - import { IssueCard } from "./components/cards/issue"; 5 - import { PullRequestCard } from "./components/cards/pull-request"; 6 - import { z } from "zod"; 7 - 8 - declare global { 9 - interface CacheStorage { 10 - default: Cache; 11 - } 12 - } 13 - 14 - interface Env { 15 - ENVIRONMENT: string; 16 - } 17 - 18 - export default { 19 - async fetch(request: Request, env: Env): Promise<Response> { 20 - if (request.method !== "POST") { 21 - return new Response("Method not allowed", { status: 405 }); 22 - } 23 - 24 - const url = new URL(request.url); 25 - const cardType = url.pathname.split("/").pop(); 26 - 27 - try { 28 - const body = await request.json(); 29 - const payload = cardPayloadSchema.parse(body); 30 - 31 - let component; 32 - switch (payload.type) { 33 - case "repository": 34 - component = <RepositoryCard {...payload} />; 35 - break; 36 - case "issue": 37 - component = <IssueCard {...payload} />; 38 - break; 39 - case "pullRequest": 40 - component = <PullRequestCard {...payload} />; 41 - break; 42 - default: 43 - return new Response("Unknown card type", { status: 400 }); 44 - } 45 - 46 - const cacheKeyUrl = new URL(request.url); 47 - cacheKeyUrl.searchParams.set("payload", JSON.stringify(payload)); 48 - const cacheKey = new Request(cacheKeyUrl.toString(), { method: "GET" }); 49 - const cache = caches.default; 50 - const cached = await cache.match(cacheKey); 51 - 52 - if (cached) { 53 - return cached; 54 - } 55 - 56 - const { png: pngBuffer } = await renderCard(component); 57 - 58 - const response = new Response(pngBuffer as any, { 59 - headers: { 60 - "Content-Type": "image/png", 61 - "Cache-Control": "public, max-age=3600", 62 - }, 63 - }); 64 - 65 - await cache.put(cacheKey, response.clone()); 66 - 67 - return response; 68 - } catch (error) { 69 - if (error instanceof z.ZodError) { 70 - return new Response( 71 - JSON.stringify({ errors: (error as z.ZodError).issues }), 72 - { 73 - status: 400, 74 - headers: { "Content-Type": "application/json" }, 75 - }, 76 - ); 77 - } 78 - 79 - console.error("Error generating card:", error); 80 - const errorMessage = 81 - error instanceof Error ? error.message : String(error); 82 - const errorStack = error instanceof Error ? error.stack : ""; 83 - console.error("Error stack:", errorStack); 84 - return new Response( 85 - JSON.stringify({ 86 - error: errorMessage, 87 - stack: errorStack, 88 - }), 89 - { 90 - status: 500, 91 - headers: { "Content-Type": "application/json" }, 92 - }, 93 - ); 94 - } 95 - }, 96 - };
-46
ogre/src/lib/render.ts
··· 1 - import type { VNode } from "preact"; 2 - import { initSatori, initResvg, loadFonts } from "@tangled/ogre-runtime"; 3 - import type { ResvgClass } from "@tangled/ogre-runtime/types"; 4 - 5 - let satoriFn: typeof import("satori").default | null = null; 6 - let Resvg: ResvgClass | null = null; 7 - let fontsLoaded = false; 8 - let cachedFonts: Awaited<ReturnType<typeof loadFonts>> | null = null; 9 - 10 - export interface RenderResult { 11 - svg: string; 12 - png: Uint8Array; 13 - } 14 - 15 - export async function renderCard(component: VNode): Promise<RenderResult> { 16 - if (!satoriFn) { 17 - satoriFn = await initSatori(); 18 - } 19 - 20 - if (!Resvg) { 21 - Resvg = await initResvg(); 22 - } 23 - 24 - if (!fontsLoaded) { 25 - cachedFonts = await loadFonts(); 26 - fontsLoaded = true; 27 - } 28 - 29 - const svg = await satoriFn(component as any, { 30 - width: 1200, 31 - height: 630, 32 - fonts: cachedFonts!, 33 - embedFont: true, 34 - }); 35 - 36 - const resvg = new Resvg!(svg, { 37 - fitTo: { mode: "width", value: 1200 }, 38 - }); 39 - 40 - const pngData = resvg.render(); 41 - 42 - return { 43 - svg, 44 - png: pngData.asPng(), 45 - }; 46 - }
-9
ogre/src/types.d.ts
··· 1 - declare module "*.wasm?module" { 2 - const value: WebAssembly.Module; 3 - export default value; 4 - } 5 - 6 - declare module "*.woff" { 7 - const value: ArrayBuffer; 8 - export default value; 9 - }
-70
ogre/src/validation.ts
··· 1 - import { z } from "zod"; 2 - 3 - const hexColor = /^#[0-9A-Fa-f]{6}$/; 4 - 5 - const languageSchema = z.object({ 6 - color: z.string().regex(hexColor), 7 - percentage: z.number().min(0).max(100), 8 - }); 9 - 10 - export const repositoryCardSchema = z.object({ 11 - type: z.literal("repository"), 12 - repoName: z.string().min(1).max(100), 13 - ownerHandle: z.string().min(1).max(100), 14 - stars: z.number().int().min(0).max(1000000), 15 - pulls: z.number().int().min(0).max(100000), 16 - issues: z.number().int().min(0).max(100000), 17 - createdAt: z.string().max(100), 18 - avatarUrl: z.string().url(), 19 - languages: z.array(languageSchema).max(5), 20 - }); 21 - 22 - export const issueCardSchema = z.object({ 23 - type: z.literal("issue"), 24 - repoName: z.string().min(1).max(100), 25 - ownerHandle: z.string().min(1).max(100), 26 - avatarUrl: z.string().url(), 27 - title: z.string().min(1).max(500), 28 - issueNumber: z.number().int().positive(), 29 - status: z.enum(["open", "closed"]), 30 - labels: z 31 - .array( 32 - z.object({ 33 - name: z.string().max(50), 34 - color: z.string().regex(hexColor), 35 - }), 36 - ) 37 - .max(10), 38 - commentCount: z.number().int().min(0), 39 - reactionCount: z.number().int().min(0), 40 - createdAt: z.string(), 41 - }); 42 - 43 - export const pullRequestCardSchema = z.object({ 44 - type: z.literal("pullRequest"), 45 - repoName: z.string().min(1).max(100), 46 - ownerHandle: z.string().min(1).max(100), 47 - avatarUrl: z.string().url(), 48 - title: z.string().min(1).max(500), 49 - pullRequestNumber: z.number().int().positive(), 50 - status: z.enum(["open", "closed", "merged"]), 51 - filesChanged: z.number().int().min(0), 52 - additions: z.number().int().min(0), 53 - deletions: z.number().int().min(0), 54 - rounds: z.number().int().min(1), 55 - // reviews: z.number().int().min(0), // TODO: implement review tracking 56 - commentCount: z.number().int().min(0), 57 - reactionCount: z.number().int().min(0), 58 - createdAt: z.string(), 59 - }); 60 - 61 - export const cardPayloadSchema = z.discriminatedUnion("type", [ 62 - repositoryCardSchema, 63 - issueCardSchema, 64 - pullRequestCardSchema, 65 - ]); 66 - 67 - export type Language = z.infer<typeof languageSchema>; 68 - export type RepositoryCardData = z.infer<typeof repositoryCardSchema>; 69 - export type IssueCardData = z.infer<typeof issueCardSchema>; 70 - export type PullRequestCardData = z.infer<typeof pullRequestCardSchema>;
-19
ogre/tsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - "target": "ES2022", 4 - "module": "ES2022", 5 - "lib": ["ES2022"], 6 - "moduleResolution": "bundler", 7 - "types": ["@cloudflare/workers-types", "node", "bun"], 8 - "jsx": "react-jsx", 9 - "jsxImportSource": "preact", 10 - "strict": true, 11 - "esModuleInterop": true, 12 - "skipLibCheck": true, 13 - "forceConsistentCasingInFileNames": true, 14 - "resolveJsonModule": true, 15 - "noEmit": true 16 - }, 17 - "include": ["src/**/*"], 18 - "exclude": ["node_modules"] 19 - }
-25
ogre/wrangler.jsonc
··· 1 - { 2 - "$schema": "node_modules/wrangler/config-schema.json", 3 - "name": "ogre", 4 - "main": "src/index.tsx", 5 - "compatibility_date": "2026-03-07", 6 - "observability": { 7 - "enabled": true, 8 - }, 9 - "routes": [ 10 - { 11 - "pattern": "ogre.tangled.network", 12 - "custom_domain": true, 13 - }, 14 - ], 15 - "vars": { 16 - "ENVIRONMENT": "production", 17 - }, 18 - "rules": [ 19 - { 20 - "type": "Data", 21 - "globs": ["**/*.woff"], 22 - "fallthrough": true, 23 - }, 24 - ], 25 - }
-1
package.json
··· 1 - {}
pr.png

This is a binary file and will not be displayed.

+5
spindle/config/config.go
··· 41 41 WorkflowTimeout string `env:"WORKFLOW_TIMEOUT, default=5m"` 42 42 } 43 43 44 + type S3 struct { 45 + LogBucket string `env:"LOG_BUCKET"` 46 + } 47 + 44 48 type Config struct { 45 49 Server Server `env:",prefix=SPINDLE_SERVER_"` 46 50 NixeryPipelines NixeryPipelines `env:",prefix=SPINDLE_NIXERY_PIPELINES_"` 51 + S3 S3 `env:",prefix=SPINDLE_S3_"` 47 52 } 48 53 49 54 func Load(ctx context.Context) (*Config, error) {
+16
spindle/engine/engine.go
··· 3 3 import ( 4 4 "context" 5 5 "errors" 6 + "fmt" 6 7 "log/slog" 8 + "path/filepath" 7 9 "sync" 8 10 9 11 securejoin "github.com/cyphar/filepath-securejoin" ··· 35 37 secretValues[i] = s.Value 36 38 } 37 39 40 + s3, err := NewS3(cfg.S3.LogBucket) 41 + if err != nil { 42 + l.Error("error creating s3 client", "err", err) 43 + } 44 + 38 45 var wg sync.WaitGroup 39 46 for eng, wfs := range pipeline.Workflows { 40 47 workflowTimeout := eng.WorkflowTimeout() ··· 49 56 PipelineId: pipelineId, 50 57 Name: w.Name, 51 58 } 59 + 60 + defer func() { 61 + if s3 != nil { 62 + logFile := filepath.Join(cfg.Server.LogDir, fmt.Sprintf("%s.log", wid.String())) 63 + if err := s3.WriteFile(ctx, logFile); err != nil { 64 + l.Error("error uploading logs", "err", err) 65 + } 66 + } 67 + }() 52 68 53 69 wfLogger, err := models.NewFileWorkflowLogger(cfg.Server.LogDir, wid, secretValues) 54 70 if err != nil {
+75
spindle/engine/s3.go
··· 1 + package engine 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "io" 7 + "os" 8 + "path/filepath" 9 + 10 + "github.com/aws/aws-sdk-go-v2/aws" 11 + "github.com/aws/aws-sdk-go-v2/config" 12 + "github.com/aws/aws-sdk-go-v2/service/s3" 13 + ) 14 + 15 + type S3 struct { 16 + bucket string 17 + client *s3.Client 18 + } 19 + 20 + const BaseS3Path = "spindle/workflows" 21 + 22 + func NewS3(bucket string) (*S3, error) { 23 + if bucket == "" { 24 + return nil, fmt.Errorf("s3 bucket not provided") 25 + } 26 + 27 + ctx := context.Background() 28 + sdkConfig, err := config.LoadDefaultConfig(ctx) 29 + 30 + if err != nil { 31 + return nil, fmt.Errorf("error loading s3 config: %w", err) 32 + } 33 + s3Client := s3.NewFromConfig(sdkConfig) 34 + 35 + return &S3{ 36 + bucket: bucket, 37 + client: s3Client, 38 + }, nil 39 + } 40 + 41 + func (s *S3) WriteFile(ctx context.Context, path string) error { 42 + s3Key := fmt.Sprintf("%s/%s", BaseS3Path, filepath.Base(path)) 43 + 44 + file, err := os.Open(path) 45 + if err != nil { 46 + return fmt.Errorf("error opening file %s: %w", path, err) 47 + } 48 + defer file.Close() 49 + 50 + _, err = s.client.PutObject(ctx, &s3.PutObjectInput{ 51 + Bucket: &s.bucket, 52 + Key: &s3Key, 53 + Body: file, 54 + }) 55 + 56 + if err != nil { 57 + return fmt.Errorf("error writing to s3: %w", err) 58 + } 59 + 60 + return nil 61 + } 62 + 63 + func (s *S3) ReadFile(ctx context.Context, name string) ([]byte, error) { 64 + res, err := s.client.GetObject(ctx, &s3.GetObjectInput{ 65 + Bucket: &s.bucket, 66 + Key: aws.String(name), 67 + }) 68 + 69 + if err != nil { 70 + return nil, fmt.Errorf("error reading file %s: %w", name, err) 71 + } 72 + defer res.Body.Close() 73 + 74 + return io.ReadAll(res.Body) 75 + }

History

10 rounds 3 comments
sign up or login to add to the discussion
2 commits
expand
spindle/engine: store workflow logs in s3
spindle/engine: read secrets from environment file
no conflicts, ready to merge
expand 0 comments
10 commits
expand
spindle/engine: store workflow logs in s3
spindle/engine: read secrets from environment file
nix,knotmirror: listen & sync local knots from local knotmirror
lexicons: sync.requestCrawl and knot.subscribeRepos
nix,knotserver,knotmirror/xrpc: sync.requestCrawl support
ogre: rename appview/ogcard to ogre
ogre: request jpeg from avatar service
nix,knotmirror: listen & sync local knots from local knotmirror
nix,knotmirror: listen & sync local knots from local knotmirror
spindle/engine: remove unrelated changes
expand 0 comments
12 commits
expand
spindle/engine: store workflow logs in s3
spindle/engine: read secrets from environment file
nix,knotmirror: listen & sync local knots from local knotmirror
lexicons: sync.requestCrawl and knot.subscribeRepos
nix,knotserver,knotmirror/xrpc: sync.requestCrawl support
ogre: rename appview/ogcard to ogre
ogre: request jpeg from avatar service
nix,knotmirror: listen & sync local knots from local knotmirror
nix,knotmirror: listen & sync local knots from local knotmirror
spindle/engine: remove unrelated changes
spindle/engine: add overwritten changes
spindle/engine: fix typo
expand 0 comments
11 commits
expand
spindle/engine: store workflow logs in s3
spindle/engine: read secrets from environment file
nix,knotmirror: listen & sync local knots from local knotmirror
lexicons: sync.requestCrawl and knot.subscribeRepos
nix,knotserver,knotmirror/xrpc: sync.requestCrawl support
ogre: rename appview/ogcard to ogre
ogre: request jpeg from avatar service
nix,knotmirror: listen & sync local knots from local knotmirror
nix,knotmirror: listen & sync local knots from local knotmirror
spindle/engine: remove unrelated changes
spindle/engine: add overwritten changes
expand 0 comments
10 commits
expand
spindle/engine: store workflow logs in s3
spindle/engine: read secrets from environment file
nix,knotmirror: listen & sync local knots from local knotmirror
lexicons: sync.requestCrawl and knot.subscribeRepos
nix,knotserver,knotmirror/xrpc: sync.requestCrawl support
ogre: rename appview/ogcard to ogre
ogre: request jpeg from avatar service
nix,knotmirror: listen & sync local knots from local knotmirror
nix,knotmirror: listen & sync local knots from local knotmirror
spindle/engine: remove unrelated changes
expand 0 comments
9 commits
expand
spindle/engine: store workflow logs in s3
spindle/engine: read secrets from environment file
nix,knotmirror: listen & sync local knots from local knotmirror
lexicons: sync.requestCrawl and knot.subscribeRepos
nix,knotserver,knotmirror/xrpc: sync.requestCrawl support
ogre: rename appview/ogcard to ogre
ogre: request jpeg from avatar service
nix,knotmirror: listen & sync local knots from local knotmirror
nix,knotmirror: listen & sync local knots from local knotmirror
expand 0 comments
8 commits
expand
spindle/engine: store workflow logs in s3
spindle/engine: read secrets from environment file
nix,knotmirror: listen & sync local knots from local knotmirror
lexicons: sync.requestCrawl and knot.subscribeRepos
nix,knotserver,knotmirror/xrpc: sync.requestCrawl support
ogre: rename appview/ogcard to ogre
ogre: request jpeg from avatar service
nix,knotmirror: listen & sync local knots from local knotmirror
expand 0 comments
7 commits
expand
spindle/engine: store workflow logs in s3
spindle/engine: read secrets from environment file
nix,knotmirror: listen & sync local knots from local knotmirror
lexicons: sync.requestCrawl and knot.subscribeRepos
nix,knotserver,knotmirror/xrpc: sync.requestCrawl support
ogre: rename appview/ogcard to ogre
ogre: request jpeg from avatar service
expand 1 comment

sorry, but you will need to rebase with master before submitting!

2 commits
expand
spindle/engine: store workflow logs in s3
spindle/engine: read secrets from environment file
expand 0 comments
1 commit
expand
spindle/engine: store workflow logs in s3
expand 2 comments

spindle/config/config.go:44-51 β€”Β this will result in SPINDLE_S3_LOG_BUCKET, but nix/modules/spindle.nix:147 we reference SPINDLE_NIXERY_PIPELINES_LOG_BUCKET here.

spindle/engine/engine.go:60 β€” probably good to parameterize the bucket name from config as well!

spindle/engine/s3.go:20 β€”Β Go convention is camel case, so BaseS3Path is preferred here.

spindle/engine/s3.go:38 β€”Β also camel case here please: s3Key

spindle/engine/engine.go:154 β€”Β this is a no-op with just a single argument.

nix/modules/spindle.nix:148-150 β€”Β we should ideally use EnvironmentFile here. Refer to how secrets are passed through to the appview service, for example.

High level design concern: NewS3 creates a new S3 client for each workflow (on every defer). We should ideally initialize this upon spindle startup and passed through via the engine struct.

We should ideally initialize this upon spindle startup and passed through via the engine struct.

Doing this means that each engine will have to register a clean up task for uploading logs which is easy to forget. Doing it in spindle/engine means that new engines don't have to worry about how to upload logs.