publish_xmgame.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. // v1.4.0
  2. // publish 2.x 也是用这个文件,需要做兼容
  3. let isPublish2 = process.argv[2].includes("publish_xmgame.js") && process.argv[3].includes("--evn=publish2");
  4. // 获取Node插件和工作路径
  5. let ideModuleDir, workSpaceDir;
  6. if (isPublish2) {
  7. //是否使用IDE自带的node环境和插件,设置false后,则使用自己环境(使用命令行方式执行)
  8. const useIDENode = process.argv[0].indexOf("LayaAir") > -1 ? true : false;
  9. ideModuleDir = useIDENode ? process.argv[1].replace("gulp\\bin\\gulp.js", "").replace("gulp/bin/gulp.js", "") : "";
  10. workSpaceDir = useIDENode ? process.argv[2].replace("--gulpfile=", "").replace("\\.laya\\publish_xmgame.js", "").replace("/.laya/publish_xmgame.js", "") + "/" : "./../";
  11. } else {
  12. ideModuleDir = global.ideModuleDir;
  13. workSpaceDir = global.workSpaceDir;
  14. }
  15. //引用插件模块
  16. const gulp = require(ideModuleDir + "gulp");
  17. const fs = require("fs");
  18. const path = require("path");
  19. const childProcess = require("child_process");
  20. const del = require(ideModuleDir + "del");
  21. const revCollector = require(ideModuleDir + 'gulp-rev-collector');
  22. let commandSuffix = ".cmd";
  23. let copyLibsTask = ["copyLibsJsFile"];
  24. let packfiletask = ["packfile"];
  25. if (isPublish2) {
  26. copyLibsTask = "";
  27. packfiletask = ["copyPlatformFile_XM"];
  28. }
  29. let
  30. config,
  31. platform,
  32. releaseDir,
  33. tempReleaseDir, // 小米临时拷贝目录
  34. projDir; // 小米快游戏工程目录
  35. let IDEXMProjPath,
  36. isUpdateIDEXMProj = false;
  37. let versionCon; // 版本管理version.json
  38. let layarepublicPath = path.join(ideModuleDir, "../", "code", "layarepublic");
  39. if (!fs.existsSync(layarepublicPath)) {
  40. layarepublicPath = path.join(ideModuleDir, "../", "out", "layarepublic");
  41. }
  42. // 创建小米项目前,拷贝小米引擎库、修改index.js
  43. // 应该在publish中的,但是为了方便发布2.0及IDE 1.x,放在这里修改
  44. gulp.task("preCreate_XM", copyLibsTask, function() {
  45. if (isPublish2) {
  46. let pubsetPath = path.join(workSpaceDir, ".laya", "pubset.json");
  47. let content = fs.readFileSync(pubsetPath, "utf8");
  48. let pubsetJson = JSON.parse(content);
  49. platform = "xmgame";
  50. releaseDir = path.join(workSpaceDir, "release", platform).replace(/\\/g, "/");
  51. releaseDir = tempReleaseDir = path.join(releaseDir, "temprelease");
  52. config = pubsetJson[4]; // 只用到了 config.xmInfo|config.xmSign
  53. } else {
  54. platform = global.platform;
  55. releaseDir = global.releaseDir;
  56. tempReleaseDir = global.tempReleaseDir;
  57. config = global.config;
  58. }
  59. // 如果不是小米快游戏
  60. if (platform !== "xmgame") {
  61. return;
  62. }
  63. if (process.platform === "darwin") {
  64. commandSuffix = "";
  65. }
  66. let copyLibsList = [`${workSpaceDir}/bin/libs/laya.xmmini.js`];
  67. var stream = gulp.src(copyLibsList, { base: `${workSpaceDir}/bin` });
  68. return stream.pipe(gulp.dest(tempReleaseDir));
  69. });
  70. gulp.task("copyPlatformFile_XM", ["preCreate_XM"], function() {
  71. // 如果不是小米快游戏
  72. if (platform !== "xmgame") {
  73. return;
  74. }
  75. let xmAdapterPath = path.join(layarepublicPath, "LayaAirProjectPack", "lib", "data", "xmfiles");
  76. let copyLibsList = [`${xmAdapterPath}/**/*.*`];
  77. var stream = gulp.src(copyLibsList);
  78. return stream.pipe(gulp.dest(tempReleaseDir));
  79. });
  80. // 新建小米项目-小米项目与其他项目不同,需要新建小米快游戏项目,并打包成.rpk文件
  81. gulp.task("checkIDEProj_XM", packfiletask, function() {
  82. // 如果不是小米快游戏
  83. if (platform !== "xmgame") {
  84. return;
  85. }
  86. if (!ideModuleDir) {
  87. return;
  88. }
  89. IDEXMProjPath = path.join(layarepublicPath, "xm");
  90. if (process.platform === "darwin") {
  91. return;
  92. }
  93. let ideLastXMProjPath = path.join(IDEXMProjPath, config.xmInfo.projName);
  94. // 如果IDE中没有小米项目,跳过这一步
  95. let isProjExist = fs.existsSync(ideLastXMProjPath + "/node_modules") &&
  96. fs.existsSync(ideLastXMProjPath + "/sign");
  97. if (!isProjExist) {
  98. console.log("IDE中没有小米项目,跳过检查小米项目版本号这一步");
  99. return;
  100. }
  101. // 如果IDE中项目已经存在了,检查版本号
  102. // npm view quickgame-cli version
  103. // npm ls quickgame-cli
  104. let remoteVersion, localVersion;
  105. let isGetRemote, isGetLocal;
  106. return new Promise((resolve, reject) => { // 远程版本号
  107. childProcess.exec("npm view quickgame-cli version", function(error, stdout, stderr) {
  108. if (!stdout) { // 获取 quickgame-cli 远程版本号失败
  109. reject();
  110. return;
  111. }
  112. remoteVersion = stdout;
  113. isGetRemote = true;
  114. if (isGetRemote && isGetLocal) {
  115. resolve();
  116. }
  117. });
  118. childProcess.exec("npm ls quickgame-cli", { cwd: ideLastXMProjPath }, function(error, stdout, stderr) {
  119. if (!stdout) { // 获取 quickgame-cli 本地版本号失败
  120. reject();
  121. return;
  122. }
  123. localVersion = stdout.match(/quickgame-cli@(.+)/);
  124. localVersion = localVersion && localVersion[1];
  125. isGetLocal = true;
  126. if (isGetRemote && isGetLocal) {
  127. resolve();
  128. }
  129. });
  130. setTimeout(() => {
  131. if (!isGetLocal || !isGetRemote) {
  132. console.log("Failed to get the remote or local version number");
  133. reject();
  134. return;
  135. }
  136. }, 10000);
  137. }).then(() => { // 比较两个版本号
  138. if (!remoteVersion || !localVersion) {
  139. console.log("Failed to get the remote or local version number!");
  140. }
  141. console.log("quickgame-cli -> ", localVersion, "|", remoteVersion);
  142. if (remoteVersion.trim() !== localVersion.trim()) { // 仅当两个版本号都获取到并且不相等,置为需要更新(true)
  143. isUpdateIDEXMProj = true;
  144. }
  145. }).catch((e) => {
  146. console.log("Failed to get the remote or local version number -> ", remoteVersion, "|", localVersion);
  147. console.log(e);
  148. });
  149. });
  150. gulp.task("createIDEProj_XM", ["checkIDEProj_XM"], function() {
  151. // 如果不是小米快游戏
  152. if (platform !== "xmgame") {
  153. return;
  154. }
  155. if (!ideModuleDir) {
  156. return;
  157. }
  158. if (process.platform === "darwin") {
  159. return;
  160. }
  161. let ideLastXMProjPath = path.join(IDEXMProjPath, config.xmInfo.projName);
  162. // 如果有即存项目,不再新建
  163. let isProjExist = fs.existsSync(ideLastXMProjPath + "/node_modules") &&
  164. fs.existsSync(ideLastXMProjPath + "/sign");
  165. if (isProjExist && !isUpdateIDEXMProj) { // 项目存在并且不需要更新IDE中的小米项目
  166. return;
  167. }
  168. return new Promise((resolve, reject) => {
  169. console.log("(IDE)开始创建小米快游戏项目,请耐心等待(预计需要10分钟)...");
  170. let cmd = `npx${commandSuffix}`;
  171. let args = ["create-quickgame", config.xmInfo.projName, `path=${IDEXMProjPath}`,
  172. `package=${config.xmInfo.package}`, `versionName=${config.xmInfo.versionName}`,
  173. `versionCode=${config.xmInfo.versionCode}`, `minPlatformVersion=${config.xmInfo.minPlatformVersion}`,
  174. `icon=/layaicon/${path.basename(config.xmInfo.icon)}`, `name=${config.xmInfo.name}`, `rebuild=true`];
  175. console.log(JSON.stringify(args));
  176. let opts = {
  177. shell: true
  178. };
  179. let cp = childProcess.spawn(cmd, args, opts);
  180. cp.stdout.on('data', (data) => {
  181. console.log(`stdout: ${data}`);
  182. });
  183. cp.stderr.on('data', (data) => {
  184. console.log(`stderr: ${data}`);
  185. // reject();
  186. });
  187. cp.on('close', (code) => {
  188. console.log(`子进程退出码:${code}`);
  189. resolve();
  190. });
  191. });
  192. });
  193. gulp.task("createProj_XM", ["createIDEProj_XM"], function() {
  194. // 如果不是小米快游戏
  195. if (platform !== "xmgame") {
  196. return;
  197. }
  198. releaseDir = path.dirname(releaseDir);
  199. projDir = path.join(releaseDir, config.xmInfo.projName);
  200. // 如果有即存项目,不再新建
  201. let isProjExist = fs.existsSync(projDir + "/node_modules") &&
  202. fs.existsSync(projDir + "/sign");
  203. if (isProjExist) {
  204. return;
  205. }
  206. // 如果IDE中有即存项目,不再新建,从IDE中拷贝
  207. let ideLastXMProjPath = path.join(IDEXMProjPath, config.xmInfo.projName);
  208. let isIDEXMProjExist = fs.existsSync(ideLastXMProjPath + "/node_modules") &&
  209. fs.existsSync(ideLastXMProjPath + "/sign");
  210. if (isIDEXMProjExist) { // 如果用的IDE并且有IDEXM目录
  211. console.log("使用IDE中的小米游戏项目,拷贝...");
  212. // node-glob语法中,* 无法匹配 .开头的文件(夹),必须手动匹配
  213. let IDEXMProjPathStr = [`${IDEXMProjPath}/**/*.*`, `${ideLastXMProjPath}/node_modules/.bin/*.*`];
  214. var stream = gulp.src(IDEXMProjPathStr, { base: IDEXMProjPath});
  215. return stream.pipe(gulp.dest(releaseDir));
  216. }
  217. // 在项目中创建小米项目
  218. return new Promise((resolve, reject) => {
  219. console.log("(proj)开始创建小米快游戏项目,请耐心等待(预计需要10分钟)...");
  220. let cmd = `npx${commandSuffix}`;
  221. let args = ["create-quickgame", config.xmInfo.projName, `path=${releaseDir}`,
  222. `package=${config.xmInfo.package}`, `versionName=${config.xmInfo.versionName}`,
  223. `versionCode=${config.xmInfo.versionCode}`, `minPlatformVersion=${config.xmInfo.minPlatformVersion}`,
  224. `icon=/layaicon/${path.basename(config.xmInfo.icon)}`, `name=${config.xmInfo.name}`, `rebuild=true`];
  225. console.log(JSON.stringify(args));
  226. let opts = {
  227. shell: true
  228. };
  229. let cp = childProcess.spawn(cmd, args, opts);
  230. cp.stdout.on('data', (data) => {
  231. console.log(`stdout: ${data}`);
  232. });
  233. cp.stderr.on('data', (data) => {
  234. console.log(`stderr: ${data}`);
  235. // reject();
  236. });
  237. cp.on('close', (code) => {
  238. console.log(`子进程退出码:${code}`);
  239. resolve();
  240. });
  241. });
  242. });
  243. // 拷贝文件到小米快游戏
  244. gulp.task("copyFileToProj_XM", ["createProj_XM"], function() {
  245. // 如果不是小米快游戏
  246. if (platform !== "xmgame") {
  247. return;
  248. }
  249. // 将临时文件夹中的文件,拷贝到项目中去
  250. let originalDir = `${tempReleaseDir}/**/*.*`;
  251. let stream = gulp.src(originalDir);
  252. return stream.pipe(gulp.dest(path.join(projDir)));
  253. });
  254. // 拷贝icon到小米快游戏
  255. gulp.task("copyIconToProj_XM", ["copyFileToProj_XM"], function() {
  256. // 如果不是小米快游戏
  257. if (platform !== "xmgame") {
  258. return;
  259. }
  260. let originalDir = config.xmInfo.icon;
  261. let stream = gulp.src(originalDir);
  262. return stream.pipe(gulp.dest(path.join(projDir, "layaicon")));
  263. });
  264. // 清除小米快游戏临时目录
  265. gulp.task("clearTempDir_XM", ["copyIconToProj_XM"], function() {
  266. // 如果不是小米快游戏
  267. if (platform !== "xmgame") {
  268. return;
  269. }
  270. // 删掉临时目录
  271. return del([tempReleaseDir], { force: true });
  272. });
  273. // 生成release签名(私钥文件 private.pem 和证书文件 certificate.pem )
  274. gulp.task("generateSign_XM", ["clearTempDir_XM"], function() {
  275. // 如果不是小米快游戏
  276. if (platform !== "xmgame") {
  277. return;
  278. }
  279. if (!config.xmSign.generateSign) {
  280. return;
  281. }
  282. // https://doc.quickapp.cn/tools/compiling-tools.html
  283. return new Promise((resolve, reject) => {
  284. let cmd = "openssl";
  285. let args = ["req", "-newkey", "rsa:2048", "-nodes", "-keyout", "private.pem",
  286. "-x509", "-days", "3650", "-out", "certificate.pem"];
  287. let opts = {
  288. cwd: projDir,
  289. shell: true
  290. };
  291. let cp = childProcess.spawn(cmd, args, opts);
  292. cp.stdout.on('data', (data) => {
  293. console.log(`stdout: ${data}`);
  294. });
  295. cp.stderr.on('data', (data) => {
  296. console.log(`stderr: ${data}`);
  297. data += "";
  298. if (data.includes("Country Name")) {
  299. cp.stdin.write(`${config.xmSign.countryName}\n`);
  300. console.log(`Country Name: ${config.xmSign.countryName}`);
  301. } else if (data.includes("Province Name")) {
  302. cp.stdin.write(`${config.xmSign.provinceName}\n`);
  303. console.log(`Province Name: ${config.xmSign.provinceName}`);
  304. } else if (data.includes("Locality Name")) {
  305. cp.stdin.write(`${config.xmSign.localityName}\n`);
  306. console.log(`Locality Name: ${config.xmSign.localityName}`);
  307. } else if (data.includes("Organization Name")) {
  308. cp.stdin.write(`${config.xmSign.orgName}\n`);
  309. console.log(`Organization Name: ${config.xmSign.orgName}`);
  310. } else if (data.includes("Organizational Unit Name")) {
  311. cp.stdin.write(`${config.xmSign.orgUnitName}\n`);
  312. console.log(`Organizational Unit Name: ${config.xmSign.orgUnitName}`);
  313. } else if (data.includes("Common Name")) {
  314. cp.stdin.write(`${config.xmSign.commonName}\n`);
  315. console.log(`Common Name: ${config.xmSign.commonName}`);
  316. } else if (data.includes("Email Address")) {
  317. cp.stdin.write(`${config.xmSign.emailAddr}\n`);
  318. console.log(`Email Address: ${config.xmSign.emailAddr}`);
  319. // cp.stdin.end();
  320. }
  321. // reject();
  322. });
  323. cp.on('close', (code) => {
  324. console.log(`子进程退出码:${code}`);
  325. resolve();
  326. });
  327. });
  328. });
  329. // 拷贝sign文件到指定位置
  330. gulp.task("copySignFile_XM", ["generateSign_XM"], function() {
  331. // 如果不是小米快游戏
  332. if (platform !== "xmgame") {
  333. return;
  334. }
  335. if (config.xmSign.generateSign) { // 新生成的签名
  336. // 移动签名文件到项目中(Laya & 小米快游戏项目中)
  337. let
  338. privatePem = path.join(projDir, "private.pem"),
  339. certificatePem = path.join(projDir, "certificate.pem");
  340. let isSignExits = fs.existsSync(privatePem) && fs.existsSync(certificatePem);
  341. if (!isSignExits) {
  342. return;
  343. }
  344. let
  345. xiaomiDest = `${projDir}/sign/release`,
  346. layaDest = `${workSpaceDir}/sign/release`;
  347. let stream = gulp.src([privatePem, certificatePem]);
  348. return stream.pipe(gulp.dest(xiaomiDest))
  349. .pipe(gulp.dest(layaDest));
  350. } else if (config.xmInfo.useReleaseSign && !config.xmSign.generateSign) { // 使用release签名,并且没有重新生成
  351. // 从项目中将签名拷贝到小米快游戏项目中
  352. let
  353. privatePem = path.join(workSpaceDir, "sign", "release", "private.pem"),
  354. certificatePem = path.join(workSpaceDir, "sign", "release", "certificate.pem");
  355. let isSignExits = fs.existsSync(privatePem) && fs.existsSync(certificatePem);
  356. if (!isSignExits) {
  357. return;
  358. }
  359. let
  360. xiaomiDest = `${projDir}/sign/release`;
  361. let stream = gulp.src([privatePem, certificatePem]);
  362. return stream.pipe(gulp.dest(xiaomiDest));
  363. }
  364. });
  365. gulp.task("deleteSignFile_XM", ["copySignFile_XM"], function() {
  366. // 如果不是小米快游戏
  367. if (platform !== "xmgame") {
  368. return;
  369. }
  370. if (config.xmSign.generateSign) { // 新生成的签名
  371. let
  372. privatePem = path.join(projDir, "private.pem"),
  373. certificatePem = path.join(projDir, "certificate.pem");
  374. return del([privatePem, certificatePem], { force: true });
  375. }
  376. });
  377. gulp.task("modifyFile_XM", ["deleteSignFile_XM"], function() {
  378. // 如果不是小米快游戏
  379. if (platform !== "xmgame") {
  380. return;
  381. }
  382. // 修改manifest.json文件
  383. let manifestPath = path.join(projDir, "manifest.json");
  384. if (!fs.existsSync(manifestPath)) {
  385. return;
  386. }
  387. let manifestContent = fs.readFileSync(manifestPath, "utf8");
  388. let manifestJson = JSON.parse(manifestContent);
  389. manifestJson.package = config.xmInfo.package;
  390. manifestJson.name = config.xmInfo.name;
  391. manifestJson.orientation = config.xmInfo.orientation;
  392. manifestJson.config.logLevel = config.xmInfo.logLevel || "off";
  393. manifestJson.versionName = config.xmInfo.versionName;
  394. manifestJson.versionCode = config.xmInfo.versionCode;
  395. manifestJson.minPlatformVersion = config.xmInfo.minPlatformVersion;
  396. manifestJson.icon = `/layaicon/${path.basename(config.xmInfo.icon)}`;
  397. if (config.xmInfo.subpack) { // 分包
  398. manifestJson.subpackages = config.xmSubpack;
  399. } else {
  400. delete manifestJson.subpackages;
  401. }
  402. fs.writeFileSync(manifestPath, JSON.stringify(manifestJson, null, 4), "utf8");
  403. if (config.version) {
  404. let versionPath = projDir + "/version.json";
  405. versionCon = fs.readFileSync(versionPath, "utf8");
  406. versionCon = JSON.parse(versionCon);
  407. }
  408. let indexJsStr = (versionCon && versionCon["index.js"]) ? versionCon["index.js"] : "index.js";
  409. // 修改main.js文件
  410. let content = 'require("./qg-adapter.js");\nrequire("./libs/laya.xmmini.js");\nrequire("./index.js");';
  411. let mainJsPath = path.join(projDir, "main.js");
  412. fs.writeFileSync(mainJsPath, content, "utf8");
  413. // 小米项目,修改index.js
  414. let filePath = path.join(projDir, indexJsStr);
  415. if (!fs.existsSync(filePath)) {
  416. return;
  417. }
  418. let fileContent = fs.readFileSync(filePath, "utf8");
  419. fileContent = fileContent.replace(/loadLib(\(['"])/gm, "require$1./");
  420. fs.writeFileSync(filePath, fileContent, "utf8");
  421. })
  422. gulp.task("version_XM", ["modifyFile_XM"], function () {
  423. // 如果不是小米快游戏
  424. if (platform !== "xmgame") {
  425. return;
  426. }
  427. if (config.version) {
  428. let versionPath = projDir + "/version.json";
  429. let mainJSPath = projDir + "/main.js";
  430. let srcList = [versionPath, mainJSPath];
  431. return gulp.src(srcList)
  432. .pipe(revCollector())
  433. .pipe(gulp.dest(projDir));
  434. }
  435. });
  436. // 打包rpk
  437. gulp.task("buildRPK_XM", ["version_XM"], function() {
  438. // 如果不是小米快游戏
  439. if (platform !== "xmgame") {
  440. return;
  441. }
  442. // 在小米轻游戏项目目录中执行:
  443. // npm run build || npm run release
  444. let cmdStr = "build";
  445. if (config.xmInfo.useReleaseSign) {
  446. cmdStr = "release";
  447. }
  448. return new Promise((resolve, reject) => {
  449. let cmd = `npm${commandSuffix}`;
  450. let args = ["run", cmdStr];
  451. let opts = {
  452. cwd: projDir,
  453. shell: true
  454. };
  455. let cp = childProcess.spawn(cmd, args, opts);
  456. // let cp = childProcess.spawn(`npx${commandSuffix}`, ['-v']);
  457. cp.stdout.on('data', (data) => {
  458. console.log(`stdout: ${data}`);
  459. });
  460. cp.stderr.on('data', (data) => {
  461. console.log(`stderr: ${data}`);
  462. // reject();
  463. });
  464. cp.on('close', (code) => {
  465. console.log(`子进程退出码:${code}`);
  466. resolve();
  467. });
  468. });
  469. });
  470. gulp.task("showQRCode_XM", ["buildRPK_XM"], function() {
  471. // 如果不是小米快游戏
  472. if (platform !== "xmgame") {
  473. return;
  474. }
  475. // 在小米轻游戏项目目录中执行:
  476. // npm run server
  477. return new Promise((resolve, reject) => {
  478. let cmd = `npm${commandSuffix}`;
  479. let args = ["run", "server"];
  480. let opts = {
  481. cwd: projDir,
  482. shell: true
  483. };
  484. let cp = childProcess.spawn(cmd, args, opts);
  485. // let cp = childProcess.spawn(`npx${commandSuffix}`, ['-v']);
  486. cp.stdout.on('data', (data) => {
  487. console.log(`${data}`);
  488. // 输出pid,macos要用: macos无法kill进程树,也无法执行命令获取3000端口pid(没有查询权限),导致无法kill这个进程
  489. console.log('xm_qrcode_pid:' + cp.pid);
  490. });
  491. cp.stderr.on('data', (data) => {
  492. console.log(`stderr: ${data}`);
  493. // reject();
  494. });
  495. cp.on('close', (code) => {
  496. console.log(`子进程退出码:${code}`);
  497. resolve();
  498. });
  499. });
  500. });
  501. gulp.task("buildXiaomiProj", ["showQRCode_XM"], function() {
  502. console.log("all tasks completed");
  503. });