@ubiquity-os/plugin-sdk 2.0.6 → 3.0.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/dist/index.d.mts CHANGED
@@ -1,17 +1,104 @@
1
1
  import * as hono_types from 'hono/types';
2
2
  import { EmitterWebhookEventName, EmitterWebhookEvent } from '@octokit/webhooks';
3
3
  import { Hono } from 'hono';
4
- import { Logs, LogLevel, LogReturn } from '@ubiquity-os/ubiquity-os-logger';
4
+ import * as _ubiquity_os_ubiquity_os_logger from '@ubiquity-os/ubiquity-os-logger';
5
+ import { LogReturn, Metadata, Logs, LogLevel } from '@ubiquity-os/ubiquity-os-logger';
6
+ import { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods';
5
7
  import { customOctokit } from './octokit.mjs';
6
8
  import { Manifest } from './manifest.mjs';
7
9
  import { TAnySchema } from '@sinclair/typebox';
8
- import { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods';
9
10
  import '@octokit/core/dist-types/types';
10
11
  import '@octokit/plugin-paginate-graphql';
11
12
  import '@octokit/plugin-paginate-rest';
12
13
  import '@octokit/request-error';
13
14
  import '@octokit/core';
14
15
 
16
+ interface CommentOptions {
17
+ raw?: boolean;
18
+ updateComment?: boolean;
19
+ }
20
+ type PostedGithubComment = RestEndpointMethodTypes["issues"]["updateComment"]["response"]["data"] | RestEndpointMethodTypes["issues"]["createComment"]["response"]["data"] | RestEndpointMethodTypes["pulls"]["createReplyForReviewComment"]["response"]["data"];
21
+ type WithIssueNumber<T> = T & {
22
+ issueNumber: number;
23
+ };
24
+ interface IssueContext {
25
+ issueNumber: number;
26
+ commentId?: number;
27
+ owner: string;
28
+ repo: string;
29
+ }
30
+ declare class CommentHandler {
31
+ static readonly HEADER_NAME = "UbiquityOS";
32
+ private _lastCommentId;
33
+ _updateIssueComment(context: Context, params: {
34
+ owner: string;
35
+ repo: string;
36
+ body: string;
37
+ issueNumber: number;
38
+ }): Promise<WithIssueNumber<PostedGithubComment>>;
39
+ _updateReviewComment(context: Context, params: {
40
+ owner: string;
41
+ repo: string;
42
+ body: string;
43
+ issueNumber: number;
44
+ }): Promise<WithIssueNumber<PostedGithubComment>>;
45
+ _createNewComment(context: Context, params: {
46
+ owner: string;
47
+ repo: string;
48
+ body: string;
49
+ issueNumber: number;
50
+ commentId?: number;
51
+ }): Promise<WithIssueNumber<PostedGithubComment>>;
52
+ _getIssueNumber(context: Context): number | undefined;
53
+ _getCommentId(context: Context): number | undefined;
54
+ _extractIssueContext(context: Context): IssueContext | null;
55
+ _processMessage(context: Context, message: LogReturn | Error): Promise<{
56
+ metadata: {
57
+ message: string;
58
+ name: string;
59
+ stack: string | undefined;
60
+ };
61
+ logMessage: {
62
+ raw: string;
63
+ diff: string;
64
+ level: _ubiquity_os_ubiquity_os_logger.LogLevel;
65
+ type: _ubiquity_os_ubiquity_os_logger.LogLevelWithOk;
66
+ };
67
+ } | {
68
+ metadata: {
69
+ message: string | undefined;
70
+ stack: string | string[] | undefined;
71
+ caller: {} | undefined;
72
+ error?: Error | {
73
+ stack: string;
74
+ } | undefined;
75
+ name?: string | undefined;
76
+ } | {
77
+ logMessage: {
78
+ raw: string;
79
+ diff: string;
80
+ level: _ubiquity_os_ubiquity_os_logger.LogLevel;
81
+ type: _ubiquity_os_ubiquity_os_logger.LogLevelWithOk;
82
+ };
83
+ metadata?: Metadata;
84
+ };
85
+ logMessage: {
86
+ raw: string;
87
+ diff: string;
88
+ level: _ubiquity_os_ubiquity_os_logger.LogLevel;
89
+ type: _ubiquity_os_ubiquity_os_logger.LogLevelWithOk;
90
+ };
91
+ }>;
92
+ _getInstigatorName(context: Context): string;
93
+ _createMetadataContent(context: Context, metadata: Metadata): Promise<{
94
+ header: string;
95
+ jsonPretty: string;
96
+ }>;
97
+ _formatMetadataContent(logMessage: LogReturn["logMessage"], header: string, jsonPretty: string): string;
98
+ _createCommentBody(context: Context, message: LogReturn | Error, options: CommentOptions): Promise<string>;
99
+ postComment(context: Context, message: LogReturn | Error, options?: CommentOptions): Promise<WithIssueNumber<PostedGithubComment> | null>;
100
+ }
101
+
15
102
  interface Context<TConfig = unknown, TEnv = unknown, TCommand = unknown, TSupportedEvents extends EmitterWebhookEventName = EmitterWebhookEventName> {
16
103
  eventName: TSupportedEvents;
17
104
  payload: {
@@ -22,6 +109,7 @@ interface Context<TConfig = unknown, TEnv = unknown, TCommand = unknown, TSuppor
22
109
  config: TConfig;
23
110
  env: TEnv;
24
111
  logger: Logs;
112
+ commentHandler: CommentHandler;
25
113
  }
26
114
 
27
115
  type Return = Record<string, unknown> | undefined | void;
@@ -44,20 +132,4 @@ declare function createPlugin<TConfig = unknown, TEnv = unknown, TCommand = unkn
44
132
 
45
133
  declare function createActionsPlugin<TConfig = unknown, TEnv = unknown, TCommand = unknown, TSupportedEvents extends EmitterWebhookEventName = EmitterWebhookEventName>(handler: (context: Context<TConfig, TEnv, TCommand, TSupportedEvents>) => HandlerReturn, options?: Options): Promise<void>;
46
134
 
47
- interface CommentOptions {
48
- raw?: boolean;
49
- updateComment?: boolean;
50
- }
51
- type WithIssueNumber<T> = T & {
52
- issueNumber: number;
53
- };
54
- type PostComment = {
55
- (context: Context, message: LogReturn | Error, options?: CommentOptions): Promise<WithIssueNumber<RestEndpointMethodTypes["issues"]["updateComment"]["response"]["data"] | RestEndpointMethodTypes["issues"]["createComment"]["response"]["data"]> | null>;
56
- lastCommentId?: number;
57
- };
58
- /**
59
- * Posts a comment on a GitHub issue if the issue exists in the context payload, embedding structured metadata to it.
60
- */
61
- declare const postComment: PostComment;
62
-
63
- export { type Context, createActionsPlugin, createPlugin, postComment };
135
+ export { CommentHandler, type Context, createActionsPlugin, createPlugin };
package/dist/index.d.ts CHANGED
@@ -1,17 +1,104 @@
1
1
  import * as hono_types from 'hono/types';
2
2
  import { EmitterWebhookEventName, EmitterWebhookEvent } from '@octokit/webhooks';
3
3
  import { Hono } from 'hono';
4
- import { Logs, LogLevel, LogReturn } from '@ubiquity-os/ubiquity-os-logger';
4
+ import * as _ubiquity_os_ubiquity_os_logger from '@ubiquity-os/ubiquity-os-logger';
5
+ import { LogReturn, Metadata, Logs, LogLevel } from '@ubiquity-os/ubiquity-os-logger';
6
+ import { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods';
5
7
  import { customOctokit } from './octokit.js';
6
8
  import { Manifest } from './manifest.js';
7
9
  import { TAnySchema } from '@sinclair/typebox';
8
- import { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods';
9
10
  import '@octokit/core/dist-types/types';
10
11
  import '@octokit/plugin-paginate-graphql';
11
12
  import '@octokit/plugin-paginate-rest';
12
13
  import '@octokit/request-error';
13
14
  import '@octokit/core';
14
15
 
16
+ interface CommentOptions {
17
+ raw?: boolean;
18
+ updateComment?: boolean;
19
+ }
20
+ type PostedGithubComment = RestEndpointMethodTypes["issues"]["updateComment"]["response"]["data"] | RestEndpointMethodTypes["issues"]["createComment"]["response"]["data"] | RestEndpointMethodTypes["pulls"]["createReplyForReviewComment"]["response"]["data"];
21
+ type WithIssueNumber<T> = T & {
22
+ issueNumber: number;
23
+ };
24
+ interface IssueContext {
25
+ issueNumber: number;
26
+ commentId?: number;
27
+ owner: string;
28
+ repo: string;
29
+ }
30
+ declare class CommentHandler {
31
+ static readonly HEADER_NAME = "UbiquityOS";
32
+ private _lastCommentId;
33
+ _updateIssueComment(context: Context, params: {
34
+ owner: string;
35
+ repo: string;
36
+ body: string;
37
+ issueNumber: number;
38
+ }): Promise<WithIssueNumber<PostedGithubComment>>;
39
+ _updateReviewComment(context: Context, params: {
40
+ owner: string;
41
+ repo: string;
42
+ body: string;
43
+ issueNumber: number;
44
+ }): Promise<WithIssueNumber<PostedGithubComment>>;
45
+ _createNewComment(context: Context, params: {
46
+ owner: string;
47
+ repo: string;
48
+ body: string;
49
+ issueNumber: number;
50
+ commentId?: number;
51
+ }): Promise<WithIssueNumber<PostedGithubComment>>;
52
+ _getIssueNumber(context: Context): number | undefined;
53
+ _getCommentId(context: Context): number | undefined;
54
+ _extractIssueContext(context: Context): IssueContext | null;
55
+ _processMessage(context: Context, message: LogReturn | Error): Promise<{
56
+ metadata: {
57
+ message: string;
58
+ name: string;
59
+ stack: string | undefined;
60
+ };
61
+ logMessage: {
62
+ raw: string;
63
+ diff: string;
64
+ level: _ubiquity_os_ubiquity_os_logger.LogLevel;
65
+ type: _ubiquity_os_ubiquity_os_logger.LogLevelWithOk;
66
+ };
67
+ } | {
68
+ metadata: {
69
+ message: string | undefined;
70
+ stack: string | string[] | undefined;
71
+ caller: {} | undefined;
72
+ error?: Error | {
73
+ stack: string;
74
+ } | undefined;
75
+ name?: string | undefined;
76
+ } | {
77
+ logMessage: {
78
+ raw: string;
79
+ diff: string;
80
+ level: _ubiquity_os_ubiquity_os_logger.LogLevel;
81
+ type: _ubiquity_os_ubiquity_os_logger.LogLevelWithOk;
82
+ };
83
+ metadata?: Metadata;
84
+ };
85
+ logMessage: {
86
+ raw: string;
87
+ diff: string;
88
+ level: _ubiquity_os_ubiquity_os_logger.LogLevel;
89
+ type: _ubiquity_os_ubiquity_os_logger.LogLevelWithOk;
90
+ };
91
+ }>;
92
+ _getInstigatorName(context: Context): string;
93
+ _createMetadataContent(context: Context, metadata: Metadata): Promise<{
94
+ header: string;
95
+ jsonPretty: string;
96
+ }>;
97
+ _formatMetadataContent(logMessage: LogReturn["logMessage"], header: string, jsonPretty: string): string;
98
+ _createCommentBody(context: Context, message: LogReturn | Error, options: CommentOptions): Promise<string>;
99
+ postComment(context: Context, message: LogReturn | Error, options?: CommentOptions): Promise<WithIssueNumber<PostedGithubComment> | null>;
100
+ }
101
+
15
102
  interface Context<TConfig = unknown, TEnv = unknown, TCommand = unknown, TSupportedEvents extends EmitterWebhookEventName = EmitterWebhookEventName> {
16
103
  eventName: TSupportedEvents;
17
104
  payload: {
@@ -22,6 +109,7 @@ interface Context<TConfig = unknown, TEnv = unknown, TCommand = unknown, TSuppor
22
109
  config: TConfig;
23
110
  env: TEnv;
24
111
  logger: Logs;
112
+ commentHandler: CommentHandler;
25
113
  }
26
114
 
27
115
  type Return = Record<string, unknown> | undefined | void;
@@ -44,20 +132,4 @@ declare function createPlugin<TConfig = unknown, TEnv = unknown, TCommand = unkn
44
132
 
45
133
  declare function createActionsPlugin<TConfig = unknown, TEnv = unknown, TCommand = unknown, TSupportedEvents extends EmitterWebhookEventName = EmitterWebhookEventName>(handler: (context: Context<TConfig, TEnv, TCommand, TSupportedEvents>) => HandlerReturn, options?: Options): Promise<void>;
46
134
 
47
- interface CommentOptions {
48
- raw?: boolean;
49
- updateComment?: boolean;
50
- }
51
- type WithIssueNumber<T> = T & {
52
- issueNumber: number;
53
- };
54
- type PostComment = {
55
- (context: Context, message: LogReturn | Error, options?: CommentOptions): Promise<WithIssueNumber<RestEndpointMethodTypes["issues"]["updateComment"]["response"]["data"] | RestEndpointMethodTypes["issues"]["createComment"]["response"]["data"]> | null>;
56
- lastCommentId?: number;
57
- };
58
- /**
59
- * Posts a comment on a GitHub issue if the issue exists in the context payload, embedding structured metadata to it.
60
- */
61
- declare const postComment: PostComment;
62
-
63
- export { type Context, createActionsPlugin, createPlugin, postComment };
135
+ export { CommentHandler, type Context, createActionsPlugin, createPlugin };
package/dist/index.js CHANGED
@@ -30,9 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ CommentHandler: () => CommentHandler,
33
34
  createActionsPlugin: () => createActionsPlugin,
34
- createPlugin: () => createPlugin,
35
- postComment: () => postComment
35
+ createPlugin: () => createPlugin
36
36
  });
37
37
  module.exports = __toCommonJS(src_exports);
38
38
 
@@ -116,90 +116,144 @@ function getPluginOptions(options) {
116
116
  }
117
117
 
118
118
  // src/comment.ts
119
- var HEADER_NAME = "UbiquityOS";
120
- var postComment = async function(context2, message, options = { updateComment: true, raw: false }) {
121
- let issueNumber;
122
- if ("issue" in context2.payload) {
123
- issueNumber = context2.payload.issue.number;
124
- } else if ("pull_request" in context2.payload) {
125
- issueNumber = context2.payload.pull_request.number;
126
- } else if ("discussion" in context2.payload) {
127
- issueNumber = context2.payload.discussion.number;
128
- } else {
129
- context2.logger.info("Cannot post comment because issue is not found in the payload.");
130
- return null;
119
+ var CommentHandler = class _CommentHandler {
120
+ static HEADER_NAME = "UbiquityOS";
121
+ _lastCommentId = { reviewCommentId: null, issueCommentId: null };
122
+ async _updateIssueComment(context2, params) {
123
+ if (!this._lastCommentId.issueCommentId) {
124
+ throw context2.logger.error("issueCommentId is missing");
125
+ }
126
+ const commentData = await context2.octokit.rest.issues.updateComment({
127
+ owner: params.owner,
128
+ repo: params.repo,
129
+ comment_id: this._lastCommentId.issueCommentId,
130
+ body: params.body
131
+ });
132
+ return { ...commentData.data, issueNumber: params.issueNumber };
131
133
  }
132
- if ("repository" in context2.payload && context2.payload.repository?.owner?.login) {
133
- const body = await createStructuredMetadataWithMessage(context2, message, options);
134
- if (options.updateComment && postComment.lastCommentId) {
135
- const commentData = await context2.octokit.rest.issues.updateComment({
136
- owner: context2.payload.repository.owner.login,
137
- repo: context2.payload.repository.name,
138
- comment_id: postComment.lastCommentId,
139
- body
140
- });
141
- return { ...commentData.data, issueNumber };
142
- } else {
143
- const commentData = await context2.octokit.rest.issues.createComment({
144
- owner: context2.payload.repository.owner.login,
145
- repo: context2.payload.repository.name,
146
- issue_number: issueNumber,
147
- body
134
+ async _updateReviewComment(context2, params) {
135
+ if (!this._lastCommentId.reviewCommentId) {
136
+ throw context2.logger.error("reviewCommentId is missing");
137
+ }
138
+ const commentData = await context2.octokit.rest.pulls.updateReviewComment({
139
+ owner: params.owner,
140
+ repo: params.repo,
141
+ comment_id: this._lastCommentId.reviewCommentId,
142
+ body: params.body
143
+ });
144
+ return { ...commentData.data, issueNumber: params.issueNumber };
145
+ }
146
+ async _createNewComment(context2, params) {
147
+ if (params.commentId) {
148
+ const commentData2 = await context2.octokit.rest.pulls.createReplyForReviewComment({
149
+ owner: params.owner,
150
+ repo: params.repo,
151
+ pull_number: params.issueNumber,
152
+ comment_id: params.commentId,
153
+ body: params.body
148
154
  });
149
- postComment.lastCommentId = commentData.data.id;
150
- return { ...commentData.data, issueNumber };
155
+ this._lastCommentId.reviewCommentId = commentData2.data.id;
156
+ return { ...commentData2.data, issueNumber: params.issueNumber };
151
157
  }
152
- } else {
153
- context2.logger.info("Cannot post comment because repository is not found in the payload.", { payload: context2.payload });
158
+ const commentData = await context2.octokit.rest.issues.createComment({
159
+ owner: params.owner,
160
+ repo: params.repo,
161
+ issue_number: params.issueNumber,
162
+ body: params.body
163
+ });
164
+ this._lastCommentId.issueCommentId = commentData.data.id;
165
+ return { ...commentData.data, issueNumber: params.issueNumber };
154
166
  }
155
- return null;
156
- };
157
- async function createStructuredMetadataWithMessage(context2, message, options) {
158
- let logMessage;
159
- let callingFnName;
160
- let instigatorName;
161
- let metadata;
162
- if (message instanceof Error) {
163
- metadata = {
164
- message: message.message,
165
- name: message.name,
166
- stack: message.stack
167
+ _getIssueNumber(context2) {
168
+ if ("issue" in context2.payload) return context2.payload.issue.number;
169
+ if ("pull_request" in context2.payload) return context2.payload.pull_request.number;
170
+ if ("discussion" in context2.payload) return context2.payload.discussion.number;
171
+ return void 0;
172
+ }
173
+ _getCommentId(context2) {
174
+ return "pull_request" in context2.payload && "comment" in context2.payload ? context2.payload.comment.id : void 0;
175
+ }
176
+ _extractIssueContext(context2) {
177
+ if (!("repository" in context2.payload) || !context2.payload.repository?.owner?.login) {
178
+ return null;
179
+ }
180
+ const issueNumber = this._getIssueNumber(context2);
181
+ if (!issueNumber) return null;
182
+ return {
183
+ issueNumber,
184
+ commentId: this._getCommentId(context2),
185
+ owner: context2.payload.repository.owner.login,
186
+ repo: context2.payload.repository.name
167
187
  };
168
- callingFnName = message.stack?.split("\n")[2]?.match(/at (\S+)/)?.[1] ?? "anonymous";
169
- logMessage = context2.logger.error(message.message).logMessage;
170
- } else if (message.metadata) {
171
- metadata = {
188
+ }
189
+ async _processMessage(context2, message) {
190
+ if (message instanceof Error) {
191
+ const metadata2 = {
192
+ message: message.message,
193
+ name: message.name,
194
+ stack: message.stack
195
+ };
196
+ return { metadata: metadata2, logMessage: context2.logger.error(message.message).logMessage };
197
+ }
198
+ const metadata = message.metadata ? {
199
+ ...message.metadata,
172
200
  message: message.metadata.message,
173
201
  stack: message.metadata.stack || message.metadata.error?.stack,
174
202
  caller: message.metadata.caller || message.metadata.error?.stack?.split("\n")[2]?.match(/at (\S+)/)?.[1]
175
- };
176
- logMessage = message.logMessage;
177
- callingFnName = metadata.caller;
178
- } else {
179
- metadata = { ...message };
203
+ } : { ...message };
204
+ return { metadata, logMessage: message.logMessage };
180
205
  }
181
- const jsonPretty = sanitizeMetadata(metadata);
182
- if ("installation" in context2.payload && context2.payload.installation && "account" in context2.payload.installation) {
183
- instigatorName = context2.payload.installation?.account?.name;
184
- } else {
185
- instigatorName = context2.payload.sender?.login || HEADER_NAME;
206
+ _getInstigatorName(context2) {
207
+ if ("installation" in context2.payload && context2.payload.installation && "account" in context2.payload.installation && context2.payload.installation?.account?.name) {
208
+ return context2.payload.installation?.account?.name;
209
+ }
210
+ return context2.payload.sender?.login || _CommentHandler.HEADER_NAME;
186
211
  }
187
- const runUrl = PluginRuntimeInfo.getInstance().runUrl;
188
- const version = await PluginRuntimeInfo.getInstance().version;
189
- const ubiquityMetadataHeader = `<!-- ${HEADER_NAME} - ${callingFnName} - ${version} - @${instigatorName} - ${runUrl}`;
190
- let metadataSerialized;
191
- const metadataSerializedVisible = ["```json", jsonPretty, "```"].join("\n");
192
- const metadataSerializedHidden = [ubiquityMetadataHeader, jsonPretty, "-->"].join("\n");
193
- if (logMessage?.type === "fatal") {
194
- metadataSerialized = [metadataSerializedVisible, metadataSerializedHidden].join("\n");
195
- } else {
196
- metadataSerialized = metadataSerializedHidden;
212
+ async _createMetadataContent(context2, metadata) {
213
+ const jsonPretty = sanitizeMetadata(metadata);
214
+ const instigatorName = this._getInstigatorName(context2);
215
+ const runUrl = PluginRuntimeInfo.getInstance().runUrl;
216
+ const version = await PluginRuntimeInfo.getInstance().version;
217
+ const callingFnName = metadata.caller || "anonymous";
218
+ return {
219
+ header: `<!-- ${_CommentHandler.HEADER_NAME} - ${callingFnName} - ${version} - @${instigatorName} - ${runUrl}`,
220
+ jsonPretty
221
+ };
222
+ }
223
+ _formatMetadataContent(logMessage, header, jsonPretty) {
224
+ const metadataVisible = ["```json", jsonPretty, "```"].join("\n");
225
+ const metadataHidden = [header, jsonPretty, "-->"].join("\n");
226
+ return logMessage?.type === "fatal" ? [metadataVisible, metadataHidden].join("\n") : metadataHidden;
197
227
  }
198
- return `${options.raw ? logMessage?.raw : logMessage?.diff}
228
+ async _createCommentBody(context2, message, options) {
229
+ const { metadata, logMessage } = await this._processMessage(context2, message);
230
+ const { header, jsonPretty } = await this._createMetadataContent(context2, metadata);
231
+ const metadataContent = this._formatMetadataContent(logMessage, header, jsonPretty);
232
+ return `${options.raw ? logMessage?.raw : logMessage?.diff}
199
233
 
200
- ${metadataSerialized}
234
+ ${metadataContent}
201
235
  `;
202
- }
236
+ }
237
+ async postComment(context2, message, options = { updateComment: true, raw: false }) {
238
+ const issueContext = this._extractIssueContext(context2);
239
+ if (!issueContext) {
240
+ context2.logger.info("Cannot post comment: missing issue context in payload");
241
+ return null;
242
+ }
243
+ const body = await this._createCommentBody(context2, message, options);
244
+ const { issueNumber, commentId, owner, repo } = issueContext;
245
+ const params = { owner, repo, body, issueNumber };
246
+ if (options.updateComment) {
247
+ if (this._lastCommentId.issueCommentId && !("pull_request" in context2.payload && "comment" in context2.payload)) {
248
+ return this._updateIssueComment(context2, params);
249
+ }
250
+ if (this._lastCommentId.reviewCommentId && "pull_request" in context2.payload && "comment" in context2.payload) {
251
+ return this._updateReviewComment(context2, params);
252
+ }
253
+ }
254
+ return this._createNewComment(context2, { ...params, commentId });
255
+ }
256
+ };
203
257
 
204
258
  // src/octokit.ts
205
259
  var import_core = require("@octokit/core");
@@ -297,7 +351,7 @@ function createPlugin(handler, manifest, options) {
297
351
  app.get("/manifest.json", (ctx) => {
298
352
  return ctx.json(manifest);
299
353
  });
300
- app.post("/", async (ctx) => {
354
+ app.post("/", async function appPost(ctx) {
301
355
  if (ctx.req.header("content-type") !== "application/json") {
302
356
  throw new import_http_exception.HTTPException(400, { message: "Content-Type must be application/json" });
303
357
  }
@@ -355,7 +409,8 @@ function createPlugin(handler, manifest, options) {
355
409
  octokit: new customOctokit({ auth: inputs.authToken }),
356
410
  config: config2,
357
411
  env,
358
- logger: new import_ubiquity_os_logger2.Logs(pluginOptions.logLevel)
412
+ logger: new import_ubiquity_os_logger2.Logs(pluginOptions.logLevel),
413
+ commentHandler: new CommentHandler()
359
414
  };
360
415
  try {
361
416
  const result = await handler(context2);
@@ -369,7 +424,7 @@ function createPlugin(handler, manifest, options) {
369
424
  loggerError = context2.logger.error(`Error: ${error}`);
370
425
  }
371
426
  if (pluginOptions.postCommentOnError && loggerError) {
372
- await postComment(context2, loggerError);
427
+ await context2.commentHandler.postComment(context2, loggerError);
373
428
  }
374
429
  throw new import_http_exception.HTTPException(500, { message: "Unexpected error" });
375
430
  }
@@ -395,7 +450,7 @@ async function createActionsPlugin(handler, options) {
395
450
  const inputSchemaErrors = [...import_value3.Value.Errors(inputSchema, body)];
396
451
  if (inputSchemaErrors.length) {
397
452
  console.dir(inputSchemaErrors, { depth: null });
398
- core.setFailed(`Error: Invalid inputs payload: ${inputSchemaErrors.join(",")}`);
453
+ core.setFailed(`Error: Invalid inputs payload: ${inputSchemaErrors.map((o) => o.message).join(", ")}`);
399
454
  return;
400
455
  }
401
456
  const signature = body.signature;
@@ -410,6 +465,7 @@ async function createActionsPlugin(handler, options) {
410
465
  config2 = import_value3.Value.Decode(pluginOptions.settingsSchema, import_value3.Value.Default(pluginOptions.settingsSchema, inputs.settings));
411
466
  } catch (e) {
412
467
  console.dir(...import_value3.Value.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
468
+ core.setFailed(`Error: Invalid settings provided.`);
413
469
  throw e;
414
470
  }
415
471
  } else {
@@ -421,6 +477,7 @@ async function createActionsPlugin(handler, options) {
421
477
  env = import_value3.Value.Decode(pluginOptions.envSchema, import_value3.Value.Default(pluginOptions.envSchema, process.env));
422
478
  } catch (e) {
423
479
  console.dir(...import_value3.Value.Errors(pluginOptions.envSchema, process.env), { depth: null });
480
+ core.setFailed(`Error: Invalid environment provided.`);
424
481
  throw e;
425
482
  }
426
483
  } else {
@@ -444,7 +501,8 @@ async function createActionsPlugin(handler, options) {
444
501
  octokit: new customOctokit({ auth: inputs.authToken }),
445
502
  config: config2,
446
503
  env,
447
- logger: new import_ubiquity_os_logger3.Logs(pluginOptions.logLevel)
504
+ logger: new import_ubiquity_os_logger3.Logs(pluginOptions.logLevel),
505
+ commentHandler: new CommentHandler()
448
506
  };
449
507
  try {
450
508
  const result = await handler(context2);
@@ -464,7 +522,7 @@ async function createActionsPlugin(handler, options) {
464
522
  loggerError = context2.logger.error(`Error: ${error}`);
465
523
  }
466
524
  if (pluginOptions.postCommentOnError && loggerError) {
467
- await postComment(context2, loggerError);
525
+ await context2.commentHandler.postComment(context2, loggerError);
468
526
  }
469
527
  }
470
528
  }
@@ -482,7 +540,7 @@ async function returnDataToKernel(repoToken, stateId, output) {
482
540
  }
483
541
  // Annotate the CommonJS export names for ESM import in node:
484
542
  0 && (module.exports = {
543
+ CommentHandler,
485
544
  createActionsPlugin,
486
- createPlugin,
487
- postComment
545
+ createPlugin
488
546
  });
package/dist/index.mjs CHANGED
@@ -78,90 +78,144 @@ function getPluginOptions(options) {
78
78
  }
79
79
 
80
80
  // src/comment.ts
81
- var HEADER_NAME = "UbiquityOS";
82
- var postComment = async function(context2, message, options = { updateComment: true, raw: false }) {
83
- let issueNumber;
84
- if ("issue" in context2.payload) {
85
- issueNumber = context2.payload.issue.number;
86
- } else if ("pull_request" in context2.payload) {
87
- issueNumber = context2.payload.pull_request.number;
88
- } else if ("discussion" in context2.payload) {
89
- issueNumber = context2.payload.discussion.number;
90
- } else {
91
- context2.logger.info("Cannot post comment because issue is not found in the payload.");
92
- return null;
81
+ var CommentHandler = class _CommentHandler {
82
+ static HEADER_NAME = "UbiquityOS";
83
+ _lastCommentId = { reviewCommentId: null, issueCommentId: null };
84
+ async _updateIssueComment(context2, params) {
85
+ if (!this._lastCommentId.issueCommentId) {
86
+ throw context2.logger.error("issueCommentId is missing");
87
+ }
88
+ const commentData = await context2.octokit.rest.issues.updateComment({
89
+ owner: params.owner,
90
+ repo: params.repo,
91
+ comment_id: this._lastCommentId.issueCommentId,
92
+ body: params.body
93
+ });
94
+ return { ...commentData.data, issueNumber: params.issueNumber };
93
95
  }
94
- if ("repository" in context2.payload && context2.payload.repository?.owner?.login) {
95
- const body = await createStructuredMetadataWithMessage(context2, message, options);
96
- if (options.updateComment && postComment.lastCommentId) {
97
- const commentData = await context2.octokit.rest.issues.updateComment({
98
- owner: context2.payload.repository.owner.login,
99
- repo: context2.payload.repository.name,
100
- comment_id: postComment.lastCommentId,
101
- body
102
- });
103
- return { ...commentData.data, issueNumber };
104
- } else {
105
- const commentData = await context2.octokit.rest.issues.createComment({
106
- owner: context2.payload.repository.owner.login,
107
- repo: context2.payload.repository.name,
108
- issue_number: issueNumber,
109
- body
96
+ async _updateReviewComment(context2, params) {
97
+ if (!this._lastCommentId.reviewCommentId) {
98
+ throw context2.logger.error("reviewCommentId is missing");
99
+ }
100
+ const commentData = await context2.octokit.rest.pulls.updateReviewComment({
101
+ owner: params.owner,
102
+ repo: params.repo,
103
+ comment_id: this._lastCommentId.reviewCommentId,
104
+ body: params.body
105
+ });
106
+ return { ...commentData.data, issueNumber: params.issueNumber };
107
+ }
108
+ async _createNewComment(context2, params) {
109
+ if (params.commentId) {
110
+ const commentData2 = await context2.octokit.rest.pulls.createReplyForReviewComment({
111
+ owner: params.owner,
112
+ repo: params.repo,
113
+ pull_number: params.issueNumber,
114
+ comment_id: params.commentId,
115
+ body: params.body
110
116
  });
111
- postComment.lastCommentId = commentData.data.id;
112
- return { ...commentData.data, issueNumber };
117
+ this._lastCommentId.reviewCommentId = commentData2.data.id;
118
+ return { ...commentData2.data, issueNumber: params.issueNumber };
113
119
  }
114
- } else {
115
- context2.logger.info("Cannot post comment because repository is not found in the payload.", { payload: context2.payload });
120
+ const commentData = await context2.octokit.rest.issues.createComment({
121
+ owner: params.owner,
122
+ repo: params.repo,
123
+ issue_number: params.issueNumber,
124
+ body: params.body
125
+ });
126
+ this._lastCommentId.issueCommentId = commentData.data.id;
127
+ return { ...commentData.data, issueNumber: params.issueNumber };
116
128
  }
117
- return null;
118
- };
119
- async function createStructuredMetadataWithMessage(context2, message, options) {
120
- let logMessage;
121
- let callingFnName;
122
- let instigatorName;
123
- let metadata;
124
- if (message instanceof Error) {
125
- metadata = {
126
- message: message.message,
127
- name: message.name,
128
- stack: message.stack
129
+ _getIssueNumber(context2) {
130
+ if ("issue" in context2.payload) return context2.payload.issue.number;
131
+ if ("pull_request" in context2.payload) return context2.payload.pull_request.number;
132
+ if ("discussion" in context2.payload) return context2.payload.discussion.number;
133
+ return void 0;
134
+ }
135
+ _getCommentId(context2) {
136
+ return "pull_request" in context2.payload && "comment" in context2.payload ? context2.payload.comment.id : void 0;
137
+ }
138
+ _extractIssueContext(context2) {
139
+ if (!("repository" in context2.payload) || !context2.payload.repository?.owner?.login) {
140
+ return null;
141
+ }
142
+ const issueNumber = this._getIssueNumber(context2);
143
+ if (!issueNumber) return null;
144
+ return {
145
+ issueNumber,
146
+ commentId: this._getCommentId(context2),
147
+ owner: context2.payload.repository.owner.login,
148
+ repo: context2.payload.repository.name
129
149
  };
130
- callingFnName = message.stack?.split("\n")[2]?.match(/at (\S+)/)?.[1] ?? "anonymous";
131
- logMessage = context2.logger.error(message.message).logMessage;
132
- } else if (message.metadata) {
133
- metadata = {
150
+ }
151
+ async _processMessage(context2, message) {
152
+ if (message instanceof Error) {
153
+ const metadata2 = {
154
+ message: message.message,
155
+ name: message.name,
156
+ stack: message.stack
157
+ };
158
+ return { metadata: metadata2, logMessage: context2.logger.error(message.message).logMessage };
159
+ }
160
+ const metadata = message.metadata ? {
161
+ ...message.metadata,
134
162
  message: message.metadata.message,
135
163
  stack: message.metadata.stack || message.metadata.error?.stack,
136
164
  caller: message.metadata.caller || message.metadata.error?.stack?.split("\n")[2]?.match(/at (\S+)/)?.[1]
137
- };
138
- logMessage = message.logMessage;
139
- callingFnName = metadata.caller;
140
- } else {
141
- metadata = { ...message };
165
+ } : { ...message };
166
+ return { metadata, logMessage: message.logMessage };
142
167
  }
143
- const jsonPretty = sanitizeMetadata(metadata);
144
- if ("installation" in context2.payload && context2.payload.installation && "account" in context2.payload.installation) {
145
- instigatorName = context2.payload.installation?.account?.name;
146
- } else {
147
- instigatorName = context2.payload.sender?.login || HEADER_NAME;
168
+ _getInstigatorName(context2) {
169
+ if ("installation" in context2.payload && context2.payload.installation && "account" in context2.payload.installation && context2.payload.installation?.account?.name) {
170
+ return context2.payload.installation?.account?.name;
171
+ }
172
+ return context2.payload.sender?.login || _CommentHandler.HEADER_NAME;
148
173
  }
149
- const runUrl = PluginRuntimeInfo.getInstance().runUrl;
150
- const version = await PluginRuntimeInfo.getInstance().version;
151
- const ubiquityMetadataHeader = `<!-- ${HEADER_NAME} - ${callingFnName} - ${version} - @${instigatorName} - ${runUrl}`;
152
- let metadataSerialized;
153
- const metadataSerializedVisible = ["```json", jsonPretty, "```"].join("\n");
154
- const metadataSerializedHidden = [ubiquityMetadataHeader, jsonPretty, "-->"].join("\n");
155
- if (logMessage?.type === "fatal") {
156
- metadataSerialized = [metadataSerializedVisible, metadataSerializedHidden].join("\n");
157
- } else {
158
- metadataSerialized = metadataSerializedHidden;
174
+ async _createMetadataContent(context2, metadata) {
175
+ const jsonPretty = sanitizeMetadata(metadata);
176
+ const instigatorName = this._getInstigatorName(context2);
177
+ const runUrl = PluginRuntimeInfo.getInstance().runUrl;
178
+ const version = await PluginRuntimeInfo.getInstance().version;
179
+ const callingFnName = metadata.caller || "anonymous";
180
+ return {
181
+ header: `<!-- ${_CommentHandler.HEADER_NAME} - ${callingFnName} - ${version} - @${instigatorName} - ${runUrl}`,
182
+ jsonPretty
183
+ };
184
+ }
185
+ _formatMetadataContent(logMessage, header, jsonPretty) {
186
+ const metadataVisible = ["```json", jsonPretty, "```"].join("\n");
187
+ const metadataHidden = [header, jsonPretty, "-->"].join("\n");
188
+ return logMessage?.type === "fatal" ? [metadataVisible, metadataHidden].join("\n") : metadataHidden;
159
189
  }
160
- return `${options.raw ? logMessage?.raw : logMessage?.diff}
190
+ async _createCommentBody(context2, message, options) {
191
+ const { metadata, logMessage } = await this._processMessage(context2, message);
192
+ const { header, jsonPretty } = await this._createMetadataContent(context2, metadata);
193
+ const metadataContent = this._formatMetadataContent(logMessage, header, jsonPretty);
194
+ return `${options.raw ? logMessage?.raw : logMessage?.diff}
161
195
 
162
- ${metadataSerialized}
196
+ ${metadataContent}
163
197
  `;
164
- }
198
+ }
199
+ async postComment(context2, message, options = { updateComment: true, raw: false }) {
200
+ const issueContext = this._extractIssueContext(context2);
201
+ if (!issueContext) {
202
+ context2.logger.info("Cannot post comment: missing issue context in payload");
203
+ return null;
204
+ }
205
+ const body = await this._createCommentBody(context2, message, options);
206
+ const { issueNumber, commentId, owner, repo } = issueContext;
207
+ const params = { owner, repo, body, issueNumber };
208
+ if (options.updateComment) {
209
+ if (this._lastCommentId.issueCommentId && !("pull_request" in context2.payload && "comment" in context2.payload)) {
210
+ return this._updateIssueComment(context2, params);
211
+ }
212
+ if (this._lastCommentId.reviewCommentId && "pull_request" in context2.payload && "comment" in context2.payload) {
213
+ return this._updateReviewComment(context2, params);
214
+ }
215
+ }
216
+ return this._createNewComment(context2, { ...params, commentId });
217
+ }
218
+ };
165
219
 
166
220
  // src/octokit.ts
167
221
  import { Octokit } from "@octokit/core";
@@ -259,7 +313,7 @@ function createPlugin(handler, manifest, options) {
259
313
  app.get("/manifest.json", (ctx) => {
260
314
  return ctx.json(manifest);
261
315
  });
262
- app.post("/", async (ctx) => {
316
+ app.post("/", async function appPost(ctx) {
263
317
  if (ctx.req.header("content-type") !== "application/json") {
264
318
  throw new HTTPException(400, { message: "Content-Type must be application/json" });
265
319
  }
@@ -317,7 +371,8 @@ function createPlugin(handler, manifest, options) {
317
371
  octokit: new customOctokit({ auth: inputs.authToken }),
318
372
  config: config2,
319
373
  env,
320
- logger: new Logs(pluginOptions.logLevel)
374
+ logger: new Logs(pluginOptions.logLevel),
375
+ commentHandler: new CommentHandler()
321
376
  };
322
377
  try {
323
378
  const result = await handler(context2);
@@ -331,7 +386,7 @@ function createPlugin(handler, manifest, options) {
331
386
  loggerError = context2.logger.error(`Error: ${error}`);
332
387
  }
333
388
  if (pluginOptions.postCommentOnError && loggerError) {
334
- await postComment(context2, loggerError);
389
+ await context2.commentHandler.postComment(context2, loggerError);
335
390
  }
336
391
  throw new HTTPException(500, { message: "Unexpected error" });
337
392
  }
@@ -357,7 +412,7 @@ async function createActionsPlugin(handler, options) {
357
412
  const inputSchemaErrors = [...Value3.Errors(inputSchema, body)];
358
413
  if (inputSchemaErrors.length) {
359
414
  console.dir(inputSchemaErrors, { depth: null });
360
- core.setFailed(`Error: Invalid inputs payload: ${inputSchemaErrors.join(",")}`);
415
+ core.setFailed(`Error: Invalid inputs payload: ${inputSchemaErrors.map((o) => o.message).join(", ")}`);
361
416
  return;
362
417
  }
363
418
  const signature = body.signature;
@@ -372,6 +427,7 @@ async function createActionsPlugin(handler, options) {
372
427
  config2 = Value3.Decode(pluginOptions.settingsSchema, Value3.Default(pluginOptions.settingsSchema, inputs.settings));
373
428
  } catch (e) {
374
429
  console.dir(...Value3.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
430
+ core.setFailed(`Error: Invalid settings provided.`);
375
431
  throw e;
376
432
  }
377
433
  } else {
@@ -383,6 +439,7 @@ async function createActionsPlugin(handler, options) {
383
439
  env = Value3.Decode(pluginOptions.envSchema, Value3.Default(pluginOptions.envSchema, process.env));
384
440
  } catch (e) {
385
441
  console.dir(...Value3.Errors(pluginOptions.envSchema, process.env), { depth: null });
442
+ core.setFailed(`Error: Invalid environment provided.`);
386
443
  throw e;
387
444
  }
388
445
  } else {
@@ -406,7 +463,8 @@ async function createActionsPlugin(handler, options) {
406
463
  octokit: new customOctokit({ auth: inputs.authToken }),
407
464
  config: config2,
408
465
  env,
409
- logger: new Logs2(pluginOptions.logLevel)
466
+ logger: new Logs2(pluginOptions.logLevel),
467
+ commentHandler: new CommentHandler()
410
468
  };
411
469
  try {
412
470
  const result = await handler(context2);
@@ -426,7 +484,7 @@ async function createActionsPlugin(handler, options) {
426
484
  loggerError = context2.logger.error(`Error: ${error}`);
427
485
  }
428
486
  if (pluginOptions.postCommentOnError && loggerError) {
429
- await postComment(context2, loggerError);
487
+ await context2.commentHandler.postComment(context2, loggerError);
430
488
  }
431
489
  }
432
490
  }
@@ -443,7 +501,7 @@ async function returnDataToKernel(repoToken, stateId, output) {
443
501
  });
444
502
  }
445
503
  export {
504
+ CommentHandler,
446
505
  createActionsPlugin,
447
- createPlugin,
448
- postComment
506
+ createPlugin
449
507
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubiquity-os/plugin-sdk",
3
- "version": "2.0.6",
3
+ "version": "3.0.0",
4
4
  "description": "SDK for plugin support.",
5
5
  "author": "Ubiquity DAO",
6
6
  "license": "MIT",