plugin-git-manager 1.1.7 → 1.1.10
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/client/187.08dd0bf4d0f68036.js +10 -0
- package/dist/client/components/LLMModelSelect.d.ts +10 -0
- package/dist/client/components/RunReviewButton.d.ts +1 -1
- package/dist/client/context/GitManagerContext.d.ts +2 -0
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +6 -4
- package/dist/locale/en-US.json +8 -1
- package/dist/server/actions/gitlab-api.js +14 -13
- package/dist/server/actions/review.js +15 -9
- package/dist/server/ai-tools.js +29 -3
- package/dist/server/collections/gitCodeReviews.d.ts +1 -1
- package/dist/server/collections/gitRepositories.d.ts +1 -1
- package/dist/server/collections/gitRepositories.js +12 -0
- package/dist/server/collections/gitReviewFlows.d.ts +1 -1
- package/dist/server/migrations/20260508000000-add-auto-review-flow-id.d.ts +6 -0
- package/dist/server/migrations/20260508000000-add-auto-review-flow-id.js +57 -0
- package/dist/server/plugin.d.ts +4 -0
- package/dist/server/plugin.js +38 -6
- package/dist/server/poller.js +3 -1
- package/package.json +1 -1
- package/src/client/components/CommitHistory.tsx +18 -3
- package/src/client/components/GitOperations.tsx +29 -16
- package/src/client/components/LLMModelSelect.tsx +63 -0
- package/src/client/components/PollingStatus.tsx +27 -1
- package/src/client/components/RepositoryConfig.tsx +76 -3
- package/src/client/components/ReviewFlows.tsx +17 -2
- package/src/client/components/RunReviewButton.tsx +375 -278
- package/src/client/context/GitManagerContext.tsx +2 -0
- package/src/client/index.tsx +31 -31
- package/src/locale/en-US.json +8 -1
- package/src/server/actions/gitlab-api.ts +8 -4
- package/src/server/actions/review.ts +9 -3
- package/src/server/ai-tools.ts +31 -3
- package/src/server/collections/gitRepositories.ts +12 -0
- package/src/server/migrations/20260508000000-add-auto-review-flow-id.ts +29 -0
- package/src/server/plugin.ts +216 -180
- package/src/server/poller.ts +11 -2
- package/dist/client/322.c934c7c0e25a75b0.js +0 -10
package/src/server/plugin.ts
CHANGED
|
@@ -1,180 +1,216 @@
|
|
|
1
|
-
import { Plugin } from '@nocobase/server';
|
|
2
|
-
import { resolve } from 'path';
|
|
3
|
-
import
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
6
|
-
import * as
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export class PluginGitManagerServer extends Plugin {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.app
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
'
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
'
|
|
115
|
-
'
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
'
|
|
119
|
-
'
|
|
120
|
-
'
|
|
121
|
-
'
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
'
|
|
130
|
-
'
|
|
131
|
-
'
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
1
|
+
import { Plugin } from '@nocobase/server';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { DataTypes } from 'sequelize';
|
|
4
|
+
import * as gitActions from './actions/git-actions';
|
|
5
|
+
import * as gitlabApi from './actions/gitlab-api';
|
|
6
|
+
import * as reviewActions from './actions/review';
|
|
7
|
+
import * as pollerActions from './actions/poller';
|
|
8
|
+
import { recoverStuckReviews } from './actions/review';
|
|
9
|
+
import { registerGitReviewAiTools } from './ai-tools';
|
|
10
|
+
import { startPoller, stopPoller } from './poller';
|
|
11
|
+
|
|
12
|
+
export class PluginGitManagerServer extends Plugin {
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
declare app: any;
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
declare db: any;
|
|
17
|
+
async beforeLoad() {
|
|
18
|
+
await (this as any).app.db.import({
|
|
19
|
+
directory: resolve(__dirname, 'collections'),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
(this as any).app.db.addMigrations({
|
|
23
|
+
namespace: (this as any).name,
|
|
24
|
+
directory: resolve(__dirname, 'migrations'),
|
|
25
|
+
context: { plugin: this },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async load() {
|
|
30
|
+
// Ensure dayjs timezone + utc plugins are loaded globally to prevent 'm.startOf is not a function' errors
|
|
31
|
+
const dayjsLib = require('dayjs');
|
|
32
|
+
const utcPlugin = require('dayjs/plugin/utc');
|
|
33
|
+
const timezonePlugin = require('dayjs/plugin/timezone');
|
|
34
|
+
dayjsLib.extend(utcPlugin);
|
|
35
|
+
dayjsLib.extend(timezonePlugin);
|
|
36
|
+
|
|
37
|
+
(this as any).app.resourceManager.define({
|
|
38
|
+
name: 'gitManager',
|
|
39
|
+
actions: {
|
|
40
|
+
clone: gitActions.clone,
|
|
41
|
+
pull: gitActions.pull,
|
|
42
|
+
push: gitActions.push,
|
|
43
|
+
fetch: gitActions.fetch,
|
|
44
|
+
diff: gitActions.diff,
|
|
45
|
+
status: gitActions.status,
|
|
46
|
+
log: gitActions.log,
|
|
47
|
+
branches: gitActions.branches,
|
|
48
|
+
checkout: gitActions.checkout,
|
|
49
|
+
fileTree: gitActions.fileTree,
|
|
50
|
+
fileContent: gitActions.fileContent,
|
|
51
|
+
commitDetail: gitActions.commitDetail,
|
|
52
|
+
mergeRequests: gitlabApi.mergeRequests,
|
|
53
|
+
mergeRequestDetail: gitlabApi.mergeRequestDetail,
|
|
54
|
+
mergeRequestNotes: gitlabApi.mergeRequestNotes,
|
|
55
|
+
triggerReview: reviewActions.triggerReview,
|
|
56
|
+
reviewApprovePost: reviewActions.reviewApprovePost,
|
|
57
|
+
reviewReject: reviewActions.reviewReject,
|
|
58
|
+
pollNow: pollerActions.pollNow,
|
|
59
|
+
pollerStatus: pollerActions.pollerStatus,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Suppress noisy workflow pre-action/post-action warnings for custom resources
|
|
64
|
+
(this as any).app.use(async (ctx, next) => {
|
|
65
|
+
if (ctx.logger && ctx.logger.warn) {
|
|
66
|
+
const originalWarn = ctx.logger.warn.bind(ctx.logger);
|
|
67
|
+
ctx.logger.warn = (message: any, ...args: any[]) => {
|
|
68
|
+
if (
|
|
69
|
+
typeof message === 'string' &&
|
|
70
|
+
message.includes('[Workflow') &&
|
|
71
|
+
message.includes('collection') &&
|
|
72
|
+
message.includes('not found')
|
|
73
|
+
) {
|
|
74
|
+
return ctx.logger;
|
|
75
|
+
}
|
|
76
|
+
return originalWarn(message, ...args);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return next();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
registerGitReviewAiTools((this as any).app);
|
|
83
|
+
|
|
84
|
+
(this as any).app.on('afterStart', async () => {
|
|
85
|
+
await ensureAutoReviewFlowSchema((this as any).app).catch(
|
|
86
|
+
(err) => (this as any).app.log?.error?.('plugin-git-manager: ensure schema error', err),
|
|
87
|
+
);
|
|
88
|
+
// Sweep any review left in `running` state from a previous process.
|
|
89
|
+
recoverStuckReviews((this as any).app).catch(
|
|
90
|
+
(err) => (this as any).app.log?.error?.('plugin-git-manager: recoverStuckReviews error', err),
|
|
91
|
+
);
|
|
92
|
+
startPoller((this as any).app);
|
|
93
|
+
});
|
|
94
|
+
(this as any).app.on('beforeStop', () => {
|
|
95
|
+
stopPoller();
|
|
96
|
+
});
|
|
97
|
+
(this as any).app.on('beforeDestroy', () => {
|
|
98
|
+
stopPoller();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Read-only operations available to all plugin users
|
|
102
|
+
(this as any).app.acl.registerSnippet({
|
|
103
|
+
name: `pm.${(this as any).name}.read`,
|
|
104
|
+
actions: [
|
|
105
|
+
'gitRepositories:list',
|
|
106
|
+
'gitRepositories:get',
|
|
107
|
+
'gitReviewFlows:list',
|
|
108
|
+
'gitReviewFlows:get',
|
|
109
|
+
'gitCodeReviews:list',
|
|
110
|
+
'gitCodeReviews:get',
|
|
111
|
+
'gitManager:status',
|
|
112
|
+
'gitManager:log',
|
|
113
|
+
'gitManager:branches',
|
|
114
|
+
'gitManager:diff',
|
|
115
|
+
'gitManager:fileTree',
|
|
116
|
+
'gitManager:fileContent',
|
|
117
|
+
'gitManager:commitDetail',
|
|
118
|
+
'gitManager:mergeRequests',
|
|
119
|
+
'gitManager:mergeRequestDetail',
|
|
120
|
+
'gitManager:mergeRequestNotes',
|
|
121
|
+
'gitManager:pollerStatus',
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Write operations require separate permission
|
|
126
|
+
(this as any).app.acl.registerSnippet({
|
|
127
|
+
name: `pm.${(this as any).name}.write`,
|
|
128
|
+
actions: [
|
|
129
|
+
'gitRepositories:create',
|
|
130
|
+
'gitRepositories:update',
|
|
131
|
+
'gitRepositories:destroy',
|
|
132
|
+
'gitReviewFlows:create',
|
|
133
|
+
'gitReviewFlows:update',
|
|
134
|
+
'gitReviewFlows:destroy',
|
|
135
|
+
'gitCodeReviews:create',
|
|
136
|
+
'gitCodeReviews:update',
|
|
137
|
+
'gitCodeReviews:destroy',
|
|
138
|
+
'gitManager:clone',
|
|
139
|
+
'gitManager:pull',
|
|
140
|
+
'gitManager:push',
|
|
141
|
+
'gitManager:fetch',
|
|
142
|
+
'gitManager:checkout',
|
|
143
|
+
'gitManager:triggerReview',
|
|
144
|
+
'gitManager:reviewApprovePost',
|
|
145
|
+
'gitManager:reviewReject',
|
|
146
|
+
'gitManager:pollNow',
|
|
147
|
+
],
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Prevent overwriting PAT with obfuscated value on updates
|
|
151
|
+
(this as any).app.resourceManager.use(async (ctx, next) => {
|
|
152
|
+
if (ctx.action?.resourceName === 'gitRepositories' && ['create', 'update'].includes(ctx.action?.actionName)) {
|
|
153
|
+
if (ctx.action.params?.values?.pat === '••••••••') {
|
|
154
|
+
delete ctx.action.params.values.pat;
|
|
155
|
+
}
|
|
156
|
+
if (ctx.request.body?.pat === '••••••••') {
|
|
157
|
+
delete ctx.request.body.pat;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return next();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Strip PAT from API responses — scoped to gitRepositories only
|
|
164
|
+
(this as any).app.resourceManager.use(async (ctx, next) => {
|
|
165
|
+
if (ctx.action?.resourceName !== 'gitRepositories') {
|
|
166
|
+
return next();
|
|
167
|
+
}
|
|
168
|
+
await next();
|
|
169
|
+
if (ctx.body) {
|
|
170
|
+
const items = Array.isArray(ctx.body)
|
|
171
|
+
? ctx.body
|
|
172
|
+
: ctx.body?.data
|
|
173
|
+
? Array.isArray(ctx.body.data)
|
|
174
|
+
? ctx.body.data
|
|
175
|
+
: [ctx.body.data]
|
|
176
|
+
: [ctx.body];
|
|
177
|
+
items.forEach((item) => {
|
|
178
|
+
if (item && typeof item === 'object') {
|
|
179
|
+
if (item.pat) item.pat = '••••••••';
|
|
180
|
+
if (item.dataValues?.pat) item.dataValues.pat = '••••••••';
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async install() {
|
|
188
|
+
await (this as any).app.db.getCollection('gitRepositories')?.sync();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async beforeDisable() {
|
|
192
|
+
stopPoller();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async beforeUnload() {
|
|
196
|
+
stopPoller();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function ensureAutoReviewFlowSchema(app: any) {
|
|
201
|
+
const sequelize = app.db?.sequelize;
|
|
202
|
+
const queryInterface = sequelize?.getQueryInterface?.();
|
|
203
|
+
if (!queryInterface) return;
|
|
204
|
+
|
|
205
|
+
const tablePrefix = app.db.options?.tablePrefix || '';
|
|
206
|
+
const tableName = `${tablePrefix}gitRepositories`;
|
|
207
|
+
const tableInfo = await queryInterface.describeTable(tableName).catch(() => null);
|
|
208
|
+
if (!tableInfo || tableInfo.autoReviewFlowId) return;
|
|
209
|
+
|
|
210
|
+
await queryInterface.addColumn(tableName, 'autoReviewFlowId', {
|
|
211
|
+
type: DataTypes.INTEGER,
|
|
212
|
+
allowNull: true,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export default PluginGitManagerServer;
|
package/src/server/poller.ts
CHANGED
|
@@ -218,6 +218,11 @@ export async function pollOneRepo(
|
|
|
218
218
|
});
|
|
219
219
|
if (!flows?.length) return { scanned: 0, triggered: 0 };
|
|
220
220
|
|
|
221
|
+
const primaryFlowId = repo.get('autoReviewFlowId') as number | null;
|
|
222
|
+
const primaryFlow = primaryFlowId
|
|
223
|
+
? flows.find((flow: any) => Number(flow.get('id')) === Number(primaryFlowId))
|
|
224
|
+
: null;
|
|
225
|
+
|
|
221
226
|
// Need PAT to query GitLab
|
|
222
227
|
const pat = repo.get('pat') as string;
|
|
223
228
|
if (!pat) return { scanned: 0, triggered: 0 };
|
|
@@ -227,8 +232,12 @@ export async function pollOneRepo(
|
|
|
227
232
|
|
|
228
233
|
let triggered = 0;
|
|
229
234
|
for (const mr of mrs) {
|
|
230
|
-
//
|
|
231
|
-
|
|
235
|
+
// Prefer the repository's configured primary auto-review flow, then fall
|
|
236
|
+
// back to the first matching auto-trigger flow.
|
|
237
|
+
const flow =
|
|
238
|
+
primaryFlow && branchMatches(primaryFlow, mr.source_branch)
|
|
239
|
+
? primaryFlow
|
|
240
|
+
: flows.find((f: any) => branchMatches(f, mr.source_branch));
|
|
232
241
|
if (!flow) continue;
|
|
233
242
|
|
|
234
243
|
// Auto-poll only triggers for MRs that have NEVER been reviewed.
|