install.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*******************************
  2. * Install Task
  3. *******************************/
  4. /*
  5. Install tasks
  6. For more notes
  7. * Runs automatically after npm update (hooks)
  8. * (NPM) Install - Will ask for where to put semantic (outside pm folder)
  9. * (NPM) Upgrade - Will look for semantic install, copy over files and update if new version
  10. * Standard installer runs asking for paths to site files etc
  11. */
  12. var
  13. gulp = require('gulp'),
  14. // node dependencies
  15. console = require('better-console'),
  16. extend = require('extend'),
  17. fs = require('fs'),
  18. mkdirp = require('mkdirp'),
  19. path = require('path'),
  20. // gulp dependencies
  21. chmod = require('gulp-chmod'),
  22. del = require('del'),
  23. jsonEditor = require('gulp-json-editor'),
  24. plumber = require('gulp-plumber'),
  25. inquirer = require('inquirer'),
  26. rename = require('gulp-rename'),
  27. replace = require('gulp-replace'),
  28. requireDotFile = require('require-dot-file'),
  29. wrench = require('wrench-sui'),
  30. // install config
  31. install = require('./config/project/install'),
  32. // user config
  33. config = require('./config/user'),
  34. // release config (name/title/etc)
  35. release = require('./config/project/release'),
  36. // shorthand
  37. questions = install.questions,
  38. files = install.files,
  39. folders = install.folders,
  40. regExp = install.regExp,
  41. settings = install.settings,
  42. source = install.source
  43. ;
  44. // Export install task
  45. module.exports = function (callback) {
  46. var
  47. currentConfig = requireDotFile('semantic.json'),
  48. manager = install.getPackageManager(),
  49. rootQuestions = questions.root,
  50. installFolder = false,
  51. answers
  52. ;
  53. console.clear();
  54. /* Test NPM install
  55. manager = {
  56. name : 'NPM',
  57. root : path.normalize(__dirname + '/../')
  58. };
  59. */
  60. /* Don't do end user config if SUI is a sub-module */
  61. if (install.isSubModule()) {
  62. console.info('SUI is a sub-module, skipping end-user install');
  63. return;
  64. }
  65. /*-----------------
  66. Update SUI
  67. -----------------*/
  68. // run update scripts if semantic.json exists
  69. if (currentConfig && manager.name === 'NPM') {
  70. var
  71. updateFolder = path.join(manager.root, currentConfig.base),
  72. updatePaths = {
  73. config : path.join(manager.root, files.config),
  74. tasks : path.join(updateFolder, folders.tasks),
  75. themeImport : path.join(updateFolder, folders.themeImport),
  76. definition : path.join(currentConfig.paths.source.definitions),
  77. site : path.join(currentConfig.paths.source.site),
  78. theme : path.join(currentConfig.paths.source.themes),
  79. defaultTheme: path.join(currentConfig.paths.source.themes, folders.defaultTheme)
  80. }
  81. ;
  82. // duck-type if there is a project installed
  83. if (fs.existsSync(updatePaths.definition)) {
  84. // perform update if new version
  85. if (currentConfig.version !== release.version) {
  86. console.log('Updating Semantic UI from ' + currentConfig.version + ' to ' + release.version);
  87. console.info('Updating ui definitions...');
  88. wrench.copyDirSyncRecursive(source.definitions, updatePaths.definition, settings.wrench.overwrite);
  89. console.info('Updating default theme...');
  90. wrench.copyDirSyncRecursive(source.themes, updatePaths.theme, settings.wrench.merge);
  91. wrench.copyDirSyncRecursive(source.defaultTheme, updatePaths.defaultTheme, settings.wrench.overwrite);
  92. console.info('Updating tasks...');
  93. wrench.copyDirSyncRecursive(source.tasks, updatePaths.tasks, settings.wrench.overwrite);
  94. console.info('Updating gulpfile.js');
  95. gulp.src(source.userGulpFile)
  96. .pipe(plumber())
  97. .pipe(gulp.dest(updateFolder))
  98. ;
  99. // copy theme import
  100. console.info('Updating theme import file');
  101. gulp.src(source.themeImport)
  102. .pipe(plumber())
  103. .pipe(gulp.dest(updatePaths.themeImport))
  104. ;
  105. console.info('Adding new site theme files...');
  106. wrench.copyDirSyncRecursive(source.site, updatePaths.site, settings.wrench.merge);
  107. console.info('Updating version...');
  108. // update version number in semantic.json
  109. gulp.src(updatePaths.config)
  110. .pipe(plumber())
  111. .pipe(rename(settings.rename.json)) // preserve file extension
  112. .pipe(jsonEditor({
  113. version: release.version
  114. }))
  115. .pipe(gulp.dest(manager.root))
  116. ;
  117. console.info('Update complete! Run "\x1b[92mgulp build\x1b[0m" to rebuild dist/ files.');
  118. callback();
  119. return;
  120. } else {
  121. console.log('Current version of Semantic UI already installed');
  122. callback();
  123. return;
  124. }
  125. } else {
  126. console.error('Cannot locate files to update at path: ', updatePaths.definition);
  127. console.log('Running installer');
  128. }
  129. }
  130. /*--------------
  131. Determine Root
  132. ---------------*/
  133. // PM that supports Build Tools (NPM Only Now)
  134. if (manager.name === 'NPM') {
  135. rootQuestions[0].message = rootQuestions[0].message
  136. .replace('{packageMessage}', 'We detected you are using ' + manager.name + ' Nice!')
  137. .replace('{root}', manager.root)
  138. ;
  139. // set default path to detected PM root
  140. rootQuestions[0].default = manager.root;
  141. rootQuestions[1].default = manager.root;
  142. // insert PM questions after "Install Type" question
  143. Array.prototype.splice.apply(questions.setup, [2, 0].concat(rootQuestions));
  144. // omit cleanup questions for managed install
  145. questions.cleanup = [];
  146. }
  147. /*--------------
  148. Create SUI
  149. ---------------*/
  150. gulp.task('run setup', function (callback) {
  151. // If auto-install is switched on, we skip the configuration section and simply reuse the configuration from semantic.json
  152. if (install.shouldAutoInstall()) {
  153. answers = {
  154. overwrite : 'yes',
  155. install : 'auto',
  156. useRoot : true,
  157. semanticRoot: currentConfig.base
  158. };
  159. callback();
  160. } else {
  161. return inquirer.prompt(questions.setup)
  162. .then((setupAnswers) => {
  163. // hoist
  164. answers = setupAnswers;
  165. });
  166. }
  167. });
  168. gulp.task('create install files', function (callback) {
  169. /*--------------
  170. Exit Conditions
  171. ---------------*/
  172. // if config exists and user specifies not to proceed
  173. if (answers.overwrite !== undefined && answers.overwrite == 'no') {
  174. callback();
  175. return;
  176. }
  177. console.clear();
  178. if (install.shouldAutoInstall()) {
  179. console.log('Auto-Installing (Without User Interaction)');
  180. } else {
  181. console.log('Installing');
  182. }
  183. console.log('------------------------------');
  184. /*--------------
  185. Paths
  186. ---------------*/
  187. var
  188. installPaths = {
  189. config : files.config,
  190. configFolder : folders.config,
  191. site : answers.site || folders.site,
  192. themeConfig : files.themeConfig,
  193. themeConfigFolder: folders.themeConfig
  194. }
  195. ;
  196. /*--------------
  197. NPM Install
  198. ---------------*/
  199. // Check if PM install
  200. if (manager && (answers.useRoot || answers.customRoot)) {
  201. // Set root to custom root path if set
  202. if (answers.customRoot) {
  203. if (answers.customRoot === '') {
  204. console.log('Unable to proceed, invalid project root');
  205. callback();
  206. return;
  207. }
  208. manager.root = answers.customRoot;
  209. }
  210. // special install paths only for PM install
  211. installPaths = extend(false, {}, installPaths, {
  212. definition : folders.definitions,
  213. lessImport : folders.lessImport,
  214. tasks : folders.tasks,
  215. theme : folders.themes,
  216. defaultTheme: path.join(folders.themes, folders.defaultTheme),
  217. themeImport : folders.themeImport
  218. });
  219. // add project root to semantic root
  220. installFolder = path.join(manager.root, answers.semanticRoot);
  221. // add install folder to all output paths
  222. for (var destination in installPaths) {
  223. if (installPaths.hasOwnProperty(destination)) {
  224. // config goes in project root, rest in install folder
  225. installPaths[destination] = (destination == 'config' || destination == 'configFolder')
  226. ? path.normalize(path.join(manager.root, installPaths[destination]))
  227. : path.normalize(path.join(installFolder, installPaths[destination]))
  228. ;
  229. }
  230. }
  231. // create project folders
  232. try {
  233. mkdirp.sync(installFolder);
  234. mkdirp.sync(installPaths.definition);
  235. mkdirp.sync(installPaths.theme);
  236. mkdirp.sync(installPaths.tasks);
  237. } catch (error) {
  238. console.error('NPM does not have permissions to create folders at your specified path. Adjust your folders permissions and run "npm install" again');
  239. }
  240. console.log('Installing to \x1b[92m' + answers.semanticRoot + '\x1b[0m');
  241. console.info('Copying UI definitions');
  242. wrench.copyDirSyncRecursive(source.definitions, installPaths.definition, settings.wrench.overwrite);
  243. console.info('Copying UI themes');
  244. wrench.copyDirSyncRecursive(source.themes, installPaths.theme, settings.wrench.merge);
  245. wrench.copyDirSyncRecursive(source.defaultTheme, installPaths.defaultTheme, settings.wrench.overwrite);
  246. console.info('Copying gulp tasks');
  247. wrench.copyDirSyncRecursive(source.tasks, installPaths.tasks, settings.wrench.overwrite);
  248. // copy theme import
  249. console.info('Adding theme files');
  250. gulp.src(source.themeImport)
  251. .pipe(plumber())
  252. .pipe(gulp.dest(installPaths.themeImport))
  253. ;
  254. gulp.src(source.lessImport)
  255. .pipe(plumber())
  256. .pipe(gulp.dest(installPaths.lessImport))
  257. ;
  258. // create gulp file
  259. console.info('Creating gulpfile.js');
  260. gulp.src(source.userGulpFile)
  261. .pipe(plumber())
  262. .pipe(gulp.dest(installFolder))
  263. ;
  264. }
  265. /*--------------
  266. Site Theme
  267. ---------------*/
  268. // Copy _site templates folder to destination
  269. if (fs.existsSync(installPaths.site)) {
  270. console.info('Site folder exists, merging files (no overwrite)', installPaths.site);
  271. } else {
  272. console.info('Creating site theme folder', installPaths.site);
  273. }
  274. wrench.copyDirSyncRecursive(source.site, installPaths.site, settings.wrench.merge);
  275. /*--------------
  276. Theme Config
  277. ---------------*/
  278. gulp.task('create theme.config', function () {
  279. var
  280. // determine path to site theme folder from theme config
  281. // force CSS path variable to use forward slashes for paths
  282. pathToSite = path.relative(path.resolve(installPaths.themeConfigFolder), path.resolve(installPaths.site)).replace(/\\/g, '/'),
  283. siteVariable = "@siteFolder : '" + pathToSite + "/';"
  284. ;
  285. // rewrite site variable in theme.less
  286. console.info('Adjusting @siteFolder to: ', pathToSite + '/');
  287. if (fs.existsSync(installPaths.themeConfig)) {
  288. console.info('Modifying src/theme.config (LESS config)', installPaths.themeConfig);
  289. return gulp.src(installPaths.themeConfig)
  290. .pipe(plumber())
  291. .pipe(replace(regExp.siteVariable, siteVariable))
  292. .pipe(gulp.dest(installPaths.themeConfigFolder))
  293. ;
  294. } else {
  295. console.info('Creating src/theme.config (LESS config)', installPaths.themeConfig);
  296. return gulp.src(source.themeConfig)
  297. .pipe(plumber())
  298. .pipe(rename({extname: ''}))
  299. .pipe(replace(regExp.siteVariable, siteVariable))
  300. .pipe(gulp.dest(installPaths.themeConfigFolder))
  301. ;
  302. }
  303. });
  304. /*--------------
  305. Semantic.json
  306. ---------------*/
  307. gulp.task('create semantic.json', function () {
  308. var
  309. jsonConfig = install.createJSON(answers)
  310. ;
  311. // adjust variables in theme.less
  312. if (fs.existsSync(installPaths.config)) {
  313. console.info('Extending config file (semantic.json)', installPaths.config);
  314. return gulp.src(installPaths.config)
  315. .pipe(plumber())
  316. .pipe(rename(settings.rename.json)) // preserve file extension
  317. .pipe(jsonEditor(jsonConfig))
  318. .pipe(gulp.dest(installPaths.configFolder))
  319. ;
  320. } else {
  321. console.info('Creating config file (semantic.json)', installPaths.config);
  322. return gulp.src(source.config)
  323. .pipe(plumber())
  324. .pipe(rename({extname: ''})) // remove .template from ext
  325. .pipe(jsonEditor(jsonConfig, {end_with_newline: true}))
  326. .pipe(gulp.dest(installPaths.configFolder))
  327. ;
  328. }
  329. });
  330. gulp.series('create theme.config', 'create semantic.json')(callback);
  331. });
  332. gulp.task('clean up install', function (callback) {
  333. // Completion Message
  334. if (installFolder && !install.shouldAutoInstall()) {
  335. console.log('\n Setup Complete! \n Installing Peer Dependencies. \x1b[0;31mPlease refrain from ctrl + c\x1b[0m... \n After completion navigate to \x1b[92m' + answers.semanticRoot + '\x1b[0m and run "\x1b[92mgulp build\x1b[0m" to build');
  336. callback();
  337. } else {
  338. console.log('');
  339. console.log('');
  340. // If auto-install is switched on, we skip the configuration section and simply build the dependencies
  341. if (install.shouldAutoInstall()) {
  342. gulp.series('build')(callback);
  343. } else {
  344. // We don't return the inquirer promise on purpose because we handle the callback ourselves
  345. inquirer.prompt(questions.cleanup)
  346. .then((answers) => {
  347. if (answers.cleanup === 'yes') {
  348. del(install.setupFiles);
  349. }
  350. if (answers.build === 'yes') {
  351. gulp.series('build')(callback);
  352. } else {
  353. callback();
  354. }
  355. });
  356. }
  357. }
  358. });
  359. gulp.series('run setup', 'create install files', 'clean up install')(callback);
  360. };