@kdcloudjs/cli 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +41 -0
- package/package.json +2 -1
- package/src/actions/debug/index.js +105 -0
- package/src/actions/debug/runner.js +83 -0
- package/src/actions/debug/utils.js +84 -0
- package/src/actions/env/info.js +3 -1
- package/src/actions/env/list.js +3 -1
- package/src/actions/project/create/createMetaXml.js +37 -6
- package/src/actions/project/create/page.js +11 -0
- package/src/actions/project/create/validate.js +2 -2
- package/src/actions/project/deploy/upload.js +9 -3
- package/src/actions/project/init/index.js +5 -5
- package/src/api/index.js +10 -10
- package/src/api/uploader/updateKwcMeta.js +3 -3
- package/src/api/uploader/updatePageMeta.js +3 -3
- package/src/commands/debug.js +9 -0
- package/src/commands/env/index.js +13 -11
- package/src/commands/project/index.js +5 -0
- package/src/commands/update.js +52 -0
- package/src/index.js +8 -2
- package/src/utils/help.js +18 -0
- package/src/utils/printTable.js +94 -24
- package/src/utils/projectConfig.js +2 -2
- package/src/actions/env/remote.js +0 -59
- package/src/commands/env/remote.js +0 -2
package/README.MD
CHANGED
|
@@ -19,4 +19,45 @@ kd project create kwcdemo --type kwc
|
|
|
19
19
|
#### 创建页面元数据
|
|
20
20
|
```
|
|
21
21
|
kd project create kwcdemo --type page
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
#### 环境配置
|
|
25
|
+
```
|
|
26
|
+
创建环境
|
|
27
|
+
kd env create dev --url https://dev.com
|
|
28
|
+
|
|
29
|
+
设置目标环境
|
|
30
|
+
kd env set target-env dev
|
|
31
|
+
|
|
32
|
+
当前环境信息
|
|
33
|
+
kd env info
|
|
34
|
+
|
|
35
|
+
环境列表
|
|
36
|
+
kd env list
|
|
37
|
+
|
|
38
|
+
删除环境
|
|
39
|
+
kd env delete dev
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### 认证授权
|
|
44
|
+
```
|
|
45
|
+
openapi认证
|
|
46
|
+
kd env auth openapi -e dev
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### 上传
|
|
51
|
+
```
|
|
52
|
+
kd project deploy
|
|
53
|
+
kd project deploy -e dev
|
|
54
|
+
kd project deploy -e dev -d app/kwc
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### 调试
|
|
59
|
+
```
|
|
60
|
+
kd debug
|
|
61
|
+
kd debug -e dev
|
|
62
|
+
|
|
22
63
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kdcloudjs/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Kingdee CLI",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"axios": "^1.12.2",
|
|
19
19
|
"chalk": "4.1.2",
|
|
20
|
+
"cli-table3": "^0.6.5",
|
|
20
21
|
"commander": "12.1.0",
|
|
21
22
|
"download-git-repo": "^3.0.2",
|
|
22
23
|
"form-data": "^4.0.4",
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const { findProjectRoot } = require('../project/deploy/projectRoot')
|
|
4
|
+
const { getEnvByName, loadDefaultEnv } = require('../env/config')
|
|
5
|
+
const { error, info } = require('../../utils/log')
|
|
6
|
+
const { safePrompts } = require('../../utils/prompts')
|
|
7
|
+
const { openBrowser, waitForPort, walk, getFormId } = require('./utils')
|
|
8
|
+
const { installDeps, runDebug } = require('./runner')
|
|
9
|
+
|
|
10
|
+
module.exports = async function debug(options = {}) {
|
|
11
|
+
const cwd = process.cwd()
|
|
12
|
+
const root = findProjectRoot(cwd)
|
|
13
|
+
|
|
14
|
+
if (!root) {
|
|
15
|
+
error('Current directory is not a KWC project')
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 1. Check and install dependencies
|
|
20
|
+
if (!fs.existsSync(path.join(root, 'node_modules'))) {
|
|
21
|
+
try {
|
|
22
|
+
await installDeps(root)
|
|
23
|
+
} catch (e) {
|
|
24
|
+
error(e.message)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 2. Resolve Environment
|
|
30
|
+
let env
|
|
31
|
+
const envName = options.targetEnv
|
|
32
|
+
if (envName) {
|
|
33
|
+
env = getEnvByName(envName)
|
|
34
|
+
if (!env) {
|
|
35
|
+
error(`Environment "${envName}" not found`)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
env = loadDefaultEnv()
|
|
40
|
+
if (!env || !env.name) {
|
|
41
|
+
error('No environment specified and no default environment found')
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
info(`Using default environment: ${env.name}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!env.url) {
|
|
48
|
+
error(`Environment "${env.name}" has no URL configured`)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 3. Get Form IDs
|
|
53
|
+
const pagesDir = path.join(root, 'app/pages')
|
|
54
|
+
const pageFiles = walk(pagesDir)
|
|
55
|
+
const forms = []
|
|
56
|
+
|
|
57
|
+
for (const file of pageFiles) {
|
|
58
|
+
const id = getFormId(file)
|
|
59
|
+
if (id) {
|
|
60
|
+
forms.push({
|
|
61
|
+
title: id,
|
|
62
|
+
value: id
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (forms.length === 0) {
|
|
68
|
+
error('No pages found in app/pages')
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 4. Select Form
|
|
73
|
+
const response = await safePrompts({
|
|
74
|
+
type: 'select',
|
|
75
|
+
name: 'formId',
|
|
76
|
+
message: 'Select a form to debug',
|
|
77
|
+
choices: forms
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
if (!response.formId) return
|
|
81
|
+
|
|
82
|
+
// 5. Construct URL
|
|
83
|
+
const targetUrl = new URL(env.url)
|
|
84
|
+
targetUrl.searchParams.append('formId', response.formId)
|
|
85
|
+
targetUrl.searchParams.append('kdkwc_cdn', 'http://localhost:3333')
|
|
86
|
+
const finalUrl = targetUrl.toString()
|
|
87
|
+
|
|
88
|
+
// 6. Open Browser & Start Debug
|
|
89
|
+
// Start server first
|
|
90
|
+
const { serverPromise, serverChild, buildPromise, buildReadyPromise } = runDebug(root)
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
info('Waiting for debug server and build watcher...')
|
|
94
|
+
await Promise.all([
|
|
95
|
+
waitForPort(3333, serverChild),
|
|
96
|
+
buildReadyPromise
|
|
97
|
+
])
|
|
98
|
+
info(`Opening: ${finalUrl}`)
|
|
99
|
+
openBrowser(finalUrl)
|
|
100
|
+
} catch (e) {
|
|
101
|
+
error(`Start failed: ${e.message}`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await Promise.all([serverPromise, buildPromise])
|
|
105
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const { spawn } = require('child_process')
|
|
4
|
+
const { info } = require('../../utils/log')
|
|
5
|
+
|
|
6
|
+
function installDeps(root) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
info('Installing dependencies...')
|
|
9
|
+
const child = spawn('npm', ['install'], {
|
|
10
|
+
cwd: root,
|
|
11
|
+
stdio: 'inherit',
|
|
12
|
+
shell: true
|
|
13
|
+
})
|
|
14
|
+
child.on('close', (code) => {
|
|
15
|
+
if (code === 0) resolve()
|
|
16
|
+
else reject(new Error(`npm install failed with code ${code}`))
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function runDebug(root) {
|
|
22
|
+
info('Starting build watcher...')
|
|
23
|
+
let buildChild
|
|
24
|
+
let buildReadyResolve
|
|
25
|
+
const buildReadyPromise = new Promise(resolve => {
|
|
26
|
+
buildReadyResolve = resolve
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// Poll dist/kwc for content
|
|
30
|
+
const checkInterval = setInterval(() => {
|
|
31
|
+
const distDir = path.join(root, 'dist/kwc')
|
|
32
|
+
if (fs.existsSync(distDir)) {
|
|
33
|
+
try {
|
|
34
|
+
const files = fs.readdirSync(distDir)
|
|
35
|
+
if (files.length > 0) {
|
|
36
|
+
clearInterval(checkInterval)
|
|
37
|
+
buildReadyResolve()
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
// ignore
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}, 1000)
|
|
44
|
+
|
|
45
|
+
const buildPromise = new Promise((resolve, reject) => {
|
|
46
|
+
const child = spawn('npm', ['run', 'debug'], {
|
|
47
|
+
cwd: root,
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
shell: true
|
|
50
|
+
})
|
|
51
|
+
buildChild = child
|
|
52
|
+
|
|
53
|
+
child.on('close', (code) => {
|
|
54
|
+
clearInterval(checkInterval)
|
|
55
|
+
buildReadyResolve()
|
|
56
|
+
resolve(code)
|
|
57
|
+
})
|
|
58
|
+
child.on('error', (err) => {
|
|
59
|
+
clearInterval(checkInterval)
|
|
60
|
+
buildReadyResolve()
|
|
61
|
+
reject(err)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
info('Starting debug server...')
|
|
66
|
+
let serverChild
|
|
67
|
+
const serverPromise = new Promise((resolve, reject) => {
|
|
68
|
+
const child = spawn('npm', ['run', 'server'], {
|
|
69
|
+
cwd: root,
|
|
70
|
+
stdio: 'inherit',
|
|
71
|
+
shell: true
|
|
72
|
+
})
|
|
73
|
+
serverChild = child
|
|
74
|
+
child.on('close', resolve)
|
|
75
|
+
child.on('error', reject)
|
|
76
|
+
})
|
|
77
|
+
return { serverPromise, serverChild, buildPromise, buildChild, buildReadyPromise }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
installDeps,
|
|
82
|
+
runDebug
|
|
83
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const net = require('net')
|
|
4
|
+
const { exec } = require('child_process')
|
|
5
|
+
|
|
6
|
+
function openBrowser(url) {
|
|
7
|
+
let cmd
|
|
8
|
+
if (process.platform === 'win32') {
|
|
9
|
+
cmd = `start "" "${url}"`
|
|
10
|
+
} else if (process.platform === 'darwin') {
|
|
11
|
+
cmd = `open "${url}"`
|
|
12
|
+
} else {
|
|
13
|
+
cmd = `xdg-open "${url}"`
|
|
14
|
+
}
|
|
15
|
+
exec(cmd)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function checkPort(port) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const socket = new net.Socket()
|
|
21
|
+
socket.setTimeout(200)
|
|
22
|
+
socket.on('connect', () => {
|
|
23
|
+
socket.destroy()
|
|
24
|
+
resolve(true)
|
|
25
|
+
})
|
|
26
|
+
socket.on('timeout', () => {
|
|
27
|
+
socket.destroy()
|
|
28
|
+
reject(new Error('timeout'))
|
|
29
|
+
})
|
|
30
|
+
socket.on('error', (err) => {
|
|
31
|
+
socket.destroy()
|
|
32
|
+
reject(err)
|
|
33
|
+
})
|
|
34
|
+
socket.connect(port, '127.0.0.1')
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function waitForPort(port, child, retries = 60) {
|
|
39
|
+
for (let i = 0; i < retries; i++) {
|
|
40
|
+
if (child.exitCode !== null) {
|
|
41
|
+
throw new Error(`Server process exited with code ${child.exitCode}`)
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await checkPort(port)
|
|
45
|
+
return true
|
|
46
|
+
} catch (e) {
|
|
47
|
+
await new Promise(r => setTimeout(r, 1000))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`Timeout waiting for port ${port}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function walk(dir, files = []) {
|
|
54
|
+
if (!fs.existsSync(dir)) return files
|
|
55
|
+
|
|
56
|
+
const items = fs.readdirSync(dir)
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
const fullPath = path.join(dir, item)
|
|
59
|
+
const stat = fs.statSync(fullPath)
|
|
60
|
+
if (stat.isDirectory()) {
|
|
61
|
+
walk(fullPath, files)
|
|
62
|
+
} else if (item.endsWith('.page-meta.kwp')) {
|
|
63
|
+
files.push(fullPath)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return files
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getFormId(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
72
|
+
const match = content.match(/<name>(.*?)<\/name>/)
|
|
73
|
+
return match ? match[1] : null
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
openBrowser,
|
|
81
|
+
waitForPort,
|
|
82
|
+
walk,
|
|
83
|
+
getFormId
|
|
84
|
+
}
|
package/src/actions/env/info.js
CHANGED
package/src/actions/env/list.js
CHANGED
|
@@ -10,16 +10,47 @@ module.exports = function createMetaXml({ name, moduleId, framework }) {
|
|
|
10
10
|
<moduleid>${moduleId}</moduleid>
|
|
11
11
|
<framework>${framework}</framework>
|
|
12
12
|
<targets>
|
|
13
|
-
<target>BaseFormModel</target>
|
|
14
|
-
<target>BillFormModel</target>
|
|
15
|
-
<target>DynamicFormModel</target>
|
|
16
|
-
<target>MobileBillFormModel</target>
|
|
17
|
-
<target>MobileFormModel</target>
|
|
18
13
|
<target>KWCFormModel</target>
|
|
19
14
|
</targets>
|
|
20
15
|
<targetConfigs>
|
|
21
16
|
<targetConfig>
|
|
22
|
-
<targets>
|
|
17
|
+
<targets>KWCFormModel</targets>
|
|
18
|
+
<!-- <property
|
|
19
|
+
description="文本类型属性"
|
|
20
|
+
name="StringValue"
|
|
21
|
+
caption="文本类型属性"
|
|
22
|
+
type="String"
|
|
23
|
+
length="25"
|
|
24
|
+
default="默认值">
|
|
25
|
+
</property>
|
|
26
|
+
<property
|
|
27
|
+
name="IntValue"
|
|
28
|
+
type="Integer"
|
|
29
|
+
caption="数值类型属性"
|
|
30
|
+
description="数值类型属性"
|
|
31
|
+
max="10"
|
|
32
|
+
min="2"
|
|
33
|
+
default="2"
|
|
34
|
+
></property>
|
|
35
|
+
<property
|
|
36
|
+
name="ComboValue"
|
|
37
|
+
type="Combo"
|
|
38
|
+
default="0"
|
|
39
|
+
caption="下拉列表类型属性"
|
|
40
|
+
description="下拉列表类型属性" >
|
|
41
|
+
<items>
|
|
42
|
+
<item id="0" name="默认名称" />
|
|
43
|
+
<item id="1" name="选项1" />
|
|
44
|
+
<item id="2" name="选项2" />
|
|
45
|
+
</items>
|
|
46
|
+
</property>
|
|
47
|
+
|
|
48
|
+
<property name="BooleanValue"
|
|
49
|
+
type="Boolean"
|
|
50
|
+
caption="布尔类型属性"
|
|
51
|
+
description="布尔类型属性"
|
|
52
|
+
default="true" >
|
|
53
|
+
</property> -->
|
|
23
54
|
</targetConfig>
|
|
24
55
|
</targetConfigs>
|
|
25
56
|
</KingdeeComponentBundle>`
|
|
@@ -35,6 +35,17 @@ function createPageXml(name) {
|
|
|
35
35
|
<region>
|
|
36
36
|
<name>region1</name>
|
|
37
37
|
<controls>
|
|
38
|
+
<!-- <control>
|
|
39
|
+
<type>ReactDemoAp</type>
|
|
40
|
+
<name>ReactDemo</name>
|
|
41
|
+
<label>测试组件名称</label>
|
|
42
|
+
<propertys>
|
|
43
|
+
<property>
|
|
44
|
+
<name>StringValue</name>
|
|
45
|
+
<value>3333</value>
|
|
46
|
+
</property>
|
|
47
|
+
</propertys>
|
|
48
|
+
</control> -->
|
|
38
49
|
</controls>
|
|
39
50
|
</region>
|
|
40
51
|
</regions>
|
|
@@ -27,8 +27,8 @@ module.exports = function validate({ name, type, framework }) {
|
|
|
27
27
|
}
|
|
28
28
|
break
|
|
29
29
|
case FRAMEWORKS.React:
|
|
30
|
-
if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)
|
|
31
|
-
error('React component name must follow PascalCase (start with uppercase)
|
|
30
|
+
if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
|
|
31
|
+
error('React component name must follow PascalCase (start with uppercase)')
|
|
32
32
|
process.exit(1)
|
|
33
33
|
}
|
|
34
34
|
break
|
|
@@ -50,11 +50,17 @@ exports.upload = async function upload(files, env) {
|
|
|
50
50
|
const headers = ['Type', 'File', 'Status', 'Message']
|
|
51
51
|
const rows = results.map(file => [
|
|
52
52
|
file.type,
|
|
53
|
-
file.path,
|
|
53
|
+
path.relative(process.cwd(), file.path),
|
|
54
54
|
file.status === 'success' ? '✔' : '✖',
|
|
55
|
-
file.status === 'failed' ?
|
|
55
|
+
file.status === 'failed' ? `${file.message}` : 'Success'
|
|
56
56
|
])
|
|
57
|
-
|
|
57
|
+
|
|
58
|
+
const hasFailed = results.some(r => r.status === 'failed')
|
|
59
|
+
const colWidths = hasFailed ? [10, 30, 10, 50] : [10, 50, 10, 30]
|
|
60
|
+
|
|
61
|
+
printTable(headers, rows, {
|
|
62
|
+
colWidths
|
|
63
|
+
})
|
|
58
64
|
console.timeEnd(`🕒 Upload duration`)
|
|
59
65
|
return results
|
|
60
66
|
}
|
|
@@ -8,15 +8,15 @@ const { TEMPLATES, INNER_TEMPLATES } = require('../constants')
|
|
|
8
8
|
|
|
9
9
|
module.exports = async function initProject({ projectName: _projectName, source }) {
|
|
10
10
|
try {
|
|
11
|
+
if (fs.existsSync(path.resolve(process.cwd(), _projectName))) {
|
|
12
|
+
error(`Directory already exists: ${_projectName}`)
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
const { projectName, moduleId, framework, language } = await collectInitInfo(_projectName) || {}
|
|
12
17
|
|
|
13
18
|
const targetDir = path.resolve(process.cwd(), projectName)
|
|
14
19
|
|
|
15
|
-
if (fs.existsSync(targetDir)) {
|
|
16
|
-
error(`Directory already exists: ${projectName}`)
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
|
|
20
20
|
tip(`\n🚀 Creating project: ${projectName}\n`)
|
|
21
21
|
console.time('🕒 Project initialization')
|
|
22
22
|
|
package/src/api/index.js
CHANGED
|
@@ -18,17 +18,17 @@ async function getAllDataCenters (url) {
|
|
|
18
18
|
value: d.accountId
|
|
19
19
|
})
|
|
20
20
|
} else {
|
|
21
|
-
error(`⚠
|
|
21
|
+
error(`⚠ Data center response error, please check if url: ${url} is correct`)
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
} else {
|
|
25
|
-
error(`⚠
|
|
25
|
+
error(`⚠ Data center list is empty, please check if url: ${url} is correct`)
|
|
26
26
|
}
|
|
27
27
|
} else {
|
|
28
|
-
error(`⚠
|
|
28
|
+
error(`⚠ Request failed, please check if url: ${url} is correct. error message: ${resp?.statusText}`)
|
|
29
29
|
}
|
|
30
30
|
} catch (e) {
|
|
31
|
-
error(`⚠
|
|
31
|
+
error(`⚠ Request failed, please check if url: ${url} is correct. error message: ${e?.message}`)
|
|
32
32
|
}
|
|
33
33
|
return res
|
|
34
34
|
}
|
|
@@ -58,13 +58,13 @@ async function getAccessToken (url, params) {
|
|
|
58
58
|
if (respData?.status) {
|
|
59
59
|
res = respData?.data || {}
|
|
60
60
|
} else {
|
|
61
|
-
error(`⚠
|
|
61
|
+
error(`⚠ Failed to get access_token, please check url, client_id, client_secret, username. error message: ${respData?.message}`)
|
|
62
62
|
}
|
|
63
63
|
} else {
|
|
64
|
-
error(`⚠
|
|
64
|
+
error(`⚠ Failed to get access_token, please check url, client_id, client_secret, username. response status: ${resp?.status}`)
|
|
65
65
|
}
|
|
66
66
|
} catch (e) {
|
|
67
|
-
error(`⚠
|
|
67
|
+
error(`⚠ Failed to get access_token, please check url, client_id, client_secret, username. error message: ${e?.message}`)
|
|
68
68
|
}
|
|
69
69
|
return res
|
|
70
70
|
}
|
|
@@ -85,13 +85,13 @@ async function getIsv (url, access_token) {
|
|
|
85
85
|
if (respData?.status) {
|
|
86
86
|
res = respData?.data || {}
|
|
87
87
|
} else {
|
|
88
|
-
error(`⚠ get isv fail
|
|
88
|
+
error(`⚠ get isv fail, pls check url and credentials info, errormessage: ${respData?.message}`)
|
|
89
89
|
}
|
|
90
90
|
} else {
|
|
91
|
-
error(`⚠ get isv fail
|
|
91
|
+
error(`⚠ get isv fail, pls check url and credentials info, response status: ${resp?.status}`)
|
|
92
92
|
}
|
|
93
93
|
} catch (err) {
|
|
94
|
-
error(`⚠ getIsv fail
|
|
94
|
+
error(`⚠ getIsv fail. error message: ${err.message}`)
|
|
95
95
|
}
|
|
96
96
|
return res
|
|
97
97
|
}
|
|
@@ -9,7 +9,7 @@ const { decrypt } = require('../../utils/crypto')
|
|
|
9
9
|
module.exports.updateKwcMeta = async function updateKwcMeta(filePath, env = {}) {
|
|
10
10
|
// TODO: 调用真实接口
|
|
11
11
|
if (!fs.existsSync(filePath)) {
|
|
12
|
-
throw new Error(`❌
|
|
12
|
+
throw new Error(`❌ File not found: ${filePath}`)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const { url: backendUrl, client_id, client_secret, username, accountId, access_token } = env
|
|
@@ -62,7 +62,7 @@ module.exports.updateKwcMeta = async function updateKwcMeta(filePath, env = {})
|
|
|
62
62
|
return res
|
|
63
63
|
}
|
|
64
64
|
} catch (err) {
|
|
65
|
-
console.warn(`⚠
|
|
65
|
+
console.warn(`⚠ Text upload failed, trying file upload: ${err.message}`)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// ============ 第二阶段:FormData 文件流上传 ============
|
|
@@ -85,6 +85,6 @@ module.exports.updateKwcMeta = async function updateKwcMeta(filePath, env = {})
|
|
|
85
85
|
}
|
|
86
86
|
throw new Error(`File upload failed: ${res?.data?.message || res.status}`)
|
|
87
87
|
} catch (err) {
|
|
88
|
-
throw new Error(
|
|
88
|
+
throw new Error(`${err.message}`)
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -14,7 +14,7 @@ const { decrypt } = require('../../utils/crypto')
|
|
|
14
14
|
module.exports.updatePageMeta = async function updatePageMeta(filePath, env = {}) {
|
|
15
15
|
// TODO: 调用真实接口
|
|
16
16
|
if (!fs.existsSync(filePath)) {
|
|
17
|
-
throw new Error(`❌
|
|
17
|
+
throw new Error(`❌ File not found: ${filePath}`)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const { url: backendUrl, client_id, client_secret, username, accountId, access_token } = env
|
|
@@ -66,7 +66,7 @@ module.exports.updatePageMeta = async function updatePageMeta(filePath, env = {}
|
|
|
66
66
|
return res
|
|
67
67
|
}
|
|
68
68
|
} catch (err) {
|
|
69
|
-
console.warn(`⚠
|
|
69
|
+
console.warn(`⚠ Text upload failed, trying file upload: ${err.message}`)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// ============ 第二阶段:FormData 文件流上传 ============
|
|
@@ -89,6 +89,6 @@ module.exports.updatePageMeta = async function updatePageMeta(filePath, env = {}
|
|
|
89
89
|
}
|
|
90
90
|
throw new Error(`File upload failed: ${res?.data?.message || res.status}`)
|
|
91
91
|
} catch (err) {
|
|
92
|
-
throw new Error(
|
|
92
|
+
throw new Error(`${err.message}`)
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
const KdHelp = require('../../utils/help')
|
|
2
|
+
|
|
1
3
|
module.exports = function registerEnv(program) {
|
|
2
4
|
const env = program
|
|
3
5
|
.command('env')
|
|
4
6
|
.description('Environment management')
|
|
7
|
+
.usage('[command] [options]')
|
|
8
|
+
|
|
9
|
+
env.createHelp = () => new KdHelp(env.configureHelp())
|
|
5
10
|
|
|
6
11
|
env
|
|
7
12
|
.command('create <name>')
|
|
@@ -11,9 +16,14 @@ module.exports = function registerEnv(program) {
|
|
|
11
16
|
require('./create')(name, options)
|
|
12
17
|
)
|
|
13
18
|
|
|
14
|
-
env
|
|
19
|
+
const set = env
|
|
15
20
|
.command('set')
|
|
16
21
|
.description('Set environment config')
|
|
22
|
+
.usage('[command] [options]')
|
|
23
|
+
|
|
24
|
+
set.createHelp = () => new KdHelp(set.configureHelp())
|
|
25
|
+
|
|
26
|
+
set
|
|
17
27
|
.command('target-env <name>')
|
|
18
28
|
.description('Set default environment')
|
|
19
29
|
.action(name =>
|
|
@@ -41,14 +51,6 @@ module.exports = function registerEnv(program) {
|
|
|
41
51
|
require('./delete')(name)
|
|
42
52
|
)
|
|
43
53
|
|
|
44
|
-
env
|
|
45
|
-
.command('remote [envName]')
|
|
46
|
-
.description('Open remote environment page with local debug server')
|
|
47
|
-
.requiredOption('-f, --form-id <id>', 'Form ID')
|
|
48
|
-
.action((envName, options) =>
|
|
49
|
-
require('./remote')(envName, options)
|
|
50
|
-
)
|
|
51
|
-
|
|
52
54
|
const auth = env
|
|
53
55
|
.command('auth')
|
|
54
56
|
.description('Authenticate environment')
|
|
@@ -56,7 +58,7 @@ module.exports = function registerEnv(program) {
|
|
|
56
58
|
auth
|
|
57
59
|
.command('web')
|
|
58
60
|
.description('Authenticate via Web (username/password)')
|
|
59
|
-
.option('--env <name>', 'target environment name')
|
|
61
|
+
.option('-e --target-env <name>', 'target environment name')
|
|
60
62
|
.action((options) =>
|
|
61
63
|
require('./auth')('web', options)
|
|
62
64
|
)
|
|
@@ -64,7 +66,7 @@ module.exports = function registerEnv(program) {
|
|
|
64
66
|
auth
|
|
65
67
|
.command('openapi')
|
|
66
68
|
.description('Authenticate via OpenAPI (client credentials)')
|
|
67
|
-
.option('--env <name>', 'target environment name')
|
|
69
|
+
.option('-e --target-env <name>', 'target environment name')
|
|
68
70
|
.action((options) =>
|
|
69
71
|
require('./auth')('openapi', options)
|
|
70
72
|
)
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
const KdHelp = require('../../utils/help')
|
|
2
|
+
|
|
1
3
|
module.exports = function registerProject(program) {
|
|
2
4
|
const project = program
|
|
3
5
|
.command('project')
|
|
4
6
|
.description('Project management')
|
|
7
|
+
.usage('[command] [options]')
|
|
8
|
+
|
|
9
|
+
project.createHelp = () => new KdHelp(project.configureHelp())
|
|
5
10
|
|
|
6
11
|
project
|
|
7
12
|
.command('init <name>')
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const { execSync } = require('child_process')
|
|
2
|
+
const os = require('os')
|
|
3
|
+
const ora = require('ora')
|
|
4
|
+
const chalk = require('chalk')
|
|
5
|
+
const { info, success, error } = require('../utils/log')
|
|
6
|
+
const pkg = require('../../package.json')
|
|
7
|
+
|
|
8
|
+
module.exports = async () => {
|
|
9
|
+
const spinner = ora('Checking for updates...').start()
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// Check latest version
|
|
13
|
+
let latestVersion
|
|
14
|
+
try {
|
|
15
|
+
latestVersion = execSync(`npm view ${pkg.name} version`, { encoding: 'utf8', cwd: os.homedir() }).trim()
|
|
16
|
+
} catch (e) {
|
|
17
|
+
spinner.stop()
|
|
18
|
+
error('Failed to check for updates. Please check your network connection.')
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (latestVersion === pkg.version) {
|
|
23
|
+
spinner.stop()
|
|
24
|
+
success(`You are already using the latest version (${chalk.green(pkg.version)}).`)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
spinner.stop()
|
|
29
|
+
info(`New version available: ${chalk.green(latestVersion)} (current: ${chalk.yellow(pkg.version)})`)
|
|
30
|
+
spinner.start('Updating...')
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// Execute update
|
|
34
|
+
// Use os.homedir() as cwd to avoid reading project-specific .npmrc which might cause warnings
|
|
35
|
+
execSync(`npm install -g ${pkg.name}@latest`, { stdio: 'inherit', cwd: os.homedir() })
|
|
36
|
+
spinner.stop()
|
|
37
|
+
success(`Successfully updated to version ${chalk.green(latestVersion)}`)
|
|
38
|
+
} catch (err) {
|
|
39
|
+
spinner.stop()
|
|
40
|
+
error('Update failed')
|
|
41
|
+
console.log(chalk.red('\nPlease try running the following command manually:'))
|
|
42
|
+
console.log(chalk.cyan(`npm install -g ${pkg.name}@latest`))
|
|
43
|
+
if (process.platform !== 'win32') {
|
|
44
|
+
console.log(chalk.dim('(You may need to use sudo)'))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
spinner.stop()
|
|
49
|
+
error('An error occurred during update')
|
|
50
|
+
console.error(error)
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/index.js
CHANGED
|
@@ -3,15 +3,20 @@
|
|
|
3
3
|
const { Command } = require('commander')
|
|
4
4
|
const pkg = require('../package.json')
|
|
5
5
|
const checkUpdate = require('./utils/checkUpdate')
|
|
6
|
+
const KdHelp = require('./utils/help')
|
|
6
7
|
|
|
7
8
|
const program = new Command()
|
|
9
|
+
program.createHelp = () => new KdHelp(program.configureHelp())
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
if (!process.argv.includes('update')) {
|
|
12
|
+
checkUpdate()
|
|
13
|
+
}
|
|
10
14
|
|
|
11
15
|
program
|
|
12
16
|
.name('kd')
|
|
17
|
+
.usage('[command] [options]')
|
|
13
18
|
.description('Kingdee CLI')
|
|
14
|
-
.version(pkg.version)
|
|
19
|
+
.version(pkg.version, '-v, --version')
|
|
15
20
|
|
|
16
21
|
program
|
|
17
22
|
.command('update')
|
|
@@ -20,5 +25,6 @@ program
|
|
|
20
25
|
|
|
21
26
|
require('./commands/project')(program)
|
|
22
27
|
require('./commands/env')(program)
|
|
28
|
+
require('./commands/debug')(program)
|
|
23
29
|
|
|
24
30
|
program.parse(process.argv)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { Help } = require('commander')
|
|
2
|
+
|
|
3
|
+
class KdHelp extends Help {
|
|
4
|
+
subcommandTerm(cmd) {
|
|
5
|
+
if (cmd.name() === 'init') {
|
|
6
|
+
return 'init <name> [options]'
|
|
7
|
+
}
|
|
8
|
+
if (cmd.name() === 'create') {
|
|
9
|
+
return 'create <name> [options]'
|
|
10
|
+
}
|
|
11
|
+
if (cmd.name() === 'target-env') {
|
|
12
|
+
return 'target-env <name> [options]'
|
|
13
|
+
}
|
|
14
|
+
return super.subcommandTerm(cmd)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = KdHelp
|
package/src/utils/printTable.js
CHANGED
|
@@ -1,28 +1,98 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const Table = require('cli-table3')
|
|
2
|
+
|
|
3
|
+
let stringWidth
|
|
4
|
+
try {
|
|
5
|
+
const m = require('string-width')
|
|
6
|
+
stringWidth = m.default || m
|
|
7
|
+
} catch (e) {
|
|
8
|
+
// Fallback (simple approximation) if string-width is not available
|
|
9
|
+
stringWidth = (str) => {
|
|
10
|
+
let width = 0
|
|
11
|
+
for (let i = 0; i < str.length; i++) {
|
|
12
|
+
width += str.charCodeAt(i) > 255 ? 2 : 1
|
|
13
|
+
}
|
|
14
|
+
return width
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function wrapText(text, width) {
|
|
19
|
+
if (width <= 0) return text
|
|
20
|
+
const lines = []
|
|
21
|
+
let line = ''
|
|
22
|
+
let lineWidth = 0
|
|
23
|
+
|
|
24
|
+
for (const char of text) {
|
|
25
|
+
const cw = stringWidth(char)
|
|
26
|
+
if (lineWidth + cw > width) {
|
|
27
|
+
lines.push(line)
|
|
28
|
+
line = char
|
|
29
|
+
lineWidth = cw
|
|
30
|
+
} else {
|
|
31
|
+
line += char
|
|
32
|
+
lineWidth += cw
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
lines.push(line)
|
|
36
|
+
return lines.join('\n')
|
|
4
37
|
}
|
|
5
38
|
|
|
6
|
-
module.exports = function printTable(headers, rows) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
module.exports = function printTable(headers, rows, options = {}) {
|
|
40
|
+
const MAX_LENGTH = 500
|
|
41
|
+
|
|
42
|
+
// Pre-process rows to handle flattening, truncation, and manual wrapping
|
|
43
|
+
const processedRows = rows.map(row => {
|
|
44
|
+
return row.map((cell, colIndex) => {
|
|
45
|
+
if (typeof cell !== 'string') return cell
|
|
46
|
+
|
|
47
|
+
let text = cell
|
|
48
|
+
|
|
49
|
+
// 1. Truncate: Limit total length first to avoid processing huge strings
|
|
50
|
+
if (text.length > MAX_LENGTH) {
|
|
51
|
+
text = text.slice(0, MAX_LENGTH) + '... (truncated)'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2. Manual Wrap: If colWidth is provided, wrap manually to avoid cli-table3 CJK bugs
|
|
55
|
+
// We must handle existing newlines by wrapping each line individually
|
|
56
|
+
const colWidth = options.colWidths ? options.colWidths[colIndex] : null
|
|
57
|
+
if (colWidth && typeof colWidth === 'number') {
|
|
58
|
+
// cli-table3 default padding is 1 left + 1 right = 2
|
|
59
|
+
// We use a larger safety margin (3 or 4) to ensure we NEVER exceed the width
|
|
60
|
+
// due to potential width calculation mismatches between string-width versions
|
|
61
|
+
const padding = 2
|
|
62
|
+
const safetyMargin = 2
|
|
63
|
+
const contentWidth = colWidth - padding - safetyMargin
|
|
64
|
+
|
|
65
|
+
if (contentWidth > 0) {
|
|
66
|
+
// Split by existing newlines, wrap each segment, then rejoin
|
|
67
|
+
text = text.split('\n').map(line => wrapText(line, contentWidth)).join('\n')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return text
|
|
72
|
+
})
|
|
27
73
|
})
|
|
74
|
+
|
|
75
|
+
const table = new Table({
|
|
76
|
+
head: headers,
|
|
77
|
+
// Keep internal and horizontal borders, but remove left and right outer borders
|
|
78
|
+
chars: {
|
|
79
|
+
'top': '─', 'top-mid': '┬', 'top-left': '', 'top-right': '',
|
|
80
|
+
'bottom': '─', 'bottom-mid': '┴', 'bottom-left': '', 'bottom-right': '',
|
|
81
|
+
'left': '', 'left-mid': '', 'mid': '─', 'mid-mid': '┼',
|
|
82
|
+
'right': '', 'right-mid': '', 'middle': '│'
|
|
83
|
+
},
|
|
84
|
+
style: {
|
|
85
|
+
head: ['cyan'],
|
|
86
|
+
border: ['grey']
|
|
87
|
+
},
|
|
88
|
+
// Disable cli-table3's wordWrap if we have explicit column widths (since we manually wrapped)
|
|
89
|
+
// Otherwise enable it to let cli-table3 handle auto-sizing
|
|
90
|
+
wordWrap: !options.colWidths,
|
|
91
|
+
wrapOnWordBoundary: false,
|
|
92
|
+
colWidths: options.colWidths
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
processedRows.forEach(row => table.push(row))
|
|
96
|
+
|
|
97
|
+
console.log(table.toString())
|
|
28
98
|
}
|
|
@@ -5,7 +5,7 @@ function getConfig(root) {
|
|
|
5
5
|
const configPath = path.join(root, '.kd', 'config.json')
|
|
6
6
|
|
|
7
7
|
if (!fs.existsSync(configPath)) {
|
|
8
|
-
throw new Error('
|
|
8
|
+
throw new Error('.kd/config.json not found')
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
@@ -15,7 +15,7 @@ function getConfigValue(root, key) {
|
|
|
15
15
|
const config = getConfig(root)
|
|
16
16
|
|
|
17
17
|
if (!config[key]) {
|
|
18
|
-
throw new Error(
|
|
18
|
+
throw new Error(`${key} is missing in config.json`)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
return config[key]
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
const { exec } = require('child_process')
|
|
2
|
-
const { getEnvByName, loadDefaultEnv } = require('./config')
|
|
3
|
-
const { error, info } = require('../../utils/log')
|
|
4
|
-
|
|
5
|
-
module.exports = async function remote(envName, options) {
|
|
6
|
-
const { formId } = options
|
|
7
|
-
|
|
8
|
-
if (!formId) {
|
|
9
|
-
error('Error: --form-id (-f) is required')
|
|
10
|
-
return
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let env
|
|
14
|
-
if (envName) {
|
|
15
|
-
env = getEnvByName(envName)
|
|
16
|
-
if (!env) {
|
|
17
|
-
error(`Error: Environment "${envName}" not found`)
|
|
18
|
-
return
|
|
19
|
-
}
|
|
20
|
-
} else {
|
|
21
|
-
env = loadDefaultEnv()
|
|
22
|
-
if (!env || !env.name) {
|
|
23
|
-
error('Error: No environment specified and no default environment found')
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
info(`Using default environment: ${env.name}`)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!env.url) {
|
|
30
|
-
error(`Error: Environment "${env.name}" has no URL configured`)
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const targetUrl = new URL(env.url)
|
|
36
|
-
targetUrl.searchParams.append('formId', formId)
|
|
37
|
-
targetUrl.searchParams.append('kdkwc_cdn', 'http://localhost:3333')
|
|
38
|
-
|
|
39
|
-
const finalUrl = targetUrl.toString()
|
|
40
|
-
info(`Opening: ${finalUrl}`)
|
|
41
|
-
|
|
42
|
-
let cmd
|
|
43
|
-
if (process.platform === 'win32') {
|
|
44
|
-
cmd = `start "" "${finalUrl}"`
|
|
45
|
-
} else if (process.platform === 'darwin') {
|
|
46
|
-
cmd = `open "${finalUrl}"`
|
|
47
|
-
} else {
|
|
48
|
-
cmd = `xdg-open "${finalUrl}"`
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
exec(cmd, (err) => {
|
|
52
|
-
if (err) {
|
|
53
|
-
error(`Failed to open browser: ${err.message}`)
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
} catch (e) {
|
|
57
|
-
error(`Invalid Environment URL: ${env.url}`)
|
|
58
|
-
}
|
|
59
|
-
}
|