-
Notifications
You must be signed in to change notification settings - Fork 2.7k
307 lines (264 loc) · 14.1 KB
/
validate-theme-entry.yml
1
2
3
4
5
6
7
8
9
10
11
12
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
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
name: Validate Theme Entry
on:
pull_request_target:
branches:
- master
paths:
- community-css-themes.json
jobs:
theme-validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: "refs/pull/${{ github.event.number }}/merge"
- uses: actions/setup-node@v2
- run: npm install -g probe-image-size && npm link probe-image-size
- uses: actions/github-script@v6
with:
script: |
if (context.payload.pull_request.additions <= context.payload.pull_request.deletions) {
// Don't run any validation checks if the user is just modifying existing theme config
return;
}
const probe = require('probe-image-size');
const fs = require('fs');
const errors = [];
const addError = (error) => {
errors.push(`:x: ${error}`);
console.log('Found issue: ' + error);
};
const warnings = [];
const addWarning = (warning) => {
warnings.push(`:warning: ${warning}`);
console.log('Found issue: ' + warning);
}
// core validation logic
await (async () => {
if (context.payload.pull_request.changed_files > 1) {
addError('You modified files other than `community-css-themes.json`.');
}
if (!context.payload.pull_request.maintainer_can_modify) {
addWarning('Maintainers of this repo should be allowed to edit this pull request. This speeds up the approval process.');
}
if (context.payload.pull_request.body.includes('Please switch to **Preview** and select one of the following links:')) {
addError('You did not follow the pull request template.');
}
let themes;
try {
themes = JSON.parse(fs.readFileSync('community-css-themes.json', 'utf8'));
} catch (e) {
addError('Could not parse `community-css-themes.json`, invalid JSON. ' + e.message);
return;
}
const theme = themes[themes.length - 1];
const validPrKeys = ['name', 'author', 'repo', 'screenshot', 'modes'];
for (let key of validPrKeys) {
if (!theme.hasOwnProperty(key)) {
addError(`Your PR does not have the required \`${key}\` property.`);
}
}
for (let key of Object.keys(theme)) {
if (!validPrKeys.includes(key)) {
addError(`Your PR has the invalid \`${key}\` property.`);
}
}
// Validate theme repo
let repoInfo = theme.repo.split('/');
if (repoInfo.length !== 2) {
addError(`It seems like you made a typo in the repository field ${theme.repo}`);
return;
}
let [owner, repo] = repoInfo;
console.log(`Repo info: ${owner}/${repo}`);
const author = context.payload.pull_request.user.login;
if (owner.toLowerCase() !== author.toLowerCase()) {
try {
const isInOrg = await github.rest.orgs.checkMembershipForUser({ org: owner, username: author });
if (!isInOrg) {
throw undefined;
}
} catch (e) {
addError(`The newly added entry is not at the end, or you are submitting on someone else's behalf. The last theme in the list is: \`${theme.repo}\`. If you are submitting from a GitHub org, you need to be a public member of the org.`);
}
}
try {
const repository = await github.rest.repos.get({ owner, repo });
if (!repository.data.has_issues) {
addWarning('Your repository does not have issues enabled. Users will not be able to report bugs and request features.');
}
} catch (e) {
addError(`It seems like you made a typo in the repository field ${theme.repo}`);
return;
}
if (theme.name.toLowerCase().includes('obsidian')) {
addError(`We discourage themes from including the word \`Obsidian\` in their name since it's redundant and makes the theme selection screen harder to visually parse.`);
}
if (theme.name.toLowerCase().includes('theme')) {
addError(`We discourage themes from including the word \`theme\` in their name since it's redundant and makes the theme selection screen harder to visually parse.`);
}
if (/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(theme.author)) {
addWarning('We generally discourage from including email addresses in the `author` field.');
}
if (themes.filter(t => t.name === theme.name).length > 1) {
addError('There is already a theme with this name');
}
if (themes.filter(t => t.repo === theme.repo).length > 1) {
addError('There is already a entry pointing to the `' + theme.repo + '` repository');
}
let manifest = null;
try {
let manifestFile = await github.rest.repos.getContent({
owner,
repo,
path: 'manifest.json',
});
manifest = JSON.parse(Buffer.from(manifestFile.data.content, 'base64').toString('utf-8'));
} catch (e) {
addError(`You don't have a valid \`manifest.json\` at the root of your repo.`);
}
if (manifest) {
let requiredManifestKeys = ['name', 'minAppVersion', 'author', 'version'];
for (let key of requiredManifestKeys) {
if (!manifest.hasOwnProperty(key)) {
addError(`Your manifest does not have the required \`${key}\` property.`);
}
}
if (manifest.name !== theme.name) {
addError(`Theme name mismatch, the name in this PR (\`${theme.name}\`) is not the same as the one in your repo (\`${manifest.name}\`). If you just changed your theme name, remember to change it in the manifest.json in your repo, and your latest GitHub release, if you have one.`);
}
if (manifest.authorUrl) {
if (manifest.authorUrl === "https://obsidian.md") {
addError(`The \`authorUrl\` field in your manifest should not point to the Obsidian Website. If you don't have a website you can just point it to your GitHub profile.`);
}
if (manifest.authorUrl.toLowerCase().includes("github.com/" + theme.repo.toLowerCase())) {
addError(`The \`authorUrl\` field in your manifest should not point to the GitHub repository of the theme.`);
}
}
if (manifest.fundingUrl && manifest.fundingUrl === "https://obsidian.md/pricing") {
addError(`The \`fundingUrl\` field in your manifest should not point to the Obsidian Website, If you don't have a link were users can donate to you, you can just omit this.`);
}
if (!(/^[0-9.]+$/i.test(manifest.version))) {
addError('Your latest version number is not valid. Only numbers and dots are allowed.');
}
try {
await github.rest.repos.getContent({
owner, repo, path: 'theme.css'
});
} catch (e) {
addError('Your repository does not include a `theme.css` file');
}
try {
await github.rest.repos.getContent({
owner, repo, path: 'obsidian.css'
});
addWarning('Your repository includes a `obsidian.css` file, this is only used in legacy versions of Obsidian.');
} catch (e) { }
let imageMeta = null;
try {
const screenshot = await github.rest.repos.getContent({
owner, repo, path: theme.screenshot
});
imageMeta = await probe(screenshot.data.download_url);
} catch (e) {
console.log(e);
addError('The theme screenshot cannot be found.');
}
if (imageMeta) {
if (imageMeta.type !== 'png' && imageMeta.type !== 'jpg') {
addError('Theme screenshot is not of filetype `.png` or `.jpg`');
}
const recommendedSize = `we generally recommend a size around 512 × 288 pixels.\n Detected size: ${imageMeta.width} x ${imageMeta.height} pixels`
if (imageMeta.width > 1000 || imageMeta.height > 500) {
addError(`Your theme screenshot is too big, ${recommendedSize}`);
}
else if (imageMeta.width < 250 || imageMeta.height < 100) {
addError(`Your theme screenshot is too small, ${recommendedSize}`);
} else if (imageMeta.width !==512 || imageMeta.height !== 288) {
addWarning(`Theme theme screenshot size is not optimal, ${recommendedSize}`);
}
}
// only validate releases if version is included
if (manifest.hasOwnProperty('version')) {
try {
let release = await github.rest.repos.getReleaseByTag({
owner,
repo,
tag: manifest.version,
});
const assets = release.data.assets || [];
if (!assets.find(p => p.name === 'theme.css')) {
addError('Your latest Release is missing the `theme.css` file.');
}
if (!assets.find(p => p.name === 'manifest.json')) {
addError('Your latest Release is missing the `manifest.json` file.');
}
} catch (e) { }
}
}
try {
await github.rest.licenses.getForRepo({ owner, repo });
} catch (e) {
addWarning('Your repository does not include a license. It is generally recommended for open-source projects to have a license. Go to <https://choosealicense.com/> to compare different open source licenses.');
}
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
title: `Add theme: ${theme.name}`
});
})();
if (errors.length > 0 || warnings.length > 0) {
let message = [`#### Hello!\n`];
message.push(`**I found the following issues in your theme submission**\n`);
if (errors.length > 0) {
message.push(`**Errors:**\n`);
message = message.concat(errors);
message.push(`\n---\n`);
}
if (warnings.length > 0) {
message.push(`**Warnings:**\n`);
message = message.concat(warnings);
message.push(`\n---\n`);
}
message.push(`<sup>This check was done automatically. Do <b>NOT</b> open a new PR for re-validation. Instead, to trigger this check again, make a change to your PR and wait a few minutes, or close and re-open it.</sup>`);
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message.join('\n'),
});
}
if (errors.length > 0) {
core.setFailed("Failed to validate theme");
}
let labels = errors.length > 0 ? ['Validation failed'] : ['Ready for review'];
if (context.payload.pull_request.labels.includes('Changes requested')) {
labels.push('Changes requested');
}
if (context.payload.pull_request.labels.includes('Additional review required')) {
labels.push('Additional review required');
}
if (context.payload.pull_request.labels.includes('Minor changes requested')) {
labels.push('Minor changes requested');
}
if (context.payload.pull_request.labels.includes('Requires author rebase')) {
labels.push('requires author rebase');
}
if (context.payload.pull_request.labels.includes('Installation not recommended')) {
labels.push('Installation not recommended');
}
if (context.payload.pull_request.labels.includes('Changes made')) {
labels.push('Changes made');
}
labels.push('theme');
await github.rest.issues.setLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels,
});
permissions:
contents: read
issues: write
pull-requests: write