wpsjs
Version:
用于开发wps加载项的工具包
573 lines (531 loc) • 17.6 kB
JavaScript
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)
}