-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathvalidate-fs.ts
More file actions
120 lines (94 loc) · 3.5 KB
/
validate-fs.ts
File metadata and controls
120 lines (94 loc) · 3.5 KB
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
import fs from "fs/promises";
import path from "path";
import * as github from "./github";
import * as messages from "./messages";
const onChainTypes = ["vaults", "operators", "networks", "tokens"] as const;
const offChainTypes = ["points", "curators"] as const;
const allowedTypes = [...onChainTypes, ...offChainTypes];
const allowedFiles = ["info.json", "logo.png"];
const addressRegex = /^0x[a-fA-F0-9]{40}$/;
const nameRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
type OnChainEntityType = (typeof onChainTypes)[number];
export type EntityType = (typeof allowedTypes)[number];
export type Entity = {
metadata?: string;
logo?: string;
isDeleted?: boolean;
entityId: string;
entityType: EntityType;
};
const isValidEntity = (entityType: string) => allowedTypes.includes(entityType as EntityType);
export async function validateFs(changedFiles: string[]): Promise<Entity> {
const notAllowed = new Set<string>();
const entityDirs = new Set<string>();
for (const filePath of changedFiles) {
const dir = path.dirname(filePath);
const [type, identifier, fileName] = filePath.split(path.sep);
if (!isValidEntity(type) || !allowedFiles.includes(fileName)) {
notAllowed.add(filePath);
continue;
}
const isValidIdentifier = onChainTypes.includes(type as OnChainEntityType)
? addressRegex.test(identifier)
: nameRegex.test(identifier);
if (isValidIdentifier) {
entityDirs.add(dir);
} else {
notAllowed.add(filePath);
}
}
/**
* Validate that there are only allowed changes
*/
if (notAllowed.size) {
await github.addComment(messages.notAllowedChanges([...notAllowed]));
throw new Error(
`The pull request includes changes outside the allowed directories:\n ${[
...notAllowed,
].join(", ")}`,
);
}
/**
* Validate that only one entity is changed per pull request
*/
if (entityDirs.size > 1) {
await github.addComment(messages.onlyOneEntityPerPr([...entityDirs]));
throw new Error("Several entities are changed in one pull request");
}
const [entityDir] = entityDirs;
const entityType = path.basename(path.dirname(entityDir)) as EntityType;
const entityId = path.basename(entityDir);
const existingFiles: string[] = await fs.readdir(entityDir).catch(() => []);
const entityDirExists = existingFiles.length > 0;
const [metadataPath, logoPath] = allowedFiles.map((name) => {
return existingFiles.includes(name) ? path.join(entityDir, name) : undefined;
});
const [isMetadataChanged, isLogoChanged] = allowedFiles.map((name) => {
return changedFiles.some((file) => path.basename(file) === name);
});
/**
* Validate that metadata present in the entity folder.
*/
if (entityDirExists && !metadataPath) {
await github.addComment(messages.noInfoJson(entityDir));
throw new Error("`info.json` is not found in the entity folder");
}
const result: Entity = {
entityId,
entityType,
isDeleted: !entityDirExists,
};
/**
* Add metadata to result only if the file was changed and exists.
*/
if (isMetadataChanged && metadataPath) {
result.metadata = metadataPath;
}
/**
* Add logo to result only if the file was changed and exists.
*/
if (isLogoChanged && logoPath) {
result.logo = logoPath;
}
return result;
}