@kdcloudjs/cli 0.0.1

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.
Files changed (59) hide show
  1. package/README.MD +22 -0
  2. package/package.json +28 -0
  3. package/src/actions/env/auth/index.js +33 -0
  4. package/src/actions/env/auth/openapi.js +83 -0
  5. package/src/actions/env/auth/web.js +3 -0
  6. package/src/actions/env/config.js +102 -0
  7. package/src/actions/env/create.js +36 -0
  8. package/src/actions/env/info.js +28 -0
  9. package/src/actions/env/list.js +21 -0
  10. package/src/actions/env/remote.js +59 -0
  11. package/src/actions/env/remove.js +23 -0
  12. package/src/actions/env/resolveEnv.js +27 -0
  13. package/src/actions/env/setDefault.js +20 -0
  14. package/src/actions/project/constants.js +33 -0
  15. package/src/actions/project/create/createMetaXml.js +26 -0
  16. package/src/actions/project/create/index.js +24 -0
  17. package/src/actions/project/create/kwc.js +25 -0
  18. package/src/actions/project/create/page.js +42 -0
  19. package/src/actions/project/create/validate.js +45 -0
  20. package/src/actions/project/create/writeKwcFiles/index.js +15 -0
  21. package/src/actions/project/create/writeKwcFiles/lwc.js +25 -0
  22. package/src/actions/project/create/writeKwcFiles/react.js +49 -0
  23. package/src/actions/project/create/writeKwcFiles/vue.js +48 -0
  24. package/src/actions/project/deploy/collect.js +43 -0
  25. package/src/actions/project/deploy/context.js +26 -0
  26. package/src/actions/project/deploy/index.js +38 -0
  27. package/src/actions/project/deploy/projectRoot.js +32 -0
  28. package/src/actions/project/deploy/updateIsv.js +109 -0
  29. package/src/actions/project/deploy/upload.js +60 -0
  30. package/src/actions/project/deploy/validate.js +13 -0
  31. package/src/actions/project/init/index.js +42 -0
  32. package/src/actions/project/init/post.js +20 -0
  33. package/src/actions/project/init/prompts.js +50 -0
  34. package/src/api/index.js +99 -0
  35. package/src/api/uploader/index.js +7 -0
  36. package/src/api/uploader/updateKwcMeta.js +90 -0
  37. package/src/api/uploader/updatePageMeta.js +94 -0
  38. package/src/commands/env/auth.js +8 -0
  39. package/src/commands/env/create.js +2 -0
  40. package/src/commands/env/delete.js +2 -0
  41. package/src/commands/env/index.js +72 -0
  42. package/src/commands/env/info.js +2 -0
  43. package/src/commands/env/list.js +2 -0
  44. package/src/commands/env/remote.js +2 -0
  45. package/src/commands/env/set.js +2 -0
  46. package/src/commands/project/create.js +8 -0
  47. package/src/commands/project/deploy.js +2 -0
  48. package/src/commands/project/index.js +28 -0
  49. package/src/commands/project/init.js +5 -0
  50. package/src/index.js +24 -0
  51. package/src/utils/checkUpdate.js +18 -0
  52. package/src/utils/crypto.js +53 -0
  53. package/src/utils/download.js +21 -0
  54. package/src/utils/index.js +9 -0
  55. package/src/utils/log.js +20 -0
  56. package/src/utils/printTable.js +28 -0
  57. package/src/utils/projectConfig.js +32 -0
  58. package/src/utils/prompts.js +14 -0
  59. package/src/utils/validator.js +14 -0
@@ -0,0 +1,99 @@
1
+ const { error } = require('../utils/log')
2
+ const crypto = require('crypto')
3
+ async function getAllDataCenters (url) {
4
+ if (!url) return []
5
+ const res = []
6
+ try {
7
+ url = url.trim().replace(/\/+$/, '')
8
+ const resp = await fetch(`${url}/auth/getAllDatacenters.do`)
9
+ if (resp?.status === 200) {
10
+ const data = await resp.json()
11
+ if (Array.isArray(data) && data.length > 0) {
12
+ for (let i = 0; i < data.length; i++) {
13
+ const d = data[i]
14
+ if (d.accountId) {
15
+ res.push({
16
+ ...d,
17
+ title: d.accountName,
18
+ value: d.accountId
19
+ })
20
+ } else {
21
+ error(`⚠ 数据中心返回异常,请检查url: ${url} 是否正确`)
22
+ }
23
+ }
24
+ } else {
25
+ error(`⚠ 数据中心列表为空,请检查url: ${url} 是否正确`)
26
+ }
27
+ } else {
28
+ error(`⚠ 请求失败,请检查url: ${url} 是否正确。error message: ${resp?.statusText}`)
29
+ }
30
+ } catch (e) {
31
+ error(`⚠ 请求失败,请检查url: ${url} 是否正确。error message: ${e?.message}`)
32
+ }
33
+ return res
34
+ }
35
+
36
+ async function getAccessToken (url, params) {
37
+ if (!url) return {}
38
+ let res = {}
39
+ try {
40
+ // 构造请求体
41
+ const body = {
42
+ client_id: params.client_id,
43
+ client_secret: params.client_secret,
44
+ username: params.username,
45
+ accountId: params.accountId,
46
+ language: 'zh_CN',
47
+ nonce: crypto.randomBytes(8).toString('hex'),
48
+ timestamp: `${Date.now()}`
49
+ }
50
+ url = url.trim().replace(/\/+$/, '')
51
+ const resp = await fetch(`${url}/kapi/oauth2/getToken`, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify(body)
55
+ })
56
+ if (resp?.status === 200) {
57
+ const respData = await resp.json()
58
+ if (respData?.status) {
59
+ res = respData?.data || {}
60
+ } else {
61
+ error(`⚠ 获取access_token失败,请检查url、client_id、client_secret、username是否正确。errormessage: ${respData?.message}`)
62
+ }
63
+ } else {
64
+ error(`⚠ 获取access_token失败,请检查url、client_id、client_secret、username是否正确, response status: ${resp?.status}`)
65
+ }
66
+ } catch (e) {
67
+ error(`⚠ 获取access_token失败,请检查url、client_id、client_secret、username是否正确。error message: ${e?.message}`)
68
+ }
69
+ return res
70
+ }
71
+
72
+ async function getIsv (url, access_token) {
73
+ if (!url || !access_token) return {}
74
+ let res = {}
75
+ const baseUrl = url.trim().replace(/\/+$/, '')
76
+ const isvUrl = `${baseUrl}/kapi/v2/mdl/kwcisv`
77
+ try {
78
+ const resp = await fetch(isvUrl, {
79
+ method: 'GET',
80
+ headers: { 'Content-Type': 'application/json', access_token }
81
+ })
82
+
83
+ if (resp.status === 200) {
84
+ const respData = await resp.json()
85
+ if (respData?.status) {
86
+ res = respData?.data || {}
87
+ } else {
88
+ error(`⚠ get isv fail,pls check url and credentials info, errormessage: ${respData?.message}`)
89
+ }
90
+ } else {
91
+ error(`⚠ get isv fail,pls check url and credentials info, response status: ${resp?.status}`)
92
+ }
93
+ } catch (err) {
94
+ error(`⚠ getIsv fail。error message: ${err.message}`)
95
+ }
96
+ return res
97
+ }
98
+
99
+ module.exports = { getAllDataCenters, getAccessToken, getIsv }
@@ -0,0 +1,7 @@
1
+ const { updateKwcMeta } = require('./updateKwcMeta')
2
+ const { updatePageMeta } = require('./updatePageMeta')
3
+
4
+ module.exports = {
5
+ updateKwcMeta,
6
+ updatePageMeta
7
+ }
@@ -0,0 +1,90 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const FormData = require('form-data')
4
+ const axios = require('axios')
5
+ const { getAccessToken } = require('../../api')
6
+ const { isValidUrl } = require('../../utils/validator')
7
+ const { decrypt } = require('../../utils/crypto')
8
+
9
+ module.exports.updateKwcMeta = async function updateKwcMeta(filePath, env = {}) {
10
+ // TODO: 调用真实接口
11
+ if (!fs.existsSync(filePath)) {
12
+ throw new Error(`❌ 未找到文件: ${filePath}`)
13
+ }
14
+
15
+ const { url: backendUrl, client_id, client_secret, username, accountId, access_token } = env
16
+
17
+ // 这里accessToken可能已过期,所以如果有client_id、client_secret、username、accountId这些信息就可以重新获取一下token
18
+ // todo,现在先写死
19
+ // 获取token
20
+ if (!isValidUrl(backendUrl)) {
21
+ throw new Error('Invalid URL format:', backendUrl)
22
+ }
23
+ let token = access_token
24
+ if (client_id && client_secret && username && accountId) {
25
+ const getAccessTokenParams = {
26
+ client_id,
27
+ client_secret: decrypt(client_secret),
28
+ username,
29
+ accountId
30
+ }
31
+ const tokenData = await getAccessToken(backendUrl, getAccessTokenParams) || {}
32
+ token = tokenData.access_token || ''
33
+ if (!token) {
34
+ throw new Error('Failed to obtain access_token. Please check your credentials and reauthentication.')
35
+ }
36
+ }
37
+
38
+ // 去掉 backendUrl 尾部的 /
39
+ const baseUrl = backendUrl.trim().replace(/\/+$/, '')
40
+ const url = `${baseUrl}/kapi/v2/mdl/updateKwc`
41
+
42
+ // ============ 第一阶段:文本方式上传 ============
43
+ try {
44
+ const xmlContent = fs.readFileSync(filePath, 'utf-8')
45
+
46
+ const res = await axios.post(
47
+ url,
48
+ {
49
+ content: xmlContent, // ✅ 将 XML 文件内容作为纯文本传输
50
+ filename: path.basename(filePath), // 文件名
51
+ forceSave: 'true'
52
+ },
53
+ {
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ access_token: token
57
+ }
58
+ }
59
+ )
60
+
61
+ if (res.status === 200 && res.data?.status) {
62
+ return res
63
+ }
64
+ } catch (err) {
65
+ console.warn(`⚠ 文本上传失败,尝试文件上传方式: ${err.message}`)
66
+ }
67
+
68
+ // ============ 第二阶段:FormData 文件流上传 ============
69
+ try {
70
+ // 构造 FormData
71
+ const formData = new FormData()
72
+ formData.append('file', fs.createReadStream(filePath), {
73
+ filename: path.basename(filePath)
74
+ })
75
+ formData.append('forceSave', 'true')
76
+
77
+ const res = await axios.post(url, formData, {
78
+ headers: {
79
+ access_token: token, // ✅ 后端要求的 header
80
+ ...formData.getHeaders() // ✅ 自动补上 multipart 边界
81
+ }
82
+ })
83
+ if (res.status === 200 && res.data?.status) {
84
+ return res
85
+ }
86
+ throw new Error(`File upload failed: ${res?.data?.message || res.status}`)
87
+ } catch (err) {
88
+ throw new Error(`元数据上传失败:${err.message}`)
89
+ }
90
+ }
@@ -0,0 +1,94 @@
1
+ // const { getAccessToken } = require('../../api')
2
+ // module.exports.updatePageMeta = async function updatePageMeta(files, env) {
3
+ // // TODO: 调用真实接口
4
+ // // console.log(files, env, 'updatePageMeta')
5
+ // }
6
+ const fs = require('fs')
7
+ const path = require('path')
8
+ const FormData = require('form-data')
9
+ const axios = require('axios')
10
+ const { getAccessToken } = require('../../api')
11
+ const { isValidUrl } = require('../../utils/validator')
12
+ const { decrypt } = require('../../utils/crypto')
13
+
14
+ module.exports.updatePageMeta = async function updatePageMeta(filePath, env = {}) {
15
+ // TODO: 调用真实接口
16
+ if (!fs.existsSync(filePath)) {
17
+ throw new Error(`❌ 未找到文件: ${filePath}`)
18
+ }
19
+
20
+ const { url: backendUrl, client_id, client_secret, username, accountId, access_token } = env
21
+
22
+ // 这里accessToken可能已过期,所以如果有client_id、client_secret、username、accountId这些信息就可以重新获取一下token
23
+ // todo,现在先写死
24
+ // 获取token
25
+ if (!isValidUrl(backendUrl)) {
26
+ throw new Error('Invalid URL format:', backendUrl)
27
+ }
28
+ let token = access_token
29
+ if (client_id && client_secret && username && accountId) {
30
+ const getAccessTokenParams = {
31
+ client_id,
32
+ client_secret: decrypt(client_secret),
33
+ username,
34
+ accountId
35
+ }
36
+ const tokenData = await getAccessToken(backendUrl, getAccessTokenParams) || {}
37
+ token = tokenData.access_token || ''
38
+ if (!token) {
39
+ throw new Error('Failed to obtain access_token. Please check your credentials and reauthentication.')
40
+ }
41
+ }
42
+
43
+ // 去掉 backendUrl 尾部的 /
44
+ const baseUrl = backendUrl.trim().replace(/\/+$/, '')
45
+ const url = `${baseUrl}/kapi/v2/mdl/updatePageMeta`
46
+
47
+ // ============ 第一阶段:文本方式上传 ============
48
+ try {
49
+ const xmlContent = fs.readFileSync(filePath, 'utf-8')
50
+
51
+ const res = await axios.post(
52
+ url,
53
+ {
54
+ content: xmlContent, // ✅ 将 XML 文件内容作为纯文本传输
55
+ filename: path.basename(filePath), // 文件名
56
+ forceSave: 'true'
57
+ },
58
+ {
59
+ headers: {
60
+ 'Content-Type': 'application/json',
61
+ access_token: token
62
+ }
63
+ }
64
+ )
65
+ if (res.status === 200 && res.data?.status) {
66
+ return res
67
+ }
68
+ } catch (err) {
69
+ console.warn(`⚠ 文本上传失败,尝试文件上传方式: ${err.message}`)
70
+ }
71
+
72
+ // ============ 第二阶段:FormData 文件流上传 ============
73
+ try {
74
+ // 构造 FormData
75
+ const formData = new FormData()
76
+ formData.append('file', fs.createReadStream(filePath), {
77
+ filename: path.basename(filePath)
78
+ })
79
+ formData.append('forceSave', 'true')
80
+
81
+ const res = await axios.post(url, formData, {
82
+ headers: {
83
+ access_token: token, // ✅ 后端要求的 header
84
+ ...formData.getHeaders() // ✅ 自动补上 multipart 边界
85
+ }
86
+ })
87
+ if (res.status === 200 && res.data?.status) {
88
+ return res
89
+ }
90
+ throw new Error(`File upload failed: ${res?.data?.message || res.status}`)
91
+ } catch (err) {
92
+ throw new Error(`元数据上传失败:${err.message}`)
93
+ }
94
+ }
@@ -0,0 +1,8 @@
1
+ const doAuth = require('../../actions/env/auth')
2
+
3
+ module.exports = async function auth(type, options) {
4
+ await doAuth({
5
+ type,
6
+ ...options
7
+ })
8
+ }
@@ -0,0 +1,2 @@
1
+ module.exports = (name, options) =>
2
+ require('../../actions/env/create')(name, options)
@@ -0,0 +1,2 @@
1
+ module.exports = name =>
2
+ require('../../actions/env/remove')(name)
@@ -0,0 +1,72 @@
1
+ module.exports = function registerEnv(program) {
2
+ const env = program
3
+ .command('env')
4
+ .description('Environment management')
5
+
6
+ env
7
+ .command('create <name>')
8
+ .description('Create a new environment')
9
+ .option('--url <url>', 'environment url')
10
+ .action((name, options) =>
11
+ require('./create')(name, options)
12
+ )
13
+
14
+ env
15
+ .command('set')
16
+ .description('Set environment config')
17
+ .command('target-env <name>')
18
+ .description('Set default environment')
19
+ .action(name =>
20
+ require('./set')(name)
21
+ )
22
+
23
+ env
24
+ .command('info')
25
+ .description('Show current environment info')
26
+ .action(() =>
27
+ require('./info')()
28
+ )
29
+
30
+ env
31
+ .command('list')
32
+ .description('List all environments')
33
+ .action(() =>
34
+ require('./list')()
35
+ )
36
+
37
+ env
38
+ .command('delete <name>')
39
+ .description('Delete an environment')
40
+ .action(name =>
41
+ require('./delete')(name)
42
+ )
43
+
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
+ const auth = env
53
+ .command('auth')
54
+ .description('Authenticate environment')
55
+
56
+ auth
57
+ .command('web')
58
+ .description('Authenticate via Web (username/password)')
59
+ .option('--env <name>', 'target environment name')
60
+ .action((options) =>
61
+ require('./auth')('web', options)
62
+ )
63
+
64
+ auth
65
+ .command('openapi')
66
+ .description('Authenticate via OpenAPI (client credentials)')
67
+ .option('--env <name>', 'target environment name')
68
+ .action((options) =>
69
+ require('./auth')('openapi', options)
70
+ )
71
+
72
+ }
@@ -0,0 +1,2 @@
1
+ module.exports = () =>
2
+ require('../../actions/env/info')()
@@ -0,0 +1,2 @@
1
+ module.exports = () =>
2
+ require('../../actions/env/list')()
@@ -0,0 +1,2 @@
1
+ module.exports = (envName, options) =>
2
+ require('../../actions/env/remote')(envName, options)
@@ -0,0 +1,2 @@
1
+ module.exports = name =>
2
+ require('../../actions/env/setDefault')(name)
@@ -0,0 +1,8 @@
1
+ const createProject = require('../../actions/project/create')
2
+
3
+ module.exports = async function create(name, options) {
4
+ await createProject({
5
+ name,
6
+ type: options.type
7
+ })
8
+ }
@@ -0,0 +1,2 @@
1
+ module.exports = (options) =>
2
+ require('../../actions/project/deploy')(options)
@@ -0,0 +1,28 @@
1
+ module.exports = function registerProject(program) {
2
+ const project = program
3
+ .command('project')
4
+ .description('Project management')
5
+
6
+ project
7
+ .command('init <name>')
8
+ .description('Initialize a new project')
9
+ .option('-s, --source <source>', 'specified repository source', 'outer')
10
+ .action((name, options) => require('./init')(name, options))
11
+
12
+ project
13
+ .command('create <name>')
14
+ .description('Create control or page xml')
15
+ .option('--type <type>', 'project type: kwc | page')
16
+ .action((name, options) =>
17
+ require('./create')(name, options)
18
+ )
19
+
20
+ project
21
+ .command('deploy')
22
+ .description('Deploy current project')
23
+ .option('-e --target-env <env>', 'specified target environment')
24
+ .option('-d --source-dir <dir>', 'specified source directory to deploy')
25
+ // .option('-f --force', 'force deploy, ignore warnings')
26
+ // .option('-a --all', 'deploy all metadata')
27
+ .action((options) => require('./deploy')(options))
28
+ }
@@ -0,0 +1,5 @@
1
+ const initProject = require('../../actions/project/init')
2
+
3
+ module.exports = async function init(projectName, options) {
4
+ await initProject({ projectName, source: options?.source })
5
+ }
package/src/index.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander')
4
+ const pkg = require('../package.json')
5
+ const checkUpdate = require('./utils/checkUpdate')
6
+
7
+ const program = new Command()
8
+
9
+ checkUpdate()
10
+
11
+ program
12
+ .name('kd')
13
+ .description('Kingdee CLI')
14
+ .version(pkg.version)
15
+
16
+ program
17
+ .command('update')
18
+ .description('update kd to latest version')
19
+ .action(() => require('./commands/update')())
20
+
21
+ require('./commands/project')(program)
22
+ require('./commands/env')(program)
23
+
24
+ program.parse(process.argv)
@@ -0,0 +1,18 @@
1
+ const pkg = require('../../package.json')
2
+
3
+ async function checkUpdate() {
4
+ const { default: updateNotifier } = await import('update-notifier')
5
+ const notifier = updateNotifier({
6
+ pkg,
7
+ updateCheckInterval: 1000 * 60 * 60 * 24 // 1 天
8
+ })
9
+
10
+ if (notifier.update) {
11
+ notifier.notify({
12
+ defer: false,
13
+ isGlobal: true
14
+ })
15
+ }
16
+ }
17
+
18
+ module.exports = checkUpdate
@@ -0,0 +1,53 @@
1
+ const crypto = require('crypto')
2
+ const fs = require('fs')
3
+ const path = require('path')
4
+ const os = require('os')
5
+
6
+ const KEY_FILE = path.join(os.homedir(), '.kd', 'secret.key')
7
+ const ALGORITHM = 'aes-256-cbc'
8
+
9
+ /**
10
+ * 获取加密密钥,如果不存在则自动生成并保存
11
+ */
12
+ function getKey () {
13
+ if (!fs.existsSync(KEY_FILE)) {
14
+ const dir = path.dirname(KEY_FILE)
15
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
16
+ const key = crypto.randomBytes(32) // 256 bit
17
+ fs.writeFileSync(KEY_FILE, key.toString('hex'), 'utf-8')
18
+ return key
19
+ }
20
+ return Buffer.from(fs.readFileSync(KEY_FILE, 'utf-8'), 'hex')
21
+ }
22
+
23
+ /**
24
+ * 加密文本
25
+ * @param {string} text - 明文
26
+ * @returns {string} - base64编码的密文(包含 iv)
27
+ */
28
+ function encrypt (text) {
29
+ const key = getKey()
30
+ const iv = crypto.randomBytes(16) // 128 bit
31
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
32
+ let encrypted = cipher.update(text, 'utf8', 'base64')
33
+ encrypted += cipher.final('base64')
34
+ return iv.toString('base64') + ':' + encrypted
35
+ }
36
+
37
+ /**
38
+ * 解密文本
39
+ * @param {string} data - base64编码的密文(iv:密文)
40
+ * @returns {string} - 明文
41
+ */
42
+ function decrypt (data) {
43
+ if (!data) return ''
44
+ const key = getKey()
45
+ const [ivStr, encrypted] = data.split(':')
46
+ const iv = Buffer.from(ivStr, 'base64')
47
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv)
48
+ let decrypted = decipher.update(encrypted, 'base64', 'utf8')
49
+ decrypted += decipher.final('utf8')
50
+ return decrypted
51
+ }
52
+
53
+ module.exports = { encrypt, decrypt }
@@ -0,0 +1,21 @@
1
+ const download = require('download-git-repo')
2
+ const ora = require('ora')
3
+ const { error, success } = require('./log.js')
4
+
5
+ module.exports = function downloadTemplate(repo, dest, template) {
6
+ const spinner = ora('downloading ...').start()
7
+
8
+ return new Promise((resolve, reject) => {
9
+ download(repo, dest, { clone: true }, err => {
10
+ if (err) {
11
+ spinner.fail()
12
+ error(`Template ${template} download failed: ${err.message || err}`)
13
+ reject(err)
14
+ } else {
15
+ spinner.stop()
16
+ success('\n✅ project created successfully!\n')
17
+ resolve()
18
+ }
19
+ })
20
+ })
21
+ }
@@ -0,0 +1,9 @@
1
+ exports.capitalize = function (name) {
2
+ return name[0].toUpperCase() + name.slice(1)
3
+ }
4
+
5
+ exports.toPascalCase = function (name) {
6
+ return name
7
+ .replace(/(-\w)/g, (match) => match[1].toUpperCase()) // kebab-case to camelCase
8
+ .replace(/^[a-z]/, (match) => match.toUpperCase()) // first char to Upper
9
+ }
@@ -0,0 +1,20 @@
1
+ const chalk = require('chalk')
2
+
3
+ const warn = (msg) => console.log(chalk.yellow(msg))
4
+ const error = (msg) => console.log(chalk.red(msg))
5
+
6
+ const success = (msg) => console.log(chalk.green(msg))
7
+
8
+ const mark = (msg) => console.log(chalk.magenta(msg))
9
+
10
+ const info = (msg) => console.log(chalk.gray(msg))
11
+ const tip = (msg) => console.log(chalk.cyan(msg))
12
+
13
+ module.exports = {
14
+ warn,
15
+ error,
16
+ success,
17
+ mark,
18
+ info,
19
+ tip
20
+ }
@@ -0,0 +1,28 @@
1
+ function pad(str, len) {
2
+ str = String(str)
3
+ return str + ' '.repeat(Math.max(len - str.length, 0))
4
+ }
5
+
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
+ )
27
+ })
28
+ }
@@ -0,0 +1,32 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ function getConfig(root) {
5
+ const configPath = path.join(root, '.kd', 'config.json')
6
+
7
+ if (!fs.existsSync(configPath)) {
8
+ throw new Error('未找到 .kd/config.json')
9
+ }
10
+
11
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
12
+ }
13
+
14
+ function getConfigValue(root, key) {
15
+ const config = getConfig(root)
16
+
17
+ if (!config[key]) {
18
+ throw new Error(`config.json 中缺少 ${key}`)
19
+ }
20
+
21
+ return config[key]
22
+ }
23
+
24
+ exports.getConfig = getConfig
25
+
26
+ exports.getModuleId = function (root) {
27
+ return getConfigValue(root, 'moduleId')
28
+ }
29
+
30
+ exports.getFramework = function (root) {
31
+ return getConfigValue(root, 'framework')
32
+ }
@@ -0,0 +1,14 @@
1
+ const prompts = require('prompts')
2
+
3
+ function safePrompts (questions) {
4
+ return prompts(questions, {
5
+ onCancel: () => {
6
+ console.log('\n❌ action already canceled, exiting...')
7
+ process.exit(0)
8
+ }
9
+ })
10
+ }
11
+
12
+ module.exports = {
13
+ safePrompts
14
+ }