wuffle 0.56.1 → 0.58.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/bin/run.js +4 -3
- package/lib/apps/github-client/GithubClient.js +12 -1
- package/lib/columns.js +6 -0
- package/lib/probot/CustomProbot.js +40 -21
- package/lib/probot/apps/setup.js +112 -44
- package/lib/store.js +17 -5
- package/package.json +13 -13
- package/public/bundle.js.map +1 -1
- package/public/service-worker.js +1 -1
- package/public/service-worker.js.map +1 -1
- package/wuffle.config.example.js +5 -3
- package/public/favicon.ico +0 -0
package/bin/run.js
CHANGED
|
@@ -205,7 +205,7 @@ async function validate() {
|
|
|
205
205
|
|
|
206
206
|
async function performSetup() {
|
|
207
207
|
|
|
208
|
-
log.
|
|
208
|
+
log.warn('Running first time setup');
|
|
209
209
|
|
|
210
210
|
const configPath = path.resolve('wuffle.config.js');
|
|
211
211
|
|
|
@@ -232,7 +232,7 @@ async function performSetup() {
|
|
|
232
232
|
log.error(error.message);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
log.
|
|
235
|
+
log.warn('Please correct above errors and restart');
|
|
236
236
|
|
|
237
237
|
process.exit(1);
|
|
238
238
|
}
|
|
@@ -256,7 +256,8 @@ async function open() {
|
|
|
256
256
|
const url = process.env.BASE_URL || 'http://localhost:3000';
|
|
257
257
|
|
|
258
258
|
if (IS_SETUP) {
|
|
259
|
-
log.warn(
|
|
259
|
+
log.warn('GitHub App is not configured yet');
|
|
260
|
+
log.warn(`Visit ${url} to continue the setup`);
|
|
260
261
|
} else {
|
|
261
262
|
log.info(`Wuffle started on ${url}`);
|
|
262
263
|
}
|
|
@@ -98,9 +98,20 @@ function GitHubClient(app, webhookEvents, logger, githubApp, events) {
|
|
|
98
98
|
|
|
99
99
|
const access_token = typeof user === 'string' ? user : user.access_token;
|
|
100
100
|
|
|
101
|
+
const log = logger.child({ name: 'github:user-auth' });
|
|
102
|
+
|
|
101
103
|
return cache.get(`user_scoped=${access_token}`, () => {
|
|
102
104
|
return new ProbotOctokit({
|
|
103
|
-
|
|
105
|
+
|
|
106
|
+
// fallout from Probot@13 migration
|
|
107
|
+
//
|
|
108
|
+
// see https://github.com/probot/probot/pull/1874#issuecomment-1837779069
|
|
109
|
+
log: {
|
|
110
|
+
debug: log.debug.bind(log),
|
|
111
|
+
info: log.info.bind(log),
|
|
112
|
+
warn: log.warn.bind(log),
|
|
113
|
+
error: log.error.bind(log)
|
|
114
|
+
},
|
|
104
115
|
auth: {
|
|
105
116
|
token: access_token
|
|
106
117
|
}
|
package/lib/columns.js
CHANGED
|
@@ -3,39 +3,60 @@ const { Server, Probot } = require('probot');
|
|
|
3
3
|
const { getLog } = require('probot/lib/helpers/get-log');
|
|
4
4
|
const { setupAppFactory } = require('./apps/setup');
|
|
5
5
|
|
|
6
|
+
const { isProduction } = require('probot/lib/helpers/is-production');
|
|
7
|
+
|
|
6
8
|
const { ManifestCreation } = require('probot/lib/manifest-creation');
|
|
7
9
|
|
|
10
|
+
const { readEnvOptions } = require('probot/lib/bin/read-env-options');
|
|
11
|
+
|
|
8
12
|
const { getErrorHandler } = require('probot/lib/helpers/get-error-handler');
|
|
9
|
-
const { getPrivateKey } = require('@probot/get-private-key');
|
|
10
13
|
|
|
11
14
|
|
|
12
|
-
async function run(appFn) {
|
|
15
|
+
async function run(appFn, additionalOptions) {
|
|
16
|
+
|
|
17
|
+
const {
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const port = parseInt(process.env.PORT || '3000', 10);
|
|
17
|
-
const privateKey = getPrivateKey() || undefined;
|
|
19
|
+
// log options
|
|
20
|
+
logLevel: level, logFormat, logLevelInString, logMessageKey, sentryDsn,
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
// server options
|
|
23
|
+
host, port, webhookPath, webhookProxy,
|
|
24
|
+
|
|
25
|
+
// probot options
|
|
26
|
+
appId, privateKey, redisConfig, secret, baseUrl
|
|
27
|
+
|
|
28
|
+
} = readEnvOptions(additionalOptions?.env);
|
|
29
|
+
|
|
30
|
+
const logOptions = {
|
|
31
|
+
level,
|
|
32
|
+
logFormat,
|
|
33
|
+
logLevelInString,
|
|
34
|
+
logMessageKey,
|
|
35
|
+
sentryDsn
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const log = getLog(logOptions);
|
|
20
39
|
|
|
21
40
|
const serverOptions = {
|
|
22
41
|
host,
|
|
23
|
-
log: log.child({ name: 'server' }),
|
|
24
42
|
port,
|
|
25
|
-
webhookPath
|
|
26
|
-
webhookProxy
|
|
43
|
+
webhookPath,
|
|
44
|
+
webhookProxy,
|
|
45
|
+
log: log.child({ name: 'server' })
|
|
27
46
|
};
|
|
28
47
|
|
|
29
48
|
let probotOptions = {
|
|
30
49
|
appId,
|
|
31
|
-
log: log.child({ name: 'probot' }),
|
|
32
50
|
privateKey,
|
|
33
|
-
|
|
51
|
+
redisConfig,
|
|
52
|
+
secret,
|
|
53
|
+
baseUrl,
|
|
54
|
+
log: log.child({ name: 'probot' })
|
|
34
55
|
};
|
|
35
56
|
|
|
36
57
|
// use probots own setup app if the probot app
|
|
37
58
|
// is not configured yet
|
|
38
|
-
if (!isProduction() && !isSetup()) {
|
|
59
|
+
if (!isProduction() && !isSetup(probotOptions)) {
|
|
39
60
|
|
|
40
61
|
// Workaround for setup (probot/probot#1512)
|
|
41
62
|
// When probot is started for the first time, it gets into a setup mode
|
|
@@ -72,22 +93,20 @@ async function run(appFn) {
|
|
|
72
93
|
return server;
|
|
73
94
|
}
|
|
74
95
|
|
|
75
|
-
function isSetup() {
|
|
76
|
-
const
|
|
77
|
-
|
|
96
|
+
function isSetup(options) {
|
|
97
|
+
const {
|
|
98
|
+
appId,
|
|
99
|
+
privateKey
|
|
100
|
+
} = options || readEnvOptions(process.env);
|
|
78
101
|
|
|
79
102
|
return !!(appId && privateKey);
|
|
80
103
|
}
|
|
81
104
|
|
|
82
|
-
function isProduction() {
|
|
83
|
-
return process.env.NODE_ENV === 'production';
|
|
84
|
-
}
|
|
85
|
-
|
|
86
105
|
function validateSetup() {
|
|
87
106
|
|
|
88
107
|
const setup = new ManifestCreation();
|
|
89
108
|
|
|
90
|
-
const manifest = JSON.parse(setup.getManifest(setup.pkg));
|
|
109
|
+
const manifest = JSON.parse(setup.getManifest(setup.pkg, process.env.BASE_URL));
|
|
91
110
|
|
|
92
111
|
return [
|
|
93
112
|
!manifest.url && new Error('No <url> configured in app.yml'),
|
package/lib/probot/apps/setup.js
CHANGED
|
@@ -9,6 +9,8 @@ const { getLog } = require('probot/lib/helpers/get-log');
|
|
|
9
9
|
|
|
10
10
|
const { randomString } = require('../../util');
|
|
11
11
|
|
|
12
|
+
const { importView } = require('probot/lib/views/import');
|
|
13
|
+
const { setupView } = require('probot/lib/views/setup');
|
|
12
14
|
|
|
13
15
|
function setupAppFactory(host, port) {
|
|
14
16
|
|
|
@@ -17,7 +19,11 @@ function setupAppFactory(host, port) {
|
|
|
17
19
|
|
|
18
20
|
const log = getLog().child({ name: 'wuffle:setup' });
|
|
19
21
|
|
|
20
|
-
if (
|
|
22
|
+
if (!(
|
|
23
|
+
process.env.NODE_ENV === 'production' ||
|
|
24
|
+
process.env.WEBHOOK_PROXY_URL ||
|
|
25
|
+
process.env.NO_SMEE_SETUP === 'true'
|
|
26
|
+
)) {
|
|
21
27
|
await setup.createWebhookChannel();
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -32,48 +38,110 @@ function setupAppFactory(host, port) {
|
|
|
32
38
|
route.use(getLoggingMiddleware(app.log));
|
|
33
39
|
|
|
34
40
|
route.get('/probot', async (req, res) => {
|
|
35
|
-
const baseUrl = getBaseUrl(req);
|
|
41
|
+
const baseUrl = process.env.BASE_URL || getBaseUrl(req);
|
|
36
42
|
const pkg = setup.pkg;
|
|
37
43
|
const manifest = setup.getManifest(pkg, baseUrl);
|
|
38
44
|
const createAppUrl = setup.createAppUrl;
|
|
39
45
|
|
|
46
|
+
await setup.updateEnv({
|
|
47
|
+
BASE_URL: baseUrl
|
|
48
|
+
});
|
|
49
|
+
|
|
40
50
|
// Pass the manifest to be POST'd
|
|
41
|
-
res.
|
|
51
|
+
res.writeHead(200, { 'content-type': 'text/html' }).end(setupView({
|
|
52
|
+
name: pkg.name || 'Wuffle',
|
|
53
|
+
version: pkg.version,
|
|
54
|
+
description: pkg.description,
|
|
55
|
+
createAppUrl,
|
|
56
|
+
manifest
|
|
57
|
+
}));
|
|
42
58
|
});
|
|
43
59
|
|
|
44
60
|
route.get('/probot/setup', async (req, res) => {
|
|
45
61
|
const { code } = req.query;
|
|
46
|
-
const app_url = await setup.createAppFromCode(code);
|
|
47
62
|
|
|
48
|
-
|
|
63
|
+
if (!code || typeof code !== 'string' || code.length === 0) {
|
|
64
|
+
return res
|
|
65
|
+
.writeHead(400, { 'content-type': 'text/plain' })
|
|
66
|
+
.end('code missing or invalid');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const response = await setup.createAppFromCode(code, {
|
|
70
|
+
request: app.state.request
|
|
71
|
+
});
|
|
49
72
|
|
|
50
|
-
|
|
73
|
+
const appUrl = app.state.appUrl = `${response}/installations/new`;
|
|
74
|
+
|
|
75
|
+
log.warn('Setup completed, please restart the app');
|
|
76
|
+
|
|
77
|
+
log.info(`Visit ${appUrl} to connect GitHub repositories`);
|
|
78
|
+
|
|
79
|
+
const location = '/probot/success';
|
|
80
|
+
|
|
81
|
+
return res
|
|
82
|
+
.writeHead(302, {
|
|
83
|
+
'content-type': 'text/plain',
|
|
84
|
+
location
|
|
85
|
+
})
|
|
86
|
+
.end(`Redirecting to ${ location }`);
|
|
51
87
|
});
|
|
52
88
|
|
|
53
89
|
route.get('/probot/import', async (_req, res) => {
|
|
90
|
+
|
|
91
|
+
const pkg = setup.pkg;
|
|
54
92
|
const { WEBHOOK_PROXY_URL, GHE_HOST } = process.env;
|
|
55
93
|
const GH_HOST = `https://${GHE_HOST ?? 'github.com'}`;
|
|
56
|
-
|
|
94
|
+
|
|
95
|
+
return res
|
|
96
|
+
.writeHead(200, {
|
|
97
|
+
'content-type': 'text/html'
|
|
98
|
+
})
|
|
99
|
+
.end(importView({
|
|
100
|
+
name: pkg.name || 'Wuffle',
|
|
101
|
+
WEBHOOK_PROXY_URL,
|
|
102
|
+
GH_HOST
|
|
103
|
+
}));
|
|
57
104
|
});
|
|
58
105
|
|
|
59
106
|
route.post('/probot/import', bodyParser.json(), async (req, res) => {
|
|
60
107
|
const { appId, pem, webhook_secret } = req.body;
|
|
61
108
|
if (!appId || !pem || !webhook_secret) {
|
|
62
|
-
res
|
|
63
|
-
|
|
109
|
+
return res
|
|
110
|
+
.writeHead(400, {
|
|
111
|
+
'content-type': 'text/plain'
|
|
112
|
+
})
|
|
113
|
+
.end('appId and/or pem and/or webhook_secret missing');
|
|
64
114
|
}
|
|
115
|
+
|
|
65
116
|
await updateDotenv({
|
|
66
117
|
APP_ID: appId,
|
|
67
118
|
PRIVATE_KEY: `"${pem}"`,
|
|
68
119
|
WEBHOOK_SECRET: webhook_secret
|
|
69
120
|
});
|
|
70
121
|
|
|
71
|
-
log.
|
|
122
|
+
log.warn('Setup completed, please restart the app.');
|
|
72
123
|
|
|
73
|
-
res.
|
|
124
|
+
return res.redirect('/probot/success');
|
|
74
125
|
});
|
|
75
126
|
|
|
76
|
-
route.get('/', (
|
|
127
|
+
route.get('/probot/success', (_req, res) => {
|
|
128
|
+
const pkg = setup.pkg;
|
|
129
|
+
|
|
130
|
+
const appUrl = app.state.appUrl;
|
|
131
|
+
|
|
132
|
+
return res
|
|
133
|
+
.writeHead(200, { 'content-type': 'text/html' })
|
|
134
|
+
.end(successView({
|
|
135
|
+
name: pkg.name || 'Wuffle',
|
|
136
|
+
appUrl
|
|
137
|
+
}));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
route.get('/', (_req, res) => {
|
|
141
|
+
return res
|
|
142
|
+
.writeHead(302, { 'content-type': 'text/plain', location: '/probot' })
|
|
143
|
+
.end();
|
|
144
|
+
});
|
|
77
145
|
};
|
|
78
146
|
}
|
|
79
147
|
|
|
@@ -90,40 +158,40 @@ function getBaseUrl(req) {
|
|
|
90
158
|
return baseUrl;
|
|
91
159
|
}
|
|
92
160
|
|
|
93
|
-
|
|
94
|
-
function renderSuccess(appUrl = null) {
|
|
161
|
+
function successView({ name, appUrl }) {
|
|
95
162
|
|
|
96
163
|
return `
|
|
97
|
-
<!DOCTYPE html>
|
|
98
|
-
<html lang="en" class="height-full">
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
164
|
+
<!DOCTYPE html>
|
|
165
|
+
<html lang="en" class="height-full" data-color-mode="auto" data-light-theme="light" data-dark-theme="dark">
|
|
166
|
+
<head>
|
|
167
|
+
<meta charset="UTF-8">
|
|
168
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
169
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
170
|
+
<title>${ name } Setup complete</title>
|
|
171
|
+
<link rel="icon" href="/probot/static/probot-head.png">
|
|
172
|
+
<link rel="stylesheet" href="/probot/static/primer.css">
|
|
173
|
+
</head>
|
|
174
|
+
<body class="height-full bg-gray-light">
|
|
175
|
+
<div class="d-flex flex-column flex-justify-center flex-items-center text-center height-full">
|
|
176
|
+
<img src="/probot/static/robot.svg" alt="Probot Logo" width="100" class="mb-6">
|
|
177
|
+
<div class="box-shadow rounded-2 border p-6 bg-white">
|
|
178
|
+
<div class="text-center">
|
|
179
|
+
<h1 class="alt-h3 mb-2">You completed your ${name} setup!</h1>
|
|
180
|
+
|
|
181
|
+
<p class="mb-2">
|
|
182
|
+
Go ahead and ${ appUrl ? `<a href="${appUrl}" target="_blank" rel="no-opener">` : ''}
|
|
183
|
+
connect GitHub repositories
|
|
184
|
+
${appUrl ? '</a>' : ''} to your board.
|
|
185
|
+
</p>
|
|
186
|
+
|
|
187
|
+
<p>
|
|
188
|
+
Please restart the server to complete the setup.
|
|
189
|
+
</p>
|
|
190
|
+
|
|
191
|
+
</div>
|
|
123
192
|
</div>
|
|
124
193
|
</div>
|
|
125
|
-
</
|
|
126
|
-
</
|
|
127
|
-
|
|
128
|
-
|
|
194
|
+
</body>
|
|
195
|
+
</html>
|
|
196
|
+
`;
|
|
129
197
|
}
|
package/lib/store.js
CHANGED
|
@@ -102,6 +102,7 @@ class Store {
|
|
|
102
102
|
} = event;
|
|
103
103
|
|
|
104
104
|
let firstIssue = this.issues[0];
|
|
105
|
+
let lastIssue = this.issues[this.issues.length - 1];
|
|
105
106
|
|
|
106
107
|
for (const update of context.getUpdates()) {
|
|
107
108
|
|
|
@@ -125,11 +126,15 @@ class Store {
|
|
|
125
126
|
...inverseLinks
|
|
126
127
|
};
|
|
127
128
|
|
|
128
|
-
issue.order = this.getSemanticIssueOrder(issue, links, firstIssue);
|
|
129
|
+
issue.order = this.getSemanticIssueOrder(issue, links, firstIssue, lastIssue);
|
|
129
130
|
|
|
130
131
|
if (!firstIssue || firstIssue.order > issue.order) {
|
|
131
132
|
firstIssue = issue;
|
|
132
133
|
}
|
|
134
|
+
|
|
135
|
+
if (!lastIssue || lastIssue.order < issue.order) {
|
|
136
|
+
lastIssue = issue;
|
|
137
|
+
}
|
|
133
138
|
}
|
|
134
139
|
}, 1250);
|
|
135
140
|
|
|
@@ -396,7 +401,7 @@ class Store {
|
|
|
396
401
|
return updatedIssue;
|
|
397
402
|
}
|
|
398
403
|
|
|
399
|
-
getSemanticIssueOrder(issue, links, firstIssue) {
|
|
404
|
+
getSemanticIssueOrder(issue, links, firstIssue, lastIssue) {
|
|
400
405
|
|
|
401
406
|
const {
|
|
402
407
|
id,
|
|
@@ -450,11 +455,18 @@ class Store {
|
|
|
450
455
|
return currentOrder;
|
|
451
456
|
}
|
|
452
457
|
|
|
453
|
-
|
|
454
|
-
|
|
458
|
+
if (this.columns.isFifo(column)) {
|
|
459
|
+
|
|
460
|
+
// insert after other issues
|
|
461
|
+
after = lastIssue;
|
|
462
|
+
} else {
|
|
463
|
+
|
|
464
|
+
// insert before other issues
|
|
465
|
+
before = firstIssue;
|
|
466
|
+
}
|
|
455
467
|
}
|
|
456
468
|
|
|
457
|
-
return this._computeOrder(before
|
|
469
|
+
return this._computeOrder(before?.order, after?.order, currentOrder);
|
|
458
470
|
}
|
|
459
471
|
|
|
460
472
|
createLinks(context, issue) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wuffle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "A multi-repository task board for GitHub issues",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Nico Rehwaldt",
|
|
@@ -36,34 +36,34 @@
|
|
|
36
36
|
"auto-test": "npm test -- --watch"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@aws-sdk/client-s3": "^3.
|
|
39
|
+
"@aws-sdk/client-s3": "^3.503.1",
|
|
40
40
|
"async-didi": "^0.3.1",
|
|
41
41
|
"body-parser": "^1.20.0",
|
|
42
42
|
"compression": "^1.7.4",
|
|
43
|
-
"express-session": "^1.
|
|
43
|
+
"express-session": "^1.18.0",
|
|
44
44
|
"fake-tag": "^3.0.0",
|
|
45
45
|
"memorystore": "^1.6.7",
|
|
46
46
|
"min-dash": "^4.1.1",
|
|
47
47
|
"mkdirp": "^3.0.1",
|
|
48
48
|
"p-defer": "^3.0.0",
|
|
49
|
-
"prexit": "0.0
|
|
50
|
-
"probot": "^
|
|
51
|
-
"smee-client": "^
|
|
49
|
+
"prexit": "^1.0.0",
|
|
50
|
+
"probot": "^13.0.1",
|
|
51
|
+
"smee-client": "^2.0.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@graphql-eslint/eslint-plugin": "^3.20.1",
|
|
55
|
-
"@octokit/graphql-schema": "^
|
|
55
|
+
"@octokit/graphql-schema": "^14.53.0",
|
|
56
56
|
"@types/compression": "^1.7.2",
|
|
57
|
-
"@types/express-session": "^1.17.
|
|
58
|
-
"chai": "^4.
|
|
57
|
+
"@types/express-session": "^1.17.10",
|
|
58
|
+
"chai": "^4.4.1",
|
|
59
59
|
"graphql": "^16.6.0",
|
|
60
60
|
"mocha": "^10.2.0",
|
|
61
|
-
"nock": "^13.
|
|
62
|
-
"nodemon": "^3.0.
|
|
61
|
+
"nock": "^13.5.1",
|
|
62
|
+
"nodemon": "^3.0.3",
|
|
63
63
|
"npm-run-all": "^4.1.5",
|
|
64
64
|
"sinon": "^17.0.1",
|
|
65
65
|
"sinon-chai": "^3.7.0",
|
|
66
|
-
"typescript": "^5.
|
|
66
|
+
"typescript": "^5.3.3"
|
|
67
67
|
},
|
|
68
68
|
"engines": {
|
|
69
69
|
"node": ">= 16"
|
|
@@ -91,5 +91,5 @@
|
|
|
91
91
|
"index.js",
|
|
92
92
|
"wuffle.config.example.js"
|
|
93
93
|
],
|
|
94
|
-
"gitHead": "
|
|
94
|
+
"gitHead": "07336a53705a1ba55405e6817cafa574aeefbe10"
|
|
95
95
|
}
|