@ubiquity-os/plugin-sdk 2.0.5 → 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,14 +351,14 @@ 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
  }
304
358
  const body = await ctx.req.json();
305
359
  const inputSchemaErrors = [...import_value2.Value.Errors(inputSchema, body)];
306
360
  if (inputSchemaErrors.length) {
307
- console.log(inputSchemaErrors, { depth: null });
361
+ console.dir(inputSchemaErrors, { depth: null });
308
362
  throw new import_http_exception.HTTPException(400, { message: "Invalid body" });
309
363
  }
310
364
  const signature = body.signature;
@@ -317,7 +371,7 @@ function createPlugin(handler, manifest, options) {
317
371
  try {
318
372
  config2 = import_value2.Value.Decode(pluginOptions.settingsSchema, import_value2.Value.Default(pluginOptions.settingsSchema, inputs.settings));
319
373
  } catch (e) {
320
- console.log(...import_value2.Value.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
374
+ console.dir(...import_value2.Value.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
321
375
  throw e;
322
376
  }
323
377
  } else {
@@ -329,7 +383,7 @@ function createPlugin(handler, manifest, options) {
329
383
  try {
330
384
  env = import_value2.Value.Decode(pluginOptions.envSchema, import_value2.Value.Default(pluginOptions.envSchema, honoEnvironment));
331
385
  } catch (e) {
332
- console.log(...import_value2.Value.Errors(pluginOptions.envSchema, honoEnvironment), { depth: null });
386
+ console.dir(...import_value2.Value.Errors(pluginOptions.envSchema, honoEnvironment), { depth: null });
333
387
  throw e;
334
388
  }
335
389
  } else {
@@ -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
  }
@@ -392,25 +447,25 @@ async function createActionsPlugin(handler, options) {
392
447
  return;
393
448
  }
394
449
  const body = github2.context.payload.inputs;
450
+ const inputSchemaErrors = [...import_value3.Value.Errors(inputSchema, body)];
451
+ if (inputSchemaErrors.length) {
452
+ console.dir(inputSchemaErrors, { depth: null });
453
+ core.setFailed(`Error: Invalid inputs payload: ${inputSchemaErrors.map((o) => o.message).join(", ")}`);
454
+ return;
455
+ }
395
456
  const signature = body.signature;
396
457
  if (!pluginOptions.bypassSignatureVerification && !await verifySignature(pluginOptions.kernelPublicKey, body, signature)) {
397
458
  core.setFailed(`Error: Invalid signature`);
398
459
  return;
399
460
  }
400
- const inputPayload = github2.context.payload.inputs;
401
- const inputSchemaErrors = [...import_value3.Value.Errors(inputSchema, inputPayload)];
402
- if (inputSchemaErrors.length) {
403
- console.dir(inputSchemaErrors, { depth: null });
404
- core.setFailed(`Error: Invalid inputs payload: ${inputSchemaErrors.join(",")}`);
405
- return;
406
- }
407
- const inputs = import_value3.Value.Decode(inputSchema, inputPayload);
461
+ const inputs = import_value3.Value.Decode(inputSchema, body);
408
462
  let config2;
409
463
  if (pluginOptions.settingsSchema) {
410
464
  try {
411
465
  config2 = import_value3.Value.Decode(pluginOptions.settingsSchema, import_value3.Value.Default(pluginOptions.settingsSchema, inputs.settings));
412
466
  } catch (e) {
413
467
  console.dir(...import_value3.Value.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
468
+ core.setFailed(`Error: Invalid settings provided.`);
414
469
  throw e;
415
470
  }
416
471
  } else {
@@ -422,6 +477,7 @@ async function createActionsPlugin(handler, options) {
422
477
  env = import_value3.Value.Decode(pluginOptions.envSchema, import_value3.Value.Default(pluginOptions.envSchema, process.env));
423
478
  } catch (e) {
424
479
  console.dir(...import_value3.Value.Errors(pluginOptions.envSchema, process.env), { depth: null });
480
+ core.setFailed(`Error: Invalid environment provided.`);
425
481
  throw e;
426
482
  }
427
483
  } else {
@@ -445,7 +501,8 @@ async function createActionsPlugin(handler, options) {
445
501
  octokit: new customOctokit({ auth: inputs.authToken }),
446
502
  config: config2,
447
503
  env,
448
- logger: new import_ubiquity_os_logger3.Logs(pluginOptions.logLevel)
504
+ logger: new import_ubiquity_os_logger3.Logs(pluginOptions.logLevel),
505
+ commentHandler: new CommentHandler()
449
506
  };
450
507
  try {
451
508
  const result = await handler(context2);
@@ -465,7 +522,7 @@ async function createActionsPlugin(handler, options) {
465
522
  loggerError = context2.logger.error(`Error: ${error}`);
466
523
  }
467
524
  if (pluginOptions.postCommentOnError && loggerError) {
468
- await postComment(context2, loggerError);
525
+ await context2.commentHandler.postComment(context2, loggerError);
469
526
  }
470
527
  }
471
528
  }
@@ -483,7 +540,7 @@ async function returnDataToKernel(repoToken, stateId, output) {
483
540
  }
484
541
  // Annotate the CommonJS export names for ESM import in node:
485
542
  0 && (module.exports = {
543
+ CommentHandler,
486
544
  createActionsPlugin,
487
- createPlugin,
488
- postComment
545
+ createPlugin
489
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,14 +313,14 @@ 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
  }
266
320
  const body = await ctx.req.json();
267
321
  const inputSchemaErrors = [...Value2.Errors(inputSchema, body)];
268
322
  if (inputSchemaErrors.length) {
269
- console.log(inputSchemaErrors, { depth: null });
323
+ console.dir(inputSchemaErrors, { depth: null });
270
324
  throw new HTTPException(400, { message: "Invalid body" });
271
325
  }
272
326
  const signature = body.signature;
@@ -279,7 +333,7 @@ function createPlugin(handler, manifest, options) {
279
333
  try {
280
334
  config2 = Value2.Decode(pluginOptions.settingsSchema, Value2.Default(pluginOptions.settingsSchema, inputs.settings));
281
335
  } catch (e) {
282
- console.log(...Value2.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
336
+ console.dir(...Value2.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
283
337
  throw e;
284
338
  }
285
339
  } else {
@@ -291,7 +345,7 @@ function createPlugin(handler, manifest, options) {
291
345
  try {
292
346
  env = Value2.Decode(pluginOptions.envSchema, Value2.Default(pluginOptions.envSchema, honoEnvironment));
293
347
  } catch (e) {
294
- console.log(...Value2.Errors(pluginOptions.envSchema, honoEnvironment), { depth: null });
348
+ console.dir(...Value2.Errors(pluginOptions.envSchema, honoEnvironment), { depth: null });
295
349
  throw e;
296
350
  }
297
351
  } else {
@@ -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
  }
@@ -354,25 +409,25 @@ async function createActionsPlugin(handler, options) {
354
409
  return;
355
410
  }
356
411
  const body = github2.context.payload.inputs;
412
+ const inputSchemaErrors = [...Value3.Errors(inputSchema, body)];
413
+ if (inputSchemaErrors.length) {
414
+ console.dir(inputSchemaErrors, { depth: null });
415
+ core.setFailed(`Error: Invalid inputs payload: ${inputSchemaErrors.map((o) => o.message).join(", ")}`);
416
+ return;
417
+ }
357
418
  const signature = body.signature;
358
419
  if (!pluginOptions.bypassSignatureVerification && !await verifySignature(pluginOptions.kernelPublicKey, body, signature)) {
359
420
  core.setFailed(`Error: Invalid signature`);
360
421
  return;
361
422
  }
362
- const inputPayload = github2.context.payload.inputs;
363
- const inputSchemaErrors = [...Value3.Errors(inputSchema, inputPayload)];
364
- if (inputSchemaErrors.length) {
365
- console.dir(inputSchemaErrors, { depth: null });
366
- core.setFailed(`Error: Invalid inputs payload: ${inputSchemaErrors.join(",")}`);
367
- return;
368
- }
369
- const inputs = Value3.Decode(inputSchema, inputPayload);
423
+ const inputs = Value3.Decode(inputSchema, body);
370
424
  let config2;
371
425
  if (pluginOptions.settingsSchema) {
372
426
  try {
373
427
  config2 = Value3.Decode(pluginOptions.settingsSchema, Value3.Default(pluginOptions.settingsSchema, inputs.settings));
374
428
  } catch (e) {
375
429
  console.dir(...Value3.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
430
+ core.setFailed(`Error: Invalid settings provided.`);
376
431
  throw e;
377
432
  }
378
433
  } else {
@@ -384,6 +439,7 @@ async function createActionsPlugin(handler, options) {
384
439
  env = Value3.Decode(pluginOptions.envSchema, Value3.Default(pluginOptions.envSchema, process.env));
385
440
  } catch (e) {
386
441
  console.dir(...Value3.Errors(pluginOptions.envSchema, process.env), { depth: null });
442
+ core.setFailed(`Error: Invalid environment provided.`);
387
443
  throw e;
388
444
  }
389
445
  } else {
@@ -407,7 +463,8 @@ async function createActionsPlugin(handler, options) {
407
463
  octokit: new customOctokit({ auth: inputs.authToken }),
408
464
  config: config2,
409
465
  env,
410
- logger: new Logs2(pluginOptions.logLevel)
466
+ logger: new Logs2(pluginOptions.logLevel),
467
+ commentHandler: new CommentHandler()
411
468
  };
412
469
  try {
413
470
  const result = await handler(context2);
@@ -427,7 +484,7 @@ async function createActionsPlugin(handler, options) {
427
484
  loggerError = context2.logger.error(`Error: ${error}`);
428
485
  }
429
486
  if (pluginOptions.postCommentOnError && loggerError) {
430
- await postComment(context2, loggerError);
487
+ await context2.commentHandler.postComment(context2, loggerError);
431
488
  }
432
489
  }
433
490
  }
@@ -444,7 +501,7 @@ async function returnDataToKernel(repoToken, stateId, output) {
444
501
  });
445
502
  }
446
503
  export {
504
+ CommentHandler,
447
505
  createActionsPlugin,
448
- createPlugin,
449
- postComment
506
+ createPlugin
450
507
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubiquity-os/plugin-sdk",
3
- "version": "2.0.5",
3
+ "version": "3.0.0",
4
4
  "description": "SDK for plugin support.",
5
5
  "author": "Ubiquity DAO",
6
6
  "license": "MIT",