@rocketh/doc 0.10.1
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/.prettierignore +4 -0
- package/.prettierrc +7 -0
- package/CHANGELOG.md +9 -0
- package/README.md +11 -0
- package/dist/cli.cjs +80 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.mjs +77 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +248 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.mjs +241 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +46 -0
- package/public/default_templates/{{contracts}}.hbs +77 -0
- package/src/cli.ts +26 -0
- package/src/index.ts +311 -0
- package/src/types.ts +61 -0
- package/tsconfig.json +15 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import type {
|
|
3
|
+
Deployment,
|
|
4
|
+
ResolvedConfig,
|
|
5
|
+
NoticeUserDoc,
|
|
6
|
+
Artifact,
|
|
7
|
+
Abi,
|
|
8
|
+
UnknownDeployments,
|
|
9
|
+
AbiConstructor,
|
|
10
|
+
AbiFunction,
|
|
11
|
+
AbiError,
|
|
12
|
+
AbiEvent,
|
|
13
|
+
} from 'rocketh';
|
|
14
|
+
import {loadDeployments} from 'rocketh';
|
|
15
|
+
import Handlebars from 'handlebars';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import {Fragment, FunctionFragment} from 'ethers';
|
|
18
|
+
import {dirname} from 'path';
|
|
19
|
+
import {fileURLToPath} from 'url';
|
|
20
|
+
|
|
21
|
+
import {DocumentationData, ErrorDoc, EventDoc, MethodDoc, ParamDoc, ReturnDoc} from './types';
|
|
22
|
+
|
|
23
|
+
export * from './types';
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
|
|
27
|
+
export type RunOptions = {template?: string; output?: string; exceptSuffix?: string[]};
|
|
28
|
+
|
|
29
|
+
function filter(options: RunOptions, name: string): boolean {
|
|
30
|
+
if (options.exceptSuffix) {
|
|
31
|
+
for (const suffix of options.exceptSuffix) {
|
|
32
|
+
if (name.endsWith(suffix)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function run(config: ResolvedConfig, options: RunOptions) {
|
|
41
|
+
const {deployments, chainId} = loadDeployments(config.deployments, config.network.name);
|
|
42
|
+
if (!chainId) {
|
|
43
|
+
throw new Error(`no chainId found for ${config.network.name}`);
|
|
44
|
+
}
|
|
45
|
+
generate({deployments}, options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function generate(
|
|
49
|
+
{deployments}: {deployments: UnknownDeployments; chainId?: string},
|
|
50
|
+
options: RunOptions
|
|
51
|
+
) {
|
|
52
|
+
if (!deployments || Object.keys(deployments).length === 0) {
|
|
53
|
+
console.log(`no deployments to export`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const toDocument: UnknownDeployments = {};
|
|
58
|
+
for (const name of Object.keys(deployments)) {
|
|
59
|
+
if (!filter(options, name)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const deployment = deployments[name];
|
|
63
|
+
toDocument[name] = deployment;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return generateFromDeployments(toDocument, options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function runFromFolder(folder: string, options: RunOptions) {
|
|
70
|
+
const files = fs.readdirSync(folder);
|
|
71
|
+
const deployments: UnknownDeployments = {};
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
if (file.endsWith('.json')) {
|
|
74
|
+
const name = path.basename(file, '.json');
|
|
75
|
+
if (!filter(options, name)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const deploymentString = fs.readFileSync(path.join(folder, file), 'utf-8');
|
|
80
|
+
const deployment = JSON.parse(deploymentString);
|
|
81
|
+
deployments[name] = deployment;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return generateFromDeployments(deployments, options);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// export async function runFromArtifacts(folder: string, options: {template?: string; outputFolder?: string}) {
|
|
89
|
+
// const files = fs.readdirSync(folder);
|
|
90
|
+
// const deployments: UnknownDeployments = {};
|
|
91
|
+
// for (const file of files) {
|
|
92
|
+
// if (file.endsWith('.json')) {
|
|
93
|
+
// const deploymentString = fs.readFileSync(path.join(folder, file), 'utf-8');
|
|
94
|
+
// const deployment = JSON.parse(deploymentString);
|
|
95
|
+
// deployments[path.basename(file, '.json')] = deployment;
|
|
96
|
+
// }
|
|
97
|
+
// }
|
|
98
|
+
|
|
99
|
+
// return generateFromDeployments(deployments, options);
|
|
100
|
+
// }
|
|
101
|
+
|
|
102
|
+
export async function generateFromDeployments(deployments: UnknownDeployments, options: RunOptions) {
|
|
103
|
+
const outputFolder = options.output || 'docs';
|
|
104
|
+
const templateFilepath = options.template || path.join(__dirname, 'default_templates/{{contracts}}.hbs');
|
|
105
|
+
const templateName = path.basename(templateFilepath, '.hbs');
|
|
106
|
+
const templateContent = fs.readFileSync(templateFilepath, 'utf-8');
|
|
107
|
+
const template = Handlebars.compile(templateContent);
|
|
108
|
+
|
|
109
|
+
const deploymentsList: DocumentationData[] = [];
|
|
110
|
+
for (const name of Object.keys(deployments)) {
|
|
111
|
+
const deployment = deployments[name];
|
|
112
|
+
const data = generateDocumentationData(name, deployment);
|
|
113
|
+
deploymentsList.push(data);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fs.emptyDirSync(outputFolder);
|
|
117
|
+
if (templateName === '{{contracts}}') {
|
|
118
|
+
for (const deployment of deploymentsList) {
|
|
119
|
+
const generated = template(deployment);
|
|
120
|
+
if (generated.trim() !== '') {
|
|
121
|
+
fs.writeFileSync(path.join(outputFolder, deployment.name + '.md'), generated);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
const generated = template({contracts: deploymentsList});
|
|
126
|
+
fs.writeFileSync(path.join(outputFolder, templateName + '.md'), generated);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function generateDocumentationData(
|
|
131
|
+
name: string,
|
|
132
|
+
deploymentOrArfifact: Partial<Deployment<Abi>> & Artifact<Abi>
|
|
133
|
+
): DocumentationData {
|
|
134
|
+
const abi = deploymentOrArfifact.abi;
|
|
135
|
+
const abiMap = new Map<string, AbiConstructor | AbiError | AbiEvent | AbiFunction>();
|
|
136
|
+
for (const abiElement of abi) {
|
|
137
|
+
switch (abiElement.type) {
|
|
138
|
+
case 'constructor':
|
|
139
|
+
abiMap.set('constructor', abiElement);
|
|
140
|
+
break;
|
|
141
|
+
case 'error':
|
|
142
|
+
abiMap.set(abiElement.name, abiElement);
|
|
143
|
+
break;
|
|
144
|
+
case 'event':
|
|
145
|
+
abiMap.set(abiElement.name, abiElement);
|
|
146
|
+
break;
|
|
147
|
+
case 'function':
|
|
148
|
+
abiMap.set(abiElement.name, abiElement);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const errors: ErrorDoc[] = [];
|
|
154
|
+
const events: EventDoc[] = [];
|
|
155
|
+
const methods: MethodDoc[] = [];
|
|
156
|
+
|
|
157
|
+
if (deploymentOrArfifact.userdoc?.errors) {
|
|
158
|
+
// we loop only through userdoc
|
|
159
|
+
for (const errorSignature of Object.keys(deploymentOrArfifact.userdoc.errors)) {
|
|
160
|
+
const errorName =
|
|
161
|
+
errorSignature.indexOf('(') > 0 ? errorSignature.slice(0, errorSignature.indexOf('(')) : errorSignature;
|
|
162
|
+
|
|
163
|
+
const abi = abiMap.get(errorName) as AbiError;
|
|
164
|
+
if (!abi) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const fullFormat = Fragment.from(abi).format('full');
|
|
168
|
+
const paramNames = abi.inputs.map((v, index) => v.name || `_${index}`);
|
|
169
|
+
|
|
170
|
+
const errorFromUserDoc = deploymentOrArfifact.userdoc.errors[errorSignature];
|
|
171
|
+
const errorFromDevDoc = deploymentOrArfifact.devdoc?.errors?.[errorSignature];
|
|
172
|
+
const params: ParamDoc[] = [];
|
|
173
|
+
if (errorFromDevDoc) {
|
|
174
|
+
for (const doc of errorFromDevDoc) {
|
|
175
|
+
if (doc.params) {
|
|
176
|
+
for (const paramName of paramNames || Object.keys(doc.params)) {
|
|
177
|
+
params.push({name: paramName, description: doc.params[paramName]});
|
|
178
|
+
}
|
|
179
|
+
// TODO what if same name
|
|
180
|
+
// TODO what is the array for ? (look at solidity doc)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const notice: string[] = [];
|
|
185
|
+
if (errorFromUserDoc) {
|
|
186
|
+
for (const doc of errorFromUserDoc) {
|
|
187
|
+
if (doc.notice) {
|
|
188
|
+
const notes = doc.notice.split('\\');
|
|
189
|
+
for (const note of notes) {
|
|
190
|
+
if (note != '') {
|
|
191
|
+
notice.push(note);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
errors.push({
|
|
199
|
+
name: errorName,
|
|
200
|
+
signature: errorSignature,
|
|
201
|
+
abi: abi,
|
|
202
|
+
fullFormat,
|
|
203
|
+
notice,
|
|
204
|
+
params,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (deploymentOrArfifact.userdoc?.events) {
|
|
210
|
+
// we loop only through userdoc
|
|
211
|
+
for (const eventSignature of Object.keys(deploymentOrArfifact.userdoc.events)) {
|
|
212
|
+
const eventName =
|
|
213
|
+
eventSignature.indexOf('(') > 0 ? eventSignature.slice(0, eventSignature.indexOf('(')) : eventSignature;
|
|
214
|
+
|
|
215
|
+
const abi = abiMap.get(eventName) as AbiEvent;
|
|
216
|
+
if (!abi) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const fullFormat = Fragment.from(abi).format('full');
|
|
220
|
+
const paramNames = abi.inputs.map((v, index) => v.name || `_${index}`);
|
|
221
|
+
|
|
222
|
+
const eventFromUserDoc = deploymentOrArfifact.userdoc.events[eventSignature];
|
|
223
|
+
const eventFromDevDoc = deploymentOrArfifact.devdoc?.events?.[eventSignature];
|
|
224
|
+
const params: ParamDoc[] = [];
|
|
225
|
+
if (eventFromDevDoc?.params) {
|
|
226
|
+
for (const paramName of paramNames || Object.keys(eventFromDevDoc.params)) {
|
|
227
|
+
params.push({name: paramName, description: eventFromDevDoc.params[paramName]});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
events.push({
|
|
232
|
+
name: eventName,
|
|
233
|
+
signature: eventSignature,
|
|
234
|
+
abi: abi as AbiEvent,
|
|
235
|
+
fullFormat,
|
|
236
|
+
notice: eventFromUserDoc.notice,
|
|
237
|
+
params,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (deploymentOrArfifact.userdoc?.methods) {
|
|
243
|
+
// we loop only through userdoc
|
|
244
|
+
for (const methodSignature of Object.keys(deploymentOrArfifact.userdoc.methods)) {
|
|
245
|
+
const methodName =
|
|
246
|
+
methodSignature.indexOf('(') > 0 ? methodSignature.slice(0, methodSignature.indexOf('(')) : methodSignature;
|
|
247
|
+
|
|
248
|
+
const abi = abiMap.get(methodName) as AbiFunction | AbiConstructor;
|
|
249
|
+
if (!abi) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const fullFormat = Fragment.from(abi).format('full');
|
|
253
|
+
const paramNames = abi ? abi.inputs.map((v, index) => v.name || `_${index}`) : undefined;
|
|
254
|
+
const returnNames = abi && 'outputs' in abi ? abi.outputs.map((v, index) => v.name || `_${index}`) : undefined;
|
|
255
|
+
|
|
256
|
+
const methodFromUserDoc = deploymentOrArfifact.userdoc.methods[methodSignature];
|
|
257
|
+
const methodFromDevDoc = deploymentOrArfifact.devdoc?.methods?.[methodSignature];
|
|
258
|
+
const params: ParamDoc[] = [];
|
|
259
|
+
if (methodFromDevDoc?.params) {
|
|
260
|
+
for (const paramName of paramNames || Object.keys(methodFromDevDoc.params)) {
|
|
261
|
+
params.push({name: paramName, description: methodFromDevDoc.params[paramName]});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const returns: ReturnDoc[] = [];
|
|
265
|
+
if (methodFromDevDoc?.returns) {
|
|
266
|
+
for (const returnName of returnNames || Object.keys(methodFromDevDoc.returns)) {
|
|
267
|
+
returns.push({name: returnName, description: methodFromDevDoc.returns[returnName]});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (methodName === 'constructor') {
|
|
272
|
+
methods.push({
|
|
273
|
+
type: 'constructor',
|
|
274
|
+
name: 'constructor',
|
|
275
|
+
abi: abi as AbiConstructor,
|
|
276
|
+
signature: methodSignature,
|
|
277
|
+
fullFormat,
|
|
278
|
+
notice: methodFromUserDoc.notice,
|
|
279
|
+
params,
|
|
280
|
+
returns,
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
const selector = FunctionFragment.from(abi).selector as `0x${string}`;
|
|
284
|
+
methods.push({
|
|
285
|
+
type: 'function',
|
|
286
|
+
name: methodName,
|
|
287
|
+
abi: abi as AbiFunction,
|
|
288
|
+
signature: methodSignature,
|
|
289
|
+
fullFormat,
|
|
290
|
+
bytes4: selector,
|
|
291
|
+
notice: methodFromUserDoc.notice,
|
|
292
|
+
params,
|
|
293
|
+
returns,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const data: DocumentationData = {
|
|
300
|
+
name,
|
|
301
|
+
address: deploymentOrArfifact.address,
|
|
302
|
+
abi,
|
|
303
|
+
author: deploymentOrArfifact.devdoc?.author,
|
|
304
|
+
title: deploymentOrArfifact.devdoc?.title,
|
|
305
|
+
notice: deploymentOrArfifact.userdoc?.notice,
|
|
306
|
+
errors,
|
|
307
|
+
events,
|
|
308
|
+
methods,
|
|
309
|
+
};
|
|
310
|
+
return data;
|
|
311
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type {NoticeUserDoc, Abi, AbiConstructor, AbiFunction, AbiError, AbiEvent} from 'rocketh';
|
|
2
|
+
|
|
3
|
+
export type ParamDoc = {name: string | `_${number}`; description: string};
|
|
4
|
+
export type ReturnDoc = {name: string | `_${number}`; description: string};
|
|
5
|
+
|
|
6
|
+
export type EventDoc = NoticeUserDoc & {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly signature: string;
|
|
9
|
+
readonly abi: AbiEvent;
|
|
10
|
+
readonly fullFormat: string;
|
|
11
|
+
readonly details?: string;
|
|
12
|
+
readonly params?: ParamDoc[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ErrorDoc = {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly signature: string;
|
|
18
|
+
readonly abi: AbiError;
|
|
19
|
+
readonly fullFormat: string;
|
|
20
|
+
readonly notice?: string[];
|
|
21
|
+
// TODO
|
|
22
|
+
// readonly details?: string; // TODO check if it can exists
|
|
23
|
+
readonly params?: ParamDoc[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type NonConstructorMethodDoc = NoticeUserDoc & {
|
|
27
|
+
readonly type: 'function';
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly signature: string;
|
|
30
|
+
readonly bytes4: `0x${string}`;
|
|
31
|
+
readonly abi: AbiFunction;
|
|
32
|
+
readonly fullFormat: string;
|
|
33
|
+
readonly details?: string; // TODO check if it can exists
|
|
34
|
+
readonly params?: ParamDoc[];
|
|
35
|
+
readonly returns?: ReturnDoc[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type ConstructorDoc = NoticeUserDoc & {
|
|
39
|
+
readonly type: 'constructor';
|
|
40
|
+
readonly name: 'constructor';
|
|
41
|
+
readonly signature: string;
|
|
42
|
+
readonly abi: AbiConstructor;
|
|
43
|
+
readonly fullFormat: string;
|
|
44
|
+
readonly details?: string; // TODO check if it can exists
|
|
45
|
+
readonly params?: ParamDoc[];
|
|
46
|
+
readonly returns?: ReturnDoc[];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type MethodDoc = NonConstructorMethodDoc | ConstructorDoc;
|
|
50
|
+
|
|
51
|
+
export type DocumentationData = {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly abi: Abi;
|
|
54
|
+
readonly events: EventDoc[];
|
|
55
|
+
readonly methods: MethodDoc[];
|
|
56
|
+
readonly errors: ErrorDoc[];
|
|
57
|
+
readonly address?: string;
|
|
58
|
+
readonly title?: string;
|
|
59
|
+
readonly author?: string;
|
|
60
|
+
readonly notice?: string;
|
|
61
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"moduleResolution": "Node",
|
|
4
|
+
"lib": ["ES2020", "dom"],
|
|
5
|
+
"target": "ES2020",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"module": "ES2020"
|
|
14
|
+
}
|
|
15
|
+
}
|