publish.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /* eslint-disable no-await-in-loop */
  2. /*
  3. * @Author: cheaz
  4. * @Date: 2024-08-01 16:27:18
  5. * @LastEditors: cheaz
  6. * @LastEditTime: 2024-08-12 22:09:47
  7. * @Description: 工程发布至私有仓库
  8. */
  9. const params = process.argv.slice(2)[2]; // 获取打包参数
  10. const fs = require('fs');
  11. const path = require('path');
  12. const fsPromise = require('fs').promises;
  13. const { exec } = require('child_process');
  14. const { Parser } = require('acorn');
  15. const vueParser = require('vue-eslint-parser');
  16. const { resolveConfig } = require('vite');
  17. let packageVersion = require('./package.json').version;
  18. let packageName = require('./package.json').name;
  19. const entryFile = require('./package.json').main;
  20. const packageDependencies = require('./package.json').dependencies;
  21. // 自定义文件夹列表
  22. const customPathList = [`src/${packageName}`, `src/views/${packageName}`];
  23. // 添加支持多模块打包
  24. packageName = params || packageName;
  25. params && (packageVersion = require(`./src/views/${params}/package.json`).version);
  26. const viteConfig = require('./vite.config.js');
  27. let aliasPlugin;
  28. let resolvPlugin;
  29. /**
  30. * 加载vite配置,从vite中获取alias和resolve插件
  31. */
  32. const getResolvePlugin = async () => {
  33. const vitePlugins = (await resolveConfig(viteConfig, 'build', 'production', 'production')).worker.plugins;
  34. aliasPlugin = vitePlugins.find(({ name }) => name === 'alias');
  35. resolvPlugin = vitePlugins.find(({ name }) => name === 'vite:resolve');
  36. };
  37. /**
  38. * 分析依赖,先调用vite alias插件,再调用resolve插件,获取依赖的绝对路径
  39. * @param {*} importee 被引用内容
  40. * @param {*} importer 引用者
  41. * @returns 被依赖内容的绝对路径
  42. */
  43. const resolveModule = async (importee, importer) => {
  44. let aliasResult = await aliasPlugin.resolveId.call(Promise, importee, importer);
  45. aliasResult = aliasResult || importee;
  46. const rst = await resolvPlugin.resolveId.call({ getModuleInfo: () => ({}) }, aliasResult, importer, {});
  47. return rst?.id || rst;
  48. };
  49. /**
  50. * 获取package.json中的依赖项,排除宿主中已存在的依赖
  51. * @returns
  52. */
  53. const getPackageDependencies = () => {
  54. const existDependencies = [
  55. '@yufp/constant',
  56. '@yufp/icon',
  57. '@yufp/layout',
  58. '@yufp/layout-micro',
  59. '@yufp/layout-utils',
  60. '@yufp/locale',
  61. '@yufp/svgicon',
  62. '@yufp/toolbar',
  63. '@yufp/xy-utils',
  64. '@yuxp/compile',
  65. '@yuxp/integration',
  66. 'xy-utils',
  67. 'axios',
  68. 'element-ui',
  69. 'fingerprintjs2',
  70. 'nprogress',
  71. 'oca-web',
  72. 'qs',
  73. 'vue',
  74. 'vue-i18n',
  75. 'vue-router',
  76. 'vue-seamless-scroll',
  77. 'vuex',
  78. 'vuex-persist',
  79. 'yuwp-ui',
  80. ];
  81. return Object.fromEntries(
  82. Object.keys(packageDependencies)
  83. .filter((dep) => !existDependencies.includes(dep))
  84. .map((key) => [key, packageDependencies[key]]),
  85. );
  86. };
  87. /**
  88. * 判断文件是否需要拷贝到包中
  89. * 文件在 src/${packageName} 和 src/views/${packageName} 中,且仅支持 .js .vue 文件
  90. * @param {String} filePath 文件路径
  91. * @returns 是否为自定义的文件
  92. */
  93. const isCustomFile = (filePath) => {
  94. console.log('filePath===', filePath)
  95. if(!filePath) return false;
  96. if (!filePath.endsWith('.js') && !filePath.endsWith('.vue')) {
  97. return false;
  98. }
  99. if (!path.isAbsolute(filePath)) {
  100. filePath = path.join(__dirname, filePath);
  101. }
  102. const absolutPathList = customPathList.map((filePath) => path.join(__dirname, filePath));
  103. return !absolutPathList.every((absolutPath) => path.relative(absolutPath, filePath).startsWith('..'));
  104. };
  105. /**
  106. * 分析一个文件的依赖
  107. * 将文件中 @/views/xxx.js 转换为 ./views/xxx.js 的实际相对路径
  108. * 返回转换后的内容(字符串)以及此文件所依赖的文件
  109. * @param {*} entryFile 所分析的文件
  110. * @param {*} onlyCustomFile 是否限制自定义目录,true为限制,入口文件直接引用的文件不限制
  111. * @returns dependecyFileList:依赖的文件列表,replacedFile替换后的内容
  112. */
  113. const parseFileDependency = async (entryFile = './src/index.js', onlyCustomFile = true) => {
  114. const entryFileContent = String(fs.readFileSync(entryFile));
  115. let result;
  116. let replacedFile = entryFileContent;
  117. if (entryFile.endsWith('.vue')) {
  118. result = vueParser.parse(entryFileContent, { ecmaVersion: 2020, sourceType: 'module' });
  119. } else if (entryFile.endsWith('.js')) {
  120. result = Parser.parse(entryFileContent, { ecmaVersion: 2020, sourceType: 'module' });
  121. } else {
  122. return { dependecyFileList: [], replacedFile: '' };
  123. }
  124. const dependecyFileList = await Promise.all(
  125. result.body
  126. .filter(({ type }) => type === 'ImportDeclaration')
  127. .map(async ({ source }) => {
  128. const absolutPath = await resolveModule(source.value, entryFile);
  129. if (absolutPath === null) return null;
  130. if (!onlyCustomFile || isCustomFile(absolutPath)) {
  131. const toFile = path.relative(
  132. path.dirname(path.isAbsolute(entryFile) ? entryFile : path.join(__dirname, entryFile)),
  133. absolutPath,
  134. );
  135. replacedFile = replacedFile.replaceAll(source.value, `./${toFile.replaceAll(`\\`, '/')}`);
  136. return absolutPath;
  137. }
  138. return null;
  139. }),
  140. );
  141. return {
  142. dependecyFileList: dependecyFileList.filter((a) => a !== null),
  143. replacedFile,
  144. };
  145. };
  146. /**
  147. * 递归解析文件,调用vite resolve plugin,分析所依赖的文件。
  148. * 并将依赖转换为无alias的相对路径,并写道destDir中。
  149. * @param {*} entryFile 入口文件。
  150. */
  151. const parseAndeCopyCustomJs = async (entryFile) => {
  152. let parseFileStack = [entryFile];
  153. const parsedSet = new Set();
  154. const writeFileQueue = [];
  155. while (parseFileStack.length) {
  156. const parseFile = parseFileStack.pop();
  157. parsedSet.add(parseFile);
  158. // eslint-disable-next-line no-await-in-loop
  159. // entryFile支持引入非自定义路径下的路径,其他不支持
  160. const { dependecyFileList, replacedFile } = await parseFileDependency(parseFile, parseFile !== entryFile);
  161. const writeFilePath = `${packageName}/${path.relative(__dirname, parseFile)}`;
  162. // eslint-disable-next-line no-await-in-loop
  163. writeFileQueue.push(
  164. fsPromise
  165. .mkdir(path.dirname(path.join(__dirname, writeFilePath)), { recursive: true })
  166. .then(() => fsPromise.writeFile(writeFilePath, replacedFile)),
  167. );
  168. const filted = dependecyFileList.filter((f) => !parsedSet.has(f));
  169. // console.log(`${parseFile}依赖:${filted}${filted.length}`);
  170. parseFileStack = parseFileStack.concat(filted);
  171. }
  172. // 等待所有文件写入完成
  173. await Promise.all(writeFileQueue);
  174. console.log(`拷贝自定义文件列表:${[...parsedSet].join('\n')}
  175. 共${parsedSet.size}个文件`);
  176. };
  177. /**
  178. * 递归复制目录
  179. * @param {*} src 源目录
  180. * @param {*} dest 目标目录
  181. */
  182. async function copyDirectory(src, dest) {
  183. // 确保目标目录存在
  184. if (!fs.existsSync(dest)) {
  185. fs.mkdirSync(dest, { recursive: true });
  186. }
  187. // 获取源目录的内容
  188. const files = await fsPromise.readdir(src, { withFileTypes: true });
  189. for (const file of files) {
  190. const srcPath = path.join(src, file.name);
  191. const destPath = path.join(dest, file.name);
  192. if (file.isDirectory()) {
  193. // 如果是目录,则递归复制该目录
  194. // eslint-disable-next-line no-await-in-loop
  195. await copyDirectory(srcPath, destPath);
  196. } else {
  197. // 如果是文件,则直接复制
  198. // console.log(`Copying ${srcPath} to ${destPath}`);
  199. try {
  200. // eslint-disable-next-line no-await-in-loop
  201. await fsPromise.copyFile(srcPath, destPath);
  202. } catch (error) {
  203. console.error(`Error copying ${srcPath}:`, error);
  204. }
  205. }
  206. }
  207. }
  208. /**
  209. * 递归删除目录或文件
  210. * @param {*} fileUrl
  211. * @returns
  212. */
  213. function deleteTarget(fileUrl) {
  214. // 如果当前url不存在,则退出
  215. if (!fs.existsSync(fileUrl)) return;
  216. // 当前文件为文件夹时
  217. if (fs.statSync(fileUrl).isDirectory()) {
  218. const files = fs.readdirSync(fileUrl);
  219. const len = files.length;
  220. let removeNumber = 0;
  221. if (len > 0) {
  222. files.forEach((file) => {
  223. removeNumber++;
  224. const url = `${fileUrl}/${file}`;
  225. if (fs.statSync(url).isDirectory()) {
  226. deleteTarget(url);
  227. } else {
  228. fs.unlinkSync(url);
  229. }
  230. });
  231. if (removeNumber === len) {
  232. // 删除当前文件夹下的所有文件后,删除当前空文件夹(注:所有的都采用同步删除)
  233. fs.rmdirSync(fileUrl);
  234. console.log(`删除文件夹${fileUrl}成功`);
  235. }
  236. } else {
  237. fs.rmdirSync(fileUrl);
  238. }
  239. } else {
  240. // 当前文件为文件时
  241. fs.unlinkSync(fileUrl);
  242. console.log(`删除文件${fileUrl}成功`);
  243. }
  244. }
  245. (async function run(params) {
  246. await getResolvePlugin();
  247. await parseAndeCopyCustomJs(entryFile);
  248. await copyDirectory(path.join(__dirname, 'src/views/xpVue'), path.join(__dirname, packageName, 'xpVue'));
  249. console.log('xpCopy completed.');
  250. // 复制目录到指定位置
  251. await copyDirectory(path.join(__dirname, 'src/views', packageName), path.join(__dirname, packageName));
  252. console.log('Copy completed.');
  253. // 写入package.json 文件
  254. const packageContent = JSON.stringify(
  255. {
  256. name: packageName,
  257. version: packageVersion,
  258. license: 'MIT',
  259. description: 'Lightweight Rule Engine Frontend Page Code.',
  260. author: 'oca@yusys.com.cn',
  261. main: entryFile,
  262. dependencies: getPackageDependencies(),
  263. },
  264. '\n',
  265. 4,
  266. );
  267. fs.writeFileSync(`${packageName}/package.json`, packageContent);
  268. console.log('INFO=====准备将OCA推送至私有仓库');
  269. console.log(`INFO=====包名:${packageName}`);
  270. console.log(`INFO=====版本:${packageVersion}`);
  271. exec(`cd ${packageName} && yarn publish`, (error, stdout, stderr) => {
  272. if (error) {
  273. console.error(`ERROR=====执行出错: ${error}`);
  274. if (`${error}`.indexOf('not allow updating') > -1) {
  275. console.error(`ERROR=====仓库上已存在该版本,不允许覆盖推送,请调整发布版本号。`);
  276. }
  277. return;
  278. }
  279. // 发布完成删除文件夹
  280. setTimeout(() => {
  281. let deleteName = packageName;
  282. if (packageName.includes('/')) {
  283. deleteName = packageName.split('/')[0];
  284. }
  285. deleteTarget(path.join(__dirname, deleteName));
  286. }, 1000);
  287. console.log(`INFO=====${stdout}`);
  288. });
  289. })();