@noodleseed/one 0.97.0 → 0.98.0
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/node_modules/@noodle-borg/service/dist/feedback-operations/bootstrap.d.ts +7 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/bootstrap.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/bootstrap.js +23 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/bootstrap.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/config.d.ts +12 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/config.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/config.js +47 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/config.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/contracts.d.ts +24 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/contracts.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/contracts.js +2 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/contracts.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/delivery-worker.d.ts +29 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/delivery-worker.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/delivery-worker.js +256 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/delivery-worker.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/linear-client.d.ts +39 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/linear-client.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/linear-client.js +142 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/linear-client.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/reporter-email.d.ts +33 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/reporter-email.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/reporter-email.js +142 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/reporter-email.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-card.d.ts +138 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-card.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-card.js +199 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-card.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-client.d.ts +44 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-client.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-client.js +95 -0
- package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-client.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/main.js +38 -3
- package/node_modules/@noodle-borg/service/dist/main.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/options.d.ts +14 -0
- package/node_modules/@noodle-borg/service/dist/options.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/feedback-operations-dispatch.d.ts +9 -0
- package/node_modules/@noodle-borg/service/dist/routes/feedback-operations-dispatch.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/feedback-operations-dispatch.js +53 -0
- package/node_modules/@noodle-borg/service/dist/routes/feedback-operations-dispatch.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/feedback.d.ts +2 -0
- package/node_modules/@noodle-borg/service/dist/routes/feedback.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/feedback.js +22 -0
- package/node_modules/@noodle-borg/service/dist/routes/feedback.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/linear-feedback-webhook.d.ts +17 -0
- package/node_modules/@noodle-borg/service/dist/routes/linear-feedback-webhook.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/linear-feedback-webhook.js +141 -0
- package/node_modules/@noodle-borg/service/dist/routes/linear-feedback-webhook.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/slack-feedback-actions.d.ts +13 -0
- package/node_modules/@noodle-borg/service/dist/routes/slack-feedback-actions.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/slack-feedback-actions.js +99 -0
- package/node_modules/@noodle-borg/service/dist/routes/slack-feedback-actions.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/serve.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/serve.js +23 -0
- package/node_modules/@noodle-borg/service/dist/serve.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.js +2 -0
- package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/service.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/service.js +6 -0
- package/node_modules/@noodle-borg/service/dist/service.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/feedback-operations.d.ts +186 -0
- package/node_modules/@noodle-borg/service/dist/store/feedback-operations.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/feedback-operations.js +483 -0
- package/node_modules/@noodle-borg/service/dist/store/feedback-operations.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-feedback-operations.d.ts +35 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-feedback-operations.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-feedback-operations.js +172 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-feedback-operations.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ServeServiceOptions } from '../serve-options.js';
|
|
2
|
+
import type { FeedbackOperationsStore } from '../store/feedback-operations.js';
|
|
3
|
+
export declare function startConfiguredFeedbackOperationsWorker(input: {
|
|
4
|
+
readonly options: ServeServiceOptions;
|
|
5
|
+
readonly store: FeedbackOperationsStore | undefined;
|
|
6
|
+
}): NodeJS.Timeout | undefined;
|
|
7
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/feedback-operations/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAG/E,wBAAgB,uCAAuC,CAAC,KAAK,EAAE;IAC7D,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAAC;CACrD,GAAG,MAAM,CAAC,OAAO,GAAG,SAAS,CAuB7B"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { startFeedbackOperationsWorker } from './delivery-worker.js';
|
|
2
|
+
export function startConfiguredFeedbackOperationsWorker(input) {
|
|
3
|
+
const { options, store } = input;
|
|
4
|
+
const config = options.feedbackOperationsConfig;
|
|
5
|
+
if (config === undefined ||
|
|
6
|
+
store === undefined ||
|
|
7
|
+
options.linearFeedbackClient === undefined ||
|
|
8
|
+
options.slackFeedbackClient === undefined ||
|
|
9
|
+
options.feedbackReporterEmailSender === undefined) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
return startFeedbackOperationsWorker({
|
|
13
|
+
store,
|
|
14
|
+
linear: options.linearFeedbackClient,
|
|
15
|
+
slack: options.slackFeedbackClient,
|
|
16
|
+
email: options.feedbackReporterEmailSender,
|
|
17
|
+
slackChannelId: config.NOODLE_FEEDBACK_SLACK_CHANNEL_ID,
|
|
18
|
+
inProgressStatusId: config.NOODLE_FEEDBACK_LINEAR_IN_PROGRESS_STATUS_ID,
|
|
19
|
+
...(options.clock !== undefined ? { clock: options.clock } : {}),
|
|
20
|
+
...(options.logger !== undefined ? { logger: options.logger } : {}),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../src/feedback-operations/bootstrap.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAErE,MAAM,UAAU,uCAAuC,CAAC,KAGvD;IACC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAChD,IACE,MAAM,KAAK,SAAS;QACpB,KAAK,KAAK,SAAS;QACnB,OAAO,CAAC,oBAAoB,KAAK,SAAS;QAC1C,OAAO,CAAC,mBAAmB,KAAK,SAAS;QACzC,OAAO,CAAC,2BAA2B,KAAK,SAAS,EACjD,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,6BAA6B,CAAC;QACnC,KAAK;QACL,MAAM,EAAE,OAAO,CAAC,oBAAoB;QACpC,KAAK,EAAE,OAAO,CAAC,mBAAmB;QAClC,KAAK,EAAE,OAAO,CAAC,2BAA2B;QAC1C,cAAc,EAAE,MAAM,CAAC,gCAAgC;QACvD,kBAAkB,EAAE,MAAM,CAAC,4CAA4C;QACvE,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpE,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const DEFAULT_FEEDBACK_GITHUB_REPOSITORY = "NoodleSeed-com/noodle-borg";
|
|
2
|
+
export interface FeedbackGithubRepository {
|
|
3
|
+
readonly owner: string;
|
|
4
|
+
readonly repo: string;
|
|
5
|
+
}
|
|
6
|
+
declare const REQUIRED_CONFIG_KEYS: readonly ["NOODLE_FEEDBACK_LINEAR_WEBHOOK_SECRET", "NOODLE_FEEDBACK_LINEAR_API_TOKEN", "NOODLE_FEEDBACK_LINEAR_TEAM_ID", "NOODLE_FEEDBACK_LINEAR_IN_PROGRESS_STATUS_ID", "NOODLE_FEEDBACK_SLACK_BOT_TOKEN", "NOODLE_FEEDBACK_SLACK_SIGNING_SECRET", "NOODLE_FEEDBACK_SLACK_WORKSPACE_ID", "NOODLE_FEEDBACK_SLACK_CHANNEL_ID", "NOODLE_FEEDBACK_EMAIL_FROM", "NOODLE_FEEDBACK_EMAIL_REPLY_TO", "RESEND_API_KEY"];
|
|
7
|
+
type FeedbackConfigKey = (typeof REQUIRED_CONFIG_KEYS)[number];
|
|
8
|
+
export type FeedbackOperationsConfig = Readonly<Record<FeedbackConfigKey, string>>;
|
|
9
|
+
export declare function resolveFeedbackOperationsConfig(env: Readonly<Record<string, string | undefined>>): FeedbackOperationsConfig | undefined;
|
|
10
|
+
export declare function resolveFeedbackGithubRepository(env: Readonly<Record<string, string | undefined>>): FeedbackGithubRepository;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/feedback-operations/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kCAAkC,+BAA+B,CAAC;AAE/E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAeD,QAAA,MAAM,oBAAoB,iZAAuD,CAAC;AAElF,KAAK,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC;AAOnF,wBAAgB,+BAA+B,CAC7C,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAChD,wBAAwB,GAAG,SAAS,CAWtC;AAED,wBAAgB,+BAA+B,CAC7C,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAChD,wBAAwB,CAmB1B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const DEFAULT_FEEDBACK_GITHUB_REPOSITORY = 'NoodleSeed-com/noodle-borg';
|
|
3
|
+
const FEEDBACK_CONFIG_KEYS = [
|
|
4
|
+
'NOODLE_FEEDBACK_LINEAR_WEBHOOK_SECRET',
|
|
5
|
+
'NOODLE_FEEDBACK_LINEAR_API_TOKEN',
|
|
6
|
+
'NOODLE_FEEDBACK_LINEAR_TEAM_ID',
|
|
7
|
+
'NOODLE_FEEDBACK_LINEAR_IN_PROGRESS_STATUS_ID',
|
|
8
|
+
'NOODLE_FEEDBACK_SLACK_BOT_TOKEN',
|
|
9
|
+
'NOODLE_FEEDBACK_SLACK_SIGNING_SECRET',
|
|
10
|
+
'NOODLE_FEEDBACK_SLACK_WORKSPACE_ID',
|
|
11
|
+
'NOODLE_FEEDBACK_SLACK_CHANNEL_ID',
|
|
12
|
+
'NOODLE_FEEDBACK_EMAIL_FROM',
|
|
13
|
+
'NOODLE_FEEDBACK_EMAIL_REPLY_TO',
|
|
14
|
+
];
|
|
15
|
+
const REQUIRED_CONFIG_KEYS = [...FEEDBACK_CONFIG_KEYS, 'RESEND_API_KEY'];
|
|
16
|
+
const FeedbackOperationsConfigSchema = z.record(z.enum(REQUIRED_CONFIG_KEYS), z.string().trim().min(1));
|
|
17
|
+
export function resolveFeedbackOperationsConfig(env) {
|
|
18
|
+
const enabled = FEEDBACK_CONFIG_KEYS.some((key) => Boolean(env[key]?.trim()));
|
|
19
|
+
if (!enabled)
|
|
20
|
+
return undefined;
|
|
21
|
+
const candidate = Object.fromEntries(REQUIRED_CONFIG_KEYS.map((key) => [key, env[key]]));
|
|
22
|
+
const parsed = FeedbackOperationsConfigSchema.safeParse(candidate);
|
|
23
|
+
if (!parsed.success) {
|
|
24
|
+
const missing = REQUIRED_CONFIG_KEYS.filter((key) => !env[key]?.trim());
|
|
25
|
+
throw new Error(`feedback operations configuration is missing: ${missing.join(', ')}`);
|
|
26
|
+
}
|
|
27
|
+
return parsed.data;
|
|
28
|
+
}
|
|
29
|
+
export function resolveFeedbackGithubRepository(env) {
|
|
30
|
+
const value = env.NOODLE_FEEDBACK_GITHUB_REPO ?? DEFAULT_FEEDBACK_GITHUB_REPOSITORY;
|
|
31
|
+
const [rawOwner, rawRepo, ...extra] = value.split('/');
|
|
32
|
+
const owner = rawOwner?.trim();
|
|
33
|
+
const repo = rawRepo?.trim();
|
|
34
|
+
if (!owner ||
|
|
35
|
+
!repo ||
|
|
36
|
+
extra.length > 0 ||
|
|
37
|
+
!/^[A-Za-z0-9_.-]+$/.test(owner) ||
|
|
38
|
+
!/^[A-Za-z0-9_.-]+$/.test(repo) ||
|
|
39
|
+
owner === '.' ||
|
|
40
|
+
owner === '..' ||
|
|
41
|
+
repo === '.' ||
|
|
42
|
+
repo === '..') {
|
|
43
|
+
throw new Error('invalid feedback GitHub repository');
|
|
44
|
+
}
|
|
45
|
+
return { owner, repo };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/feedback-operations/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,kCAAkC,GAAG,4BAA4B,CAAC;AAO/E,MAAM,oBAAoB,GAAG;IAC3B,uCAAuC;IACvC,kCAAkC;IAClC,gCAAgC;IAChC,8CAA8C;IAC9C,iCAAiC;IACjC,sCAAsC;IACtC,oCAAoC;IACpC,kCAAkC;IAClC,4BAA4B;IAC5B,gCAAgC;CACxB,CAAC;AAEX,MAAM,oBAAoB,GAAG,CAAC,GAAG,oBAAoB,EAAE,gBAAgB,CAAU,CAAC;AAMlF,MAAM,8BAA8B,GAAG,CAAC,CAAC,MAAM,CAC7C,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAC5B,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CACzB,CAAC;AAEF,MAAM,UAAU,+BAA+B,CAC7C,GAAiD;IAEjD,MAAM,OAAO,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9E,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACzF,MAAM,MAAM,GAAG,8BAA8B,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACnE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,iDAAiD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,GAAiD;IAEjD,MAAM,KAAK,GAAG,GAAG,CAAC,2BAA2B,IAAI,kCAAkC,CAAC;IACpF,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,IACE,CAAC,KAAK;QACN,CAAC,IAAI;QACL,KAAK,CAAC,MAAM,GAAG,CAAC;QAChB,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC;QAChC,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC/B,KAAK,KAAK,GAAG;QACb,KAAK,KAAK,IAAI;QACd,IAAI,KAAK,GAAG;QACZ,IAAI,KAAK,IAAI,EACb,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type LinearWorkflowStateType = 'backlog' | 'unstarted' | 'started' | 'completed' | 'canceled';
|
|
2
|
+
export type ReporterNotificationState = 'pending' | 'queued' | 'delayed' | 'sent' | 'unknown' | 'suppressed';
|
|
3
|
+
export interface SlackFeedbackCardInput {
|
|
4
|
+
readonly linearIssueId: string;
|
|
5
|
+
readonly linearIdentifier: string;
|
|
6
|
+
readonly linearUrl: string;
|
|
7
|
+
readonly title: string;
|
|
8
|
+
readonly message: string;
|
|
9
|
+
readonly status: {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly type: LinearWorkflowStateType;
|
|
13
|
+
};
|
|
14
|
+
readonly assigneeName?: string;
|
|
15
|
+
readonly labels: readonly string[];
|
|
16
|
+
readonly trustedReporter: boolean;
|
|
17
|
+
readonly reporterNotification: ReporterNotificationState;
|
|
18
|
+
}
|
|
19
|
+
export interface RenderedFeedbackEmail {
|
|
20
|
+
readonly subject: string;
|
|
21
|
+
readonly text: string;
|
|
22
|
+
readonly html: string;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/feedback-operations/contracts.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,uBAAuB,GAC/B,SAAS,GACT,WAAW,GACX,SAAS,GACT,WAAW,GACX,UAAU,CAAC;AAEf,MAAM,MAAM,yBAAyB,GACjC,SAAS,GACT,QAAQ,GACR,SAAS,GACT,MAAM,GACN,SAAS,GACT,YAAY,CAAC;AAEjB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;KACxC,CAAC;IACF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,oBAAoB,EAAE,yBAAyB,CAAC;CAC1D;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.js","sourceRoot":"","sources":["../../src/feedback-operations/contracts.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Logger } from '@noodle-borg/transport-http';
|
|
2
|
+
import type { FeedbackOperationsStore } from '../store/feedback-operations.js';
|
|
3
|
+
import type { LinearFeedbackIssueClient } from './linear-client.js';
|
|
4
|
+
import type { FeedbackReporterEmailSender } from './reporter-email.js';
|
|
5
|
+
import type { SlackFeedbackClient } from './slack-client.js';
|
|
6
|
+
export declare const FEEDBACK_OPERATIONS_LEASE_MS = 120000;
|
|
7
|
+
export declare const FEEDBACK_OPERATIONS_PROVIDER_TIMEOUT_MS = 15000;
|
|
8
|
+
export declare const FEEDBACK_OPERATIONS_SWEEP_INTERVAL_MS = 30000;
|
|
9
|
+
type WorkKind = 'slack' | 'email' | 'start-work';
|
|
10
|
+
export interface FeedbackOperationsWorkerOptions {
|
|
11
|
+
readonly store: FeedbackOperationsStore;
|
|
12
|
+
readonly slack: SlackFeedbackClient;
|
|
13
|
+
readonly linear: LinearFeedbackIssueClient;
|
|
14
|
+
readonly email: FeedbackReporterEmailSender;
|
|
15
|
+
readonly slackChannelId: string;
|
|
16
|
+
readonly inProgressStatusId: string;
|
|
17
|
+
readonly clock?: () => Date;
|
|
18
|
+
readonly logger?: Logger;
|
|
19
|
+
}
|
|
20
|
+
export declare class FeedbackOperationsWorker {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(options: FeedbackOperationsWorkerOptions);
|
|
23
|
+
runOnce(input?: {
|
|
24
|
+
kinds?: readonly WorkKind[];
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export declare function startFeedbackOperationsWorker(options: FeedbackOperationsWorkerOptions): NodeJS.Timeout;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=delivery-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-worker.d.ts","sourceRoot":"","sources":["../../src/feedback-operations/delivery-worker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAc,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAEV,uBAAuB,EAExB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAEvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,eAAO,MAAM,4BAA4B,SAAU,CAAC;AACpD,eAAO,MAAM,uCAAuC,QAAS,CAAC;AAI9D,eAAO,MAAM,qCAAqC,QAAS,CAAC;AAE5D,KAAK,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,CAAC;AAEjD,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,KAAK,EAAE,uBAAuB,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,2BAA2B,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,wBAAwB;;gBAIvB,OAAO,EAAE,+BAA+B;IAK9C,OAAO,CAAC,KAAK,GAAE;QAAE,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAyM1E;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,+BAA+B,GACvC,MAAM,CAAC,OAAO,CAiBhB"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { noopLogger } from '@noodle-borg/transport-http';
|
|
3
|
+
import { renderSlackFeedbackCard } from './slack-card.js';
|
|
4
|
+
export const FEEDBACK_OPERATIONS_LEASE_MS = 120_000;
|
|
5
|
+
export const FEEDBACK_OPERATIONS_PROVIDER_TIMEOUT_MS = 15_000;
|
|
6
|
+
const BASE_RETRY_MS = 60_000;
|
|
7
|
+
const MAX_RETRY_MS = 24 * 60 * 60_000;
|
|
8
|
+
const EMAIL_RETRY_WINDOW_MS = 23 * 60 * 60_000;
|
|
9
|
+
export const FEEDBACK_OPERATIONS_SWEEP_INTERVAL_MS = 30_000;
|
|
10
|
+
export class FeedbackOperationsWorker {
|
|
11
|
+
#options;
|
|
12
|
+
#logger;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.#options = options;
|
|
15
|
+
this.#logger = options.logger ?? noopLogger;
|
|
16
|
+
}
|
|
17
|
+
async runOnce(input = {}) {
|
|
18
|
+
const kinds = input.kinds ?? ['start-work', 'email', 'slack'];
|
|
19
|
+
for (const kind of kinds) {
|
|
20
|
+
if (kind === 'slack')
|
|
21
|
+
await this.#deliverSlack();
|
|
22
|
+
if (kind === 'email')
|
|
23
|
+
await this.#deliverEmail();
|
|
24
|
+
if (kind === 'start-work')
|
|
25
|
+
await this.#startWork();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async #deliverSlack() {
|
|
29
|
+
const now = this.#now();
|
|
30
|
+
const record = await this.#options.store.claimSlackDelivery({
|
|
31
|
+
now,
|
|
32
|
+
leaseMs: FEEDBACK_OPERATIONS_LEASE_MS,
|
|
33
|
+
});
|
|
34
|
+
if (record === undefined)
|
|
35
|
+
return;
|
|
36
|
+
const linearIssueId = record.linearIssueId;
|
|
37
|
+
const linearStatus = record.linearStatus;
|
|
38
|
+
if (linearIssueId === undefined || linearStatus === undefined) {
|
|
39
|
+
await this.#options.store.markSlackSuppressed({
|
|
40
|
+
recordId: record.id,
|
|
41
|
+
suppressedAt: now.toISOString(),
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const result = await withProviderTimeout((signal) => this.#options.slack.publishCard({
|
|
47
|
+
channelId: this.#options.slackChannelId,
|
|
48
|
+
messageTs: record.slackMessageTs,
|
|
49
|
+
clientMessageId: slackClientMessageId(record.id),
|
|
50
|
+
card: renderSlackFeedbackCard({
|
|
51
|
+
linearIssueId,
|
|
52
|
+
linearIdentifier: record.linearIdentifier ?? linearIssueId,
|
|
53
|
+
linearUrl: record.linearUrl ?? '',
|
|
54
|
+
title: record.title,
|
|
55
|
+
message: record.message,
|
|
56
|
+
status: linearStatus,
|
|
57
|
+
...(record.linearAssigneeName !== undefined
|
|
58
|
+
? { assigneeName: record.linearAssigneeName }
|
|
59
|
+
: {}),
|
|
60
|
+
labels: record.linearLabels,
|
|
61
|
+
trustedReporter: record.trustedReporter && record.reporterEmail !== undefined,
|
|
62
|
+
reporterNotification: notificationState(record),
|
|
63
|
+
}),
|
|
64
|
+
}, { signal }));
|
|
65
|
+
await this.#options.store.markSlackDelivered({
|
|
66
|
+
recordId: record.id,
|
|
67
|
+
channelId: this.#options.slackChannelId,
|
|
68
|
+
messageTs: result.messageTs,
|
|
69
|
+
deliveredAt: now.toISOString(),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
this.#logDeliveryFailure('slack', record.id, record.slackAttemptCount, error);
|
|
74
|
+
await this.#options.store.markSlackFailed({
|
|
75
|
+
recordId: record.id,
|
|
76
|
+
nextAttemptAt: retryAt(now, record.slackAttemptCount),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async #deliverEmail() {
|
|
81
|
+
const now = this.#now();
|
|
82
|
+
const record = await this.#options.store.claimReporterEmail({
|
|
83
|
+
now,
|
|
84
|
+
leaseMs: FEEDBACK_OPERATIONS_LEASE_MS,
|
|
85
|
+
});
|
|
86
|
+
if (record === undefined)
|
|
87
|
+
return;
|
|
88
|
+
const githubIssueNumber = record.githubIssueNumber;
|
|
89
|
+
const reporterEmail = record.reporterEmail;
|
|
90
|
+
if (githubIssueNumber === undefined || reporterEmail === undefined) {
|
|
91
|
+
await this.#options.store.markReporterEmailSuppressed({
|
|
92
|
+
recordId: record.id,
|
|
93
|
+
suppressedAt: now.toISOString(),
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const parsedFirstAttemptAt = Date.parse(record.emailFirstAttemptAt ?? now.toISOString());
|
|
98
|
+
const retryDeadline = Number.isFinite(parsedFirstAttemptAt)
|
|
99
|
+
? parsedFirstAttemptAt + EMAIL_RETRY_WINDOW_MS
|
|
100
|
+
: now.getTime();
|
|
101
|
+
if (record.emailAttemptCount > 1 && now.getTime() >= retryDeadline) {
|
|
102
|
+
await this.#options.store.markReporterEmailUnknown({
|
|
103
|
+
recordId: record.id,
|
|
104
|
+
unknownAt: now.toISOString(),
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const result = await withProviderTimeout((signal) => this.#options.email.send({
|
|
110
|
+
githubIssueNumber,
|
|
111
|
+
recipient: reporterEmail,
|
|
112
|
+
title: record.title,
|
|
113
|
+
message: record.message,
|
|
114
|
+
}, { signal }));
|
|
115
|
+
await this.#options.store.markReporterEmailSent({
|
|
116
|
+
recordId: record.id,
|
|
117
|
+
providerMessageId: result.providerMessageId,
|
|
118
|
+
sentAt: now.toISOString(),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
this.#logDeliveryFailure('email', record.id, record.emailAttemptCount, error);
|
|
123
|
+
const nextAttemptAt = retryAt(now, record.emailAttemptCount);
|
|
124
|
+
if (nextAttemptAt.getTime() >= retryDeadline) {
|
|
125
|
+
await this.#options.store.markReporterEmailUnknown({
|
|
126
|
+
recordId: record.id,
|
|
127
|
+
unknownAt: now.toISOString(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
await this.#options.store.markReporterEmailFailed({
|
|
132
|
+
recordId: record.id,
|
|
133
|
+
nextAttemptAt,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async #startWork() {
|
|
139
|
+
const now = this.#now();
|
|
140
|
+
const command = await this.#options.store.claimStartWork({
|
|
141
|
+
now,
|
|
142
|
+
leaseMs: FEEDBACK_OPERATIONS_LEASE_MS,
|
|
143
|
+
});
|
|
144
|
+
if (command === undefined)
|
|
145
|
+
return;
|
|
146
|
+
try {
|
|
147
|
+
const issue = await withProviderTimeout((signal) => this.#options.linear.getIssue(command.linearIssueId, { signal }));
|
|
148
|
+
if (issue === undefined)
|
|
149
|
+
throw new Error('Linear issue is unavailable');
|
|
150
|
+
if (issue.status.id !== this.#options.inProgressStatusId) {
|
|
151
|
+
await withProviderTimeout((signal) => this.#options.linear.moveIssueToState(command.linearIssueId, this.#options.inProgressStatusId, { signal }));
|
|
152
|
+
}
|
|
153
|
+
await this.#options.store.markStartWorkCompleted({
|
|
154
|
+
actionKey: command.actionKey,
|
|
155
|
+
completedAt: now.toISOString(),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
this.#logDeliveryFailure('start-work', command.linearIssueId, command.attemptCount, error);
|
|
160
|
+
await this.#notifyStartWorkFailure(command);
|
|
161
|
+
await this.#options.store.markStartWorkFailed({
|
|
162
|
+
actionKey: command.actionKey,
|
|
163
|
+
nextAttemptAt: retryAt(now, command.attemptCount),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async #notifyStartWorkFailure(command) {
|
|
168
|
+
if (command.attemptCount !== 1)
|
|
169
|
+
return;
|
|
170
|
+
try {
|
|
171
|
+
await withProviderTimeout((signal) => this.#options.slack.sendPrivateResponse({
|
|
172
|
+
responseUrl: command.responseUrl,
|
|
173
|
+
text: 'Noodle Seed could not update Linear yet. The request is queued for retry.',
|
|
174
|
+
}, { signal }));
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
this.#logDeliveryFailure('start-work-private-response', command.linearIssueId, command.attemptCount, error);
|
|
178
|
+
// The durable Linear command remains retryable even if Slack's ephemeral response expires.
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
#logDeliveryFailure(kind, recordId, attemptCount, error) {
|
|
182
|
+
this.#logger.warn('feedback.delivery.failed', {
|
|
183
|
+
recordId,
|
|
184
|
+
kind,
|
|
185
|
+
attemptCount,
|
|
186
|
+
errorName: error instanceof Error ? error.name : typeof error,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
#now() {
|
|
190
|
+
return this.#options.clock?.() ?? new Date();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export function startFeedbackOperationsWorker(options) {
|
|
194
|
+
const worker = new FeedbackOperationsWorker(options);
|
|
195
|
+
let running = false;
|
|
196
|
+
const sweep = () => {
|
|
197
|
+
if (running)
|
|
198
|
+
return;
|
|
199
|
+
running = true;
|
|
200
|
+
void worker
|
|
201
|
+
.runOnce()
|
|
202
|
+
.catch(() => undefined)
|
|
203
|
+
.finally(() => {
|
|
204
|
+
running = false;
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
sweep();
|
|
208
|
+
const timer = setInterval(sweep, FEEDBACK_OPERATIONS_SWEEP_INTERVAL_MS);
|
|
209
|
+
timer.unref?.();
|
|
210
|
+
return timer;
|
|
211
|
+
}
|
|
212
|
+
function notificationState(record) {
|
|
213
|
+
if (!record.trustedReporter ||
|
|
214
|
+
record.reporterEmail === undefined ||
|
|
215
|
+
record.emailSuppressedAt !== undefined)
|
|
216
|
+
return 'suppressed';
|
|
217
|
+
if (record.emailSentAt !== undefined)
|
|
218
|
+
return 'sent';
|
|
219
|
+
if (record.emailDeliveryUnknownAt !== undefined)
|
|
220
|
+
return 'unknown';
|
|
221
|
+
if (record.emailAttemptCount > 0)
|
|
222
|
+
return 'delayed';
|
|
223
|
+
if (record.acceptedAt !== undefined)
|
|
224
|
+
return 'queued';
|
|
225
|
+
return 'pending';
|
|
226
|
+
}
|
|
227
|
+
function slackClientMessageId(recordId) {
|
|
228
|
+
const value = createHash('sha256').update(`feedback-slack:${recordId}`).digest('hex');
|
|
229
|
+
return `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20, 32)}`;
|
|
230
|
+
}
|
|
231
|
+
function retryAt(now, attemptCount) {
|
|
232
|
+
const delay = Math.min(MAX_RETRY_MS, BASE_RETRY_MS * 2 ** Math.max(0, attemptCount - 1));
|
|
233
|
+
return new Date(now.getTime() + delay);
|
|
234
|
+
}
|
|
235
|
+
async function withProviderTimeout(operation) {
|
|
236
|
+
const controller = new AbortController();
|
|
237
|
+
let timer;
|
|
238
|
+
const timedOut = new Promise((_resolve, reject) => {
|
|
239
|
+
timer = setTimeout(() => {
|
|
240
|
+
const error = new Error('feedback provider call timed out');
|
|
241
|
+
controller.abort(error);
|
|
242
|
+
reject(error);
|
|
243
|
+
}, FEEDBACK_OPERATIONS_PROVIDER_TIMEOUT_MS);
|
|
244
|
+
timer.unref?.();
|
|
245
|
+
});
|
|
246
|
+
const running = Promise.resolve().then(() => operation(controller.signal));
|
|
247
|
+
running.catch(() => undefined);
|
|
248
|
+
try {
|
|
249
|
+
return await Promise.race([running, timedOut]);
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
if (timer !== undefined)
|
|
253
|
+
clearTimeout(timer);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=delivery-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-worker.js","sourceRoot":"","sources":["../../src/feedback-operations/delivery-worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { LinearWorkflowStateType } from './contracts.js';
|
|
2
|
+
export interface LinearFeedbackIssue {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly identifier: string;
|
|
5
|
+
readonly url: string;
|
|
6
|
+
readonly title: string;
|
|
7
|
+
readonly description: string;
|
|
8
|
+
readonly teamId: string;
|
|
9
|
+
readonly status: {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly type: LinearWorkflowStateType;
|
|
13
|
+
};
|
|
14
|
+
readonly assigneeName?: string;
|
|
15
|
+
readonly labels: readonly string[];
|
|
16
|
+
readonly attachments: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export interface LinearFeedbackIssueClient {
|
|
19
|
+
getIssue(id: string, options?: {
|
|
20
|
+
readonly signal?: AbortSignal;
|
|
21
|
+
}): Promise<LinearFeedbackIssue | undefined>;
|
|
22
|
+
moveIssueToState(issueId: string, stateId: string, options?: {
|
|
23
|
+
readonly signal?: AbortSignal;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare class LinearGraphqlFeedbackClient implements LinearFeedbackIssueClient {
|
|
27
|
+
#private;
|
|
28
|
+
constructor(options: {
|
|
29
|
+
apiToken: string;
|
|
30
|
+
fetch?: typeof fetch;
|
|
31
|
+
});
|
|
32
|
+
getIssue(id: string, options?: {
|
|
33
|
+
readonly signal?: AbortSignal;
|
|
34
|
+
}): Promise<LinearFeedbackIssue | undefined>;
|
|
35
|
+
moveIssueToState(issueId: string, stateId: string, options?: {
|
|
36
|
+
readonly signal?: AbortSignal;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=linear-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-client.d.ts","sourceRoot":"","sources":["../../src/feedback-operations/linear-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AA6B9D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;KACxC,CAAC;IACF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CACN,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC1C,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;IAC5C,gBAAgB,CACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC1C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,qBAAa,2BAA4B,YAAW,yBAAyB;;gBAI/D,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;KAAE;IAMzD,QAAQ,CACZ,EAAE,EAAE,MAAM,EACV,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC9C,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAiCrC,gBAAgB,CACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC9C,OAAO,CAAC,IAAI,CAAC;CAgDjB"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const LINEAR_GRAPHQL_ENDPOINT = 'https://api.linear.app/graphql';
|
|
3
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
4
|
+
const MAX_RESPONSE_BYTES = 512_000;
|
|
5
|
+
const LinearIssueResponseSchema = z.object({
|
|
6
|
+
data: z.object({
|
|
7
|
+
issue: z
|
|
8
|
+
.object({
|
|
9
|
+
id: z.string().min(1),
|
|
10
|
+
identifier: z.string().min(1),
|
|
11
|
+
url: z.url(),
|
|
12
|
+
title: z.string(),
|
|
13
|
+
description: z.string().nullable(),
|
|
14
|
+
team: z.object({ id: z.string().min(1) }),
|
|
15
|
+
state: z.object({
|
|
16
|
+
id: z.string().min(1),
|
|
17
|
+
name: z.string(),
|
|
18
|
+
type: z.enum(['backlog', 'unstarted', 'started', 'completed', 'canceled']),
|
|
19
|
+
}),
|
|
20
|
+
assignee: z.object({ name: z.string() }).nullable(),
|
|
21
|
+
labels: z.object({ nodes: z.array(z.object({ name: z.string() })) }),
|
|
22
|
+
attachments: z.object({ nodes: z.array(z.object({ url: z.url() })) }),
|
|
23
|
+
})
|
|
24
|
+
.nullable(),
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
export class LinearGraphqlFeedbackClient {
|
|
28
|
+
#apiToken;
|
|
29
|
+
#fetch;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
if (options.apiToken.trim() === '')
|
|
32
|
+
throw new Error('Linear API token is required');
|
|
33
|
+
this.#apiToken = options.apiToken;
|
|
34
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
35
|
+
}
|
|
36
|
+
async getIssue(id, options = {}) {
|
|
37
|
+
const data = await this.#request(`query FeedbackIssue($id: String!) {
|
|
38
|
+
issue(id: $id) {
|
|
39
|
+
id identifier url title description
|
|
40
|
+
team { id }
|
|
41
|
+
state { id name type }
|
|
42
|
+
assignee { name }
|
|
43
|
+
labels { nodes { name } }
|
|
44
|
+
attachments { nodes { url } }
|
|
45
|
+
}
|
|
46
|
+
}`, { id }, options);
|
|
47
|
+
const parsed = LinearIssueResponseSchema.safeParse(data);
|
|
48
|
+
if (!parsed.success)
|
|
49
|
+
throw new Error('Linear returned a malformed issue response');
|
|
50
|
+
const issue = parsed.data.data.issue;
|
|
51
|
+
if (issue === null)
|
|
52
|
+
return undefined;
|
|
53
|
+
return {
|
|
54
|
+
id: issue.id,
|
|
55
|
+
identifier: issue.identifier,
|
|
56
|
+
url: issue.url,
|
|
57
|
+
title: issue.title,
|
|
58
|
+
description: issue.description ?? '',
|
|
59
|
+
teamId: issue.team.id,
|
|
60
|
+
status: issue.state,
|
|
61
|
+
...(issue.assignee !== null ? { assigneeName: issue.assignee.name } : {}),
|
|
62
|
+
labels: issue.labels.nodes.map((label) => label.name),
|
|
63
|
+
attachments: issue.attachments.nodes.map((attachment) => attachment.url),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async moveIssueToState(issueId, stateId, options = {}) {
|
|
67
|
+
const response = await this.#request(`mutation StartFeedbackWork($issueId: String!, $stateId: String!) {
|
|
68
|
+
issueUpdate(id: $issueId, input: { stateId: $stateId }) { success }
|
|
69
|
+
}`, { issueId, stateId }, options);
|
|
70
|
+
const success = z
|
|
71
|
+
.object({ data: z.object({ issueUpdate: z.object({ success: z.literal(true) }) }) })
|
|
72
|
+
.safeParse(response);
|
|
73
|
+
if (!success.success)
|
|
74
|
+
throw new Error('Linear rejected the feedback state transition');
|
|
75
|
+
}
|
|
76
|
+
async #request(query, variables, options = {}) {
|
|
77
|
+
const response = await this.#fetch(LINEAR_GRAPHQL_ENDPOINT, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
redirect: 'error',
|
|
80
|
+
signal: boundedSignal(options.signal),
|
|
81
|
+
headers: {
|
|
82
|
+
authorization: this.#apiToken,
|
|
83
|
+
'content-type': 'application/json',
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({ query, variables }),
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok)
|
|
88
|
+
throw new Error(`Linear request failed with status ${response.status}`);
|
|
89
|
+
const length = Number(response.headers.get('content-length') ?? '0');
|
|
90
|
+
if (Number.isFinite(length) && length > MAX_RESPONSE_BYTES) {
|
|
91
|
+
throw new Error('Linear response exceeded the size limit');
|
|
92
|
+
}
|
|
93
|
+
const text = await readBoundedText(response, MAX_RESPONSE_BYTES);
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = JSON.parse(text);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new Error('Linear returned invalid JSON');
|
|
100
|
+
}
|
|
101
|
+
const errors = typeof parsed === 'object' && parsed !== null ? Reflect.get(parsed, 'errors') : undefined;
|
|
102
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
103
|
+
throw new Error('Linear GraphQL request failed');
|
|
104
|
+
}
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function boundedSignal(signal) {
|
|
109
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
110
|
+
return signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
|
|
111
|
+
}
|
|
112
|
+
async function readBoundedText(response, maxBytes) {
|
|
113
|
+
if (response.body === null)
|
|
114
|
+
return '';
|
|
115
|
+
const reader = response.body.getReader();
|
|
116
|
+
const chunks = [];
|
|
117
|
+
let total = 0;
|
|
118
|
+
try {
|
|
119
|
+
while (true) {
|
|
120
|
+
const { done, value } = await reader.read();
|
|
121
|
+
if (done)
|
|
122
|
+
break;
|
|
123
|
+
total += value.byteLength;
|
|
124
|
+
if (total > maxBytes) {
|
|
125
|
+
await reader.cancel().catch(() => undefined);
|
|
126
|
+
throw new Error('Linear response exceeded the size limit');
|
|
127
|
+
}
|
|
128
|
+
chunks.push(value);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
reader.releaseLock();
|
|
133
|
+
}
|
|
134
|
+
const body = new Uint8Array(total);
|
|
135
|
+
let offset = 0;
|
|
136
|
+
for (const chunk of chunks) {
|
|
137
|
+
body.set(chunk, offset);
|
|
138
|
+
offset += chunk.byteLength;
|
|
139
|
+
}
|
|
140
|
+
return new TextDecoder().decode(body);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=linear-client.js.map
|