1 | #!/usr/bin/env node
|
2 |
|
3 | const path = require('path');
|
4 | const fs = require('fs');
|
5 | const program = require('commander');
|
6 | const npm = require('npm');
|
7 | const ini = require('ini');
|
8 | const echo = require('node-echo');
|
9 | const extend = require('extend');
|
10 | const open = require('open');
|
11 | const async = require('async');
|
12 | const request = require('request');
|
13 | const only = require('only');
|
14 |
|
15 | const registries = require('./registries.json');
|
16 | const PKG = require('./package.json');
|
17 | const NRMRC = path.join(process.env.HOME, '.nrmrc');
|
18 |
|
19 | const REGISTRY_ATTRS = [];
|
20 | const FIELD_AUTH = '_auth';
|
21 | const FIELD_ALWAYS_AUTH = 'always-auth';
|
22 | const FIELD_IS_CURRENT = 'is-current';
|
23 | const FIELD_REPOSITORY = 'repository';
|
24 | const IGNORED_ATTRS = [FIELD_IS_CURRENT, FIELD_REPOSITORY];
|
25 |
|
26 |
|
27 | program
|
28 | .version(PKG.version);
|
29 |
|
30 | program
|
31 | .command('ls')
|
32 | .description('List all the registries')
|
33 | .action(onList);
|
34 |
|
35 | program
|
36 | .command('current')
|
37 | .description('Show current registry name')
|
38 | .action(showCurrent);
|
39 |
|
40 | program
|
41 | .command('use <registry>')
|
42 | .description('Change registry to registry')
|
43 | .action(onUse);
|
44 |
|
45 | program
|
46 | .command('add <registry> <url> [home]')
|
47 | .description('Add one custom registry')
|
48 | .action(onAdd);
|
49 |
|
50 | program
|
51 | .command('set-auth <registry> [value]')
|
52 | .option('-a, --always-auth', 'Set is always auth')
|
53 | .option('-u, --username <username>', 'Your user name for this registry')
|
54 | .option('-p, --password <password>', 'Your password for this registry')
|
55 | .description('Set authorize information for a custom registry with a base64 encoded string or username and pasword')
|
56 | .action(onSetAuth);
|
57 |
|
58 | program
|
59 | .command('set-email <registry> <value>')
|
60 | .description('Set email for a custom registry')
|
61 | .action(onSetEmail);
|
62 |
|
63 | program
|
64 | .command('set-hosted-repo <registry> <value>')
|
65 | .description('Set hosted npm repository for a custom registry to publish packages')
|
66 | .action(onSetRepository);
|
67 |
|
68 | program
|
69 | .command('del <registry>')
|
70 | .description('Delete one custom registry')
|
71 | .action(onDel);
|
72 |
|
73 | program
|
74 | .command('home <registry> [browser]')
|
75 | .description('Open the homepage of registry with optional browser')
|
76 | .action(onHome);
|
77 |
|
78 | program
|
79 | .command('publish [<tarball>|<folder>]')
|
80 | .option('-t, --tag [tag]', 'Add tag')
|
81 | .option('-a, --access <public|restricted>', 'Set access')
|
82 | .option('-o, --otp [otpcode]', 'Set otpcode')
|
83 | .option('-dr, --dry-run', 'Set is dry run')
|
84 | .description('Publish package to current registry if current registry is a custom registry.\n if you\'re not using custom registry, this command will run npm publish directly')
|
85 | .action(onPublish);
|
86 |
|
87 | program
|
88 | .command('test [registry]')
|
89 | .description('Show response time for specific or all registries')
|
90 | .action(onTest);
|
91 |
|
92 | program
|
93 | .command('help', { isDefault: true })
|
94 | .description('Print this help')
|
95 | .action(function() {
|
96 | program.outputHelp();
|
97 | });
|
98 |
|
99 | program
|
100 | .parse(process.argv);
|
101 |
|
102 |
|
103 | if (process.argv.length === 2) {
|
104 | program.outputHelp();
|
105 | }
|
106 |
|
107 |
|
108 |
|
109 | function onList() {
|
110 | getCurrentRegistry(function(cur) {
|
111 | var info = [''];
|
112 | var allRegistries = getAllRegistry();
|
113 |
|
114 | Object.keys(allRegistries).forEach(function(key) {
|
115 | var item = allRegistries[key];
|
116 | var prefix = item.registry === cur ? '* ' : ' ';
|
117 | info.push(prefix + key + line(key, 12) + item.registry);
|
118 | });
|
119 |
|
120 | info.push('');
|
121 | printMsg(info);
|
122 | });
|
123 | }
|
124 |
|
125 | function showCurrent() {
|
126 | getCurrentRegistry(function(cur) {
|
127 | var allRegistries = getAllRegistry();
|
128 | Object.keys(allRegistries).forEach(function(key) {
|
129 | var item = allRegistries[key];
|
130 | if (item.registry === cur) {
|
131 | printMsg([key]);
|
132 | return;
|
133 | }
|
134 | });
|
135 | });
|
136 | }
|
137 |
|
138 | function config(attrArray, registry, index = 0) {
|
139 | return new Promise((resolve, reject) => {
|
140 | const attr = attrArray[index];
|
141 | const command = registry.hasOwnProperty(attr) ? ['set', attr, String(registry[attr])] : ['delete', attr];
|
142 | npm.commands.config(command, function(err, data) {
|
143 | return err ? reject(err) : resolve(index + 1);
|
144 | });
|
145 | }).then(next => {
|
146 | if (next < attrArray.length) {
|
147 | return config(attrArray, registry, next);
|
148 | } else {
|
149 | return Promise.resolve();
|
150 | }
|
151 | });
|
152 | }
|
153 |
|
154 | function onUse(name) {
|
155 | var allRegistries = getAllRegistry();
|
156 | if (allRegistries.hasOwnProperty(name)) {
|
157 | var registry = allRegistries[name];
|
158 | npm.load(function(err) {
|
159 | if (err) return exit(err);
|
160 | const attrs = [].concat(REGISTRY_ATTRS);
|
161 | for (let attr in registry) {
|
162 | if (!REGISTRY_ATTRS.includes(attr) && !IGNORED_ATTRS.includes(attr)) {
|
163 | attrs.push(attr);
|
164 | }
|
165 | }
|
166 | config(attrs, registry).then(() => {
|
167 | console.log(' ');
|
168 | var newR = npm.config.get('registry');
|
169 | var customRegistries = getCustomRegistry();
|
170 | Object.keys(customRegistries).forEach(key => {
|
171 | delete customRegistries[key][FIELD_IS_CURRENT];
|
172 | });
|
173 | if (customRegistries.hasOwnProperty(name) && customRegistries[name].registry === registry.registry) {
|
174 | registry[FIELD_IS_CURRENT] = true;
|
175 | customRegistries[name] = registry;
|
176 | }
|
177 | setCustomRegistry(customRegistries);
|
178 | printMsg(['', ' Registry has been set to: ' + newR, '']);
|
179 | }).catch(err => {
|
180 | exit(err);
|
181 | });
|
182 | });
|
183 | } else {
|
184 | printMsg(['', ' Not find registry: ' + name, '']);
|
185 | }
|
186 | }
|
187 |
|
188 | function onDel(name) {
|
189 | var customRegistries = getCustomRegistry();
|
190 | if (!customRegistries.hasOwnProperty(name)) return;
|
191 | getCurrentRegistry(function(cur) {
|
192 | if (cur === customRegistries[name].registry) {
|
193 | onUse('npm');
|
194 | }
|
195 | delete customRegistries[name];
|
196 | setCustomRegistry(customRegistries, function(err) {
|
197 | if (err) return exit(err);
|
198 | printMsg(['', ' delete registry ' + name + ' success', '']);
|
199 | });
|
200 | });
|
201 | }
|
202 |
|
203 | function onAdd(name, url, home) {
|
204 | var customRegistries = getCustomRegistry();
|
205 | if (customRegistries.hasOwnProperty(name)) return;
|
206 | var config = customRegistries[name] = {};
|
207 | if (url[url.length - 1] !== '/') url += '/';
|
208 | config.registry = url;
|
209 | if (home) {
|
210 | config.home = home;
|
211 | }
|
212 | setCustomRegistry(customRegistries, function(err) {
|
213 | if (err) return exit(err);
|
214 | printMsg(['', ' add registry ' + name + ' success', '']);
|
215 | });
|
216 | }
|
217 |
|
218 | function onSetAuth(registry, value, cmd) {
|
219 | const customRegistries = getCustomRegistry();
|
220 | if (!customRegistries.hasOwnProperty(registry)) return;
|
221 | const config = customRegistries[registry];
|
222 | const attrs = [FIELD_AUTH];
|
223 | if (value) {
|
224 | config[FIELD_AUTH] = value;
|
225 | } else if (cmd.username && cmd.password) {
|
226 | config[FIELD_AUTH] = new Buffer(`${cmd.username}:${cmd.password}`).toString('base64');
|
227 | } else {
|
228 | return exit(new Error('your username & password or auth value is required'));
|
229 | }
|
230 | if (cmd[FIELD_ALWAYS_AUTH]) {
|
231 | config[FIELD_ALWAYS_AUTH] = true;
|
232 | attrs.push(FIELD_ALWAYS_AUTH);
|
233 | }
|
234 | new Promise(resolve => {
|
235 | config[FIELD_IS_CURRENT] ? resolve(config(attrs, config)) : resolve();
|
236 | }).then(() => {
|
237 | customRegistries[registry] = config;
|
238 | setCustomRegistry(customRegistries, function(err) {
|
239 | if (err) return exit(err);
|
240 | printMsg(['', ' set authorize info to registry ' + registry + ' success', '']);
|
241 | });
|
242 | }).catch(exit);
|
243 | }
|
244 |
|
245 | function onSetEmail(registry, value) {
|
246 | const customRegistries = getCustomRegistry();
|
247 | if (!customRegistries.hasOwnProperty(registry)) return;
|
248 | const config = customRegistries[registry];
|
249 | config.email = value;
|
250 | new Promise(resolve => {
|
251 | config[FIELD_IS_CURRENT] ? resolve(config(['email'], config)) : resolve();
|
252 | }).then(() => {
|
253 | customRegistries[registry] = config;
|
254 | setCustomRegistry(customRegistries, function(err) {
|
255 | if (err) return exit(err);
|
256 | printMsg(['', ' set email to registry ' + registry + ' success', '']);
|
257 | });
|
258 | }).catch(exit);
|
259 | }
|
260 |
|
261 | function onSetRepository(registry, value) {
|
262 | var customRegistries = getCustomRegistry();
|
263 | if (!customRegistries.hasOwnProperty(registry)) return;
|
264 | var config = customRegistries[registry];
|
265 | config[FIELD_REPOSITORY] = value;
|
266 | new Promise(resolve => {
|
267 | config[FIELD_IS_CURRENT] ? resolve(config([FIELD_REPOSITORY], config)) : resolve();
|
268 | }).then(() => {
|
269 | setCustomRegistry(customRegistries, function(err) {
|
270 | if (err) return exit(err);
|
271 | printMsg(['', ` set ${FIELD_REPOSITORY} to registry [${registry}] success`, '']);
|
272 | });
|
273 | }).catch(exit);
|
274 | }
|
275 |
|
276 | function onHome(name, browser) {
|
277 | var allRegistries = getAllRegistry();
|
278 | var home = allRegistries[name] && allRegistries[name].home;
|
279 | if (home) {
|
280 | var args = [home];
|
281 | if (browser) args.push(browser);
|
282 | open.apply(null, args);
|
283 | }
|
284 | }
|
285 |
|
286 | function onPublish(tarballOrFolder, cmd) {
|
287 | getCurrentRegistry(registry => {
|
288 | const customRegistries = getCustomRegistry();
|
289 | let currentRegistry;
|
290 |
|
291 | Object.keys(customRegistries).forEach(function(key) {
|
292 | const item = customRegistries[key];
|
293 | if (item.registry === registry && item['is-current']) {
|
294 | currentRegistry = item;
|
295 | currentRegistry.name = key;
|
296 | }
|
297 | });
|
298 | const attrs = ['registry'];
|
299 | let command = '> npm publish';
|
300 | const optionData = {};
|
301 | cmd.options.forEach(option => {
|
302 | const opt = option.long.substring(2);
|
303 | const optionValue = cmd[opt];
|
304 | if (optionValue) {
|
305 | optionData[opt] = cmd[opt];
|
306 | attrs.push(opt);
|
307 | }
|
308 | });
|
309 | new Promise((resolve, reject) => {
|
310 | if (currentRegistry) {
|
311 | if (currentRegistry[FIELD_REPOSITORY]) {
|
312 | printMsg(['',
|
313 | ' current registry is a custom registry, publish to custom repository.',
|
314 | ''
|
315 | ]);
|
316 | optionData.registry = currentRegistry[FIELD_REPOSITORY];
|
317 | command += ` --registry ${currentRegistry[FIELD_REPOSITORY]}`;
|
318 | Object.keys(optionData).forEach((key) => {
|
319 | command += ` --${key} ${optionData[key]}`;
|
320 | });
|
321 | printMsg([command]);
|
322 | resolve(config(attrs, optionData));
|
323 | } else {
|
324 | reject(new Error(` current using registry [${currentRegistry.name}] has no ${FIELD_REPOSITORY} field, can't execute publish.`));
|
325 | }
|
326 | } else {
|
327 | printMsg(['',
|
328 | ' current using registry is not a custom registry, will publish to npm official repository.',
|
329 | ''
|
330 | ]);
|
331 | optionData.registry = registries.npm.registry;
|
332 |
|
333 | Object.keys(registries).forEach(function(key) {
|
334 | const item = registries[key];
|
335 | if (item.registry === registry) {
|
336 | currentRegistry = item;
|
337 | currentRegistry.name = key;
|
338 | }
|
339 | });
|
340 | printMsg([command]);
|
341 | resolve(config(attrs, optionData));
|
342 | }
|
343 | }).then(() => {
|
344 | const callback = (err) => {
|
345 | config(attrs, currentRegistry).then(() => {
|
346 | err && exit(err);
|
347 | printMsg(['', ` published to registry ${currentRegistry[FIELD_REPOSITORY]} successfully.`, '']);
|
348 | }).catch(exit);
|
349 | };
|
350 | try {
|
351 | tarballOrFolder ? npm.publish(tarballOrFolder, callback) : npm.publish(callback);
|
352 | } catch (e) {
|
353 | callback(err);
|
354 | }
|
355 | }).catch(err => {
|
356 | printErr(err);
|
357 | });
|
358 | });
|
359 | }
|
360 |
|
361 | function onTest(registry) {
|
362 | var allRegistries = getAllRegistry();
|
363 |
|
364 | var toTest;
|
365 |
|
366 | if (registry) {
|
367 | if (!allRegistries.hasOwnProperty(registry)) {
|
368 | return;
|
369 | }
|
370 | toTest = only(allRegistries, registry);
|
371 | } else {
|
372 | toTest = allRegistries;
|
373 | }
|
374 |
|
375 | async.map(Object.keys(toTest), function(name, cbk) {
|
376 | var registry = toTest[name];
|
377 | var start = +new Date();
|
378 | request(registry.registry + 'pedding', function(error) {
|
379 | cbk(null, {
|
380 | name: name,
|
381 | registry: registry.registry,
|
382 | time: (+new Date() - start),
|
383 | error: error ? true : false
|
384 | });
|
385 | });
|
386 | }, function(err, results) {
|
387 | getCurrentRegistry(function(cur) {
|
388 | var msg = [''];
|
389 | results.forEach(function(result) {
|
390 | var prefix = result.registry === cur ? '* ' : ' ';
|
391 | var suffix = result.error ? 'Fetch Error' : result.time + 'ms';
|
392 | msg.push(prefix + result.name + line(result.name, 8) + suffix);
|
393 | });
|
394 | msg.push('');
|
395 | printMsg(msg);
|
396 | });
|
397 | });
|
398 | }
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 | function getCurrentRegistry(cbk) {
|
408 | npm.load(function(err, conf) {
|
409 | if (err) return exit(err);
|
410 | cbk(npm.config.get('registry'));
|
411 | });
|
412 | }
|
413 |
|
414 | function getCustomRegistry() {
|
415 | return fs.existsSync(NRMRC) ? ini.parse(fs.readFileSync(NRMRC, 'utf-8')) : {};
|
416 | }
|
417 |
|
418 | function setCustomRegistry(config, cbk) {
|
419 | echo(ini.stringify(config), '>', NRMRC, cbk);
|
420 | }
|
421 |
|
422 | function getAllRegistry() {
|
423 | return extend({}, registries, getCustomRegistry());
|
424 | }
|
425 |
|
426 | function printErr(err) {
|
427 | console.error('an error occured: ' + err);
|
428 | }
|
429 |
|
430 | function printMsg(infos) {
|
431 | infos.forEach(function(info) {
|
432 | console.log(info);
|
433 | });
|
434 | }
|
435 |
|
436 |
|
437 |
|
438 |
|
439 | function exit(err) {
|
440 | printErr(err);
|
441 | process.exit(1);
|
442 | }
|
443 |
|
444 | function line(str, len) {
|
445 | var line = new Array(Math.max(1, len - str.length)).join('-');
|
446 | return ' ' + line + ' ';
|
447 | }
|