-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.js
More file actions
191 lines (167 loc) · 7.02 KB
/
index.js
File metadata and controls
191 lines (167 loc) · 7.02 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
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
#!/usr/bin/env node
// HACK: Fix `process.argv` to replace dots with spaces.
const replaceDotCommands = require("./lib/replace-dot-commands");
process.argv = replaceDotCommands(process.argv);
const debug = require("debug")("ssb-cli");
const lodash = require("lodash");
const pull = require("pull-stream");
const ssbClient = require("ssb-client");
const yargs = require("yargs");
const createUsageGetter = require("./lib/create-usage-getter");
const defineCommand = require("./lib/define-command");
const getUsage = require("./lib/get-usage");
const handleError = require("./lib/handle-err")(yargs.exit);
const pruneManifest = require("./lib/prune-manifest");
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
const outputAsJSON = data => console.log(JSON.stringify(data, null, 2));
const getNormalizedEntries = obj =>
Object.entries(obj)
.filter(([key]) => key !== "help")
.sort(([aKey], [bKey]) => aKey.localeCompare(bKey));
// HACK: The regular `yargs` module doesn't let us have asynchronous builders,
// so we don't have an elegant way of taking --host and --port into account
// before calling SSB-Client. Instead, we're forced to use environment
// variables to avoid having these understood as command flags.
const host = process.env.SSB_HOST;
const port = process.env.SSB_PORT;
// Connect to an SSB service on the standard host and port (localhost + 8008).
ssbClient(null, { host, port })
.then(api => {
const showHelpAndClose = code => {
yargs.showHelp();
api.close();
yargs.exit(code);
};
// Get the manifest from the server, which, strangely, exports methods we don't have access to.
api
.manifest()
.then(async rawManifest => {
const manifest = pruneManifest(rawManifest, api);
const usage = await getUsage(api);
const getCommandUsage = createUsageGetter(usage);
const getGroupUsage = parts =>
lodash.get(usage, parts, {
description: ""
});
// Now we want to add commands for each of the muxrpc methods above.
// There are two types of "commands" we're adding:
//
// - Actual commands: these are sync/async/source methods that do something
// - Groups: these are actually objects that encapsulate muxrpc methods, and
// if these are called we should just call `yargs.showHelp()`.
const walk = ([key, value], previous, subYargs) => {
if (typeof value === "object") {
// Group of commands. It should show the help text and then exit.
// If someone tries to call this command, we show the help text and exit.
const groupUsage = getGroupUsage([...previous, key]);
const description = `${capitalize(groupUsage.description)} (group)`;
subYargs.command(
key,
description,
subSubYargs => {
getNormalizedEntries(value).forEach(entry =>
walk(entry, [key, ...previous], subSubYargs)
);
},
() => showHelpAndClose(1)
);
} else if (typeof value === "string") {
const methodType = value;
const commandUsage = getCommandUsage([...previous, key]);
const description = `${capitalize(
commandUsage.description
)} (${methodType})`;
subYargs.command(
key,
description,
builder => {
if (typeof commandUsage.args === "object") {
debug("%O", commandUsage.args);
getNormalizedEntries(commandUsage.args).forEach(
([key, value]) => {
// Parse muxrpc-usage info and add yargs options
const definition = defineCommand(value);
builder.option(key, definition);
}
);
}
},
argv => {
const positionalInputArray = argv._.slice(previous.length + 1);
const hasPositionalInput = positionalInputArray.length;
const positionalInput = hasPositionalInput
? positionalInputArray.join(" ")
: positionalInputArray[0];
const flagInput = JSON.parse(JSON.stringify(argv));
delete flagInput._;
delete flagInput.$0;
const hasFlagInput = Object.entries(flagInput).length;
debug("%O", { positionalInput, flagInput });
const method = lodash.get(api, [...previous, key], null);
if (method === null) {
debug("Method does not exist");
showHelpAndClose(1);
return;
}
if (hasPositionalInput && hasFlagInput) {
handleError(
new Error(
"You must provide positional arguments or --flag arguments, not both."
)
);
}
const input = hasPositionalInput ? positionalInput : flagInput;
// Remove yargs-specific CLI options and pass the rest to the method.
debug("Method input: %O", input);
// Remove CLI-specific arguments.
// Maybe we should be making a copy instead of mutating the object...
if (methodType === "source") {
pull(method(input), pull.drain(outputAsJSON, api.close));
} else if (methodType === "sync" || methodType === "async") {
method(input)
.then(value => {
outputAsJSON(value);
api.close();
})
.catch(err => {
handleError(err);
});
} else {
// This should never happen because we should be pruning method
// types that we don't support in `pruneManifest()`.
throw new Error(`Unsupported method type: ${methodType}`);
}
}
);
} else {
debug('Unknown type "%s": %O', key, value);
}
};
yargs
.parserConfiguration({
"camel-case-expansion": false
})
.scriptName("ssb")
.command(
"*",
"Friendly command-line interface for Secure Scuttlebutt",
() => {
getNormalizedEntries(manifest).forEach(entry =>
walk(entry, [], yargs)
);
},
argv => {
yargs.showHelp();
api.close();
if (argv._.length > 0) {
// Use was actually trying to do something. Maybe a typo?
yargs.exit(1);
}
}
);
// This is magical and seems to start yargs.
yargs.argv; // eslint-disable-line
})
.catch(handleError);
})
.catch(handleError);