wpsjs
Version:
用于开发wps加载项的工具包
418 lines (386 loc) • 15.6 kB
JavaScript
const cp = require('child_process');
const express = require('express');
const ini = require('ini')
const os = require('os');
const xml2js = require("xml2js");
const fs = require('fs')
const fsEx = require('fs-extra')
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 { setTimeout } = require('timers/promises');
let projectCfg = jsUtil.projectCfg()
let remoteDebuggingPort = -1
let bSuRoot = false
var serverHost
var serverPort
function debug(...arg){
GetWebSiteInfo(arg).then(arg => {
serverHost = arg[0]
serverPort = arg[1]
return configPublish(serverHost, serverPort)
}).then((serverPort)=>{
return startServer(serverPort)
}).then(()=>{
return checkServer()
}).then(()=>{
//startWpsReady()
startWps()
}).finally(err=>{
//console.log(chalk.red('启动server失败'))
})
}
async function startServer(serverPort){
let bSuccess = await debugVite('serve', serverPort)
if (bSuccess)
return new Promise(r=>r())
bSuccess = await debugVite('dev', serverPort)
if (bSuccess)
return new Promise(r=>r())
bSuccess = await debugReact('start', serverPort)
if (bSuccess)
return new Promise(r=>r())
bSuccess = await debugReact('dev', serverPort)
if (bSuccess)
return new Promise(r=>r())
startNormalServer(serverPort)
return new Promise((r, j)=>r())
}
function startNormalServer(serverPort){
//如果不是使用的vue/react, 直接启动server
const app = express()
const clients = []
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("/publish.xml", function (request, response) {
response.writeHead(200, "OK", { "Content-Type": "text/xml" })
response.end("")
});
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`)
});
}
})
});
server.on('error', (e) => {
if (e.code === 'EADDRINUSE') {
console.log('地址正被使用,重试中...');
setTimeout(() => {
server.close();
server.listen(serverPort);
}, 2000);
}
});
}
async function debugVite(tag, serverPort) {
if (projectCfg.scripts && typeof projectCfg.scripts[tag] == 'string') {
let devCmd = projectCfg.scripts[tag].trim()
if (devCmd.startsWith("vite")) {
projectCfg.scripts[tag] = `vite --port ${serverPort}`
cfgData = JSON.stringify(projectCfg, "", "\t")
fsEx.writeFileSync('package.json', cfgData)
jsUtil.SpawnNpm(tag)
return true
}
}
return false
}
function debugReact(tag, serverPort) {
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)
return true
}
}
return false
}
async function checkServer() {
const testServer = ()=>{
return new Promise((resolve, reject) => {
http.get(`${serverHost}/index.html`, (res) => {
resolve()
}).on('error', (e) => {
reject()
})
});
}
const startTime = Date.now();
return new Promise((resolve, reject) => {
const attempt = () => {
testServer().then(()=>{
resolve()
}).catch((err) => {
const elapsedTime = (Date.now() - startTime);
if(elapsedTime > 1000 * 8) {
reject()
} else {
attempt()
}
})
}
attempt()
})
}
function startWps(){
const 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, [])
}
}
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 startWpsReady() {
const StartWpsReadyInner = ()=>{
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)
}
}
try {
StartWpsReadyInner()
} 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 = GetPublistXmlPath();
directPath = path.resolve(directPath, '..')
sudo.exec(['chmod', 'a+rw', directPath], (err, pid, result) => {
StartWpsReadyInner()
})
}
})
}
}
}
async function GetWebSiteInfo(arg){
return new Promise((r,j)=>{
jsUtil.GetWebSiteHost(arg.port, (host, port)=>{
if (arg.port && arg.port != port){
console.log(chalk.red(`服务启动失败,端口(${port})被占用`))
j()
return
}
r([host, port])
})
})
}
async function configPublish(serverHost, port){
const getXmlStr = ()=>{
let resultStr = '<jsplugins></jsplugins>'
if (fsEx.existsSync(GetPublistXmlPath())){
resultStr = fsEx.readFileSync(GetPublistXmlPath()).toString()
}
return resultStr
}
let parseResult = await xml2js.parseStringPromise(getXmlStr())
if (parseResult && parseResult.jsplugins === ''){
parseResult.jsplugins = {}
}
var publishXml =""
const onlinePlugin = {$:{
name:projectCfg.name,
type:projectCfg.addonType ? projectCfg.addonType : "wps",
url:`${serverHost}/`,
debug:'',
enable:'enable_dev',
install:'null',
customDomain:projectCfg.customDomain
}}
if (parseResult.jsplugins.jspluginonline){
let bFind = false
for (let idx = parseResult.jsplugins.jspluginonline.length - 1; idx >= 0; --idx){
if (parseResult.jsplugins.jspluginonline[idx].$.name == onlinePlugin.$.name){
parseResult.jsplugins.jspluginonline[idx] = onlinePlugin
bFind = true
continue
} else if (parseResult.jsplugins.jspluginonline[idx].$.url == onlinePlugin.$.url){
delete parseResult.jsplugins.jspluginonline[idx]
}
}
if (!bFind){
parseResult.jsplugins.jspluginonline.push(onlinePlugin)
}
}else{
parseResult.jsplugins.jspluginonline = [].concat(onlinePlugin)
}
publishXml = new xml2js.Builder().buildObject(parseResult)
let xmlString = publishXml.toString({ pretty: true })
const publishXmlPath = GetPublistXmlPath()
return new Promise((r, j)=>{
fsEx.ensureDirSync(path.dirname(publishXmlPath))
fs.writeFile(publishXmlPath, xmlString, ()=>{
r(port);
})
})
}
function GetPublistXmlPath(){
let directPath = ""
if (os.platform() == 'win32') {
directPath = path.resolve(process.env.APPDATA, 'kingsoft/wps/jsaddons/publish.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/publish.xml")
}
return directPath
}
module.exports = {
debug
}