@kdcloudjs/cli 0.0.2 → 0.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kdcloudjs/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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",
@@ -87,16 +87,19 @@ module.exports = async function debug(options = {}) {
87
87
 
88
88
  // 6. Open Browser & Start Debug
89
89
  // Start server first
90
- const { promise: serverPromise, child } = runDebug(root)
90
+ const { serverPromise, serverChild, buildPromise, buildReadyPromise } = runDebug(root)
91
91
 
92
92
  try {
93
- info('Waiting for debug server to be ready...')
94
- await waitForPort(3333, child)
93
+ info('Waiting for debug server and build watcher...')
94
+ await Promise.all([
95
+ waitForPort(3333, serverChild),
96
+ buildReadyPromise
97
+ ])
95
98
  info(`Opening: ${finalUrl}`)
96
99
  openBrowser(finalUrl)
97
100
  } catch (e) {
98
- error(`Server start failed: ${e.message}`)
101
+ error(`Start failed: ${e.message}`)
99
102
  }
100
103
 
101
- await serverPromise
104
+ await Promise.all([serverPromise, buildPromise])
102
105
  }
@@ -1,3 +1,5 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
1
3
  const { spawn } = require('child_process')
2
4
  const { info } = require('../../utils/log')
3
5
 
@@ -17,19 +19,62 @@ function installDeps(root) {
17
19
  }
18
20
 
19
21
  function runDebug(root) {
20
- info('Starting debug server...')
21
- let childProcess
22
- const promise = new Promise((resolve, reject) => {
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) => {
23
46
  const child = spawn('npm', ['run', 'debug'], {
24
47
  cwd: root,
25
48
  stdio: 'inherit',
26
49
  shell: true
27
50
  })
28
- childProcess = child
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
29
74
  child.on('close', resolve)
30
75
  child.on('error', reject)
31
76
  })
32
- return { promise, child: childProcess }
77
+ return { serverPromise, serverChild, buildPromise, buildChild, buildReadyPromise }
33
78
  }
34
79
 
35
80
  module.exports = {
@@ -7,7 +7,7 @@ const openapiAuth = require('./openapi')
7
7
  const { error } = require('../../../utils/log')
8
8
 
9
9
  module.exports = async function doAuth(options ={}) {
10
- let { type, env } = options
10
+ let { type, targetEnv: env } = options
11
11
 
12
12
  if (env === undefined) {
13
13
  env = loadDefaultEnv()?.name
@@ -3,7 +3,7 @@ const { getAllDataCenters, getAccessToken, getIsv } = require('../../../api')
3
3
  const { safePrompts } = require('../../../utils/prompts')
4
4
  const { encrypt } = require('../../../utils/crypto')
5
5
  const { isValidUrl } = require('../../../utils/validator')
6
- const { error } = require('../../../utils/log')
6
+ const { error, success } = require('../../../utils/log')
7
7
  module.exports = async function authOpenAPI(options = {}) {
8
8
  const { env } = options
9
9
 
@@ -63,6 +63,8 @@ module.exports = async function authOpenAPI(options = {}) {
63
63
  error('Failed to obtain access_token. Please check your credentials and reauthentication.')
64
64
  return
65
65
  }
66
+ success(`Successfully authenticated with OpenAPI.`)
67
+
66
68
 
67
69
  // 获取isv
68
70
  const { isv } = await getIsv(url, token) || {}
@@ -1,3 +1,3 @@
1
1
  module.exports = async function authWeb(options) {
2
- console.log('Web authentication initiated with options:', options)
2
+ console.log('Web authentication is not supported at the moment; OpenAPI authentication is recommended.')
3
3
  }
@@ -24,5 +24,7 @@ module.exports = function envInfo() {
24
24
  env.url || '',
25
25
  env.default ? '★' : ''
26
26
  ])
27
- printTable(headers, rows)
27
+ printTable(headers, rows, {
28
+ colWidths: [20, 50, 10]
29
+ })
28
30
  }
@@ -17,5 +17,7 @@ module.exports = function listEnv() {
17
17
  envs[name].url || '',
18
18
  envs[name].default ? '★' : ''
19
19
  ])
20
- printTable(headers, rows)
20
+ printTable(headers, rows, {
21
+ colWidths: [20, 50, 10]
22
+ })
21
23
  }
@@ -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>BaseFormModel,BillFormModel,DynamicFormModel,MobileBillFormModel,MobileFormModel,KWCFormModel</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>controlAp</type>
40
+ <name>controlKey</name>
41
+ <label>组件名称</label>
42
+ <propertys>
43
+ <property>
44
+ <name>StringValue</name>
45
+ <value>hello world</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) && !/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name)) {
31
- error('React component name must follow PascalCase (start with uppercase) or kebab-case (hyphen-separated)')
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
@@ -20,19 +20,24 @@ module.exports = async function deploy(options = {}) {
20
20
 
21
21
  validateDeploy(ctx)
22
22
 
23
- const files = collectMeta(ctx)
23
+ try {
24
+ const files = collectMeta(ctx)
24
25
 
25
- if(!files || files.length === 0) {
26
- error('No files to deploy')
27
- return
28
- }
26
+ if(!files || files.length === 0) {
27
+ error('No files to deploy')
28
+ return
29
+ }
29
30
 
30
- // 更新isv信息到元数据描述文件 updateIsv
31
- // 在上传前,统一注入 ISV
32
- updateIsv(files, env?.isv)
31
+ // 更新isv信息到元数据描述文件 updateIsv
32
+ // 在上传前,统一注入 ISV
33
+ updateIsv(files, env?.isv)
33
34
 
34
- await upload(files, env)
35
+ await upload(files, env)
35
36
 
36
- // success('部署完成')
37
+ // success('部署完成')
38
+ } catch (err) {
39
+ error(err.message)
40
+ return
41
+ }
37
42
 
38
43
  }
@@ -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' ? `Failed: ${file.message}` : 'Success'
55
+ file.status === 'failed' ? `${file.message}` : 'Success'
56
56
  ])
57
- printTable(headers, rows)
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
@@ -7,7 +7,13 @@ async function getAllDataCenters (url) {
7
7
  url = url.trim().replace(/\/+$/, '')
8
8
  const resp = await fetch(`${url}/auth/getAllDatacenters.do`)
9
9
  if (resp?.status === 200) {
10
- const data = await resp.json()
10
+ let data
11
+ try {
12
+ data = await resp.json()
13
+ } catch (e) {
14
+ error(`⚠ Data center response parsing failed, error message: ${e?.message}`)
15
+ return []
16
+ }
11
17
  if (Array.isArray(data) && data.length > 0) {
12
18
  for (let i = 0; i < data.length; i++) {
13
19
  const d = data[i]
@@ -18,17 +24,17 @@ async function getAllDataCenters (url) {
18
24
  value: d.accountId
19
25
  })
20
26
  } else {
21
- error(`⚠ 数据中心返回异常,请检查url: ${url} 是否正确`)
27
+ error(`⚠ Data center response error, please check if url: ${url} is correct`)
22
28
  }
23
29
  }
24
30
  } else {
25
- error(`⚠ 数据中心列表为空,请检查url: ${url} 是否正确`)
31
+ error(`⚠ Data center list is empty, please check if url: ${url} is correct`)
26
32
  }
27
33
  } else {
28
- error(`⚠ 请求失败,请检查url: ${url} 是否正确。error message: ${resp?.statusText}`)
34
+ error(`⚠ Request failed, please check if url: ${url} is correct. error message: ${resp?.statusText}`)
29
35
  }
30
36
  } catch (e) {
31
- error(`⚠ 请求失败,请检查url: ${url} 是否正确。error message: ${e?.message}`)
37
+ error(`⚠ Request failed, please check if url: ${url} is correct. error message: ${e?.message}`)
32
38
  }
33
39
  return res
34
40
  }
@@ -54,17 +60,23 @@ async function getAccessToken (url, params) {
54
60
  body: JSON.stringify(body)
55
61
  })
56
62
  if (resp?.status === 200) {
57
- const respData = await resp.json()
63
+ let respData
64
+ try {
65
+ respData = await resp.json()
66
+ } catch (e) {
67
+ error(`⚠ Token response parsing failed, error message: ${e?.message}`)
68
+ return {}
69
+ }
58
70
  if (respData?.status) {
59
71
  res = respData?.data || {}
60
72
  } else {
61
- error(`⚠ 获取access_token失败,请检查urlclient_idclient_secretusername是否正确。errormessage: ${respData?.message}`)
73
+ error(`⚠ Failed to get access_token, please check url, client_id, client_secret, username. error message: ${respData?.message}`)
62
74
  }
63
75
  } else {
64
- error(`⚠ 获取access_token失败,请检查urlclient_idclient_secretusername是否正确, response status: ${resp?.status}`)
76
+ error(`⚠ Failed to get access_token, please check url, client_id, client_secret, username. response status: ${resp?.status}`)
65
77
  }
66
78
  } catch (e) {
67
- error(`⚠ 获取access_token失败,请检查urlclient_idclient_secretusername是否正确。error message: ${e?.message}`)
79
+ error(`⚠ Failed to get access_token, please check url, client_id, client_secret, username. error message: ${e?.message}`)
68
80
  }
69
81
  return res
70
82
  }
@@ -81,17 +93,23 @@ async function getIsv (url, access_token) {
81
93
  })
82
94
 
83
95
  if (resp.status === 200) {
84
- const respData = await resp.json()
96
+ let respData
97
+ try {
98
+ respData = await resp.json()
99
+ } catch (e) {
100
+ error(`⚠ ISV response parsing failed, error message: ${e?.message}`)
101
+ return {}
102
+ }
85
103
  if (respData?.status) {
86
104
  res = respData?.data || {}
87
105
  } else {
88
- error(`⚠ get isv failpls check url and credentials info, errormessage: ${respData?.message}`)
106
+ error(`⚠ get isv fail, pls check url and credentials info, errormessage: ${respData?.message}`)
89
107
  }
90
108
  } else {
91
- error(`⚠ get isv failpls check url and credentials info, response status: ${resp?.status}`)
109
+ error(`⚠ get isv fail, pls check url and credentials info, response status: ${resp?.status}`)
92
110
  }
93
111
  } catch (err) {
94
- error(`⚠ getIsv failerror message: ${err.message}`)
112
+ error(`⚠ getIsv fail. error message: ${err.message}`)
95
113
  }
96
114
  return res
97
115
  }
@@ -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(`❌ 未找到文件: ${filePath}`)
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(`⚠ 文本上传失败,尝试文件上传方式: ${err.message}`)
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(`元数据上传失败:${err.message}`)
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(`❌ 未找到文件: ${filePath}`)
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(`⚠ 文本上传失败,尝试文件上传方式: ${err.message}`)
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(`元数据上传失败:${err.message}`)
92
+ throw new Error(`${err.message}`)
93
93
  }
94
94
  }
@@ -1,7 +1,7 @@
1
1
  module.exports = function registerDebug(program) {
2
2
  program
3
3
  .command('debug')
4
- .description('Debug project')
4
+ .description('debug project')
5
5
  .option('-e, --target-env <env>', 'specified target environment')
6
6
  .action((options) =>
7
7
  require('../actions/debug')(options)
@@ -1,53 +1,63 @@
1
+ const KdHelp = require('../../utils/help')
2
+
1
3
  module.exports = function registerEnv(program) {
2
4
  const env = program
3
5
  .command('env')
4
- .description('Environment management')
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>')
8
- .description('Create a new environment')
13
+ .description('create a new environment')
9
14
  .option('--url <url>', 'environment url')
10
15
  .action((name, options) =>
11
16
  require('./create')(name, options)
12
17
  )
13
18
 
14
- env
19
+ const set = env
15
20
  .command('set')
16
- .description('Set environment config')
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
- .description('Set default environment')
28
+ .description('set default environment')
19
29
  .action(name =>
20
30
  require('./set')(name)
21
31
  )
22
32
 
23
33
  env
24
34
  .command('info')
25
- .description('Show current environment info')
35
+ .description('show current environment info')
26
36
  .action(() =>
27
37
  require('./info')()
28
38
  )
29
39
 
30
40
  env
31
41
  .command('list')
32
- .description('List all environments')
42
+ .description('list all environments')
33
43
  .action(() =>
34
44
  require('./list')()
35
45
  )
36
46
 
37
47
  env
38
48
  .command('delete <name>')
39
- .description('Delete an environment')
49
+ .description('delete an environment')
40
50
  .action(name =>
41
51
  require('./delete')(name)
42
52
  )
43
53
 
44
54
  const auth = env
45
55
  .command('auth')
46
- .description('Authenticate environment')
56
+ .description('authenticate environment')
47
57
 
48
58
  auth
49
59
  .command('web')
50
- .description('Authenticate via Web (username/password)')
60
+ .description('authenticate via Web (username/password)')
51
61
  .option('-e --target-env <name>', 'target environment name')
52
62
  .action((options) =>
53
63
  require('./auth')('web', options)
@@ -55,7 +65,7 @@ module.exports = function registerEnv(program) {
55
65
 
56
66
  auth
57
67
  .command('openapi')
58
- .description('Authenticate via OpenAPI (client credentials)')
68
+ .description('authenticate via OpenAPI (client credentials)')
59
69
  .option('-e --target-env <name>', 'target environment name')
60
70
  .action((options) =>
61
71
  require('./auth')('openapi', options)
@@ -1,17 +1,22 @@
1
+ const KdHelp = require('../../utils/help')
2
+
1
3
  module.exports = function registerProject(program) {
2
4
  const project = program
3
5
  .command('project')
4
- .description('Project management')
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>')
8
- .description('Initialize a new project')
13
+ .description('initialize a new project')
9
14
  .option('-s, --source <source>', 'specified repository source', 'outer')
10
15
  .action((name, options) => require('./init')(name, options))
11
16
 
12
17
  project
13
18
  .command('create <name>')
14
- .description('Create control or page xml')
19
+ .description('create control or page xml')
15
20
  .option('--type <type>', 'project type: kwc | page')
16
21
  .action((name, options) =>
17
22
  require('./create')(name, options)
@@ -19,7 +24,7 @@ module.exports = function registerProject(program) {
19
24
 
20
25
  project
21
26
  .command('deploy')
22
- .description('Deploy current project')
27
+ .description('deploy current project')
23
28
  .option('-e --target-env <env>', 'specified target environment')
24
29
  .option('-d --source-dir <dir>', 'specified source directory to deploy')
25
30
  // .option('-f --force', 'force deploy, ignore warnings')
@@ -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
- checkUpdate()
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')
@@ -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
@@ -1,8 +1,10 @@
1
1
  exports.capitalize = function (name) {
2
+ if (!name || typeof name !== 'string') return ''
2
3
  return name[0].toUpperCase() + name.slice(1)
3
4
  }
4
5
 
5
6
  exports.toPascalCase = function (name) {
7
+ if (!name || typeof name !== 'string') return ''
6
8
  return name
7
9
  .replace(/(-\w)/g, (match) => match[1].toUpperCase()) // kebab-case to camelCase
8
10
  .replace(/^[a-z]/, (match) => match.toUpperCase()) // first char to Upper
@@ -1,28 +1,98 @@
1
- function pad(str, len) {
2
- str = String(str)
3
- return str + ' '.repeat(Math.max(len - str.length, 0))
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 colWidths = headers.map((h, i) =>
8
- Math.max(
9
- h.length,
10
- ...rows.map(r => String(r[i] ?? '').length)
11
- )
12
- )
13
-
14
- const line = headers
15
- .map((h, i) => pad(h, colWidths[i]))
16
- .join(' ')
17
-
18
- console.log(line)
19
- console.log(
20
- colWidths.map(w => '-'.repeat(w)).join(' ')
21
- )
22
-
23
- rows.forEach(row => {
24
- console.log(
25
- row.map((cell, i) => pad(cell ?? '', colWidths[i])).join(' ')
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
  }
@@ -1,21 +1,28 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
+ const { error } = require('./log')
3
4
 
4
5
  function getConfig(root) {
5
6
  const configPath = path.join(root, '.kd', 'config.json')
6
7
 
7
8
  if (!fs.existsSync(configPath)) {
8
- throw new Error('未找到 .kd/config.json')
9
+ error('.kd/config.json not found, please run "kd project init" first')
10
+ process.exit(1)
9
11
  }
10
12
 
11
- return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
13
+ try {
14
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
15
+ } catch (e) {
16
+ error('Failed to parse .kd/config.json, please check if the file is valid JSON')
17
+ process.exit(1)
18
+ }
12
19
  }
13
20
 
14
21
  function getConfigValue(root, key) {
15
22
  const config = getConfig(root)
16
23
 
17
- if (!config[key]) {
18
- throw new Error(`config.json 中缺少 ${key}`)
24
+ if (!config || !config[key]) {
25
+ throw new Error(`${key} is missing in config.json`)
19
26
  }
20
27
 
21
28
  return config[key]