init.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. var mout = require('mout');
  2. var fs = require('graceful-fs');
  3. var path = require('path');
  4. var Q = require('q');
  5. var endpointParser = require('bower-endpoint-parser');
  6. var Project = require('../core/Project');
  7. var defaultConfig = require('../config');
  8. var GitHubResolver = require('../core/resolvers/GitHubResolver');
  9. var GitFsResolver = require('../core/resolvers/GitFsResolver');
  10. var cli = require('../util/cli');
  11. var cmd = require('../util/cmd');
  12. var createError = require('../util/createError');
  13. function init(logger, config) {
  14. var project;
  15. config = defaultConfig(config);
  16. // This command requires interactive to be enabled
  17. if (!config.interactive) {
  18. process.nextTick(function () {
  19. logger.emit('error', createError('Register requires an interactive shell', 'ENOINT', {
  20. details: 'Note that you can manually force an interactive shell with --config.interactive'
  21. }));
  22. });
  23. return logger;
  24. }
  25. project = new Project(config, logger);
  26. // Start with existing JSON details
  27. return readJson(project, logger)
  28. // Fill in defaults
  29. .then(setDefaults.bind(null, config))
  30. // Now prompt user to make changes
  31. .then(promptUser.bind(null, logger))
  32. // Set ignore based on the response
  33. .spread(setIgnore.bind(null, config))
  34. // Set dependencies based on the response
  35. .spread(setDependencies.bind(null, project))
  36. // All done!
  37. .spread(saveJson.bind(null, project, logger));
  38. }
  39. function readJson(project, logger) {
  40. return project.hasJson()
  41. .then(function (json) {
  42. if (json) {
  43. logger.warn('existing', 'The existing ' + path.basename(json) + ' file will be used and filled in');
  44. }
  45. return project.getJson();
  46. });
  47. }
  48. function saveJson(project, logger, json) {
  49. // Cleanup empty props (null values, empty strings, objects and arrays)
  50. mout.object.forOwn(json, function (value, key) {
  51. if (value == null || mout.lang.isEmpty(value)) {
  52. delete json[key];
  53. }
  54. });
  55. logger.info('json', 'Generated json', { json: json });
  56. // Confirm the json with the user
  57. return Q.nfcall(logger.prompt.bind(logger), {
  58. type: 'confirm',
  59. message: 'Looks good?',
  60. default: true
  61. })
  62. .then(function (good) {
  63. if (!good) {
  64. return null;
  65. }
  66. // Save json (true forces file creation)
  67. return project.saveJson(true);
  68. });
  69. }
  70. function setDefaults(config, json) {
  71. var name;
  72. var promise = Q.resolve();
  73. // Name
  74. if (!json.name) {
  75. json.name = path.basename(config.cwd);
  76. }
  77. // Version
  78. if (!json.version) {
  79. // Assume latest semver tag if it's a git repo
  80. promise = promise.then(function () {
  81. return GitFsResolver.versions(config.cwd)
  82. .then(function (versions) {
  83. json.version = versions[0] || '0.0.0';
  84. }, function () {
  85. json.version = '0.0.0';
  86. });
  87. });
  88. }
  89. // Main
  90. if (!json.main) {
  91. // Remove '.js' from the end of the package name if it is there
  92. name = path.basename(json.name, '.js');
  93. if (fs.existsSync(path.join(config.cwd, 'index.js'))) {
  94. json.main = 'index.js';
  95. } else if (fs.existsSync(path.join(config.cwd, name + '.js'))) {
  96. json.main = name + '.js';
  97. }
  98. }
  99. // Homepage
  100. if (!json.homepage) {
  101. // Set as GitHub homepage if it's a GitHub repository
  102. promise = promise.then(function () {
  103. return cmd('git', ['config', '--get', 'remote.origin.url'])
  104. .spread(function (stdout) {
  105. var pair;
  106. stdout = stdout.trim();
  107. if (!stdout) {
  108. return;
  109. }
  110. pair = GitHubResolver.getOrgRepoPair(stdout);
  111. if (pair) {
  112. json.homepage = 'https://github.com/' + pair.org + '/' + pair.repo;
  113. }
  114. })
  115. .fail(function () { });
  116. });
  117. }
  118. if (!json.authors) {
  119. promise = promise.then(function () {
  120. // Get the user name configured in git
  121. return cmd('git', ['config', '--get', '--global', 'user.name'])
  122. .spread(function (stdout) {
  123. var gitEmail;
  124. var gitName = stdout.trim();
  125. // Abort if no name specified
  126. if (!gitName) {
  127. return;
  128. }
  129. // Get the user email configured in git
  130. return cmd('git', ['config', '--get', '--global', 'user.email'])
  131. .spread(function (stdout) {
  132. gitEmail = stdout.trim();
  133. }, function () {})
  134. .then(function () {
  135. json.authors = gitName;
  136. json.authors += gitEmail ? ' <' + gitEmail + '>' : '';
  137. });
  138. }, function () {});
  139. });
  140. }
  141. return promise.then(function () {
  142. return json;
  143. });
  144. }
  145. function promptUser(logger, json) {
  146. var questions = [
  147. {
  148. 'name': 'name',
  149. 'message': 'name',
  150. 'default': json.name,
  151. 'type': 'input'
  152. },
  153. {
  154. 'name': 'version',
  155. 'message': 'version',
  156. 'default': json.version,
  157. 'type': 'input'
  158. },
  159. {
  160. 'name': 'description',
  161. 'message': 'description',
  162. 'default': json.description,
  163. 'type': 'input'
  164. },
  165. {
  166. 'name': 'main',
  167. 'message': 'main file',
  168. 'default': json.main,
  169. 'type': 'input'
  170. },
  171. {
  172. 'name': 'moduleType',
  173. 'message': 'what types of modules does this package expose?',
  174. 'type': 'checkbox',
  175. 'choices': ['amd', 'es6', 'globals', 'node', 'yui']
  176. },
  177. {
  178. 'name': 'keywords',
  179. 'message': 'keywords',
  180. 'default': json.keywords ? json.keywords.toString() : null,
  181. 'type': 'input'
  182. },
  183. {
  184. 'name': 'authors',
  185. 'message': 'authors',
  186. 'default': json.authors ? json.authors.toString() : null,
  187. 'type': 'input'
  188. },
  189. {
  190. 'name': 'license',
  191. 'message': 'license',
  192. 'default': json.license || 'MIT',
  193. 'type': 'input'
  194. },
  195. {
  196. 'name': 'homepage',
  197. 'message': 'homepage',
  198. 'default': json.homepage,
  199. 'type': 'input'
  200. },
  201. {
  202. 'name': 'dependencies',
  203. 'message': 'set currently installed components as dependencies?',
  204. 'default': !mout.object.size(json.dependencies) && !mout.object.size(json.devDependencies),
  205. 'type': 'confirm'
  206. },
  207. {
  208. 'name': 'ignore',
  209. 'message': 'add commonly ignored files to ignore list?',
  210. 'default': true,
  211. 'type': 'confirm'
  212. },
  213. {
  214. 'name': 'private',
  215. 'message': 'would you like to mark this package as private which prevents it from being accidentally published to the registry?',
  216. 'default': !!json.private,
  217. 'type': 'confirm'
  218. }
  219. ];
  220. return Q.nfcall(logger.prompt.bind(logger), questions)
  221. .then(function (answers) {
  222. json.name = answers.name;
  223. json.version = answers.version;
  224. json.description = answers.description;
  225. json.main = answers.main;
  226. json.moduleType = answers.moduleType;
  227. json.keywords = toArray(answers.keywords);
  228. json.authors = toArray(answers.authors, ',');
  229. json.license = answers.license;
  230. json.homepage = answers.homepage;
  231. json.private = answers.private || null;
  232. return [json, answers];
  233. });
  234. }
  235. function toArray(value, splitter) {
  236. var arr = value.split(splitter || /[\s,]/);
  237. // Trim values
  238. arr = arr.map(function (item) {
  239. return item.trim();
  240. });
  241. // Filter empty values
  242. arr = arr.filter(function (item) {
  243. return !!item;
  244. });
  245. return arr.length ? arr : null;
  246. }
  247. function setIgnore(config, json, answers) {
  248. if (answers.ignore) {
  249. json.ignore = mout.array.combine(json.ignore || [], [
  250. '**/.*',
  251. 'node_modules',
  252. 'bower_components',
  253. config.directory,
  254. 'test',
  255. 'tests'
  256. ]);
  257. }
  258. return [json, answers];
  259. }
  260. function setDependencies(project, json, answers) {
  261. if (answers.dependencies) {
  262. return project.getTree()
  263. .spread(function (tree, flattened, extraneous) {
  264. if (extraneous.length) {
  265. json.dependencies = {};
  266. // Add extraneous as dependencies
  267. extraneous.forEach(function (extra) {
  268. var jsonEndpoint;
  269. // Skip linked packages
  270. if (extra.linked) {
  271. return;
  272. }
  273. jsonEndpoint = endpointParser.decomposed2json(extra.endpoint);
  274. mout.object.mixIn(json.dependencies, jsonEndpoint);
  275. });
  276. }
  277. return [json, answers];
  278. });
  279. }
  280. return [json, answers];
  281. }
  282. // -------------------
  283. init.line = function (logger, argv) {
  284. var options = cli.readOptions(argv);
  285. return init(logger, options);
  286. };
  287. init.completion = function () {
  288. // TODO:
  289. };
  290. module.exports = init;