install.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  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: "flag", checked: true },
  323. { name: "header", checked: true },
  324. { name: "icon", checked: true },
  325. { name: "image", checked: true },
  326. { name: "input", checked: true },
  327. { name: "label", checked: true },
  328. { name: "list", checked: true },
  329. { name: "loader", checked: true },
  330. { name: "rail", checked: true },
  331. { name: "reveal", checked: true },
  332. { name: "segment", checked: true },
  333. { name: "step", checked: true },
  334. { name: "breadcrumb", checked: true },
  335. { name: "form", checked: true },
  336. { name: "grid", checked: true },
  337. { name: "menu", checked: true },
  338. { name: "message", checked: true },
  339. { name: "table", checked: true },
  340. { name: "ad", checked: true },
  341. { name: "card", checked: true },
  342. { name: "comment", checked: true },
  343. { name: "feed", checked: true },
  344. { name: "item", checked: true },
  345. { name: "statistic", checked: true },
  346. { name: "accordion", checked: true },
  347. { name: "calendar", checked: true },
  348. { name: "checkbox", checked: true },
  349. { name: "dimmer", checked: true },
  350. { name: "dropdown", checked: true },
  351. { name: "embed", checked: true },
  352. { name: "modal", checked: true },
  353. { name: "nag", checked: true },
  354. { name: "placeholder", checked: true },
  355. { name: "popup", checked: true },
  356. { name: "progress", checked: true },
  357. { name: "slider", checked: true },
  358. { name: "rating", checked: true },
  359. { name: "search", checked: true },
  360. { name: "shape", checked: true },
  361. { name: "sidebar", checked: true },
  362. { name: "sticky", checked: true },
  363. { name: "tab", checked: true },
  364. { name: "text", checked: true },
  365. { name: "toast", checked: true },
  366. { name: "transition", checked: true },
  367. { name: "api", checked: true },
  368. { name: "form", checked: true },
  369. { name: "state", checked: true },
  370. { name: "visibility", checked: true }
  371. ],
  372. when: when.notAuto
  373. },
  374. {
  375. type: 'list',
  376. name: 'changePermissions',
  377. when: when.notAuto,
  378. message: 'Should we set permissions on outputted files?',
  379. choices: [
  380. {
  381. name: 'No',
  382. value: false
  383. },
  384. {
  385. name: 'Yes',
  386. value: true
  387. }
  388. ]
  389. },
  390. {
  391. type: 'input',
  392. name: 'permission',
  393. message: 'What octal file permission should outputted files receive?',
  394. default: defaults.permission,
  395. when: when.changePermissions
  396. },
  397. {
  398. type: 'list',
  399. name: 'rtl',
  400. message: 'Do you use a RTL (Right-To-Left) language?',
  401. when: when.notAuto,
  402. choices: [
  403. {
  404. name: 'No',
  405. value: false
  406. },
  407. {
  408. name: 'Yes',
  409. value: true
  410. },
  411. {
  412. name: 'Build Both',
  413. value: 'both'
  414. }
  415. ]
  416. },
  417. {
  418. type: 'input',
  419. name: 'dist',
  420. message: 'Where should we output Semantic UI?',
  421. default: defaults.paths.output.packaged,
  422. filter: filter.removeTrailingSlash,
  423. when: when.express
  424. },
  425. {
  426. type: 'input',
  427. name: 'site',
  428. message: 'Where should we put your site folder?',
  429. default: defaults.paths.source.site,
  430. filter: filter.removeTrailingSlash,
  431. when: when.custom
  432. },
  433. {
  434. type: 'input',
  435. name: 'packaged',
  436. message: 'Where should we output a packaged version?',
  437. default: defaults.paths.output.packaged,
  438. filter: filter.removeTrailingSlash,
  439. when: when.custom
  440. },
  441. {
  442. type: 'input',
  443. name: 'compressed',
  444. message: 'Where should we output compressed components?',
  445. default: defaults.paths.output.compressed,
  446. filter: filter.removeTrailingSlash,
  447. when: when.custom
  448. },
  449. {
  450. type: 'input',
  451. name: 'uncompressed',
  452. message: 'Where should we output uncompressed components?',
  453. default: defaults.paths.output.uncompressed,
  454. filter: filter.removeTrailingSlash,
  455. when: when.custom
  456. }
  457. ],
  458. cleanup: [
  459. {
  460. type: 'list',
  461. name: 'cleanup',
  462. message: 'Should we remove set-up files?',
  463. choices: [
  464. {
  465. name: 'Yes (re-install will require redownloading semantic).',
  466. value: 'yes'
  467. },
  468. {
  469. name: 'No Thanks',
  470. value: 'no'
  471. }
  472. ]
  473. },
  474. {
  475. type: 'list',
  476. name: 'build',
  477. message: 'Do you want to build Semantic now?',
  478. choices: [
  479. {
  480. name: 'Yes',
  481. value: 'yes'
  482. },
  483. {
  484. name: 'No',
  485. value: 'no'
  486. }
  487. ]
  488. },
  489. ],
  490. site: [
  491. {
  492. type: 'list',
  493. name: 'customize',
  494. message: 'You have not yet customized your site, can we help you do that?',
  495. choices: [
  496. {
  497. name: 'Yes, ask me a few questions',
  498. value: true
  499. },
  500. {
  501. name: 'No I\'ll do it myself',
  502. value: false
  503. }
  504. ]
  505. },
  506. {
  507. type: 'list',
  508. name: 'headerFont',
  509. message: 'Select your header font',
  510. choices: [
  511. {
  512. name: 'Helvetica Neue, Arial, sans-serif',
  513. value: 'Helvetica Neue, Arial, sans-serif;'
  514. },
  515. {
  516. name: 'Lato (Google Fonts)',
  517. value: 'Lato'
  518. },
  519. {
  520. name: 'Open Sans (Google Fonts)',
  521. value: 'Open Sans'
  522. },
  523. {
  524. name: 'Source Sans Pro (Google Fonts)',
  525. value: 'Source Sans Pro'
  526. },
  527. {
  528. name: 'Droid (Google Fonts)',
  529. value: 'Droid'
  530. },
  531. {
  532. name: 'I\'ll choose on my own',
  533. value: false
  534. }
  535. ],
  536. when: when.customize
  537. },
  538. {
  539. type: 'list',
  540. name: 'pageFont',
  541. message: 'Select your page font',
  542. choices: [
  543. {
  544. name: 'Helvetica Neue, Arial, sans-serif',
  545. value: 'Helvetica Neue, Arial, sans-serif;'
  546. },
  547. {
  548. name: 'Lato (Import from Google Fonts)',
  549. value: 'Lato'
  550. },
  551. {
  552. name: 'Open Sans (Import from Google Fonts)',
  553. value: 'Open Sans'
  554. },
  555. {
  556. name: 'Source Sans Pro (Import from Google Fonts)',
  557. value: 'Source Sans Pro'
  558. },
  559. {
  560. name: 'Droid (Google Fonts)',
  561. value: 'Droid'
  562. },
  563. {
  564. name: 'I\'ll choose on my own',
  565. value: false
  566. }
  567. ],
  568. when: when.customize
  569. },
  570. {
  571. type: 'list',
  572. name: 'fontSize',
  573. message: 'Select your base font size',
  574. default: '14px',
  575. choices: [
  576. {
  577. name: '12px',
  578. },
  579. {
  580. name: '13px',
  581. },
  582. {
  583. name: '14px (Recommended)',
  584. value: '14px'
  585. },
  586. {
  587. name: '15px',
  588. },
  589. {
  590. name: '16px',
  591. },
  592. {
  593. name: 'I\'ll choose on my own',
  594. value: false
  595. }
  596. ],
  597. when: when.customize
  598. },
  599. {
  600. type: 'list',
  601. name: 'primaryColor',
  602. message: 'Select the closest name for your primary brand color',
  603. default: '14px',
  604. choices: [
  605. {
  606. name: 'Blue'
  607. },
  608. {
  609. name: 'Green'
  610. },
  611. {
  612. name: 'Orange'
  613. },
  614. {
  615. name: 'Pink'
  616. },
  617. {
  618. name: 'Purple'
  619. },
  620. {
  621. name: 'Red'
  622. },
  623. {
  624. name: 'Teal'
  625. },
  626. {
  627. name: 'Yellow'
  628. },
  629. {
  630. name: 'Black'
  631. },
  632. {
  633. name: 'I\'ll choose on my own',
  634. value: false
  635. }
  636. ],
  637. when: when.customize
  638. },
  639. {
  640. type: 'input',
  641. name: 'PrimaryHex',
  642. message: 'Enter a hexcode for your primary brand color',
  643. when: when.primaryColor
  644. },
  645. {
  646. type: 'list',
  647. name: 'secondaryColor',
  648. message: 'Select the closest name for your secondary brand color',
  649. default: '14px',
  650. choices: [
  651. {
  652. name: 'Blue'
  653. },
  654. {
  655. name: 'Green'
  656. },
  657. {
  658. name: 'Orange'
  659. },
  660. {
  661. name: 'Pink'
  662. },
  663. {
  664. name: 'Purple'
  665. },
  666. {
  667. name: 'Red'
  668. },
  669. {
  670. name: 'Teal'
  671. },
  672. {
  673. name: 'Yellow'
  674. },
  675. {
  676. name: 'Black'
  677. },
  678. {
  679. name: 'I\'ll choose on my own',
  680. value: false
  681. }
  682. ],
  683. when: when.customize
  684. },
  685. {
  686. type: 'input',
  687. name: 'secondaryHex',
  688. message: 'Enter a hexcode for your secondary brand color',
  689. when: when.secondaryColor
  690. }
  691. ]
  692. },
  693. settings: {
  694. /* Rename Files */
  695. rename: {
  696. json : { extname : '.json' }
  697. },
  698. /* Copy Install Folders */
  699. wrench: {
  700. // overwrite existing files update & install (default theme / definition)
  701. overwrite: {
  702. forceDelete : true,
  703. excludeHiddenUnix : true,
  704. preserveFiles : false
  705. },
  706. // only create files that don't exist (site theme update)
  707. merge: {
  708. forceDelete : false,
  709. excludeHiddenUnix : true,
  710. preserveFiles : true
  711. }
  712. }
  713. }
  714. };