diff options
author | Chocobozzz <me@florianbigard.com> | 2020-01-28 11:07:23 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-01-28 11:35:26 +0100 |
commit | 26fcf2efebc681104d8e181da42b9ec112a8d28e (patch) | |
tree | 1d2a4a5c8c5547659274a765238b6b58839493eb /server | |
parent | b764380ac23f4e9d4677d08acdc3474c2931a16d (diff) | |
download | PeerTube-26fcf2efebc681104d8e181da42b9ec112a8d28e.tar.gz PeerTube-26fcf2efebc681104d8e181da42b9ec112a8d28e.tar.zst PeerTube-26fcf2efebc681104d8e181da42b9ec112a8d28e.zip |
Add redundancy CLI
Diffstat (limited to 'server')
-rw-r--r-- | server/tests/cli/peertube.ts | 75 | ||||
-rw-r--r-- | server/tools/cli.ts | 19 | ||||
-rw-r--r-- | server/tools/package.json | 5 | ||||
-rw-r--r-- | server/tools/peertube-auth.ts | 7 | ||||
-rw-r--r-- | server/tools/peertube-plugins.ts | 25 | ||||
-rw-r--r-- | server/tools/peertube-redundancy.ts | 194 | ||||
-rw-r--r-- | server/tools/peertube.ts | 1 | ||||
-rw-r--r-- | server/tools/yarn.lock | 23 |
8 files changed, 308 insertions, 41 deletions
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts index b8c0b1f79..09cbcdb65 100644 --- a/server/tests/cli/peertube.ts +++ b/server/tests/cli/peertube.ts | |||
@@ -6,15 +6,15 @@ import { | |||
6 | addVideoChannel, | 6 | addVideoChannel, |
7 | buildAbsoluteFixturePath, | 7 | buildAbsoluteFixturePath, |
8 | cleanupTests, | 8 | cleanupTests, |
9 | createUser, | 9 | createUser, doubleFollow, |
10 | execCLI, | 10 | execCLI, |
11 | flushAndRunServer, | 11 | flushAndRunServer, |
12 | getEnvCli, | 12 | getEnvCli, getLocalIdByUUID, |
13 | getVideo, | 13 | getVideo, |
14 | getVideosList, | 14 | getVideosList, |
15 | getVideosListWithToken, removeVideo, | 15 | getVideosListWithToken, removeVideo, |
16 | ServerInfo, | 16 | ServerInfo, |
17 | setAccessTokensToServers, | 17 | setAccessTokensToServers, uploadVideo, uploadVideoAndGetId, |
18 | userLogin, | 18 | userLogin, |
19 | waitJobs | 19 | waitJobs |
20 | } from '../../../shared/extra-utils' | 20 | } from '../../../shared/extra-utils' |
@@ -210,6 +210,75 @@ describe('Test CLI wrapper', function () { | |||
210 | }) | 210 | }) |
211 | }) | 211 | }) |
212 | 212 | ||
213 | describe('Manage video redundancies', function () { | ||
214 | let anotherServer: ServerInfo | ||
215 | let video1Server2: number | ||
216 | let servers: ServerInfo[] | ||
217 | |||
218 | before(async function () { | ||
219 | this.timeout(120000) | ||
220 | |||
221 | anotherServer = await flushAndRunServer(2) | ||
222 | await setAccessTokensToServers([ anotherServer ]) | ||
223 | |||
224 | await doubleFollow(server, anotherServer) | ||
225 | |||
226 | servers = [ server, anotherServer ] | ||
227 | await waitJobs(servers) | ||
228 | |||
229 | const uuid = (await uploadVideoAndGetId({ server: anotherServer, videoName: 'super video' })).uuid | ||
230 | await waitJobs(servers) | ||
231 | |||
232 | video1Server2 = await getLocalIdByUUID(server.url, uuid) | ||
233 | }) | ||
234 | |||
235 | it('Should add a redundancy', async function () { | ||
236 | this.timeout(60000) | ||
237 | |||
238 | const env = getEnvCli(server) | ||
239 | |||
240 | const params = `add --video ${video1Server2}` | ||
241 | |||
242 | await execCLI(`${env} ${cmd} redundancy ${params}`) | ||
243 | |||
244 | await waitJobs(servers) | ||
245 | }) | ||
246 | |||
247 | it('Should list redundancies', async function () { | ||
248 | this.timeout(60000) | ||
249 | |||
250 | { | ||
251 | const env = getEnvCli(server) | ||
252 | |||
253 | const params = `list-my-redundancies` | ||
254 | const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`) | ||
255 | |||
256 | expect(stdout).to.contain('super video') | ||
257 | expect(stdout).to.contain(`localhost:${server.port}`) | ||
258 | } | ||
259 | }) | ||
260 | |||
261 | it('Should remove a redundancy', async function () { | ||
262 | this.timeout(60000) | ||
263 | |||
264 | const env = getEnvCli(server) | ||
265 | |||
266 | const params = `remove --video ${video1Server2}` | ||
267 | |||
268 | await execCLI(`${env} ${cmd} redundancy ${params}`) | ||
269 | |||
270 | await waitJobs(servers) | ||
271 | |||
272 | { | ||
273 | const env = getEnvCli(server) | ||
274 | const params = `list-my-redundancies` | ||
275 | const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`) | ||
276 | |||
277 | expect(stdout).to.not.contain('super video') | ||
278 | } | ||
279 | }) | ||
280 | }) | ||
281 | |||
213 | after(async function () { | 282 | after(async function () { |
214 | this.timeout(10000) | 283 | this.timeout(10000) |
215 | 284 | ||
diff --git a/server/tools/cli.ts b/server/tools/cli.ts index 58e2445ac..ba80872fb 100644 --- a/server/tools/cli.ts +++ b/server/tools/cli.ts | |||
@@ -6,6 +6,8 @@ import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' | |||
6 | import { Command } from 'commander' | 6 | import { Command } from 'commander' |
7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' | 7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' |
8 | import { createLogger, format, transports } from 'winston' | 8 | import { createLogger, format, transports } from 'winston' |
9 | import { getAccessToken, getMyUserInformation } from '@shared/extra-utils' | ||
10 | import { User, UserRole } from '@shared/models' | ||
9 | 11 | ||
10 | let configName = 'PeerTube/CLI' | 12 | let configName = 'PeerTube/CLI' |
11 | if (isTestInstance()) configName += `-${getAppNumber()}` | 13 | if (isTestInstance()) configName += `-${getAppNumber()}` |
@@ -14,6 +16,19 @@ const config = require('application-config')(configName) | |||
14 | 16 | ||
15 | const version = require('../../../package.json').version | 17 | const version = require('../../../package.json').version |
16 | 18 | ||
19 | async function getAdminTokenOrDie (url: string, username: string, password: string) { | ||
20 | const accessToken = await getAccessToken(url, username, password) | ||
21 | const resMe = await getMyUserInformation(url, accessToken) | ||
22 | const me: User = resMe.body | ||
23 | |||
24 | if (me.role !== UserRole.ADMINISTRATOR) { | ||
25 | console.error('You must be an administrator.') | ||
26 | process.exit(-1) | ||
27 | } | ||
28 | |||
29 | return accessToken | ||
30 | } | ||
31 | |||
17 | interface Settings { | 32 | interface Settings { |
18 | remotes: any[], | 33 | remotes: any[], |
19 | default: number | 34 | default: number |
@@ -222,5 +237,7 @@ export { | |||
222 | getServerCredentials, | 237 | getServerCredentials, |
223 | 238 | ||
224 | buildCommonVideoOptions, | 239 | buildCommonVideoOptions, |
225 | buildVideoAttributesFromCommander | 240 | buildVideoAttributesFromCommander, |
241 | |||
242 | getAdminTokenOrDie | ||
226 | } | 243 | } |
diff --git a/server/tools/package.json b/server/tools/package.json index 40959d76e..06ad31cab 100644 --- a/server/tools/package.json +++ b/server/tools/package.json | |||
@@ -4,11 +4,12 @@ | |||
4 | "private": true, | 4 | "private": true, |
5 | "dependencies": { | 5 | "dependencies": { |
6 | "application-config": "^1.0.1", | 6 | "application-config": "^1.0.1", |
7 | "cli-table": "^0.3.1", | 7 | "cli-table3": "^0.5.1", |
8 | "netrc-parser": "^3.1.6", | 8 | "netrc-parser": "^3.1.6", |
9 | "webtorrent-hybrid": "^4.0.1" | 9 | "webtorrent-hybrid": "^4.0.1" |
10 | }, | 10 | }, |
11 | "summon": { | 11 | "summon": { |
12 | "silent": true | 12 | "silent": true |
13 | } | 13 | }, |
14 | "devDependencies": {} | ||
14 | } | 15 | } |
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts index 6597a5c36..acac75034 100644 --- a/server/tools/peertube-auth.ts +++ b/server/tools/peertube-auth.ts | |||
@@ -6,8 +6,7 @@ import * as prompt from 'prompt' | |||
6 | import { getNetrc, getSettings, writeSettings } from './cli' | 6 | import { getNetrc, getSettings, writeSettings } from './cli' |
7 | import { isUserUsernameValid } from '../helpers/custom-validators/users' | 7 | import { isUserUsernameValid } from '../helpers/custom-validators/users' |
8 | import { getAccessToken, login } from '../../shared/extra-utils' | 8 | import { getAccessToken, login } from '../../shared/extra-utils' |
9 | 9 | import * as CliTable3 from 'cli-table3' | |
10 | const Table = require('cli-table') | ||
11 | 10 | ||
12 | async function delInstance (url: string) { | 11 | async function delInstance (url: string) { |
13 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) | 12 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) |
@@ -108,10 +107,10 @@ program | |||
108 | .action(async () => { | 107 | .action(async () => { |
109 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) | 108 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) |
110 | 109 | ||
111 | const table = new Table({ | 110 | const table = new CliTable3({ |
112 | head: ['instance', 'login'], | 111 | head: ['instance', 'login'], |
113 | colWidths: [30, 30] | 112 | colWidths: [30, 30] |
114 | }) | 113 | }) as CliTable3.HorizontalTable |
115 | 114 | ||
116 | settings.remotes.forEach(element => { | 115 | settings.remotes.forEach(element => { |
117 | if (!netrc.machines[element]) return | 116 | if (!netrc.machines[element]) return |
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts index e40606107..b341c14c1 100644 --- a/server/tools/peertube-plugins.ts +++ b/server/tools/peertube-plugins.ts | |||
@@ -3,15 +3,11 @@ registerTSPaths() | |||
3 | 3 | ||
4 | import * as program from 'commander' | 4 | import * as program from 'commander' |
5 | import { PluginType } from '../../shared/models/plugins/plugin.type' | 5 | import { PluginType } from '../../shared/models/plugins/plugin.type' |
6 | import { getAccessToken } from '../../shared/extra-utils/users/login' | ||
7 | import { getMyUserInformation } from '../../shared/extra-utils/users/users' | ||
8 | import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' | 6 | import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' |
9 | import { getServerCredentials } from './cli' | 7 | import { getAdminTokenOrDie, getServerCredentials } from './cli' |
10 | import { User, UserRole } from '../../shared/models/users' | ||
11 | import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' | 8 | import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' |
12 | import { isAbsolute } from 'path' | 9 | import { isAbsolute } from 'path' |
13 | 10 | import * as CliTable3 from 'cli-table3' | |
14 | const Table = require('cli-table') | ||
15 | 11 | ||
16 | program | 12 | program |
17 | .name('plugins') | 13 | .name('plugins') |
@@ -82,10 +78,10 @@ async function pluginsListCLI () { | |||
82 | }) | 78 | }) |
83 | const plugins: PeerTubePlugin[] = res.body.data | 79 | const plugins: PeerTubePlugin[] = res.body.data |
84 | 80 | ||
85 | const table = new Table({ | 81 | const table = new CliTable3({ |
86 | head: ['name', 'version', 'homepage'], | 82 | head: ['name', 'version', 'homepage'], |
87 | colWidths: [ 50, 10, 50 ] | 83 | colWidths: [ 50, 10, 50 ] |
88 | }) | 84 | }) as CliTable3.HorizontalTable |
89 | 85 | ||
90 | for (const plugin of plugins) { | 86 | for (const plugin of plugins) { |
91 | const npmName = plugin.type === PluginType.PLUGIN | 87 | const npmName = plugin.type === PluginType.PLUGIN |
@@ -192,16 +188,3 @@ async function uninstallPluginCLI (options: any) { | |||
192 | console.log('Plugin uninstalled.') | 188 | console.log('Plugin uninstalled.') |
193 | process.exit(0) | 189 | process.exit(0) |
194 | } | 190 | } |
195 | |||
196 | async function getAdminTokenOrDie (url: string, username: string, password: string) { | ||
197 | const accessToken = await getAccessToken(url, username, password) | ||
198 | const resMe = await getMyUserInformation(url, accessToken) | ||
199 | const me: User = resMe.body | ||
200 | |||
201 | if (me.role !== UserRole.ADMINISTRATOR) { | ||
202 | console.error('Cannot list plugins if you are not administrator.') | ||
203 | process.exit(-1) | ||
204 | } | ||
205 | |||
206 | return accessToken | ||
207 | } | ||
diff --git a/server/tools/peertube-redundancy.ts b/server/tools/peertube-redundancy.ts new file mode 100644 index 000000000..a71f48104 --- /dev/null +++ b/server/tools/peertube-redundancy.ts | |||
@@ -0,0 +1,194 @@ | |||
1 | import { registerTSPaths } from '../helpers/register-ts-paths' | ||
2 | registerTSPaths() | ||
3 | |||
4 | import * as program from 'commander' | ||
5 | import { getAdminTokenOrDie, getServerCredentials } from './cli' | ||
6 | import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' | ||
7 | import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy } from '@shared/extra-utils/server/redundancy' | ||
8 | import validator from 'validator' | ||
9 | import bytes = require('bytes') | ||
10 | import * as CliTable3 from 'cli-table3' | ||
11 | import { parse } from 'url' | ||
12 | import { uniq } from 'lodash' | ||
13 | |||
14 | program | ||
15 | .name('plugins') | ||
16 | .usage('[command] [options]') | ||
17 | |||
18 | program | ||
19 | .command('list-remote-redundancies') | ||
20 | .description('List remote redundancies on your videos') | ||
21 | .option('-u, --url <url>', 'Server url') | ||
22 | .option('-U, --username <username>', 'Username') | ||
23 | .option('-p, --password <token>', 'Password') | ||
24 | .action(() => listRedundanciesCLI('my-videos')) | ||
25 | |||
26 | program | ||
27 | .command('list-my-redundancies') | ||
28 | .description('List your redundancies of remote videos') | ||
29 | .option('-u, --url <url>', 'Server url') | ||
30 | .option('-U, --username <username>', 'Username') | ||
31 | .option('-p, --password <token>', 'Password') | ||
32 | .action(() => listRedundanciesCLI('remote-videos')) | ||
33 | |||
34 | program | ||
35 | .command('add') | ||
36 | .description('Duplicate a video in your redundancy system') | ||
37 | .option('-u, --url <url>', 'Server url') | ||
38 | .option('-U, --username <username>', 'Username') | ||
39 | .option('-p, --password <token>', 'Password') | ||
40 | .option('-v, --video <videoId>', 'Video id to duplicate') | ||
41 | .action((options) => addRedundancyCLI(options)) | ||
42 | |||
43 | program | ||
44 | .command('remove') | ||
45 | .description('Remove a video from your redundancies') | ||
46 | .option('-u, --url <url>', 'Server url') | ||
47 | .option('-U, --username <username>', 'Username') | ||
48 | .option('-p, --password <token>', 'Password') | ||
49 | .option('-v, --video <videoId>', 'Video id to remove from redundancies') | ||
50 | .action((options) => removeRedundancyCLI(options)) | ||
51 | |||
52 | if (!process.argv.slice(2).length) { | ||
53 | program.outputHelp() | ||
54 | } | ||
55 | |||
56 | program.parse(process.argv) | ||
57 | |||
58 | // ---------------------------------------------------------------------------- | ||
59 | |||
60 | async function listRedundanciesCLI (target: VideoRedundanciesTarget) { | ||
61 | const { url, username, password } = await getServerCredentials(program) | ||
62 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
63 | |||
64 | const redundancies = await listVideoRedundanciesData(url, accessToken, target) | ||
65 | |||
66 | const table = new CliTable3({ | ||
67 | head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ] | ||
68 | }) as CliTable3.HorizontalTable | ||
69 | |||
70 | for (const redundancy of redundancies) { | ||
71 | const webtorrentFiles = redundancy.redundancies.files | ||
72 | const streamingPlaylists = redundancy.redundancies.streamingPlaylists | ||
73 | |||
74 | let totalSize = '' | ||
75 | if (target === 'remote-videos') { | ||
76 | const tmp = webtorrentFiles.concat(streamingPlaylists) | ||
77 | .reduce((a, b) => a + b.size, 0) | ||
78 | |||
79 | totalSize = bytes(tmp) | ||
80 | } | ||
81 | |||
82 | const instances = uniq( | ||
83 | webtorrentFiles.concat(streamingPlaylists) | ||
84 | .map(r => r.fileUrl) | ||
85 | .map(u => parse(u).host) | ||
86 | ) | ||
87 | |||
88 | table.push([ | ||
89 | redundancy.id.toString(), | ||
90 | redundancy.name, | ||
91 | redundancy.url, | ||
92 | webtorrentFiles.length, | ||
93 | streamingPlaylists.length, | ||
94 | instances.join('\n'), | ||
95 | totalSize | ||
96 | ]) | ||
97 | } | ||
98 | |||
99 | console.log(table.toString()) | ||
100 | process.exit(0) | ||
101 | } | ||
102 | |||
103 | async function addRedundancyCLI (options: { videoId: number }) { | ||
104 | const { url, username, password } = await getServerCredentials(program) | ||
105 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
106 | |||
107 | if (!options[ 'video' ] || validator.isInt('' + options[ 'video' ]) === false) { | ||
108 | console.error('You need to specify the video id to duplicate and it should be a number.\n') | ||
109 | program.outputHelp() | ||
110 | process.exit(-1) | ||
111 | } | ||
112 | |||
113 | try { | ||
114 | await addVideoRedundancy({ | ||
115 | url, | ||
116 | accessToken, | ||
117 | videoId: options[ 'video' ] | ||
118 | }) | ||
119 | |||
120 | console.log('Video will be duplicated by your instance!') | ||
121 | |||
122 | process.exit(0) | ||
123 | } catch (err) { | ||
124 | if (err.message.includes(409)) { | ||
125 | console.error('This video is already duplicated by your instance.') | ||
126 | } else if (err.message.includes(404)) { | ||
127 | console.error('This video id does not exist.') | ||
128 | } else { | ||
129 | console.error(err) | ||
130 | } | ||
131 | |||
132 | process.exit(-1) | ||
133 | } | ||
134 | } | ||
135 | |||
136 | async function removeRedundancyCLI (options: { videoId: number }) { | ||
137 | const { url, username, password } = await getServerCredentials(program) | ||
138 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
139 | |||
140 | if (!options[ 'video' ] || validator.isInt('' + options[ 'video' ]) === false) { | ||
141 | console.error('You need to specify the video id to remove from your redundancies.\n') | ||
142 | program.outputHelp() | ||
143 | process.exit(-1) | ||
144 | } | ||
145 | |||
146 | const videoId = parseInt(options[ 'video' ] + '', 10) | ||
147 | |||
148 | let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos') | ||
149 | let videoRedundancy = redundancies.find(r => videoId === r.id) | ||
150 | |||
151 | if (!videoRedundancy) { | ||
152 | redundancies = await listVideoRedundanciesData(url, accessToken, 'remote-videos') | ||
153 | videoRedundancy = redundancies.find(r => videoId === r.id) | ||
154 | } | ||
155 | |||
156 | if (!videoRedundancy) { | ||
157 | console.error('Video redundancy not found.') | ||
158 | process.exit(-1) | ||
159 | } | ||
160 | |||
161 | try { | ||
162 | const ids = videoRedundancy.redundancies.files | ||
163 | .concat(videoRedundancy.redundancies.streamingPlaylists) | ||
164 | .map(r => r.id) | ||
165 | |||
166 | for (const id of ids) { | ||
167 | await removeVideoRedundancy({ | ||
168 | url, | ||
169 | accessToken, | ||
170 | redundancyId: id | ||
171 | }) | ||
172 | } | ||
173 | |||
174 | console.log('Video redundancy removed!') | ||
175 | |||
176 | process.exit(0) | ||
177 | } catch (err) { | ||
178 | console.error(err) | ||
179 | process.exit(-1) | ||
180 | } | ||
181 | } | ||
182 | |||
183 | async function listVideoRedundanciesData (url: string, accessToken: string, target: VideoRedundanciesTarget) { | ||
184 | const res = await listVideoRedundancies({ | ||
185 | url, | ||
186 | accessToken, | ||
187 | start: 0, | ||
188 | count: 100, | ||
189 | sort: 'name', | ||
190 | target | ||
191 | }) | ||
192 | |||
193 | return res.body.data as VideoRedundancy[] | ||
194 | } | ||
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts index fc85c4210..9883bbf05 100644 --- a/server/tools/peertube.ts +++ b/server/tools/peertube.ts | |||
@@ -22,6 +22,7 @@ program | |||
22 | .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') | 22 | .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') |
23 | .command('repl', 'initiate a REPL to access internals') | 23 | .command('repl', 'initiate a REPL to access internals') |
24 | .command('plugins [action]', 'manage instance plugins/themes').alias('p') | 24 | .command('plugins [action]', 'manage instance plugins/themes').alias('p') |
25 | .command('redundancy [action]', 'manage instance redundancies').alias('r') | ||
25 | 26 | ||
26 | /* Not Yet Implemented */ | 27 | /* Not Yet Implemented */ |
27 | program | 28 | program |
diff --git a/server/tools/yarn.lock b/server/tools/yarn.lock index 28756cbc2..ccd716a51 100644 --- a/server/tools/yarn.lock +++ b/server/tools/yarn.lock | |||
@@ -347,12 +347,15 @@ chunk-store-stream@^4.0.0: | |||
347 | block-stream2 "^2.0.0" | 347 | block-stream2 "^2.0.0" |
348 | readable-stream "^3.4.0" | 348 | readable-stream "^3.4.0" |
349 | 349 | ||
350 | cli-table@^0.3.1: | 350 | cli-table3@^0.5.1: |
351 | version "0.3.1" | 351 | version "0.5.1" |
352 | resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" | 352 | resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" |
353 | integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM= | 353 | integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw== |
354 | dependencies: | 354 | dependencies: |
355 | colors "1.0.3" | 355 | object-assign "^4.1.0" |
356 | string-width "^2.1.1" | ||
357 | optionalDependencies: | ||
358 | colors "^1.1.2" | ||
356 | 359 | ||
357 | clivas@^0.2.0: | 360 | clivas@^0.2.0: |
358 | version "0.2.0" | 361 | version "0.2.0" |
@@ -364,10 +367,10 @@ code-point-at@^1.0.0: | |||
364 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" | 367 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" |
365 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= | 368 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= |
366 | 369 | ||
367 | colors@1.0.3: | 370 | colors@^1.1.2: |
368 | version "1.0.3" | 371 | version "1.4.0" |
369 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" | 372 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" |
370 | integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= | 373 | integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== |
371 | 374 | ||
372 | common-tags@^1.8.0: | 375 | common-tags@^1.8.0: |
373 | version "1.8.0" | 376 | version "1.8.0" |
@@ -1609,7 +1612,7 @@ string-width@^1.0.1: | |||
1609 | is-fullwidth-code-point "^1.0.0" | 1612 | is-fullwidth-code-point "^1.0.0" |
1610 | strip-ansi "^3.0.0" | 1613 | strip-ansi "^3.0.0" |
1611 | 1614 | ||
1612 | "string-width@^1.0.2 || 2": | 1615 | "string-width@^1.0.2 || 2", string-width@^2.1.1: |
1613 | version "2.1.1" | 1616 | version "2.1.1" |
1614 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" | 1617 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" |
1615 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== | 1618 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== |