@async/github-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js ADDED
@@ -0,0 +1,42 @@
1
+ export const asyncGithubApp = {
2
+ slug: "async-github-app",
3
+ installUrl: "https://github.com/apps/async-github-app/installations/new",
4
+ callbackUrl: "https://async.dev/github/callback",
5
+ webhookEvents: [
6
+ "push",
7
+ "create",
8
+ "delete",
9
+ "pull_request",
10
+ "installation",
11
+ "installation_repositories"
12
+ ],
13
+ permissions: {
14
+ contents: "write",
15
+ metadata: "read",
16
+ pull_requests: "write"
17
+ }
18
+ };
19
+ export function defineGithubApp(options = {}) {
20
+ const metadata = {
21
+ ...asyncGithubApp,
22
+ ...options.metadata,
23
+ permissions: {
24
+ ...asyncGithubApp.permissions,
25
+ ...options.metadata?.permissions
26
+ },
27
+ webhookEvents: options.metadata?.webhookEvents ?? asyncGithubApp.webhookEvents
28
+ };
29
+ return {
30
+ metadata,
31
+ auth: options.auth,
32
+ permissions: {
33
+ ...metadata.permissions,
34
+ ...options.permissions
35
+ },
36
+ endpoints: {
37
+ install: metadata.installUrl,
38
+ callback: metadata.callbackUrl,
39
+ ...options.endpoints
40
+ }
41
+ };
42
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { ActionsBridgeAuthOptions, GitHubAppAuthOptions, GitHubAuthProvider, TokenAuthOptions } from "./types.js";
2
+ export declare class GitHubAuthError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function staticTokenAuth(options: TokenAuthOptions): GitHubAuthProvider;
6
+ export declare function githubUserAuth(options: TokenAuthOptions): GitHubAuthProvider;
7
+ export declare function actionsBridgeAuth(options?: ActionsBridgeAuthOptions): GitHubAuthProvider;
8
+ export declare function githubAppAuth(options: GitHubAppAuthOptions): GitHubAuthProvider;
9
+ export interface CreateGitHubAppJwtOptions {
10
+ readonly appId: string | number;
11
+ readonly privateKey: string;
12
+ readonly now?: () => Date;
13
+ }
14
+ export declare function createGitHubAppJwt(options: CreateGitHubAppJwtOptions): string;
15
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAElB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAOD,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,kBAAkB,CAY7E;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,kBAAkB,CAK5E;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,kBAAkB,CAgB5F;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,CAwD/E;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,CAe7E"}
package/dist/auth.js ADDED
@@ -0,0 +1,101 @@
1
+ import { createSign } from "node:crypto";
2
+ import { base64Url, DEFAULT_GITHUB_API_BASE_URL, redactSensitive } from "./util.js";
3
+ export class GitHubAuthError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "GitHubAuthError";
7
+ }
8
+ }
9
+ export function staticTokenAuth(options) {
10
+ return {
11
+ kind: "static-token",
12
+ baseUrl: options.baseUrl ?? DEFAULT_GITHUB_API_BASE_URL,
13
+ async getToken() {
14
+ if (!options.token) {
15
+ throw new GitHubAuthError("A non-empty GitHub token is required.");
16
+ }
17
+ return options.token;
18
+ }
19
+ };
20
+ }
21
+ export function githubUserAuth(options) {
22
+ return {
23
+ ...staticTokenAuth(options),
24
+ kind: "github-user"
25
+ };
26
+ }
27
+ export function actionsBridgeAuth(options = {}) {
28
+ const tokenEnv = options.tokenEnv ?? "GITHUB_TOKEN";
29
+ const env = options.env ?? process.env;
30
+ return {
31
+ kind: "actions-bridge",
32
+ baseUrl: options.baseUrl ?? DEFAULT_GITHUB_API_BASE_URL,
33
+ async getToken() {
34
+ const token = env[tokenEnv];
35
+ if (!token) {
36
+ throw new GitHubAuthError(`Missing ${tokenEnv}; Actions bridge mode needs a repo-local GitHub token.`);
37
+ }
38
+ return token;
39
+ }
40
+ };
41
+ }
42
+ export function githubAppAuth(options) {
43
+ const apiFetch = options.fetch ?? fetch;
44
+ const now = options.now ?? (() => new Date());
45
+ let cached;
46
+ return {
47
+ kind: "github-app-installation",
48
+ baseUrl: options.baseUrl ?? DEFAULT_GITHUB_API_BASE_URL,
49
+ async getToken(scope) {
50
+ const currentMs = now().getTime();
51
+ if (cached && cached.expiresAtMs - 60_000 > currentMs) {
52
+ return cached.token;
53
+ }
54
+ const jwt = createGitHubAppJwt({
55
+ appId: options.appId,
56
+ privateKey: options.privateKey,
57
+ now
58
+ });
59
+ const init = {
60
+ method: "POST",
61
+ headers: {
62
+ accept: "application/vnd.github+json",
63
+ authorization: `Bearer ${jwt}`,
64
+ "content-type": "application/json",
65
+ "x-github-api-version": "2022-11-28"
66
+ }
67
+ };
68
+ if (scope?.permissions) {
69
+ init.body = JSON.stringify({ permissions: scope.permissions });
70
+ }
71
+ const response = await apiFetch(`${this.baseUrl}/app/installations/${encodeURIComponent(String(options.installationId))}/access_tokens`, init);
72
+ if (!response.ok) {
73
+ throw new GitHubAuthError(`GitHub installation token exchange failed with ${response.status}: ${redactSensitive(await response.text())}`);
74
+ }
75
+ const payload = await response.json();
76
+ if (!payload.token || !payload.expires_at) {
77
+ throw new GitHubAuthError("GitHub installation token exchange returned an invalid payload.");
78
+ }
79
+ cached = {
80
+ token: payload.token,
81
+ expiresAtMs: Date.parse(payload.expires_at)
82
+ };
83
+ return payload.token;
84
+ }
85
+ };
86
+ }
87
+ export function createGitHubAppJwt(options) {
88
+ const nowSeconds = Math.floor((options.now?.() ?? new Date()).getTime() / 1000);
89
+ const header = base64Url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
90
+ const payload = base64Url(JSON.stringify({
91
+ iat: nowSeconds - 60,
92
+ exp: nowSeconds + 540,
93
+ iss: String(options.appId)
94
+ }));
95
+ const signingInput = `${header}.${payload}`;
96
+ const signer = createSign("RSA-SHA256");
97
+ signer.update(signingInput);
98
+ signer.end();
99
+ const signature = signer.sign(options.privateKey);
100
+ return `${signingInput}.${base64Url(signature)}`;
101
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import { applyActionsBridge, renderActionsBridgeWorkflow } from "./actions.js";
3
+ import { redactSensitive } from "./util.js";
4
+ async function main(argv) {
5
+ const [scope, command, ...rest] = argv;
6
+ if (scope !== "actions") {
7
+ usage();
8
+ process.exitCode = 1;
9
+ return;
10
+ }
11
+ if (command === "render-workflow") {
12
+ const endpoint = readFlag(rest, "--endpoint");
13
+ const branchPrefix = readFlag(rest, "--branch-prefix");
14
+ const pullRequest = parseOptionalBooleanFlag(rest, "--pull-request");
15
+ const allowedPathGlobs = readFlags(rest, "--allowed-path");
16
+ process.stdout.write(renderActionsBridgeWorkflow({
17
+ ...(endpoint ? { asyncEndpoint: endpoint } : {}),
18
+ ...(branchPrefix ? { branchPrefix } : {}),
19
+ ...(pullRequest !== undefined ? { pullRequest } : {}),
20
+ ...(allowedPathGlobs.length > 0 ? { allowedPathGlobs } : {})
21
+ }));
22
+ return;
23
+ }
24
+ if (command === "pull") {
25
+ const endpoint = readFlag(rest, "--endpoint") ?? process.env.ASYNC_PROJECT_URL;
26
+ const projectToken = process.env.ASYNC_PROJECT_TOKEN;
27
+ const repository = process.env.GITHUB_REPOSITORY;
28
+ const branchPrefix = readFlag(rest, "--branch-prefix");
29
+ const pullRequest = parseOptionalBooleanFlag(rest, "--pull-request");
30
+ const allowedPathGlobs = readFlags(rest, "--allowed-path");
31
+ if (!endpoint || !projectToken || !repository) {
32
+ throw new Error("actions pull requires ASYNC_PROJECT_URL, ASYNC_PROJECT_TOKEN, and GITHUB_REPOSITORY.");
33
+ }
34
+ const result = await applyActionsBridge({
35
+ endpoint,
36
+ projectToken,
37
+ repository,
38
+ ...(branchPrefix ? { branchPrefix } : {}),
39
+ ...(pullRequest !== undefined ? { pullRequest } : {}),
40
+ ...(allowedPathGlobs.length > 0 ? { allowedPathGlobs } : {})
41
+ });
42
+ process.stdout.write(`${JSON.stringify(result)}\n`);
43
+ return;
44
+ }
45
+ usage();
46
+ process.exitCode = 1;
47
+ }
48
+ function readFlag(args, name) {
49
+ const index = args.indexOf(name);
50
+ if (index === -1) {
51
+ return undefined;
52
+ }
53
+ return args[index + 1];
54
+ }
55
+ function readFlags(args, name) {
56
+ const values = [];
57
+ for (let index = 0; index < args.length; index += 1) {
58
+ const value = args[index + 1];
59
+ if (args[index] === name && value) {
60
+ values.push(value);
61
+ index += 1;
62
+ }
63
+ }
64
+ return values;
65
+ }
66
+ function parseOptionalBooleanFlag(args, name) {
67
+ const value = readFlag(args, name);
68
+ if (value === undefined)
69
+ return undefined;
70
+ if (value === "true")
71
+ return true;
72
+ if (value === "false")
73
+ return false;
74
+ throw new Error(`${name} must be true or false.`);
75
+ }
76
+ function usage() {
77
+ process.stderr.write(`Usage:
78
+ async-github-app actions render-workflow [--endpoint <url>] [--branch-prefix <prefix>] [--allowed-path <glob>...] [--pull-request true|false]
79
+ async-github-app actions pull [--endpoint <url>] [--branch-prefix <prefix>] [--allowed-path <glob>...] [--pull-request true|false]
80
+ `);
81
+ }
82
+ main(process.argv.slice(2)).catch((error) => {
83
+ process.stderr.write(`${redactSensitive(error instanceof Error ? error.message : error)}\n`);
84
+ process.exitCode = 1;
85
+ });
@@ -0,0 +1,42 @@
1
+ export type ContentFormat = "json" | "jsonc" | "markdown" | "mdx";
2
+ export interface JsonRenderOptions {
3
+ readonly indent?: number;
4
+ }
5
+ export interface JsoncRenderOptions extends JsonRenderOptions {
6
+ readonly allowWrite?: boolean;
7
+ }
8
+ export interface MarkdownOptions {
9
+ readonly bodyField?: string;
10
+ }
11
+ export interface ContentMappingOptions {
12
+ readonly resource: string;
13
+ readonly pattern: string;
14
+ readonly format: ContentFormat;
15
+ readonly idFromPath?: (path: string) => string;
16
+ readonly pathFromRecord?: (record: Record<string, unknown>) => string;
17
+ readonly bodyField?: string;
18
+ }
19
+ export interface ContentMapping {
20
+ readonly resource: string;
21
+ readonly pattern: string;
22
+ readonly format: ContentFormat;
23
+ readonly bodyField: string;
24
+ idFromPath(path: string): string;
25
+ pathFromRecord(record: Record<string, unknown>): string;
26
+ parse(source: string): unknown;
27
+ serialize(record: unknown): string;
28
+ }
29
+ export declare class JsoncWriteError extends Error {
30
+ constructor();
31
+ }
32
+ export declare function parseJsonContent<T = unknown>(source: string): T;
33
+ export declare function renderJsonContent(value: unknown, options?: JsonRenderOptions): string;
34
+ export declare function parseJsoncContent<T = unknown>(source: string): T;
35
+ export declare function renderJsoncContent(value: unknown, options?: JsoncRenderOptions): string;
36
+ export declare function parseMarkdownRecord(source: string, options?: MarkdownOptions): Record<string, unknown>;
37
+ export declare function renderMarkdownRecord(record: Record<string, unknown>, options?: MarkdownOptions): string;
38
+ export declare const parseMdxRecord: typeof parseMarkdownRecord;
39
+ export declare const renderMdxRecord: typeof renderMarkdownRecord;
40
+ export declare function contentMapping(options: ContentMappingOptions): ContentMapping;
41
+ export declare function stripJsonc(source: string): string;
42
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,KAAK,CAAC;AAElE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IACxD,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;CACpC;AAED,qBAAa,eAAgB,SAAQ,KAAK;;CAKzC;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAE/D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAEzF;AAED,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAEhE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAM3F;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO1G;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAc3G;AAED,eAAO,MAAM,cAAc,4BAAsB,CAAC;AAClD,eAAO,MAAM,eAAe,6BAAuB,CAAC;AAEpD,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CA6C7E;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA+DjD"}
@@ -0,0 +1,269 @@
1
+ import { ensureTrailingNewline, hasOwn } from "./util.js";
2
+ export class JsoncWriteError extends Error {
3
+ constructor() {
4
+ super("JSONC writes are disabled by default because comments and formatting cannot be preserved safely. Pass allowWrite: true to render canonical JSON content intentionally.");
5
+ this.name = "JsoncWriteError";
6
+ }
7
+ }
8
+ export function parseJsonContent(source) {
9
+ return JSON.parse(source);
10
+ }
11
+ export function renderJsonContent(value, options = {}) {
12
+ return ensureTrailingNewline(JSON.stringify(value, null, options.indent ?? 2));
13
+ }
14
+ export function parseJsoncContent(source) {
15
+ return JSON.parse(stripJsonc(source));
16
+ }
17
+ export function renderJsoncContent(value, options = {}) {
18
+ if (!options.allowWrite) {
19
+ throw new JsoncWriteError();
20
+ }
21
+ return renderJsonContent(value, options);
22
+ }
23
+ export function parseMarkdownRecord(source, options = {}) {
24
+ const bodyField = options.bodyField ?? "body";
25
+ const { data, body } = splitFrontmatter(source);
26
+ return {
27
+ ...data,
28
+ [bodyField]: body
29
+ };
30
+ }
31
+ export function renderMarkdownRecord(record, options = {}) {
32
+ const bodyField = options.bodyField ?? "body";
33
+ const body = typeof record[bodyField] === "string" ? record[bodyField] : "";
34
+ const frontmatter = Object.keys(record)
35
+ .filter((key) => key !== bodyField && record[key] !== undefined)
36
+ .sort()
37
+ .map((key) => `${key}: ${renderFrontmatterScalar(record[key])}`)
38
+ .join("\n");
39
+ if (!frontmatter) {
40
+ return ensureTrailingNewline(body);
41
+ }
42
+ return ensureTrailingNewline(`---\n${frontmatter}\n---\n${body}`);
43
+ }
44
+ export const parseMdxRecord = parseMarkdownRecord;
45
+ export const renderMdxRecord = renderMarkdownRecord;
46
+ export function contentMapping(options) {
47
+ const bodyField = options.bodyField ?? "body";
48
+ const compiled = compilePattern(options.pattern);
49
+ return {
50
+ resource: options.resource,
51
+ pattern: options.pattern,
52
+ format: options.format,
53
+ bodyField,
54
+ idFromPath: options.idFromPath ?? ((path) => {
55
+ const match = compiled.match(path);
56
+ if (!match) {
57
+ throw new Error(`Path "${path}" does not match content mapping pattern "${options.pattern}".`);
58
+ }
59
+ return match.id ?? Object.values(match)[0] ?? path;
60
+ }),
61
+ pathFromRecord: options.pathFromRecord ?? ((record) => compiled.fill(record)),
62
+ parse(source) {
63
+ if (options.format === "json") {
64
+ return parseJsonContent(source);
65
+ }
66
+ if (options.format === "jsonc") {
67
+ return parseJsoncContent(source);
68
+ }
69
+ return parseMarkdownRecord(source, { bodyField });
70
+ },
71
+ serialize(record) {
72
+ if (options.format === "json") {
73
+ return renderJsonContent(record);
74
+ }
75
+ if (options.format === "jsonc") {
76
+ return renderJsoncContent(record);
77
+ }
78
+ if (!isRecord(record)) {
79
+ throw new Error(`${options.format.toUpperCase()} content mappings require object records.`);
80
+ }
81
+ return renderMarkdownRecord(record, { bodyField });
82
+ }
83
+ };
84
+ }
85
+ export function stripJsonc(source) {
86
+ let output = "";
87
+ let inString = false;
88
+ let escaped = false;
89
+ let lineComment = false;
90
+ let blockComment = false;
91
+ for (let index = 0; index < source.length; index += 1) {
92
+ const current = source[index] ?? "";
93
+ const next = source[index + 1] ?? "";
94
+ if (lineComment) {
95
+ if (current === "\n") {
96
+ lineComment = false;
97
+ output += current;
98
+ }
99
+ continue;
100
+ }
101
+ if (blockComment) {
102
+ if (current === "*" && next === "/") {
103
+ blockComment = false;
104
+ index += 1;
105
+ }
106
+ else if (current === "\n") {
107
+ output += "\n";
108
+ }
109
+ continue;
110
+ }
111
+ if (inString) {
112
+ output += current;
113
+ if (escaped) {
114
+ escaped = false;
115
+ }
116
+ else if (current === "\\") {
117
+ escaped = true;
118
+ }
119
+ else if (current === "\"") {
120
+ inString = false;
121
+ }
122
+ continue;
123
+ }
124
+ if (current === "\"") {
125
+ inString = true;
126
+ output += current;
127
+ continue;
128
+ }
129
+ if (current === "/" && next === "/") {
130
+ lineComment = true;
131
+ index += 1;
132
+ continue;
133
+ }
134
+ if (current === "/" && next === "*") {
135
+ blockComment = true;
136
+ index += 1;
137
+ continue;
138
+ }
139
+ output += current;
140
+ }
141
+ return removeTrailingCommas(output);
142
+ }
143
+ function removeTrailingCommas(source) {
144
+ let output = "";
145
+ let inString = false;
146
+ let escaped = false;
147
+ for (let index = 0; index < source.length; index += 1) {
148
+ const current = source[index] ?? "";
149
+ if (inString) {
150
+ output += current;
151
+ if (escaped) {
152
+ escaped = false;
153
+ }
154
+ else if (current === "\\") {
155
+ escaped = true;
156
+ }
157
+ else if (current === "\"") {
158
+ inString = false;
159
+ }
160
+ continue;
161
+ }
162
+ if (current === "\"") {
163
+ inString = true;
164
+ output += current;
165
+ continue;
166
+ }
167
+ if (current === ",") {
168
+ const rest = source.slice(index + 1);
169
+ if (/^\s*[}\]]/u.test(rest)) {
170
+ continue;
171
+ }
172
+ }
173
+ output += current;
174
+ }
175
+ return output;
176
+ }
177
+ function splitFrontmatter(source) {
178
+ if (!source.startsWith("---\n")) {
179
+ return { data: {}, body: source };
180
+ }
181
+ const end = source.indexOf("\n---", 4);
182
+ if (end === -1) {
183
+ return { data: {}, body: source };
184
+ }
185
+ const rawFrontmatter = source.slice(4, end);
186
+ const bodyStart = source[end + 4] === "\n" ? end + 5 : end + 4;
187
+ return {
188
+ data: parseFrontmatter(rawFrontmatter),
189
+ body: source.slice(bodyStart)
190
+ };
191
+ }
192
+ function parseFrontmatter(source) {
193
+ const data = {};
194
+ for (const line of source.split(/\r?\n/u)) {
195
+ if (!line.trim() || line.trimStart().startsWith("#")) {
196
+ continue;
197
+ }
198
+ const separator = line.indexOf(":");
199
+ if (separator === -1) {
200
+ continue;
201
+ }
202
+ const key = line.slice(0, separator).trim();
203
+ const value = line.slice(separator + 1).trim();
204
+ if (key) {
205
+ data[key] = parseFrontmatterScalar(value);
206
+ }
207
+ }
208
+ return data;
209
+ }
210
+ function parseFrontmatterScalar(value) {
211
+ if (value === "true") {
212
+ return true;
213
+ }
214
+ if (value === "false") {
215
+ return false;
216
+ }
217
+ if (value === "null") {
218
+ return null;
219
+ }
220
+ if (/^-?\d+(?:\.\d+)?$/u.test(value)) {
221
+ return Number(value);
222
+ }
223
+ if ((value.startsWith("\"") && value.endsWith("\"")) ||
224
+ value.startsWith("[") ||
225
+ value.startsWith("{")) {
226
+ try {
227
+ return JSON.parse(value);
228
+ }
229
+ catch {
230
+ return value;
231
+ }
232
+ }
233
+ return value;
234
+ }
235
+ function renderFrontmatterScalar(value) {
236
+ if (typeof value === "string") {
237
+ return JSON.stringify(value);
238
+ }
239
+ if (typeof value === "number" || typeof value === "boolean" || value === null) {
240
+ return String(value);
241
+ }
242
+ return JSON.stringify(value);
243
+ }
244
+ function compilePattern(pattern) {
245
+ const placeholders = [...pattern.matchAll(/\{([A-Za-z_][A-Za-z0-9_]*)\}/gu)].map((match) => match[1]);
246
+ const regexSource = pattern
247
+ .replace(/[.+?^${}()|[\]\\]/gu, "\\$&")
248
+ .replace(/\\\{([A-Za-z_][A-Za-z0-9_]*)\\\}/gu, "(?<$1>[^/]+)");
249
+ const regex = new RegExp(`^${regexSource}$`, "u");
250
+ return {
251
+ match(path) {
252
+ const matched = regex.exec(path);
253
+ return matched?.groups;
254
+ },
255
+ fill(record) {
256
+ let path = pattern;
257
+ for (const placeholder of placeholders) {
258
+ if (!hasOwn(record, placeholder) || record[placeholder] === undefined) {
259
+ throw new Error(`Record is missing "${placeholder}" required by content mapping pattern "${pattern}".`);
260
+ }
261
+ path = path.replaceAll(`{${placeholder}}`, encodeURIComponent(String(record[placeholder])));
262
+ }
263
+ return path;
264
+ }
265
+ };
266
+ }
267
+ function isRecord(value) {
268
+ return typeof value === "object" && value !== null && !Array.isArray(value);
269
+ }
@@ -0,0 +1,11 @@
1
+ import type { GitHubAuthProvider, GitHubClient, GitHubRepo, GitHubRepoInput } from "./types.js";
2
+ export declare class GitHubApiError extends Error {
3
+ readonly status: number;
4
+ readonly method: string;
5
+ readonly path: string;
6
+ constructor(message: string, status: number, method: string, path: string);
7
+ }
8
+ export declare function createGitHubClient(auth: GitHubAuthProvider): GitHubClient;
9
+ export declare function parseGitHubRepo(input: GitHubRepoInput): GitHubRepo;
10
+ export declare function formatGitHubRepo(input: GitHubRepoInput): string;
11
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EASV,kBAAkB,EAClB,YAAY,EACZ,UAAU,EACV,eAAe,EAMhB,MAAM,YAAY,CAAC;AAIpB,qBAAa,cAAe,SAAQ,KAAK;IAGrC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAHrB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAKxB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,YAAY,CA2CzE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,UAAU,CAWlE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAG/D"}