@monkeyplus/flow 6.0.16 → 6.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/cms/server/database/schema.d.ts +648 -0
- package/cms/server/database/schema.mjs +43 -0
- package/cms/server/routes/api/auth/[...auth].d.ts +2 -0
- package/cms/server/routes/api/auth/[...auth].mjs +5 -0
- package/cms/server/routes/api/draft.d.ts +3 -0
- package/cms/server/routes/api/draft.mjs +26 -0
- package/cms/server/routes/api/repos/remote/[repo]/blobs/[file]/index.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/blobs/[file]/index.mjs +20 -0
- package/cms/server/routes/api/repos/remote/[repo]/blobs/index.post.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/blobs/index.post.mjs +8 -0
- package/cms/server/routes/api/repos/remote/[repo]/branches/[branch]/index.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/branches/[branch]/index.mjs +8 -0
- package/cms/server/routes/api/repos/remote/[repo]/commits/index.post.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/commits/index.post.mjs +8 -0
- package/cms/server/routes/api/repos/remote/[repo]/files/[branch]/index.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/files/[branch]/index.mjs +38 -0
- package/cms/server/routes/api/repos/remote/[repo]/files/index.post.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/files/index.post.mjs +8 -0
- package/cms/server/routes/api/repos/remote/[repo]/refs/heads/[branch]/index.patch.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/refs/heads/[branch]/index.patch.mjs +9 -0
- package/cms/server/utils/auth.d.ts +3 -0
- package/cms/server/utils/auth.mjs +16 -0
- package/cms/server/utils/db.d.ts +3 -0
- package/cms/server/utils/db.mjs +5 -0
- package/cms/server/utils/github.d.ts +15 -0
- package/cms/server/utils/github.mjs +160 -0
- package/cms/server/utils/github_token.d.ts +71 -0
- package/cms/server/utils/github_token.mjs +377 -0
- package/modules/cms/module.d.ts +11 -0
- package/modules/cms/module.mjs +163 -0
- package/modules/cms/server/api/admin.d.ts +2 -0
- package/modules/cms/server/api/admin.mjs +18 -0
- package/modules/cms/server/api/config.d.ts +2 -0
- package/modules/cms/server/api/config.mjs +88 -0
- package/modules/cms/server/api/localFs.d.ts +2 -0
- package/modules/cms/server/api/localFs.mjs +88 -0
- package/modules/cms/server/api/meta.d.ts +2 -0
- package/modules/cms/server/api/meta.mjs +12 -0
- package/modules/cms/server/lib/composables.d.ts +116 -0
- package/modules/cms/server/lib/composables.mjs +82 -0
- package/modules/cms/server/lib/fs.d.ts +1 -0
- package/modules/cms/server/lib/fs.mjs +18 -0
- package/modules/cms/server/lib/helpers.d.ts +14 -0
- package/modules/cms/server/lib/helpers.mjs +78 -0
- package/modules/cms/server/lib/types.d.ts +120 -0
- package/modules/cms/server/lib/types.mjs +0 -0
- package/modules/cms/server/lib/widgets.d.ts +82 -0
- package/modules/cms/server/lib/widgets.mjs +200 -0
- package/modules/cms/server/trial.d.ts +2 -0
- package/modules/cms/server/trial.mjs +8 -0
- package/modules/content/query.mjs +31 -3
- package/modules/images/ipx.mjs +4 -2
- package/modules/images/module.d.ts +0 -1
- package/modules/images/module.mjs +5 -3
- package/modules/images/runtime/build.mjs +12 -0
- package/modules/images/runtime/image.mjs +4 -3
- package/modules/images/runtime/renames.mjs +59 -8
- package/modules/images/runtime/types.d.ts +27 -4
- package/modules/images/watermark.d.ts +1 -0
- package/modules/images/watermark.mjs +113 -0
- package/modules/netlify-cms/handler.mjs +2 -1
- package/modules/netlify-cms/module.mjs +1 -1
- package/modules/netlify-cms/server/api/config.mjs +25 -1
- package/modules/netlify-cms/server/api/local-fs.d.ts +51 -0
- package/modules/netlify-cms/server/api/local-fs.mjs +81 -77
- package/modules/netlify-cms/server/lib/cms/handler.d.ts +1 -1
- package/modules/netlify-cms/server/lib/cms/handlerV1.d.ts +1 -1
- package/modules/netlify-cms/server/lib/composables.d.ts +8 -0
- package/modules/netlify-cms/server/lib/composables.mjs +2 -1
- package/package.json +2 -2
- package/server/lib/context.d.ts +3 -0
- package/server/lib/context.mjs +5 -0
- package/server/lib/handler.mjs +58 -23
- package/server/lib/pages.d.ts +2 -2
- package/server/lib/pages.mjs +8 -6
- package/server/lib/render.mjs +20 -4
- package/server/plugins/00.lifecycle.mjs +2 -1
- package/src/public/index.d.ts +1 -0
- package/src/public/index.mjs +1 -0
- package/src/public/nitro.mjs +2 -0
- package/src/public/query-content.mjs +9 -2
- package/src/public/shared.d.ts +1 -0
- package/src/public/shared.mjs +3 -0
- package/src/public/vite.mjs +63 -8
- package/src/runtime/components/FlowIsland.mjs +32 -5
- package/src/runtime/components/MkImage.d.ts +100 -22
- package/src/runtime/components/MkImage.mjs +20 -12
- package/src/runtime/components/MkLink.d.ts +8 -5
- package/src/runtime/components/MkLink.mjs +9 -3
- package/src/runtime/components/MkPicture.d.ts +92 -7
- package/src/runtime/components/MkPicture.mjs +8 -2
- package/src/runtime/components/image-shared.d.ts +0 -1
- package/src/runtime/components/image-shared.mjs +9 -18
- package/src/runtime/config.d.ts +6 -15
- package/src/runtime/head.d.ts +2 -1
- package/src/runtime/head.mjs +5 -2
- package/src/runtime/islands.mjs +20 -2
- package/src/runtime/page-discovery.mjs +9 -1
- package/src/runtime/pages.d.ts +14 -13
- package/src/runtime/virtual-pages.mjs +2 -2
- package/src/runtime/vue.mjs +10 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getQuery, HTTPError, readBody } from "nitro/h3";
|
|
2
|
+
import { useStorage } from "nitro/storage";
|
|
3
|
+
export default async (event) => {
|
|
4
|
+
const query = getQuery(event);
|
|
5
|
+
if (event.req.method === "GET") {
|
|
6
|
+
const file = query.file;
|
|
7
|
+
if (!file) {
|
|
8
|
+
return new HTTPError({
|
|
9
|
+
statusCode: 400,
|
|
10
|
+
statusMessage: "File is required",
|
|
11
|
+
message: "File is required"
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
const data = await useStorage("draft").getItem(file);
|
|
15
|
+
return data || {};
|
|
16
|
+
}
|
|
17
|
+
if (event.req.method === "DELETE") {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const body = await readBody(event);
|
|
21
|
+
if (!body?.file || !body?.data) {
|
|
22
|
+
throw new Error("Missing file or data");
|
|
23
|
+
}
|
|
24
|
+
await useStorage("draft").setItem(body.file, body.data);
|
|
25
|
+
return "world";
|
|
26
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineHandler, defineRouteMeta } from "nitro";
|
|
2
|
+
import { ghRepoBlob } from "#cms-utils/github";
|
|
3
|
+
defineRouteMeta({
|
|
4
|
+
openAPI: {
|
|
5
|
+
description: "(disabled - redirects to raw.githubusercontent.com)",
|
|
6
|
+
tags: ["hidden"]
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
export default defineHandler(async (event) => {
|
|
10
|
+
const repo = `${event.context.params.repo}`;
|
|
11
|
+
const fileSha = event.context.params.file;
|
|
12
|
+
const res = await ghRepoBlob(repo, fileSha);
|
|
13
|
+
const file = res;
|
|
14
|
+
return {
|
|
15
|
+
meta: {
|
|
16
|
+
sha: res.sha
|
|
17
|
+
},
|
|
18
|
+
file
|
|
19
|
+
};
|
|
20
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineEventHandler, readBody } from "h3";
|
|
2
|
+
import { ghCreateBlob } from "#cms-utils/github";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const repo = event.context.params.repo;
|
|
5
|
+
const body = await readBody(event);
|
|
6
|
+
const res = await ghCreateBlob(repo, body);
|
|
7
|
+
return res;
|
|
8
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineEventHandler } from "h3";
|
|
2
|
+
import { ghGetBranch } from "#cms-utils/github";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const repo = event.context.params.repo;
|
|
5
|
+
const branch = event.context.params.branch;
|
|
6
|
+
const res = await ghGetBranch(repo, branch);
|
|
7
|
+
return res;
|
|
8
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineEventHandler, readBody } from "h3";
|
|
2
|
+
import { ghCreateCommit } from "#cms-utils/github";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const repo = event.context.params.repo;
|
|
5
|
+
const body = await readBody(event);
|
|
6
|
+
const res = await ghCreateCommit(repo, body);
|
|
7
|
+
return res;
|
|
8
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineHandler, defineRouteMeta } from "nitro";
|
|
2
|
+
import { getQuery, HTTPError } from "nitro/h3";
|
|
3
|
+
import { ghRepoFile, ghRepoFiles } from "#cms-utils/github";
|
|
4
|
+
defineRouteMeta({
|
|
5
|
+
openAPI: {
|
|
6
|
+
description: "(disabled - redirects to raw.githubusercontent.com)",
|
|
7
|
+
tags: ["hidden"]
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
export default defineHandler(async (event) => {
|
|
11
|
+
const repo = `${event.context.params.repo}`;
|
|
12
|
+
const branch = event.context.params.branch;
|
|
13
|
+
const query = getQuery(event);
|
|
14
|
+
if (query.file) {
|
|
15
|
+
return await ghRepoFile(repo, query.file, branch);
|
|
16
|
+
}
|
|
17
|
+
if (!query.folder) {
|
|
18
|
+
return new HTTPError({
|
|
19
|
+
message: "No folder provided",
|
|
20
|
+
statusCode: 400
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const res = await ghRepoFiles(repo, `${branch}:${query.folder}`);
|
|
24
|
+
const files = res.tree.filter((i) => i.type === "blob").map(
|
|
25
|
+
(i) => ({
|
|
26
|
+
path: i.path,
|
|
27
|
+
mode: i.mode,
|
|
28
|
+
sha: i.sha,
|
|
29
|
+
size: i.size
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
return {
|
|
33
|
+
meta: {
|
|
34
|
+
sha: res.sha
|
|
35
|
+
},
|
|
36
|
+
files
|
|
37
|
+
};
|
|
38
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineEventHandler, readBody } from "h3";
|
|
2
|
+
import { ghCreateTree } from "#cms-utils/github";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const repo = event.context.params.repo;
|
|
5
|
+
const body = await readBody(event);
|
|
6
|
+
const res = await ghCreateTree(repo, body);
|
|
7
|
+
return res;
|
|
8
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineEventHandler, readBody } from "h3";
|
|
2
|
+
import { ghUpdateRef } from "#cms-utils/github";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const repo = event.context.params.repo;
|
|
5
|
+
const branch = event.context.params.branch;
|
|
6
|
+
const body = await readBody(event);
|
|
7
|
+
const res = await ghUpdateRef(repo, branch, body);
|
|
8
|
+
return res;
|
|
9
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { betterAuth } from "better-auth";
|
|
2
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
3
|
+
import * as schema from "../database/schema.mjs";
|
|
4
|
+
import { db } from "./db.mjs";
|
|
5
|
+
export const auth = betterAuth({
|
|
6
|
+
database: drizzleAdapter(db, {
|
|
7
|
+
provider: "sqlite",
|
|
8
|
+
schema
|
|
9
|
+
})
|
|
10
|
+
// socialProviders: {
|
|
11
|
+
// github: {
|
|
12
|
+
// clientId: process.env.GITHUB_CLIENT_ID as string,
|
|
13
|
+
// clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
|
|
14
|
+
// },
|
|
15
|
+
// },
|
|
16
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CacheOptions } from 'nitro/types';
|
|
2
|
+
export declare const commonCacheOptions: CacheOptions;
|
|
3
|
+
export declare const ghFetch: any;
|
|
4
|
+
export declare function ghFetchNoCache<T = any>(url: string, opts?: RequestInit): Promise<T>;
|
|
5
|
+
export declare const ghRepo: any;
|
|
6
|
+
export declare const ghRepoContributors: any;
|
|
7
|
+
export declare const ghRepoFiles: any;
|
|
8
|
+
export declare const ghRepoFile: any;
|
|
9
|
+
export declare const ghRepoBlob: any;
|
|
10
|
+
export declare const ghMarkdown: any;
|
|
11
|
+
export declare function ghCreateBlob(repo: string, body: any): Promise<any>;
|
|
12
|
+
export declare function ghCreateTree(repo: string, body: any): Promise<any>;
|
|
13
|
+
export declare function ghCreateCommit(repo: string, body: any): Promise<any>;
|
|
14
|
+
export declare function ghUpdateRef(repo: string, branch: string, body: any): Promise<any>;
|
|
15
|
+
export declare function ghGetBranch(repo: string, branch: string): Promise<any>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { defineCachedFunction } from "nitro/cache";
|
|
3
|
+
import { HTTPError } from "nitro/h3";
|
|
4
|
+
import { joinURL } from "ufo";
|
|
5
|
+
import { acquireGHToken, formatDuration, ghTokens, retryAfterSeconds } from "#cms-utils/github_token";
|
|
6
|
+
const OWNER = process.env.GH_OWNER;
|
|
7
|
+
console.log(OWNER);
|
|
8
|
+
export const commonCacheOptions = {
|
|
9
|
+
group: "gh",
|
|
10
|
+
maxAge: 60 * 60 * 24,
|
|
11
|
+
// 24 hours
|
|
12
|
+
staleMaxAge: 60 * 60 * 48,
|
|
13
|
+
// 48 hours
|
|
14
|
+
// base: ["/cache", "/redis"],
|
|
15
|
+
base: ["/cache"]
|
|
16
|
+
};
|
|
17
|
+
function cacheOptions(name) {
|
|
18
|
+
return {
|
|
19
|
+
...commonCacheOptions,
|
|
20
|
+
name
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export const ghFetch = defineCachedFunction(
|
|
24
|
+
async (url, opts = {}) => {
|
|
25
|
+
const token = await acquireGHToken();
|
|
26
|
+
if (!token) {
|
|
27
|
+
const soonestReset = ghTokens.filter((t) => t.valid && t.reset).sort((a, b) => (a.reset || 0) - (b.reset || 0))[0];
|
|
28
|
+
const resetInfo = soonestReset?.reset ? ` Rate limit resets in ${formatDuration(soonestReset.reset - Date.now())}.` : "";
|
|
29
|
+
const invalidCount = ghTokens.filter((t) => t.valid === false).length;
|
|
30
|
+
const exhaustedCount = ghTokens.filter((t) => t.valid && (t.remaining || 0) === 0).length;
|
|
31
|
+
const retryAfter = retryAfterSeconds(soonestReset?.reset);
|
|
32
|
+
throw new HTTPError({
|
|
33
|
+
message: `No valid GitHub token available (${ghTokens.length} configured: ${invalidCount} invalid, ${exhaustedCount} rate-limited).${resetInfo} You can help by installing the GitHub App: https://github.com/apps/ungh-app`,
|
|
34
|
+
statusCode: 429,
|
|
35
|
+
headers: { "Retry-After": String(retryAfter) }
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const fullUrl = url.startsWith("/") ? url : `/${url}`;
|
|
39
|
+
console.log("URL", fullUrl);
|
|
40
|
+
const res = await fetch(`https://api.github.com${fullUrl}`, {
|
|
41
|
+
...opts,
|
|
42
|
+
method: (opts.method || "GET").toUpperCase(),
|
|
43
|
+
headers: {
|
|
44
|
+
"User-Agent": "fetch",
|
|
45
|
+
"Authorization": `token ${token.token}`,
|
|
46
|
+
...opts.headers
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
token.updateStatus(res);
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
throw new HTTPError({
|
|
52
|
+
message: `GitHub API error: ${res.status} ${res.statusText}`,
|
|
53
|
+
statusCode: res.status
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const contentType = res.headers.get("content-type") || "";
|
|
57
|
+
return contentType.includes("application/json") ? await res.json() : await res.text();
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
...cacheOptions("api"),
|
|
61
|
+
integrity: "v1",
|
|
62
|
+
validate(entry) {
|
|
63
|
+
if (!entry.value || isEmptyArray(entry.value) || entry.value?.total_count === 0 || isEmptyArray(entry.value?.items)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
export async function ghFetchNoCache(url, opts = {}) {
|
|
71
|
+
const token = await acquireGHToken();
|
|
72
|
+
if (!token) {
|
|
73
|
+
throw new HTTPError({
|
|
74
|
+
message: "No valid GitHub token available",
|
|
75
|
+
statusCode: 429
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const fullUrl = url.startsWith("/") ? url : `/${url}`;
|
|
79
|
+
const res = await fetch(`https://api.github.com${fullUrl}`, {
|
|
80
|
+
...opts,
|
|
81
|
+
method: (opts.method || "GET").toUpperCase(),
|
|
82
|
+
headers: {
|
|
83
|
+
"User-Agent": "fetch",
|
|
84
|
+
"Authorization": `token ${token.token}`,
|
|
85
|
+
"Accept": "application/vnd.github+json",
|
|
86
|
+
...opts.headers
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
token.updateStatus(res);
|
|
90
|
+
if (!res.ok) {
|
|
91
|
+
throw new HTTPError({
|
|
92
|
+
message: `GitHub API error: ${res.status} ${res.statusText}`,
|
|
93
|
+
statusCode: res.status
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const contentType = res.headers.get("content-type") || "";
|
|
97
|
+
return contentType.includes("application/json") ? await res.json() : await res.text();
|
|
98
|
+
}
|
|
99
|
+
export const ghRepo = defineCachedFunction((repo) => {
|
|
100
|
+
return ghFetch(`/repos/${repo}`);
|
|
101
|
+
}, cacheOptions("repo"));
|
|
102
|
+
export const ghRepoContributors = defineCachedFunction((repo) => {
|
|
103
|
+
return ghFetch(`/repos/${repo}/contributors`);
|
|
104
|
+
}, cacheOptions("contributors"));
|
|
105
|
+
export const ghRepoFiles = defineCachedFunction((repo, ref) => {
|
|
106
|
+
repo = joinURL(OWNER, repo);
|
|
107
|
+
return ghFetch(`/repos/${repo}/git/trees/${ref}?recursive=1`);
|
|
108
|
+
}, cacheOptions("files"));
|
|
109
|
+
export const ghRepoFile = defineCachedFunction((repo, ref, branch) => {
|
|
110
|
+
repo = joinURL(OWNER, repo);
|
|
111
|
+
return ghFetch(`/repos/${repo}/contents/${ref}?branch=${branch}`);
|
|
112
|
+
}, cacheOptions("file"));
|
|
113
|
+
export const ghRepoBlob = defineCachedFunction((repo, sha) => {
|
|
114
|
+
repo = joinURL(OWNER, repo);
|
|
115
|
+
return ghFetch(`/repos/${repo}/git/blobs/${sha}`);
|
|
116
|
+
}, cacheOptions("blobs"));
|
|
117
|
+
export const ghMarkdown = defineCachedFunction(
|
|
118
|
+
(markdown, repo, _id) => {
|
|
119
|
+
if (!markdown) {
|
|
120
|
+
return "";
|
|
121
|
+
}
|
|
122
|
+
return ghFetch("/markdown", {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
accept: "application/vnd.github+json"
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
text: markdown,
|
|
129
|
+
context: repo
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
...cacheOptions("markdown"),
|
|
135
|
+
getKey: (_markdown, repo, id) => `${repo}/${id}`
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
function isEmptyArray(val) {
|
|
139
|
+
return Array.isArray(val) && val.length === 0;
|
|
140
|
+
}
|
|
141
|
+
export async function ghCreateBlob(repo, body) {
|
|
142
|
+
repo = joinURL(OWNER, repo);
|
|
143
|
+
return ghFetchNoCache(`/repos/${repo}/git/blobs`, { method: "POST", body: JSON.stringify(body) });
|
|
144
|
+
}
|
|
145
|
+
export async function ghCreateTree(repo, body) {
|
|
146
|
+
repo = joinURL(OWNER, repo);
|
|
147
|
+
return ghFetchNoCache(`/repos/${repo}/git/trees`, { method: "POST", body: JSON.stringify(body) });
|
|
148
|
+
}
|
|
149
|
+
export async function ghCreateCommit(repo, body) {
|
|
150
|
+
repo = joinURL(OWNER, repo);
|
|
151
|
+
return ghFetchNoCache(`/repos/${repo}/git/commits`, { method: "POST", body: JSON.stringify(body) });
|
|
152
|
+
}
|
|
153
|
+
export async function ghUpdateRef(repo, branch, body) {
|
|
154
|
+
repo = joinURL(OWNER, repo);
|
|
155
|
+
return ghFetchNoCache(`/repos/${repo}/git/refs/heads/${branch}`, { method: "PATCH", body: JSON.stringify(body) });
|
|
156
|
+
}
|
|
157
|
+
export async function ghGetBranch(repo, branch) {
|
|
158
|
+
repo = joinURL(OWNER, repo);
|
|
159
|
+
return ghFetchNoCache(`/repos/${repo}/git/refs/heads/${branch}`);
|
|
160
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Represents a GitHub API token with rate limit tracking and validation. */
|
|
2
|
+
export declare class GHToken {
|
|
3
|
+
token: string;
|
|
4
|
+
valid?: boolean;
|
|
5
|
+
remaining?: number;
|
|
6
|
+
limit?: number;
|
|
7
|
+
reset?: number;
|
|
8
|
+
_lastValidated?: number;
|
|
9
|
+
_app?: boolean;
|
|
10
|
+
_appInstallationId?: string;
|
|
11
|
+
constructor(token: string, opts?: {
|
|
12
|
+
app?: boolean;
|
|
13
|
+
appInstallationId?: string;
|
|
14
|
+
});
|
|
15
|
+
get maskedToken(): string;
|
|
16
|
+
toJSON(): {
|
|
17
|
+
token: string;
|
|
18
|
+
valid: boolean | undefined;
|
|
19
|
+
remaining: number | undefined;
|
|
20
|
+
limit: number | undefined;
|
|
21
|
+
reset: number | undefined;
|
|
22
|
+
};
|
|
23
|
+
toString(): string;
|
|
24
|
+
updateStatus(res: Response): void;
|
|
25
|
+
/** NOTE: Each call consumes one API request to fetch rate limit info. */
|
|
26
|
+
validate(): Promise<void>;
|
|
27
|
+
/** Returns true if this token needs revalidation */
|
|
28
|
+
isStale(now?: number): boolean;
|
|
29
|
+
/** Clears expired rate limit state */
|
|
30
|
+
clearExpiredLimits(now?: number): void;
|
|
31
|
+
/** Whether this token is usable for requests */
|
|
32
|
+
get available(): boolean | undefined;
|
|
33
|
+
}
|
|
34
|
+
/** All registered GitHub tokens (PAT and App-generated). */
|
|
35
|
+
export declare const ghTokens: GHToken[];
|
|
36
|
+
/** Bootstraps all App tokens (once), then re-validates all tokens. Use for status page. */
|
|
37
|
+
export declare function ensureAllTokensValidated(): Promise<void>;
|
|
38
|
+
/** Validates tokens until one is available, then continues the rest in background. Idempotent (once). */
|
|
39
|
+
export declare const ensureTokensValidated: (() => Promise<void>) & {
|
|
40
|
+
reset: () => void;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Revalidates only tokens that haven't been validated in the last minute.
|
|
44
|
+
* Concurrent callers share the same in-flight promise (coalesced, not once).
|
|
45
|
+
* Returns true if any tokens were revalidated.
|
|
46
|
+
*/
|
|
47
|
+
export declare const revalidateGHTokens: (() => Promise<boolean>) & {
|
|
48
|
+
reset: () => void;
|
|
49
|
+
};
|
|
50
|
+
/** Returns the best available token (highest remaining quota), or `undefined` if none available. */
|
|
51
|
+
export declare function getGHToken(): GHToken | undefined;
|
|
52
|
+
/** Ensures tokens are validated and returns the best available one. Revalidates if needed. */
|
|
53
|
+
export declare function acquireGHToken(): Promise<GHToken | undefined>;
|
|
54
|
+
/** Returns aggregate rate limit stats across all tokens. */
|
|
55
|
+
export declare function getAggregateRateLimit(): {
|
|
56
|
+
remaining: number;
|
|
57
|
+
limit: number;
|
|
58
|
+
reset: number | undefined;
|
|
59
|
+
};
|
|
60
|
+
/** Computes a Retry-After value in seconds from a reset timestamp, with ~0-30 min random jitter. */
|
|
61
|
+
export declare function retryAfterSeconds(resetMs: number | undefined, fallback?: number): number;
|
|
62
|
+
/** Formats a duration in milliseconds to a human-readable string (e.g. `"5m"`, `"1h30m"`). */
|
|
63
|
+
export declare function formatDuration(ms: number): string;
|
|
64
|
+
/** Bootstraps GitHub App installation tokens (samples up to 5 random installations). Coalesced (not once). */
|
|
65
|
+
export declare const ensureAppToken: (() => Promise<void>) & {
|
|
66
|
+
reset: () => void;
|
|
67
|
+
};
|
|
68
|
+
/** Creates an RS256-signed JWT for GitHub App authentication. Exported for testing. */
|
|
69
|
+
export declare function _createAppJWT(appId: string, privateKey: string): Promise<string>;
|
|
70
|
+
/** Encodes a UTF-8 string to base64url. Exported for testing. */
|
|
71
|
+
export declare function _base64url(str: string): string;
|