UNPKG

wpsjs

Version:

用于开发wps加载项的工具包

573 lines (531 loc) 17.6 kB
var cp = require('child_process'); const express = require('express'); var ini = require('ini') const os = require('os'); const fs = require('fs') const fsEx = require('fs-extra') const xml2js = require("xml2js"); const path = require('path') const http = require("http") const jsUtil = require('./util.js') const rcWatch = require('recursive-watch') const sudo = require('sudo-js'); const inquirer = require('inquirer') const chalk = require('chalk') const app = express() const clients = [] let jspluginsXmlUrl = "" let serverHost = "" let serverPort = 3889 let remoteDebuggingPort = -1 let bSuRoot = false let bServerOnly = false let projectCfg = jsUtil.projectCfg() let bDebug = false function GetBackOemPath() { return path.resolve(__dirname, "back.json") } function GetBackJspluginsXmlPath() { return path.resolve(__dirname, "jsplugins.xml") } function suRoot(tryCount, cb) { if (bSuRoot) { cb(true) return } if (tryCount <= 0) { cb(false) return } inquirer.prompt([ { type: 'password', name: 'password', message: '身份验证,请输入密码:', } ]).then(answers => { sudo.setPassword(answers.password); sudo.check((res) => { if (res) { bSuRoot = true cb(true) return } else { suRoot(--tryCount, cb) } }) }) } async function debug(options) { if (options.clean) { let backOemPath = GetBackOemPath() if (fsEx.pathExistsSync(backOemPath)) { await GetOemPath((path) => { fsEx.move(backOemPath, path, { overwrite: true }) }) } let backPath = GetBackJspluginsXmlPath() if (fsEx.pathExistsSync(backPath)) { let directPath = GetJspluginsXmlPath() await fsEx.move(backPath, directPath, { overwrite: true }) } return } if (options.remotePort) remoteDebuggingPort = options.remotePort if (options.server) bServerOnly = true if (options.debug) bDebug = true //config oem.ini jsUtil.GetWebSiteHost(options.port, (host, port) => { if (options.port && options.port != port) { console.log(chalk.red(`服务启动失败,端口(${options.port})被占用`)) return } serverHost = host serverPort = port //jspluginsXmlUrl = `${serverHost}/jsplugins.xml` configOem((ret) => { if (ret.status != 0) { console.error(ret.msg) return } //write jsplugins.xml let pluginRoot = {jsplugins:{ jspluginonline:[] }} var addonType = projectCfg.addonType ? projectCfg.addonType : "et" if (bDebug) { pluginRoot.jsplugins.jspluginonline.push({$:{ name:projectCfg.name, type:addonType, url:`${serverHost}/`, debug:'code', customDomain:projectCfg.customDomain }}) } else { pluginRoot.jsplugins.jspluginonline.push({$:{ name:projectCfg.name, type:addonType, url:`${serverHost}/`, customDomain:projectCfg.customDomain }}) } let pluginXml = new xml2js.Builder().buildObject(pluginRoot) let jspluginsXml = pluginXml.toString({ pretty: true }) if (DebugVite(jspluginsXml, 'dev') || DebugVite(jspluginsXml, 'serve')) return if (DebugReact(jspluginsXml, 'start' || DebugReact(jspluginsXml, 'dev'))) return StartServer(jspluginsXml, () => { StartWps(jspluginsXml) }) }) }) } let vueTryCount = 0 let vueJspluginsXml function DebugVite(jspluginsXml, tag) { if (projectCfg.scripts && typeof projectCfg.scripts[tag] == 'string') { let devCmd = projectCfg.scripts[tag].trim() if (devCmd.startsWith("vue-cli-service")) { projectCfg.scripts[tag] = `vue-cli-service serve --port ${serverPort}` cfgData = JSON.stringify(projectCfg, "", "\t") fsEx.writeFileSync('package.json', cfgData) jsUtil.SpawnNpm(tag) vueTryCount = 0 vueJspluginsXml = jspluginsXml setTimeout(TryStartWps, 1000) return true } } return false } function DebugReact(jspluginsXml, tag) { if (projectCfg.scripts && typeof projectCfg.scripts[tag] == 'string') { let devCmd = projectCfg.scripts[tag].trim() if (devCmd.includes("react-scripts")) { if (os.platform() == 'win32') projectCfg.scripts[tag] = `set PORT=${serverPort} && react-scripts start` else projectCfg.scripts[tag] = `export PORT=${serverPort} react-scripts start` cfgData = JSON.stringify(projectCfg, "", "\t") fsEx.writeFileSync('package.json', cfgData) jsUtil.SpawnNpm(tag) vueTryCount = 0 vueJspluginsXml = jspluginsXml setTimeout(TryStartWps, 1000) return true } } return false } function TryStartWps() { if (vueTryCount > 5) return http.get(`${serverHost}/index.html`, (res) => { vueTryCount = 6 StartWps(vueJspluginsXml); res.resume() }).on('error', (e) => { ++vueTryCount setTimeout(TryStartWps, 1000) }) } function StartServer(jspluginsXml, callback) { //let rootPath = path.resolve(process.cwd(), "packages/@wps-jsapi/wps/") let rootPath = process.cwd() app.all('*', function (req, response, next) { if (req.originalUrl.endsWith(".html") || req.originalUrl.endsWith(".htm")) { let filePath = rootPath + req.originalUrl var htmlData = fsEx.readFileSync(filePath) let pos = htmlData.indexOf("<body") if (pos == -1) pos = htmlData.indexOf("<script") if (pos == -1) { pos = htmlData.indexOf("<html>") pos += 6 } htmlData = htmlData.slice(0, pos) + `<script type="text/javascript" src="./hot-update-inject.js"></script>` + htmlData.slice(pos) response.writeHead(200, "OK", { "Content-Type": "text/html" }) response.end(htmlData) } else if (req.originalUrl.endsWith("/hot-update-inject.js")) { response.writeHead(200, "OK", { "Content-Type": "application/javascript; charset=utf-8" }) const inject = `function handleMessage(event) { var res = JSON.parse(event.data) if (res.update) window.location.reload() } function handleOnline(event) { } function handleDisconnect(event) { source.close();; } var source = new window.EventSource('${serverHost}/hot-update/${Math.random()}'); source.onopen = handleOnline; source.onerror = handleDisconnect; source.onmessage = handleMessage;` response.end(inject) } else { next(); } }); app.use(express.static(rootPath)) app.use("/jsplugins.xml", function (request, response) { response.writeHead(200, "OK", { "Content-Type": "text/xml" }) response.end(jspluginsXml) }); app.use("/hot-update/:id", function (request, response) { response.writeHead(200, "OK", { 'Connection': 'keep-alive', "Content-Type": "text/event-stream", 'Cache-Control': 'no-cache' }) clients.push(response) }); var server = app.listen(serverPort, function () { console.log(jsUtil.getNow() + `启动本地web服务(${serverHost})成功!`) let lastTime = new Date() rcWatch(rootPath, () => { let nowTime = new Date() if (nowTime.getTime() - lastTime.getTime() > 300) { lastTime = nowTime; let res = { update: true } clients.forEach(response => { response.write(`data:${JSON.stringify(res)}\n\n`) }); } }) callback(); }); server.on('error', (e) => { if (e.code === 'EADDRINUSE') { console.log('地址正被使用,重试中...'); setTimeout(() => { server.close(); server.listen(serverPort); }, 2000); } }); } function GetJspluginsXmlPath() { let directPath = "" if (os.platform() == 'win32') { directPath = path.resolve(process.env.APPDATA, 'kingsoft/wps/jsaddons/jsplugins.xml') } else if (os.platform() == 'darwin') { // directPath = path.resolve(process.env.HOME, '.kingsoft/wps/jsaddons/publish.xml') directPath = path.resolve(process.env.HOME, 'Library/Containers/com.kingsoft.wpsoffice.mac/Data/.kingsoft/wps/jsaddons/publish.xml') } else { directPath = path.resolve(process.env.HOME, ".local/share/Kingsoft/wps/jsaddons/jsplugins.xml") } return directPath } function StartWps(jspluginsXml) { try { StartWpsInner(jspluginsXml) } catch (e) { if (os.platform() == 'win32') { // console.log(e) } else if (os.platform() == 'darwin') { console.log(e) } else { suRoot(3, (res) => { if (res) { let directPath = GetBackJspluginsXmlPath() directPath = path.resolve(directPath, '..') sudo.exec(['chmod', 'a+rw', directPath], (err, pid, result) => { StartWpsInner(jspluginsXml) }) } }) } } } function StartWpsInner(jspluginsXml) { //立即生效 let directPath = GetJspluginsXmlPath() if (fsEx.pathExistsSync(directPath)) { let backPath = GetBackJspluginsXmlPath() if (!fsEx.pathExistsSync(backPath)) { fsEx.copySync(directPath, backPath) } } fsEx.ensureDirSync(path.dirname(directPath)) fsEx.writeFileSync(directPath, jspluginsXml) let handShake = jsUtil.GetHandShake(); fsEx.writeFileSync(handShake, process.cwd()) let demoName = 'systemdemo.html' let systemDemoPath = path.resolve(__dirname, 'res', demoName) var demoData = fs.readFileSync(systemDemoPath) let htmlDemo = path.resolve(jsUtil.GetDebugTempPath(), demoName); fsEx.writeFileSync(htmlDemo, demoData) let sdkName = 'wpsjsrpcsdk.js' let systemDemoJs = path.resolve(__dirname, '../../node_modules/wpsjs-rpc-sdk-new', sdkName) var sdkData = fs.readFileSync(systemDemoJs) let sdkDemo = path.resolve(jsUtil.GetDebugTempPath(), sdkName); fsEx.writeFileSync(sdkDemo, sdkData) let infoDemo = path.resolve(jsUtil.GetDebugTempPath(), 'project.js'); fsEx.writeFileSync(infoDemo, `var projInfo = {"name":"${projectCfg.name}","type":"${projectCfg.addonType}"}`) let urlDemo = path.resolve(jsUtil.GetDebugTempPath(), 'NotifyDemoUrl'); fsEx.writeFileSync(urlDemo, `${serverHost}/${jsUtil.GetDebugTempName()}/${demoName}`) if (projectCfg.addonType == "wps") { let wpsfileName = 'wpsDemo.docx' let wpsfilePath = path.resolve(__dirname, 'res', wpsfileName) var wpsfileData = fs.readFileSync(wpsfilePath) let wpsfileDst = path.resolve(jsUtil.GetDebugTempPath(), wpsfileName); fsEx.writeFileSync(wpsfileDst, wpsfileData) } else if (projectCfg.addonType == "wpp") { let wppfileName = 'wppDemo.pptx' let wppfilePath = path.resolve(__dirname, 'res', wppfileName) var wppfileData = fs.readFileSync(wppfilePath) let wppfileDst = path.resolve(jsUtil.GetDebugTempPath(), wppfileName); fsEx.writeFileSync(wppfileDst, wppfileData) } else if (projectCfg.addonType == "et") { let etfileName = 'etDemo.xlsx' let etfilePath = path.resolve(__dirname, 'res', etfileName) var etfileData = fs.readFileSync(etfilePath) let etfileDst = path.resolve(jsUtil.GetDebugTempPath(), etfileName); fsEx.writeFileSync(etfileDst, etfileData) } if (bServerOnly) return //start wps GetExePath((cmd, args) => { //cmd = "f:\\work\\one\\debug\\WPSOffice\\office6\\wps.exe /prometheus /wps /t" if (remoteDebuggingPort != -1) { cmd += " " + `/JsApiremotedebuggingPort=${remoteDebuggingPort}` let userDataDir = path.join(os.tmpdir(), `wpsjs-userdatadir_${remoteDebuggingPort}`) cmd += " " + `/JsApiUserDataDir=${userDataDir}` } if (os.platform() == 'win32') { cp.spawn(cmd, args, { detached: true, stdio: ['ignore'] }) } else if (os.platform() == 'darwin') { args = ['-a', `${cmd}`] cmd = 'open' cp.spawn(cmd, args, { detached: true, stdio: ['ignore'] }) } else { cp.spawn(cmd, { detached: true, stdio: ['ignore'] }) } }) } function tryconfigOemFileInner(oemPath, callback) { try { configOemFileInner(oemPath) callback({ status: 0, msg: "wps安装正常," + oemPath + "文件设置正常。" }) } catch (e) { oemResult = "配置" + oemPath + "失败,请尝试以管理员重新运行!!"; console.log(oemResult) console.log(e) return callback({ status: 1, msg: oemResult }) } } function configOemFileInner(oemPath) { var config = ini.parse(fs.readFileSync(oemPath, 'utf-8')) var sup = config.support || config.Support; var ser = config.server || config.Server; var needUpdate = false; if (!sup || !sup.JsApiPlugin || !sup.JsApiShowWebDebugger) needUpdate = true; if (!ser || ser.JSPluginsServer == undefined) needUpdate = true; if (ser && ser.JSPluginsServer != jspluginsXmlUrl) { needUpdate = true; let backPath = GetBackOemPath() if (os.platform() == 'win32') { if (!fsEx.pathExistsSync(backPath)) fs.writeFileSync(backPath, ini.stringify(config)) } else if (os.platform() == 'darwin') { if (!fsEx.pathExistsSync(backPath)) fs.writeFileSync(backPath, ini.stringify(config)) } else { sudo.exec(['chmod', 'a+rw', backPath], (err, pid, result) => { if (!fsEx.pathExistsSync(backPath)) fs.writeFileSync(backPath, ini.stringify(config)) }) } } if (!sup) { sup = {} config.Support = sup } if (!ser) { ser = {} config.Server = ser } sup.JsApiPlugin = true sup.JsApiShowWebDebugger = true ser.JSPluginsServer = jspluginsXmlUrl if (needUpdate) fs.writeFileSync(oemPath, ini.stringify(config)) } function GetExePath(callback) { if (os.platform() == 'win32') { let type = "KET.Sheet.12" if (projectCfg.addonType == "wps") type = "KWPS.Document.12" else if (projectCfg.addonType == "wpp") type = "KWPP.Presentation.12" cp.exec(`REG QUERY HKEY_CLASSES_ROOT\\${type}\\shell\\new\\command /ve`, function (error, stdout, stderr) { var strList = stdout.split(" ") var val = strList.length > 2 ? strList[3] : undefined; if (typeof (val) == "undefined" || val == null) { throw new Error("WPS未安装,请安装WPS 2019 最新版本。") } var pos = val.indexOf(".exe"); if (pos < 0) { throw new Error("wps安装异常,请确认有没有正确的安装 WPS 2019最新版本!") } val = val.trim() if (!val.endsWith("\"%1\"")) { console.log("获取 WPS 启动路径异常,继续尝试启动") } let cmdString = val.replace("\"%1\"", "") let cmds = cmdString.split("\"") let exePath = cmds[0] ? cmds[0] : cmds[1] let rawArgs = [] if (cmds.length == 1) { let data = cmds[0].split(" ") exePath = data[0] rawArgs = data.splice(1) } else if (cmds.length > 1) { let idx = cmds[0] ? 1 : 2; if (cmds[idx]) { rawArgs = cmds[idx].split(" ") } } let args = [] rawArgs.forEach(function (item) { if (item) args.push(item) }) callback(exePath, args) }); } else if (os.platform() == 'darwin') { let exePath = "/Applications/wpsoffice.app"; callback(exePath) } else { let exePath = `/opt/kingsoft/wps-office/office6/${projectCfg.addonType}` if (!fsEx.existsSync(exePath)) exePath = `/opt/apps/cn.wps.wps-office-pro/files/kingsoft/wps-office/office6/${projectCfg.addonType}` callback(exePath, []) } } function GetOffice6Path(callback) { if (os.platform() == 'win32') { cp.exec("REG QUERY HKEY_CLASSES_ROOT\\KWPS.Document.12\\shell\\open\\command /ve", function (error, stdout, stderr) { var val = undefined; try { val = stdout.split(" ")[3].split('"')[1]; } catch (err) { throw new Error("WPS未安装,请安装WPS 2019 最新版本。") } if (typeof (val) == "undefined" || val == null) { throw new Error("WPS未安装,请安装WPS 2019 最新版本。") } var pos = val.indexOf("wps.exe"); if (pos < 0) pos = val.indexOf("wpsoffice.exe"); if (pos < 0) { console.log(val) throw new Error("wps安装异常,请确认有没有正确的安装WPS 2019 最新版本!") } let oemPath = val.substring(0, pos); callback(oemPath) }); } else if (os.platform() == 'darwin') { let oemPath = "/Applications/wpsoffice.app/Contents/Resources/office6"; callback(oemPath) } else { let oemPath = "/opt/kingsoft/wps-office/office6/"; if (!fsEx.existsSync(oemPath)) oemPath = "/opt/apps/cn.wps.wps-office-pro/files/kingsoft/wps-office/office6/"; callback(oemPath) } } function GetOemPath(callback) { GetOffice6Path((path) => { oemPath = path + 'cfgs/oem.ini'; callback(oemPath) }) } function configOem(callback) { try { GetOemPath((path) => { try { configOemFileInner(oemPath, callback); callback({ status: 0, msg: "wps安装正常," + oemPath + "文件设置正常。" }) } catch (e) { if (os.platform() == 'win32') { oemResult = "配置" + oemPath + "失败,请尝试以管理员重新运行!!"; console.log(oemResult) console.log(e) return callback({ status: 1, msg: oemResult }) } else if (os.platform() == 'darwin') { oemResult = "配置" + oemPath + "失败,请尝试以管理员重新运行!!"; console.log(oemResult) console.log(e) return callback({ status: 1, msg: oemResult }) } else { suRoot(3, (res) => { if (res) { sudo.exec(['chmod', 'a+rw', oemPath], (err, pid, result) => { tryconfigOemFileInner(oemPath, callback); cp.exec("quickstartoffice restart"); }) } else { oemResult = "配置" + oemPath + "失败,请尝试以管理员重新运行!!"; console.log(oemResult) console.log(e) return callback({ status: 1, msg: oemResult }) } }) } } }) } catch (e) { return callback({ status: 1, msg: e }) } } module.exports = (...arg) => { return debug(...arg) }