install.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. /*******************************
  2. Set-up
  3. *******************************/
  4. var
  5. fs = require('fs'),
  6. path = require('path'),
  7. defaults = require('../defaults'),
  8. release = require('./release'),
  9. requireDotFile = require('require-dot-file')
  10. ;
  11. /*******************************
  12. When to Ask
  13. *******************************/
  14. /* Preconditions for install questions */
  15. var when = {
  16. // path
  17. changeRoot: function(questions) {
  18. return (questions.useRoot !== undefined && questions.useRoot !== true);
  19. },
  20. // permissions
  21. changePermissions: function(questions) {
  22. return (questions.changePermissions && questions.changePermissions === true);
  23. },
  24. // install
  25. hasConfig: function() {
  26. return requireDotFile('semantic.json', process.cwd());
  27. },
  28. allowOverwrite: function(questions) {
  29. return (questions.overwrite === undefined || questions.overwrite == 'yes');
  30. },
  31. notAuto: function(questions) {
  32. return (questions.install !== 'auto' && (questions.overwrite === undefined || questions.overwrite == 'yes'));
  33. },
  34. custom: function(questions) {
  35. return (questions.install === 'custom' && (questions.overwrite === undefined || questions.overwrite == 'yes'));
  36. },
  37. express: function(questions) {
  38. return (questions.install === 'express' && (questions.overwrite === undefined || questions.overwrite == 'yes'));
  39. },
  40. // customize
  41. customize: function(questions) {
  42. return (questions.customize === true);
  43. },
  44. primaryColor: function(questions) {
  45. return (questions.primaryColor);
  46. },
  47. secondaryColor: function(questions) {
  48. return (questions.secondaryColor);
  49. }
  50. };
  51. /*******************************
  52. Response Filters
  53. *******************************/
  54. /* Filters to user input from install questions */
  55. var filter = {
  56. removeTrailingSlash: function(path) {
  57. return path.replace(/(\/$|\\$)+/mg, '');
  58. }
  59. };
  60. /*******************************
  61. Configuration
  62. *******************************/
  63. module.exports = {
  64. // check whether install is setup
  65. isSetup: function() {
  66. return when.hasConfig();
  67. },
  68. // detect whether there is a semantic.json configuration and that the auto-install option is set to true
  69. shouldAutoInstall: function() {
  70. var
  71. config = when.hasConfig()
  72. ;
  73. return config['autoInstall'];
  74. },
  75. // checks if files are in a PM directory
  76. getPackageManager: function(directory) {
  77. var
  78. // returns last matching result (avoid sub-module detection)
  79. walk = function(directory) {
  80. var
  81. pathArray = directory.split(path.sep),
  82. folder = pathArray[pathArray.length - 1],
  83. nextDirectory = path.join(directory, path.sep, '..')
  84. ;
  85. if( folder == 'bower_components') {
  86. return {
  87. name: 'Bower',
  88. root: nextDirectory
  89. };
  90. }
  91. else if(folder == 'node_modules') {
  92. return {
  93. name: 'NPM',
  94. root: nextDirectory
  95. };
  96. }
  97. else if(folder == 'composer') {
  98. return {
  99. name: 'Composer',
  100. root: nextDirectory
  101. };
  102. }
  103. if(path.resolve(directory) == path.resolve(nextDirectory)) {
  104. return false;
  105. }
  106. // recurse downward
  107. return walk(nextDirectory);
  108. }
  109. ;
  110. // start walk from current directory if none specified
  111. directory = directory || (__dirname + path.sep);
  112. return walk(directory);
  113. },
  114. // checks if files is PMed submodule
  115. isSubModule: function(directory) {
  116. var
  117. moduleFolders = 0,
  118. walk = function(directory) {
  119. var
  120. pathArray = directory.split(path.sep),
  121. folder = pathArray[pathArray.length - 2],
  122. nextDirectory = path.join(directory, path.sep, '..')
  123. ;
  124. if( folder == 'bower_components') {
  125. moduleFolders++;
  126. }
  127. else if(folder == 'node_modules') {
  128. moduleFolders++;
  129. }
  130. else if(folder == 'composer') {
  131. moduleFolders++;
  132. }
  133. if(path.resolve(directory) == path.resolve(nextDirectory)) {
  134. return (moduleFolders > 1);
  135. }
  136. // recurse downward
  137. return walk(nextDirectory);
  138. }
  139. ;
  140. // start walk from current directory if none specified
  141. directory = directory || (__dirname + path.sep);
  142. return walk(directory);
  143. },
  144. createJSON: function(answers) {
  145. var
  146. json = {
  147. paths: {
  148. source: {},
  149. output: {}
  150. }
  151. }
  152. ;
  153. // add components
  154. if(answers.components) {
  155. json.components = answers.components;
  156. }
  157. // add rtl choice
  158. if(answers.rtl) {
  159. json.rtl = answers.rtl;
  160. }
  161. // add permissions
  162. if(answers.permission) {
  163. json.permission = answers.permission;
  164. }
  165. // add path to semantic
  166. if(answers.semanticRoot) {
  167. json.base = path.normalize(answers.semanticRoot);
  168. }
  169. // record version number to avoid re-installing on same version
  170. json.version = release.version;
  171. // add dist folder paths
  172. if(answers.dist) {
  173. answers.dist = path.normalize(answers.dist);
  174. json.paths.output = {
  175. packaged : path.normalize(answers.dist + '/'),
  176. uncompressed : path.normalize(answers.dist + '/components/'),
  177. compressed : path.normalize(answers.dist + '/components/'),
  178. themes : path.normalize(answers.dist + '/themes/')
  179. };
  180. }
  181. // add site path
  182. if(answers.site) {
  183. json.paths.source.site = path.normalize(answers.site + '/');
  184. }
  185. if(answers.packaged) {
  186. json.paths.output.packaged = path.normalize(answers.packaged + '/');
  187. }
  188. if(answers.compressed) {
  189. json.paths.output.compressed = path.normalize(answers.compressed + '/');
  190. }
  191. if(answers.uncompressed) {
  192. json.paths.output.uncompressed = path.normalize(answers.uncompressed + '/');
  193. }
  194. return json;
  195. },
  196. // files cleaned up after install
  197. setupFiles: [
  198. './src/theme.config.example',
  199. './semantic.json.example',
  200. './src/_site'
  201. ],
  202. regExp: {
  203. // used to match siteFolder variable in theme.less
  204. siteVariable: /@siteFolder .*\'(.*)/mg
  205. },
  206. // source paths (when installing)
  207. source: {
  208. config : './semantic.json.example',
  209. definitions : './src/definitions',
  210. gulpFile : './gulpfile.js',
  211. lessImport : './src/semantic.less',
  212. site : './src/_site',
  213. tasks : './tasks',
  214. themeConfig : './src/theme.config.example',
  215. themeImport : './src/theme.less',
  216. themes : './src/themes',
  217. defaultTheme : './src/themes/default',
  218. userGulpFile : './tasks/config/npm/gulpfile.js'
  219. },
  220. // expected final filenames
  221. files: {
  222. config : 'semantic.json',
  223. lessImport : 'src/semantic.less',
  224. site : 'src/site',
  225. themeConfig : 'src/theme.config',
  226. themeImport : 'src/theme.less'
  227. },
  228. // folder paths to files relative to root
  229. folders: {
  230. config : './',
  231. definitions : 'src/definitions/',
  232. lessImport : 'src/',
  233. modules : 'node_modules/',
  234. site : 'src/site/',
  235. tasks : 'tasks/',
  236. themeConfig : 'src/',
  237. themeImport : 'src/',
  238. themes : 'src/themes/',
  239. defaultTheme : 'default/' // only path that is relative to another directory and not root
  240. },
  241. // questions asked during install
  242. questions: {
  243. root: [
  244. {
  245. type : 'list',
  246. name : 'useRoot',
  247. message :
  248. '{packageMessage} Is this your project folder? {root}',
  249. choices: [
  250. {
  251. name : 'Yes',
  252. value : true
  253. },
  254. {
  255. name : 'No, let me specify',
  256. value : false
  257. }
  258. ]
  259. },
  260. {
  261. type : 'input',
  262. name : 'customRoot',
  263. message : 'Please enter the absolute path to your project root',
  264. default : '/my/project/path',
  265. when : when.changeRoot
  266. },
  267. {
  268. type : 'input',
  269. name : 'semanticRoot',
  270. message : 'Where should we put Semantic UI inside your project?',
  271. default : 'semantic/'
  272. }
  273. ],
  274. setup: [
  275. {
  276. type: 'list',
  277. name: 'overwrite',
  278. message: 'It looks like you have a semantic.json file already.',
  279. when: when.hasConfig,
  280. choices: [
  281. {
  282. name: 'Yes, extend my current settings.',
  283. value: 'yes'
  284. },
  285. {
  286. name: 'Skip install',
  287. value: 'no'
  288. }
  289. ]
  290. },
  291. {
  292. type: 'list',
  293. name: 'install',
  294. message: 'Set-up Semantic UI',
  295. when: when.allowOverwrite,
  296. choices: [
  297. {
  298. name: 'Automatic (Use default locations and all components)',
  299. value: 'auto'
  300. },
  301. {
  302. name: 'Express (Set components and output folder)',
  303. value: 'express'
  304. },
  305. {
  306. name: 'Custom (Customize all src/dist values)',
  307. value: 'custom'
  308. }
  309. ]
  310. },
  311. {
  312. type: 'checkbox',
  313. name: 'components',
  314. message: 'What components should we include in the package?',
  315. // duplicated manually from tasks/defaults.js with additional property
  316. choices: [
  317. { name: "reset", checked: true },
  318. { name: "site", checked: true },
  319. { name: "button", checked: true },
  320. { name: "container", checked: true },
  321. { name: "divider", checked: true },
  322. { name: "emoji", checked: true },
  323. { name: "flag", checked: true },
  324. { name: "header", checked: true },
  325. { name: "icon", checked: true },
  326. { name: "image", checked: true },
  327. { name: "input", checked: true },
  328. { name: "label", checked: true },
  329. { name: "list", checked: true },
  330. { name: "loader", checked: true },
  331. { name: "rail", checked: true },
  332. { name: "reveal", checked: true },
  333. { name: "segment", checked: true },
  334. { name: "step", checked: true },
  335. { name: "breadcrumb", checked: true },
  336. { name: "form", checked: true },
  337. { name: "grid", checked: true },
  338. { name: "menu", checked: true },
  339. { name: "message", checked: true },
  340. { name: "table", checked: true },
  341. { name: "ad", checked: true },
  342. { name: "card", checked: true },
  343. { name: "comment", checked: true },
  344. { name: "feed", checked: true },
  345. { name: "item", checked: true },
  346. { name: "statistic", checked: true },
  347. { name: "accordion", checked: true },
  348. { name: "calendar", checked: true },
  349. { name: "checkbox", checked: true },
  350. { name: "dimmer", checked: true },
  351. { name: "dropdown", checked: true },
  352. { name: "embed", checked: true },
  353. { name: "modal", checked: true },
  354. { name: "nag", checked: true },
  355. { name: "placeholder", checked: true },
  356. { name: "popup", checked: true },
  357. { name: "progress", checked: true },
  358. { name: "slider", checked: true },
  359. { name: "rating", checked: true },
  360. { name: "search", checked: true },
  361. { name: "shape", checked: true },
  362. { name: "sidebar", checked: true },
  363. { name: "sticky", checked: true },
  364. { name: "tab", checked: true },
  365. { name: "text", checked: true },
  366. { name: "toast", checked: true },
  367. { name: "transition", checked: true },
  368. { name: "api", checked: true },
  369. { name: "form", checked: true },
  370. { name: "state", checked: true },
  371. { name: "visibility", checked: true }
  372. ],
  373. when: when.notAuto
  374. },
  375. {
  376. type: 'list',
  377. name: 'changePermissions',
  378. when: when.notAuto,
  379. message: 'Should we set permissions on outputted files?',
  380. choices: [
  381. {
  382. name: 'No',
  383. value: false
  384. },
  385. {
  386. name: 'Yes',
  387. value: true
  388. }
  389. ]
  390. },
  391. {
  392. type: 'input',
  393. name: 'permission',
  394. message: 'What octal file permission should outputted files receive?',
  395. default: defaults.permission,
  396. when: when.changePermissions
  397. },
  398. {
  399. type: 'list',
  400. name: 'rtl',
  401. message: 'Do you use a RTL (Right-To-Left) language?',
  402. when: when.notAuto,
  403. choices: [
  404. {
  405. name: 'No',
  406. value: false
  407. },
  408. {
  409. name: 'Yes',
  410. value: true
  411. },
  412. {
  413. name: 'Build Both',
  414. value: 'both'
  415. }
  416. ]
  417. },
  418. {
  419. type: 'input',
  420. name: 'dist',
  421. message: 'Where should we output Semantic UI?',
  422. default: defaults.paths.output.packaged,
  423. filter: filter.removeTrailingSlash,
  424. when: when.express
  425. },
  426. {
  427. type: 'input',
  428. name: 'site',
  429. message: 'Where should we put your site folder?',
  430. default: defaults.paths.source.site,
  431. filter: filter.removeTrailingSlash,
  432. when: when.custom
  433. },
  434. {
  435. type: 'input',
  436. name: 'packaged',
  437. message: 'Where should we output a packaged version?',
  438. default: defaults.paths.output.packaged,
  439. filter: filter.removeTrailingSlash,
  440. when: when.custom
  441. },
  442. {
  443. type: 'input',
  444. name: 'compressed',
  445. message: 'Where should we output compressed components?',
  446. default: defaults.paths.output.compressed,
  447. filter: filter.removeTrailingSlash,
  448. when: when.custom
  449. },
  450. {
  451. type: 'input',
  452. name: 'uncompressed',
  453. message: 'Where should we output uncompressed components?',
  454. default: defaults.paths.output.uncompressed,
  455. filter: filter.removeTrailingSlash,
  456. when: when.custom
  457. }
  458. ],
  459. cleanup: [
  460. {
  461. type: 'list',
  462. name: 'cleanup',
  463. message: 'Should we remove set-up files?',
  464. choices: [
  465. {
  466. name: 'Yes (re-install will require redownloading semantic).',
  467. value: 'yes'
  468. },
  469. {
  470. name: 'No Thanks',
  471. value: 'no'
  472. }
  473. ]
  474. },
  475. {
  476. type: 'list',
  477. name: 'build',
  478. message: 'Do you want to build Semantic now?',
  479. choices: [
  480. {
  481. name: 'Yes',
  482. value: 'yes'
  483. },
  484. {
  485. name: 'No',
  486. value: 'no'
  487. }
  488. ]
  489. },
  490. ],
  491. site: [
  492. {
  493. type: 'list',
  494. name: 'customize',
  495. message: 'You have not yet customized your site, can we help you do that?',
  496. choices: [
  497. {
  498. name: 'Yes, ask me a few questions',
  499. value: true
  500. },
  501. {
  502. name: 'No I\'ll do it myself',
  503. value: false
  504. }
  505. ]
  506. },
  507. {
  508. type: 'list',
  509. name: 'headerFont',
  510. message: 'Select your header font',
  511. choices: [
  512. {
  513. name: 'Helvetica Neue, Arial, sans-serif',
  514. value: 'Helvetica Neue, Arial, sans-serif;'
  515. },
  516. {
  517. name: 'Lato (Google Fonts)',
  518. value: 'Lato'
  519. },
  520. {
  521. name: 'Open Sans (Google Fonts)',
  522. value: 'Open Sans'
  523. },
  524. {
  525. name: 'Source Sans Pro (Google Fonts)',
  526. value: 'Source Sans Pro'
  527. },
  528. {
  529. name: 'Droid (Google Fonts)',
  530. value: 'Droid'
  531. },
  532. {
  533. name: 'I\'ll choose on my own',
  534. value: false
  535. }
  536. ],
  537. when: when.customize
  538. },
  539. {
  540. type: 'list',
  541. name: 'pageFont',
  542. message: 'Select your page font',
  543. choices: [
  544. {
  545. name: 'Helvetica Neue, Arial, sans-serif',
  546. value: 'Helvetica Neue, Arial, sans-serif;'
  547. },
  548. {
  549. name: 'Lato (Import from Google Fonts)',
  550. value: 'Lato'
  551. },
  552. {
  553. name: 'Open Sans (Import from Google Fonts)',
  554. value: 'Open Sans'
  555. },
  556. {
  557. name: 'Source Sans Pro (Import from Google Fonts)',
  558. value: 'Source Sans Pro'
  559. },
  560. {
  561. name: 'Droid (Google Fonts)',
  562. value: 'Droid'
  563. },
  564. {
  565. name: 'I\'ll choose on my own',
  566. value: false
  567. }
  568. ],
  569. when: when.customize
  570. },
  571. {
  572. type: 'list',
  573. name: 'fontSize',
  574. message: 'Select your base font size',
  575. default: '14px',
  576. choices: [
  577. {
  578. name: '12px',
  579. },
  580. {
  581. name: '13px',
  582. },
  583. {
  584. name: '14px (Recommended)',
  585. value: '14px'
  586. },
  587. {
  588. name: '15px',
  589. },
  590. {
  591. name: '16px',
  592. },
  593. {
  594. name: 'I\'ll choose on my own',
  595. value: false
  596. }
  597. ],
  598. when: when.customize
  599. },
  600. {
  601. type: 'list',
  602. name: 'primaryColor',
  603. message: 'Select the closest name for your primary brand color',
  604. default: '14px',
  605. choices: [
  606. {
  607. name: 'Blue'
  608. },
  609. {
  610. name: 'Green'
  611. },
  612. {
  613. name: 'Orange'
  614. },
  615. {
  616. name: 'Pink'
  617. },
  618. {
  619. name: 'Purple'
  620. },
  621. {
  622. name: 'Red'
  623. },
  624. {
  625. name: 'Teal'
  626. },
  627. {
  628. name: 'Yellow'
  629. },
  630. {
  631. name: 'Black'
  632. },
  633. {
  634. name: 'I\'ll choose on my own',
  635. value: false
  636. }
  637. ],
  638. when: when.customize
  639. },
  640. {
  641. type: 'input',
  642. name: 'PrimaryHex',
  643. message: 'Enter a hexcode for your primary brand color',
  644. when: when.primaryColor
  645. },
  646. {
  647. type: 'list',
  648. name: 'secondaryColor',
  649. message: 'Select the closest name for your secondary brand color',
  650. default: '14px',
  651. choices: [
  652. {
  653. name: 'Blue'
  654. },
  655. {
  656. name: 'Green'
  657. },
  658. {
  659. name: 'Orange'
  660. },
  661. {
  662. name: 'Pink'
  663. },
  664. {
  665. name: 'Purple'
  666. },
  667. {
  668. name: 'Red'
  669. },
  670. {
  671. name: 'Teal'
  672. },
  673. {
  674. name: 'Yellow'
  675. },
  676. {
  677. name: 'Black'
  678. },
  679. {
  680. name: 'I\'ll choose on my own',
  681. value: false
  682. }
  683. ],
  684. when: when.customize
  685. },
  686. {
  687. type: 'input',
  688. name: 'secondaryHex',
  689. message: 'Enter a hexcode for your secondary brand color',
  690. when: when.secondaryColor
  691. }
  692. ]
  693. },
  694. settings: {
  695. /* Rename Files */
  696. rename: {
  697. json : { extname : '.json' }
  698. },
  699. /* Copy Install Folders */
  700. wrench: {
  701. // overwrite existing files update & install (default theme / definition)
  702. overwrite: {
  703. forceDelete : true,
  704. excludeHiddenUnix : true,
  705. preserveFiles : false
  706. },
  707. // only create files that don't exist (site theme update)
  708. merge: {
  709. forceDelete : false,
  710. excludeHiddenUnix : true,
  711. preserveFiles : true
  712. }
  713. }
  714. }
  715. };