configurations.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. const fs = require('fs-extra')
  2. const path = require('path')
  3. const yaml = require('js-yaml')
  4. const clone = require('clone')
  5. const stringifyJS = require('javascript-stringify')
  6. // Connectors
  7. const cwd = require('./cwd')
  8. const plugins = require('./plugins')
  9. const folders = require('./folders')
  10. const prompts = require('./prompts')
  11. // Utils
  12. const { get, set, unset, loadModule } = require('@vue/cli-shared-utils')
  13. const { log } = require('../util/logger')
  14. const extendJSConfig = require('@vue/cli/lib/util/extendJSConfig')
  15. const fileTypes = ['js', 'json', 'yaml']
  16. let current = {}
  17. function list (context) {
  18. return plugins.getApi(cwd.get()).configurations
  19. }
  20. function findOne (id, context) {
  21. return list(context).find(
  22. c => c.id === id
  23. )
  24. }
  25. function findFile (fileDescriptor, context) {
  26. if (fileDescriptor.package) {
  27. const pkg = folders.readPackage(cwd.get(), context)
  28. const data = pkg[fileDescriptor.package]
  29. if (data) {
  30. return { type: 'package', path: path.join(cwd.get(), 'package.json') }
  31. }
  32. }
  33. for (const type of fileTypes) {
  34. const files = fileDescriptor[type]
  35. if (files) {
  36. for (const file of files) {
  37. const resolvedFile = path.resolve(cwd.get(), file)
  38. if (fs.existsSync(resolvedFile)) {
  39. return { type, path: resolvedFile }
  40. }
  41. }
  42. }
  43. }
  44. }
  45. function getDefaultFile (fileDescriptor, context) {
  46. const keys = Object.keys(fileDescriptor)
  47. if (keys.length) {
  48. for (const key of keys) {
  49. if (key !== 'package') {
  50. const file = fileDescriptor[key][0]
  51. return {
  52. type: key,
  53. path: path.resolve(cwd.get(), file)
  54. }
  55. }
  56. }
  57. }
  58. }
  59. function readFile (config, fileDescriptor, context) {
  60. const file = findFile(fileDescriptor, context)
  61. let fileData = {}
  62. if (file) {
  63. if (file.type === 'package') {
  64. const pkg = folders.readPackage(cwd.get(), context)
  65. fileData = pkg[fileDescriptor.package]
  66. } else if (file.type === 'js') {
  67. fileData = loadModule(file.path, cwd.get(), true)
  68. } else {
  69. const rawContent = fs.readFileSync(file.path, { encoding: 'utf8' })
  70. if (file.type === 'json') {
  71. fileData = JSON.parse(rawContent)
  72. } else if (file.type === 'yaml') {
  73. fileData = yaml.safeLoad(rawContent)
  74. }
  75. }
  76. }
  77. return {
  78. file,
  79. fileData
  80. }
  81. }
  82. function readData (config, context) {
  83. const data = {}
  84. config.foundFiles = {}
  85. if (!config.files) return data
  86. for (const fileId in config.files) {
  87. const fileDescriptor = config.files[fileId]
  88. const { file, fileData } = readFile(config, fileDescriptor, context)
  89. config.foundFiles[fileId] = file
  90. data[fileId] = fileData
  91. }
  92. return data
  93. }
  94. function writeFile (config, fileId, data, changedFields, context) {
  95. const fileDescriptor = config.files[fileId]
  96. let file = findFile(fileDescriptor, context)
  97. if (!file) {
  98. file = getDefaultFile(fileDescriptor, context)
  99. }
  100. if (!file) return
  101. log('Config write', config.id, data, changedFields, file.path)
  102. fs.ensureFileSync(file.path)
  103. let rawContent
  104. if (file.type === 'package') {
  105. const pkg = folders.readPackage(cwd.get(), context)
  106. pkg[fileDescriptor.package] = data
  107. rawContent = JSON.stringify(pkg, null, 2)
  108. } else {
  109. if (file.type === 'json') {
  110. rawContent = JSON.stringify(data, null, 2)
  111. } else if (file.type === 'yaml') {
  112. rawContent = yaml.safeDump(data)
  113. } else if (file.type === 'js') {
  114. const source = fs.readFileSync(file.path, { encoding: 'utf8' })
  115. if (!source.trim()) {
  116. rawContent = `module.exports = ${stringifyJS(data, null, 2)}`
  117. } else {
  118. const changedData = changedFields.reduce((obj, field) => {
  119. obj[field] = data[field]
  120. return obj
  121. }, {})
  122. rawContent = extendJSConfig(changedData, source)
  123. }
  124. }
  125. }
  126. fs.writeFileSync(file.path, rawContent, { encoding: 'utf8' })
  127. }
  128. function writeData ({ config, data, changedFields }, context) {
  129. for (const fileId in data) {
  130. writeFile(config, fileId, data[fileId], changedFields[fileId], context)
  131. }
  132. }
  133. async function getPromptTabs (id, context) {
  134. const config = findOne(id, context)
  135. if (config) {
  136. const data = readData(config, context)
  137. log('Config read', config.id, data)
  138. current = {
  139. config,
  140. data
  141. }
  142. // API
  143. const onReadData = await config.onRead({
  144. cwd: cwd.get(),
  145. data
  146. })
  147. let tabs = onReadData.tabs
  148. if (!tabs) {
  149. tabs = [
  150. {
  151. id: '__default',
  152. label: 'Default',
  153. prompts: onReadData.prompts
  154. }
  155. ]
  156. }
  157. await prompts.reset()
  158. for (const tab of tabs) {
  159. tab.prompts = tab.prompts.map(data => prompts.add({
  160. ...data,
  161. tabId: tab.id
  162. }))
  163. }
  164. if (onReadData.answers) {
  165. await prompts.setAnswers(onReadData.answers)
  166. }
  167. await prompts.start()
  168. plugins.callHook({
  169. id: 'configRead',
  170. args: [{
  171. config,
  172. data,
  173. onReadData,
  174. tabs,
  175. cwd: cwd.get()
  176. }],
  177. file: cwd.get()
  178. }, context)
  179. return tabs
  180. }
  181. return []
  182. }
  183. async function save (id, context) {
  184. const config = findOne(id, context)
  185. if (config) {
  186. if (current.config === config) {
  187. const answers = prompts.getAnswers()
  188. const data = clone(current.data)
  189. const changedFields = {}
  190. const getChangedFields = fileId => changedFields[fileId] || (changedFields[fileId] = [])
  191. // API
  192. await config.onWrite({
  193. prompts: prompts.list(),
  194. answers,
  195. data: current.data,
  196. files: config.foundFiles,
  197. cwd: cwd.get(),
  198. api: {
  199. assignData: (fileId, newData) => {
  200. getChangedFields(fileId).push(...Object.keys(newData))
  201. Object.assign(data[fileId], newData)
  202. },
  203. setData: (fileId, newData) => {
  204. Object.keys(newData).forEach(key => {
  205. let field = key
  206. const dotIndex = key.indexOf('.')
  207. if (dotIndex !== -1) {
  208. field = key.substr(0, dotIndex)
  209. }
  210. getChangedFields(fileId).push(field)
  211. const value = newData[key]
  212. if (typeof value === 'undefined') {
  213. unset(data[fileId], key)
  214. } else {
  215. set(data[fileId], key, value)
  216. }
  217. })
  218. },
  219. getAnswer: async (id, mapper) => {
  220. const prompt = prompts.findOne(id)
  221. if (prompt) {
  222. const defaultValue = await prompts.getDefaultValue(prompt)
  223. if (defaultValue !== prompt.rawValue) {
  224. let value = get(answers, prompt.id)
  225. if (mapper) {
  226. value = mapper(value)
  227. }
  228. return value
  229. }
  230. }
  231. }
  232. }
  233. })
  234. writeData({ config, data, changedFields }, context)
  235. plugins.callHook({
  236. id: 'configWrite',
  237. args: [{
  238. config,
  239. data,
  240. changedFields,
  241. cwd: cwd.get()
  242. }],
  243. file: cwd.get()
  244. }, context)
  245. current = {}
  246. }
  247. }
  248. return config
  249. }
  250. function cancel (id, context) {
  251. const config = findOne(id, context)
  252. if (config) {
  253. current = {}
  254. }
  255. return config
  256. }
  257. module.exports = {
  258. list,
  259. findOne,
  260. getPromptTabs,
  261. save,
  262. cancel
  263. }