@mastra/github-signals 0.4.1 → 0.5.0-alpha.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/github-app-owner.d.ts +13 -0
- package/dist/github-app-owner.d.ts.map +1 -0
- package/dist/github-auth-env.d.ts +19 -0
- package/dist/github-auth-env.d.ts.map +1 -0
- package/dist/index.cjs +121 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +121 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type GithubAppOwner = {
|
|
2
|
+
login: string;
|
|
3
|
+
type: 'User' | 'Organization';
|
|
4
|
+
};
|
|
5
|
+
export type GithubAppOwnerCommandRunner = (args: readonly string[]) => Promise<{
|
|
6
|
+
stdout: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare class GithubAppOwnerResolver {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(runGhApi?: GithubAppOwnerCommandRunner);
|
|
11
|
+
getOwner(botLogin: string, isCurrentGeneration?: () => boolean): Promise<GithubAppOwner | undefined>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=github-app-owner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-app-owner.d.ts","sourceRoot":"","sources":["../src/github-app-owner.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAyBnG,qBAAa,sBAAsB;;IAIjC,YAAY,QAAQ,GAAE,2BAA6C,EAElE;IAEK,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,mBAAmB,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CA2BzG;CAWF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a GitHub credential from the global `gh` CLI and return an environment
|
|
3
|
+
* that presents it to a child process.
|
|
4
|
+
*
|
|
5
|
+
* gitcrawl takes the first non-empty value of the environment variable named by
|
|
6
|
+
* `[github].token_env` (default `GITHUB_TOKEN`) and only discovers it is invalid
|
|
7
|
+
* when GitHub rejects it, so a stale exported token fails `sync` outright even
|
|
8
|
+
* when `gh` can still mint a working credential. Injecting a credential here
|
|
9
|
+
* makes gitcrawl's own env lookup win, so the stale value is never consulted.
|
|
10
|
+
*
|
|
11
|
+
* `gh auth token` is asked with the token variables removed: `gh` answers with
|
|
12
|
+
* `GH_TOKEN`/`GITHUB_TOKEN` verbatim when either is set, which would hand back
|
|
13
|
+
* the very credential being replaced.
|
|
14
|
+
*
|
|
15
|
+
* Returns `undefined` when no credential can be resolved, so callers leave the
|
|
16
|
+
* inherited environment untouched rather than stripping a working token.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveGithubAuthEnv(): Promise<NodeJS.ProcessEnv | undefined>;
|
|
19
|
+
//# sourceMappingURL=github-auth-env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-auth-env.d.ts","sourceRoot":"","sources":["../src/github-auth-env.ts"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC,CAqBnF"}
|
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,111 @@ let _mastra_core_signals = require("@mastra/core/signals");
|
|
|
30
30
|
let _mastra_core_tools = require("@mastra/core/tools");
|
|
31
31
|
let zod = require("zod");
|
|
32
32
|
zod = __toESM(zod, 1);
|
|
33
|
+
//#region src/github-auth-env.ts
|
|
34
|
+
let execFileAsync$2;
|
|
35
|
+
/** Environment variables both gitcrawl and the `gh` CLI treat as a GitHub credential. */
|
|
36
|
+
const GITHUB_TOKEN_ENV_VARS = ["GH_TOKEN", "GITHUB_TOKEN"];
|
|
37
|
+
/** Remove every casing of the token variables: Windows treats environment names case-insensitively. */
|
|
38
|
+
function withoutGithubTokens(env) {
|
|
39
|
+
const scrubbed = { ...env };
|
|
40
|
+
for (const name of Object.keys(scrubbed)) if (GITHUB_TOKEN_ENV_VARS.some((tokenEnv) => tokenEnv.toLowerCase() === name.toLowerCase())) delete scrubbed[name];
|
|
41
|
+
return scrubbed;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a GitHub credential from the global `gh` CLI and return an environment
|
|
45
|
+
* that presents it to a child process.
|
|
46
|
+
*
|
|
47
|
+
* gitcrawl takes the first non-empty value of the environment variable named by
|
|
48
|
+
* `[github].token_env` (default `GITHUB_TOKEN`) and only discovers it is invalid
|
|
49
|
+
* when GitHub rejects it, so a stale exported token fails `sync` outright even
|
|
50
|
+
* when `gh` can still mint a working credential. Injecting a credential here
|
|
51
|
+
* makes gitcrawl's own env lookup win, so the stale value is never consulted.
|
|
52
|
+
*
|
|
53
|
+
* `gh auth token` is asked with the token variables removed: `gh` answers with
|
|
54
|
+
* `GH_TOKEN`/`GITHUB_TOKEN` verbatim when either is set, which would hand back
|
|
55
|
+
* the very credential being replaced.
|
|
56
|
+
*
|
|
57
|
+
* Returns `undefined` when no credential can be resolved, so callers leave the
|
|
58
|
+
* inherited environment untouched rather than stripping a working token.
|
|
59
|
+
*/
|
|
60
|
+
async function resolveGithubAuthEnv() {
|
|
61
|
+
const scrubbed = withoutGithubTokens(process.env);
|
|
62
|
+
if (!execFileAsync$2) {
|
|
63
|
+
const { execFile } = await import("child_process");
|
|
64
|
+
execFileAsync$2 = (0, util.promisify)(execFile);
|
|
65
|
+
}
|
|
66
|
+
let token;
|
|
67
|
+
try {
|
|
68
|
+
const { stdout } = await execFileAsync$2("gh", ["auth", "token"], { env: scrubbed });
|
|
69
|
+
token = stdout.trim();
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!token) return void 0;
|
|
74
|
+
return {
|
|
75
|
+
...process.env,
|
|
76
|
+
GH_TOKEN: token,
|
|
77
|
+
GITHUB_TOKEN: token
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/github-app-owner.ts
|
|
82
|
+
const OWNER_CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
83
|
+
let execFileAsync$1;
|
|
84
|
+
const defaultRunGhApi = async (args) => {
|
|
85
|
+
if (!execFileAsync$1) {
|
|
86
|
+
const { execFile } = await import("child_process");
|
|
87
|
+
execFileAsync$1 = (0, util.promisify)(execFile);
|
|
88
|
+
}
|
|
89
|
+
return execFileAsync$1("gh", args, { env: await resolveGithubAuthEnv() });
|
|
90
|
+
};
|
|
91
|
+
var GithubAppOwnerResolver = class {
|
|
92
|
+
#cache = /* @__PURE__ */ new Map();
|
|
93
|
+
#runGhApi;
|
|
94
|
+
constructor(runGhApi = defaultRunGhApi) {
|
|
95
|
+
this.#runGhApi = runGhApi;
|
|
96
|
+
}
|
|
97
|
+
async getOwner(botLogin, isCurrentGeneration) {
|
|
98
|
+
const appSlug = botLogin.replace(/\[bot\]$/i, "");
|
|
99
|
+
if (!appSlug || isCurrentGeneration && !isCurrentGeneration()) return void 0;
|
|
100
|
+
const cacheKey = appSlug.toLowerCase();
|
|
101
|
+
const cached = this.#cache.get(cacheKey);
|
|
102
|
+
if (cached?.expiresAt && cached.expiresAt > Date.now()) {
|
|
103
|
+
if (isCurrentGeneration && !isCurrentGeneration()) return void 0;
|
|
104
|
+
return cached.owner;
|
|
105
|
+
}
|
|
106
|
+
if (cached) this.#cache.delete(cacheKey);
|
|
107
|
+
try {
|
|
108
|
+
const { stdout } = await this.#runGhApi(["api", `apps/${appSlug}`]);
|
|
109
|
+
const parsed = JSON.parse(stdout);
|
|
110
|
+
const owner = this.#parseOwner(parsed);
|
|
111
|
+
if (!owner || isCurrentGeneration && !isCurrentGeneration()) return void 0;
|
|
112
|
+
this.#cache.set(cacheKey, {
|
|
113
|
+
owner,
|
|
114
|
+
expiresAt: Date.now() + OWNER_CACHE_TTL_MS
|
|
115
|
+
});
|
|
116
|
+
if (isCurrentGeneration && !isCurrentGeneration()) {
|
|
117
|
+
this.#cache.delete(cacheKey);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
return owner;
|
|
121
|
+
} catch {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
#parseOwner(value) {
|
|
126
|
+
if (!value || typeof value !== "object" || !("owner" in value)) return void 0;
|
|
127
|
+
const owner = value.owner;
|
|
128
|
+
if (!owner || typeof owner !== "object" || !("login" in owner) || !("type" in owner)) return void 0;
|
|
129
|
+
if (typeof owner.login !== "string" || !owner.login.trim()) return void 0;
|
|
130
|
+
if (owner.type !== "User" && owner.type !== "Organization") return void 0;
|
|
131
|
+
return {
|
|
132
|
+
login: owner.login,
|
|
133
|
+
type: owner.type
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
//#endregion
|
|
33
138
|
//#region src/index.ts
|
|
34
139
|
let _execFileAsync;
|
|
35
140
|
async function execFileAsync(file, args, options) {
|
|
@@ -603,7 +708,8 @@ var GitcrawlSyncClient = class {
|
|
|
603
708
|
const { stdout, stderr } = await execFileAsync(this.#command, args, {
|
|
604
709
|
cwd: input.cwd,
|
|
605
710
|
signal: input.abortSignal,
|
|
606
|
-
maxBuffer: 10 * 1024 * 1024
|
|
711
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
712
|
+
env: await resolveGithubAuthEnv()
|
|
607
713
|
});
|
|
608
714
|
return {
|
|
609
715
|
ok: true,
|
|
@@ -664,12 +770,12 @@ var GitcrawlSyncClient = class {
|
|
|
664
770
|
join repositories r on r.id=t.repo_id
|
|
665
771
|
where r.owner=${owner} and r.name=${repo} and t.number=${number} and rt.is_resolved=0`);
|
|
666
772
|
const latestComments = await this.#queryDb(`select c.author_login, c.author_type, c.is_bot, c.body, json_extract(c.raw_json, '$.html_url') as html_url,
|
|
667
|
-
coalesce(c.updated_at_gh, c.created_at_gh) as updated_at
|
|
773
|
+
coalesce(c.updated_at_gh, c.created_at_gh, json_extract(c.raw_json, '$.submitted_at')) as updated_at
|
|
668
774
|
from comments c
|
|
669
775
|
join threads t on t.id=c.thread_id
|
|
670
776
|
join repositories r on r.id=t.repo_id
|
|
671
777
|
where r.owner=${owner} and r.name=${repo} and t.number=${number}
|
|
672
|
-
order by coalesce(c.updated_at_gh, c.created_at_gh) desc
|
|
778
|
+
order by coalesce(c.updated_at_gh, c.created_at_gh, json_extract(c.raw_json, '$.submitted_at')) desc
|
|
673
779
|
limit 20`);
|
|
674
780
|
const latestComment = latestComments[0];
|
|
675
781
|
const checks = normalizeGithubChecksForSnapshot({
|
|
@@ -794,6 +900,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
|
|
|
794
900
|
#options;
|
|
795
901
|
#syncClient;
|
|
796
902
|
#repositoryResolver;
|
|
903
|
+
#appOwnerResolver;
|
|
797
904
|
#polling = /* @__PURE__ */ new Map();
|
|
798
905
|
#pollingThreadGenerations = /* @__PURE__ */ new Map();
|
|
799
906
|
#pollingGeneration = 0;
|
|
@@ -807,6 +914,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
|
|
|
807
914
|
this.#options = options;
|
|
808
915
|
this.#syncClient = options.syncClient ?? new GitcrawlSyncClient({ command: options.gitcrawlCommand });
|
|
809
916
|
this.#repositoryResolver = options.repositoryResolver ?? new GitRemoteRepositoryResolver();
|
|
917
|
+
this.#appOwnerResolver = new GithubAppOwnerResolver();
|
|
810
918
|
if (options.getNotificationStreamOptions) this.#agentOptions = { getNotificationStreamOptions: options.getNotificationStreamOptions };
|
|
811
919
|
}
|
|
812
920
|
/**
|
|
@@ -1425,7 +1533,15 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
|
|
|
1425
1533
|
const normalizedUser = user.toLowerCase();
|
|
1426
1534
|
if (metadata.isBot === true || metadata.authorType?.toLowerCase() === "bot" || normalizedUser.endsWith("[bot]")) {
|
|
1427
1535
|
if ((this.#options.ignoredBots ?? []).some((bot) => bot.toLowerCase() === normalizedUser)) return false;
|
|
1428
|
-
|
|
1536
|
+
if ((this.#options.authorizedBots ?? DEFAULT_AUTHORIZED_BOTS).some((bot) => bot.toLowerCase() === normalizedUser)) return true;
|
|
1537
|
+
const appOwner = await this.#appOwnerResolver.getOwner(user, isCurrentGeneration);
|
|
1538
|
+
if (!appOwner || isCurrentGeneration && !isCurrentGeneration()) return false;
|
|
1539
|
+
if (appOwner.type === "Organization") return appOwner.login.toLowerCase() === owner.toLowerCase();
|
|
1540
|
+
if (appOwner.type !== "User") return false;
|
|
1541
|
+
const permission = await this.#loadAuthorPermission(owner, repo, appOwner.login, isCurrentGeneration);
|
|
1542
|
+
if (isCurrentGeneration && !isCurrentGeneration()) return false;
|
|
1543
|
+
const authorizedPermissions = this.#options.authorizedPermissions ?? DEFAULT_AUTHORIZED_PERMISSIONS;
|
|
1544
|
+
return !!permission && authorizedPermissions.includes(permission);
|
|
1429
1545
|
}
|
|
1430
1546
|
const permission = await this.#loadAuthorPermission(owner, repo, user, isCurrentGeneration);
|
|
1431
1547
|
if (isCurrentGeneration && !isCurrentGeneration()) return false;
|
|
@@ -1487,7 +1603,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
|
|
|
1487
1603
|
`repos/${owner}/${repo}/collaborators/${user}/permission`,
|
|
1488
1604
|
"--jq",
|
|
1489
1605
|
".permission"
|
|
1490
|
-
]);
|
|
1606
|
+
], { env: await resolveGithubAuthEnv() });
|
|
1491
1607
|
const raw = stdout.trim();
|
|
1492
1608
|
permission = [
|
|
1493
1609
|
"admin",
|