@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,45 @@
1
+ const { error } = require('../../../utils/log')
2
+ const { PROJECT_TYPES, FRAMEWORKS } = require('../constants')
3
+
4
+ module.exports = function validate({ name, type, framework }) {
5
+ if (!name) {
6
+ error('Component/Page name cannot be empty')
7
+ process.exit(1)
8
+ }
9
+
10
+ if (!type) {
11
+ error('Please specify --type kwc or --type page')
12
+ process.exit(1)
13
+ }
14
+
15
+ if (!Object.values(PROJECT_TYPES).includes(type)) {
16
+ error(`Invalid type: ${type}, supported types: kwc | page`)
17
+ process.exit(1)
18
+ }
19
+
20
+ // 创建的是组件
21
+ if (type === PROJECT_TYPES.KWC) {
22
+ switch (framework) {
23
+ case FRAMEWORKS.LWC:
24
+ if (!/^[a-z][a-zA-Z0-9]*$/.test(name)) {
25
+ error('LWC component name must be lowercase or camelCase')
26
+ process.exit(1)
27
+ }
28
+ break
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)')
32
+ process.exit(1)
33
+ }
34
+ break
35
+ case FRAMEWORKS.Vue:
36
+ if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
37
+ error('Vue component name must follow PascalCase (start with uppercase)')
38
+ process.exit(1)
39
+ }
40
+ break
41
+ default:
42
+ break
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,15 @@
1
+ const { FRAMEWORKS } = require('../../constants')
2
+ const writeLwcFiles = require('./lwc')
3
+ const writeReactFiles = require('./react')
4
+ const writeVueFiles = require('./vue')
5
+ module.exports = function writeKwcFiles(config) {
6
+ const { framework } = config
7
+
8
+ if (framework === FRAMEWORKS.LWC) {
9
+ writeLwcFiles(config)
10
+ } else if (framework === FRAMEWORKS.React) {
11
+ writeReactFiles(config)
12
+ } else if (framework === FRAMEWORKS.Vue) {
13
+ writeVueFiles(config)
14
+ }
15
+ }
@@ -0,0 +1,25 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const { capitalize } = require('../../../../utils/index')
4
+ const createMetaXml = require('../createMetaXml')
5
+ module.exports = function writeLwcFiles(config) {
6
+ const { dir, name, moduleId, framework } = config
7
+ fs.writeFileSync(
8
+ path.join(dir, `${name}.html`),
9
+ `<template>
10
+ </template>
11
+ `
12
+ )
13
+ fs.writeFileSync(
14
+ path.join(dir, `${name}.js`),
15
+ `import { KingdeeElement } from '@kdcloudjs/kwc';
16
+
17
+ export default class ${capitalize(name)} extends KingdeeElement {
18
+ }
19
+ `
20
+ )
21
+ fs.writeFileSync(
22
+ path.join(dir, `${name}.js-meta.kwc`),
23
+ createMetaXml({ name, moduleId, framework })
24
+ )
25
+ }
@@ -0,0 +1,49 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const { toPascalCase } = require('../../../../utils/index')
4
+ const createMetaXml = require('../createMetaXml')
5
+ const { LANGUAGES } = require('../../constants')
6
+
7
+ module.exports = function writeReactFiles(config) {
8
+ const { dir, name, moduleId, framework, language } = config
9
+ const componentName = toPascalCase(name)
10
+
11
+ fs.mkdirSync(path.join(dir, '__tests__'), { recursive: true })
12
+
13
+ fs.writeFileSync(
14
+ path.join(dir, `${name}.module.scss`),
15
+ ''
16
+ )
17
+ fs.writeFileSync(
18
+ path.join(dir, `${name}.js-meta.kwc`),
19
+ createMetaXml({ name, moduleId, framework })
20
+ )
21
+
22
+ if (language === LANGUAGES.JavaScript) {
23
+ fs.writeFileSync(
24
+ path.join(dir, `${name}.jsx`),
25
+ `import styles from './${name}.module.scss';
26
+
27
+ export default function ${componentName} (config) {
28
+ return (
29
+ <div>
30
+ </div>
31
+ );
32
+ }
33
+ `
34
+ )
35
+ } else if (language === LANGUAGES.TypeScript) {
36
+ fs.writeFileSync(
37
+ path.join(dir, `${name}.tsx`),
38
+ `import styles from './${name}.module.scss';
39
+
40
+ export default function ${componentName}(config: KwcConfig) {
41
+ return (
42
+ <div>
43
+ </div>
44
+ );
45
+ }
46
+ `
47
+ )
48
+ }
49
+ }
@@ -0,0 +1,48 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const { capitalize } = require('../../../../utils/index')
4
+ const createMetaXml = require('../createMetaXml')
5
+ const { LANGUAGES } = require('../../constants')
6
+
7
+ module.exports = function writeVueFiles(config) {
8
+ const { dir, name, moduleId, framework, language } = config
9
+
10
+ fs.mkdirSync(path.join(dir, '__tests__'), { recursive: true })
11
+
12
+ fs.writeFileSync(
13
+ path.join(dir, `${name}.js-meta.kwc`),
14
+ createMetaXml({ name, moduleId, framework })
15
+ )
16
+
17
+ if (language === LANGUAGES.JavaScript) {
18
+ fs.writeFileSync(
19
+ path.join(dir, `${name}.ce.vue`),
20
+ `<script setup>
21
+ </script>
22
+
23
+ <template>
24
+ <div>
25
+ </div>
26
+ </template>
27
+
28
+ <style lang="scss" scoped>
29
+ </style>
30
+ `
31
+ )
32
+ } else if (language === LANGUAGES.TypeScript) {
33
+ fs.writeFileSync(
34
+ path.join(dir, `${name}.ce.vue`),
35
+ `<script setup lang="ts">
36
+ </script>
37
+
38
+ <template>
39
+ <div>
40
+ </div>
41
+ </template>
42
+
43
+ <style lang="scss" scoped>
44
+ </style>
45
+ `
46
+ )
47
+ }
48
+ }
@@ -0,0 +1,43 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ function walk(dir, files = []) {
5
+ fs.readdirSync(dir).forEach(name => {
6
+ const full = path.join(dir, name)
7
+ const stat = fs.statSync(full)
8
+ stat.isDirectory()
9
+ ? walk(full, files)
10
+ : files.push(full)
11
+ })
12
+ return files
13
+ }
14
+
15
+ exports.collectMeta = function ({ baseDir, projectRoot, hasSourceDir }) {
16
+ const results = []
17
+
18
+ const files = walk(baseDir)
19
+
20
+ files.forEach(file => {
21
+ if (file.endsWith('.js-meta.kwc')) {
22
+ results.push({
23
+ type: 'kwc',
24
+ path: file
25
+ })
26
+ }
27
+ if (file.endsWith('.page-meta.kwp')) {
28
+ results.push({
29
+ type: 'page',
30
+ path: file
31
+ })
32
+ }
33
+ })
34
+
35
+ // 如果不是指定 -d,限制只能上传 app/kwc 和 app/pages
36
+ if (!hasSourceDir) {
37
+ return results.filter(f =>
38
+ f.path.startsWith(path.join(projectRoot, 'app'))
39
+ )
40
+ }
41
+
42
+ return results
43
+ }
@@ -0,0 +1,26 @@
1
+ const path = require('path')
2
+ const { findProjectRoot, isProjectRoot } = require('./projectRoot')
3
+
4
+ exports.resolveContext = function (options) {
5
+ const cwd = process.cwd()
6
+ const sourceDir = options.sourceDir
7
+
8
+ let baseDir
9
+ if (sourceDir) {
10
+ baseDir = path.isAbsolute(sourceDir)
11
+ ? path.resolve(sourceDir)
12
+ : path.resolve(cwd, sourceDir)
13
+ } else {
14
+ baseDir = cwd
15
+ }
16
+
17
+ const projectRoot = findProjectRoot(baseDir)
18
+
19
+ return {
20
+ cwd,
21
+ baseDir,
22
+ projectRoot,
23
+ hasSourceDir: !!sourceDir,
24
+ isRoot: isProjectRoot(cwd)
25
+ }
26
+ }
@@ -0,0 +1,38 @@
1
+
2
+ const resolveEnv = require('../../env/resolveEnv')
3
+ const { resolveContext } = require('./context')
4
+ const { validateDeploy } = require('./validate')
5
+ const updateIsv = require('./updateIsv')
6
+ const { collectMeta } = require('./collect')
7
+ const { upload } = require('./upload')
8
+ const { error } = require('../../../utils/log')
9
+ module.exports = async function deploy(options = {}) {
10
+ const { targetEnv } = options
11
+ const { env } = resolveEnv(targetEnv) || {}
12
+ if (!env) return
13
+ if (!env.access_token) {
14
+ error(
15
+ `Environment not authenticated. Please run "kd env auth" first`
16
+ )
17
+ return
18
+ }
19
+ const ctx = resolveContext(options)
20
+
21
+ validateDeploy(ctx)
22
+
23
+ const files = collectMeta(ctx)
24
+
25
+ if(!files || files.length === 0) {
26
+ error('No files to deploy')
27
+ return
28
+ }
29
+
30
+ // 更新isv信息到元数据描述文件 updateIsv
31
+ // 在上传前,统一注入 ISV
32
+ updateIsv(files, env?.isv)
33
+
34
+ await upload(files, env)
35
+
36
+ // success('部署完成')
37
+
38
+ }
@@ -0,0 +1,32 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ const ROOT_MARKS = [
5
+ '.kd',
6
+ 'app/kwc',
7
+ 'package.json'
8
+ ]
9
+
10
+ function isProjectRoot(dir) {
11
+ return ROOT_MARKS.every(mark =>
12
+ fs.existsSync(path.join(dir, mark))
13
+ )
14
+ }
15
+
16
+ function findProjectRoot(startDir) {
17
+ let current = path.resolve(startDir)
18
+
19
+ while (true) {
20
+ if (isProjectRoot(current)) return current
21
+
22
+ const parent = path.dirname(current)
23
+ if (parent === current) return null
24
+
25
+ current = parent
26
+ }
27
+ }
28
+
29
+ module.exports = {
30
+ isProjectRoot,
31
+ findProjectRoot
32
+ }
@@ -0,0 +1,109 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const { info, error } = require('../../../utils/log')
4
+ const { findProjectRoot } = require('./projectRoot')
5
+
6
+ /**
7
+ * 更新元数据文件中的 <isv> 信息
8
+ * @param {Array} files collectMeta 返回的文件列表
9
+ * @param {string} isv ISV 标识
10
+ */
11
+ module.exports = function updateIsv(files, isv) {
12
+ if (!isv) return
13
+ try {
14
+ files.forEach(file => {
15
+ if (!['kwc', 'page'].includes(file.type)) return
16
+
17
+ const filePath = file.path
18
+ if (!fs.existsSync(filePath)) return
19
+
20
+ let content = fs.readFileSync(filePath, 'utf-8')
21
+
22
+ // 更新isv标签
23
+ // 已存在 <isv> 标签 → 直接替换
24
+ if (/<isv>[\s\S]*?<\/isv>/.test(content)) {
25
+ content = content.replace(
26
+ /<isv>[\s\S]*?<\/isv>/,
27
+ `<isv>${isv}</isv>`
28
+ )
29
+ } else {
30
+ // 不存在 <isv> 标签 → 插入
31
+ content = injectIsvTag(content, isv)
32
+ }
33
+
34
+ // 更新name标签 这里在name标签前加上isv前缀
35
+ if (file.type === 'page') {
36
+ content = content.replace(
37
+ /<name>([\s\S]*?)<\/name>/,
38
+ (match, p1) => {
39
+ if (p1.startsWith(`${isv}_`)) {
40
+ return match
41
+ }
42
+ return `<name>${isv}_${p1}</name>`
43
+ }
44
+ )
45
+ }
46
+
47
+ fs.writeFileSync(filePath, content, 'utf-8')
48
+ })
49
+
50
+ // 更新根目录下.kd/config.json 填充开发商标识
51
+ const projectRoot = findProjectRoot(process.cwd())
52
+ if (projectRoot) {
53
+ const kdDir = path.join(projectRoot, '.kd')
54
+ const kdConfigPath = path.join(kdDir, 'config.json')
55
+
56
+ let kdConfig = {}
57
+
58
+ if (fs.existsSync(kdConfigPath)) {
59
+ try {
60
+ kdConfig = JSON.parse(fs.readFileSync(kdConfigPath, 'utf-8'))
61
+ } catch {
62
+ // ignore
63
+ }
64
+ }
65
+
66
+ kdConfig.isv = isv
67
+
68
+ if (!fs.existsSync(kdDir)) {
69
+ fs.mkdirSync(kdDir, { recursive: true })
70
+ }
71
+
72
+ fs.writeFileSync(
73
+ kdConfigPath,
74
+ JSON.stringify(kdConfig, null, 2),
75
+ 'utf-8'
76
+ )
77
+ }
78
+ // info(`ℹ️ Updated ISV info for ${files.length} metadata files`)
79
+ } catch (e) {
80
+ error(`Failed to update ISV info: ${e.message}`)
81
+ }
82
+ }
83
+
84
+ /**
85
+ * 将 <isv> 标签插入到合适位置
86
+ * 优先放在 <name> / <masterLabel> 后面
87
+ */
88
+ function injectIsvTag(xml, isv) {
89
+ // 常见安全插入点
90
+ const candidates = [
91
+ /<\/masterLabel>/,
92
+ /<\/name>/
93
+ ]
94
+
95
+ for (const reg of candidates) {
96
+ if (reg.test(xml)) {
97
+ return xml.replace(
98
+ reg,
99
+ match => `${match}\n <isv>${isv}</isv>`
100
+ )
101
+ }
102
+ }
103
+
104
+ // fallback:直接插到根节点内
105
+ return xml.replace(
106
+ /(<[^>]+>)/,
107
+ `$1\n <isv>${isv}</isv>`
108
+ )
109
+ }
@@ -0,0 +1,60 @@
1
+ const ora = require('ora')
2
+ const path = require('path')
3
+ const { updateKwcMeta, updatePageMeta } = require('../../../api/uploader')
4
+ const printTable = require('../../../utils/printTable')
5
+ exports.upload = async function upload(files, env) {
6
+ const spinner = ora(`🚀 Uploading metadata files (${files.length})...`).start()
7
+ console.time(`🕒 Upload duration`)
8
+
9
+ const tasks = files.map(file => {
10
+ return (async () => {
11
+ const record = {
12
+ type: file.type,
13
+ path: file.path,
14
+ status: 'success'
15
+ }
16
+
17
+ try {
18
+ if (file.type === 'kwc') {
19
+ await updateKwcMeta(file.path, env)
20
+ } else if (file.type === 'page') {
21
+ await updatePageMeta(file.path, env)
22
+ } else {
23
+ throw new Error(`Unsupported metadata type: ${file.type}`)
24
+ }
25
+ } catch (err) {
26
+ record.status = 'failed'
27
+ record.message = err?.message || 'unknown error'
28
+ }
29
+
30
+ return record
31
+ })()
32
+ })
33
+
34
+ // ⭐ 核心:并行执行,永不抛异常
35
+ const settledResults = await Promise.allSettled(tasks)
36
+
37
+ spinner.stop()
38
+
39
+ // 提取结果
40
+ const results = settledResults.map(r =>
41
+ r.status === 'fulfilled'
42
+ ? r.value
43
+ : {
44
+ type: 'unknown',
45
+ path: '',
46
+ status: 'failed',
47
+ message: r.reason?.message || 'unknown error'
48
+ }
49
+ )
50
+ const headers = ['Type', 'File', 'Status', 'Message']
51
+ const rows = results.map(file => [
52
+ file.type,
53
+ file.path,
54
+ file.status === 'success' ? '✔' : '✖',
55
+ file.status === 'failed' ? `Failed: ${file.message}` : 'Success'
56
+ ])
57
+ printTable(headers, rows)
58
+ console.timeEnd(`🕒 Upload duration`)
59
+ return results
60
+ }
@@ -0,0 +1,13 @@
1
+ const { error } = require('../../../utils/log')
2
+
3
+ exports.validateDeploy = function (ctx) {
4
+ if (!ctx.projectRoot) {
5
+ error('Current directory is not a KWC project, deployment aborted')
6
+ process.exit(1)
7
+ }
8
+
9
+ if (!ctx.hasSourceDir && !ctx.isRoot) {
10
+ error('Please run in the project root directory or use -d to specify a path')
11
+ process.exit(1)
12
+ }
13
+ }
@@ -0,0 +1,42 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const collectInitInfo = require('./prompts')
4
+ const { writeProjectConfig, cleanupGitkeep } = require('./post')
5
+ const downloadTemplate = require('../../../utils/download')
6
+ const { error, info, tip } = require('../../../utils/log')
7
+ const { TEMPLATES, INNER_TEMPLATES } = require('../constants')
8
+
9
+ module.exports = async function initProject({ projectName: _projectName, source }) {
10
+ try {
11
+ const { projectName, moduleId, framework, language } = await collectInitInfo(_projectName) || {}
12
+
13
+ const targetDir = path.resolve(process.cwd(), projectName)
14
+
15
+ if (fs.existsSync(targetDir)) {
16
+ error(`Directory already exists: ${projectName}`)
17
+ return
18
+ }
19
+
20
+ tip(`\n🚀 Creating project: ${projectName}\n`)
21
+ console.time('🕒 Project initialization')
22
+
23
+ const templateSource = source === 'outer' ? TEMPLATES[`${framework}_${language}`] : INNER_TEMPLATES[`${framework}_${language}`]
24
+
25
+ await downloadTemplate(
26
+ templateSource,
27
+ targetDir,
28
+ projectName
29
+ )
30
+
31
+ writeProjectConfig(targetDir, { moduleId, framework, language })
32
+
33
+ cleanupGitkeep(targetDir)
34
+
35
+ console.timeEnd('🕒 Project initialization')
36
+ info('next steps:')
37
+ info(` cd ${projectName}`)
38
+ info(' npm install')
39
+ } catch (err) {
40
+ error(err?.message || err)
41
+ }
42
+ }
@@ -0,0 +1,20 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ exports.writeProjectConfig = function (targetDir, config) {
5
+ const configFile = path.join(targetDir, '.kd', 'config.json')
6
+ fs.mkdirSync(path.dirname(configFile), { recursive: true })
7
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2))
8
+ }
9
+
10
+ exports.cleanupGitkeep = function (targetDir) {
11
+ const gitkeepFiles = [
12
+ path.join(targetDir, 'app', 'pages', '.gitkeep')
13
+ ]
14
+
15
+ gitkeepFiles.forEach(file => {
16
+ if (fs.existsSync(file)) {
17
+ fs.unlinkSync(file)
18
+ }
19
+ })
20
+ }
@@ -0,0 +1,50 @@
1
+ const { safePrompts } = require('../../../utils/prompts')
2
+ const { FRAMEWORKS, LANGUAGES } = require('../constants')
3
+
4
+ module.exports = async function runPrompts(name) {
5
+ let projectName = name
6
+
7
+ if (!projectName) {
8
+ const res = await safePrompts({
9
+ type: 'text',
10
+ name: 'projectName',
11
+ message: 'Please enter the project name',
12
+ validate: name => name ? true : 'Project name is required'
13
+ })
14
+ projectName = res.projectName
15
+ }
16
+
17
+ const { framework } = await safePrompts({
18
+ type: 'select',
19
+ name: 'framework',
20
+ message: 'Please choose the framework',
21
+ choices: Object.keys(FRAMEWORKS).map(item => ({
22
+ title: item,
23
+ value: FRAMEWORKS[item]
24
+ }))
25
+ }) || {}
26
+
27
+ // 如果框架是vue或者react,可以选择是用ts,还是js
28
+ let language = LANGUAGES.JavaScript
29
+ if ([FRAMEWORKS.Vue, FRAMEWORKS.React].includes(framework)) {
30
+ const { language: _language } = await safePrompts({
31
+ type: 'select',
32
+ name: 'language',
33
+ message: 'Please choose the language',
34
+ choices: Object.keys(LANGUAGES).map(item => ({
35
+ title: item,
36
+ value: LANGUAGES[item]
37
+ }))
38
+ }) || {}
39
+ language = _language
40
+ }
41
+
42
+ const { moduleId } = await safePrompts({
43
+ type: 'text',
44
+ name: 'moduleId',
45
+ message: 'Please enter the domain identifier',
46
+ validate: id => id ? true : 'Domain identifier is required'
47
+ }) || {}
48
+
49
+ return { projectName, framework, language, moduleId }
50
+ }