lan-low-code-cli 0.0.0-stage → 2.0.0

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/bin/llc.js ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ // 入口垫片:加载编译后的 CLI
5
+ try {
6
+ require('../dist/index.js')
7
+ } catch (err) {
8
+ if (err && err.code === 'MODULE_NOT_FOUND' && /dist[\\/]index\.js/.test(err.message)) {
9
+ console.error('[lcode] 未找到编译产物,请先执行: npm run build')
10
+ process.exit(1)
11
+ }
12
+ throw err
13
+ }
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dataSetApi = exports.modelingAppApi = exports.modelingXFormApi = exports.designApi = exports.wordsFieldApi = exports.dataModelApi = exports.formCategoryApi = exports.formDefineApi = void 0;
4
+ /**
5
+ * 表单定义接口(后端 SysGeneralFormDefineController)
6
+ * 基础路径:/data/sys-xform/generalFormDefine/
7
+ * 所有接口均为 POST + JSON
8
+ */
9
+ const BASE = 'sys-xform/generalFormDefine';
10
+ exports.formDefineApi = {
11
+ /** 表单初始化:返回带默认值的 VO */
12
+ init: (client, payload = {}) => client.post(`${BASE}/init`, payload),
13
+ /** 表单新建:返回 IdVO { fdId } */
14
+ add: (client, payload) => client.post(`${BASE}/add`, payload),
15
+ /** 表单加载 */
16
+ get: (client, fdId, extra = {}) => client.post(`${BASE}/get`, { fdId, ...extra }),
17
+ /** 表单保存 */
18
+ save: (client, payload) => client.post(`${BASE}/save`, payload),
19
+ /** 表单保存并发布 */
20
+ publish: (client, payload) => client.post(`${BASE}/publish`, payload),
21
+ /** 表单删除 */
22
+ delete: (client, fdId) => client.post(`${BASE}/delete`, { fdId }),
23
+ /** 表单批量删除 */
24
+ deleteAll: (client, fdIds) => client.post(`${BASE}/deleteAll`, { fdIds }),
25
+ /** 表单列表查询 */
26
+ list: (client, request = {}) => client.post(`${BASE}/list`, request),
27
+ };
28
+ /** 表单分类接口(后端 generalFormCategory) */
29
+ const CATEGORY_BASE = 'sys-xform/generalFormCategory';
30
+ exports.formCategoryApi = {
31
+ list: (client, request = {}) => client.post(`${CATEGORY_BASE}/list`, request),
32
+ };
33
+ /** 数据模型接口(后端 SysXFormDataModelController / SysXFormDataModelFieldController) */
34
+ const MODEL_BASE = 'sys-xform/sysXFormDataModel';
35
+ const FIELD_BASE = 'sys-xform/sysXFormDataModelField';
36
+ exports.dataModelApi = {
37
+ /** 根据表单 id 加载数据模型(含字段列表) */
38
+ loadByFormId: (client, fdId) => client.post(`${MODEL_BASE}/loadByFormId`, { fdId }),
39
+ /** 根据数据模型 id 加载单个数据模型 */
40
+ load: (client, fdId) => client.post(`${MODEL_BASE}/load`, { fdId }),
41
+ /** 根据数据模型 id 获取字段 */
42
+ getModelFields: (client, fdId) => client.post(`${MODEL_BASE}/getModelFields`, { fdId }),
43
+ /** 生成明细表/从表名 */
44
+ generateTableName: (client, payload) => client.post(`${MODEL_BASE}/generateTableName`, payload),
45
+ /** 表名唯一性校验 */
46
+ validateTableName: (client, payload) => client.post(`${MODEL_BASE}/validateTableName`, { isAdd: true, ...payload }),
47
+ /**
48
+ * 获取系统默认数据模型(含服务端生成的 fdId 与系统字段)。
49
+ *
50
+ * `fdType: 'detail'` 时返回 `{ fdId, fdType, fdFields:[fd_id, fd_main_id, fd_order], … }`,
51
+ * 这是新建明细表的权威模板(见前端 `ide-model/operation/addTableModal.tsx`)。
52
+ */
53
+ getSystemDefaultModel: (client, payload = {}) => client.post(`${MODEL_BASE}/getSystemDefaultModel`, payload),
54
+ /** 根据表名模糊匹配表集合 */
55
+ loadModelMeta: (client, payload) => client.post(`${MODEL_BASE}/loadModelMeta`, payload),
56
+ /** 数据模型列表 */
57
+ list: (client, request = {}) => client.post(`${MODEL_BASE}/list`, request),
58
+ /** 根据表名获取外部表字段集合 */
59
+ loadFieldMeta: (client, payload) => client.post(`${FIELD_BASE}/loadFieldMeta`, payload),
60
+ /** 字段列表查询(分页/搜索) */
61
+ listFields: (client, request = {}) => client.post(`${FIELD_BASE}/list`, request),
62
+ };
63
+ /** 字段库接口(后端 SysXFormWordsFieldController) */
64
+ const WORDS_BASE = 'sys-xform/sysXFormWordsField';
65
+ exports.wordsFieldApi = {
66
+ /** 字段库列表 */
67
+ list: (client, request = {}) => client.post(`${WORDS_BASE}/list`, request),
68
+ /** 编码自动填充:根据名称生成字段编码 */
69
+ generateFdKey: (client, payload = {}) => client.post(`${WORDS_BASE}/generateFdKey`, payload),
70
+ /** 检查字段编码是否存在 */
71
+ checkCodeExist: (client, fdCode) => client.post(`${WORDS_BASE}/checkCodeExist`, { fdCode }),
72
+ };
73
+ /** 表单设计器接口(后端 SysXFormDesignController) */
74
+ const DESIGN_BASE = 'sys-xform/sysXFormDesign';
75
+ exports.designApi = {
76
+ /** 获取主表单详情 */
77
+ load: (client, fdId) => client.post(`${DESIGN_BASE}/load`, { fdId }),
78
+ /** 直接用设计器接口新建 */
79
+ create: (client, payload) => client.post(`${DESIGN_BASE}/create`, payload),
80
+ /** 保存表单设计数据 */
81
+ save: (client, payload) => client.post(`${DESIGN_BASE}/save`, payload),
82
+ /** 列表查询 */
83
+ list: (client, request = {}) => client.post(`${DESIGN_BASE}/list`, request),
84
+ /** 表单发布(请求体为 XFormDesignPushVO) */
85
+ publish: (client, payload) => client.post(`${DESIGN_BASE}/publish`, payload),
86
+ };
87
+ /**
88
+ * 业务建模(低代码应用)表单接口
89
+ * 基础路径:/data/sys-modeling/sysModelingXForm/
90
+ *
91
+ * 说明:在"应用"下建表单走这条链路,表单通过 fdAppId 归属应用,
92
+ * 服务端会写死 fdEntityId=表单id、fdEntityName=fdRunEntityName='sys-modeling'。
93
+ */
94
+ const MODELING_XFORM_BASE = 'sys-modeling/sysModelingXForm';
95
+ exports.modelingXFormApi = {
96
+ /** 应用下表单列表 */
97
+ list: (client, request) => client.post(`${MODELING_XFORM_BASE}/list`, {
98
+ pageNo: request.pageNo ?? 1,
99
+ pageSize: request.pageSize ?? 200,
100
+ conditions: { 'fdApp.fdId': request.fdAppId },
101
+ columns: ['fdId', 'fdName', 'fdStatus', 'fdTableName'],
102
+ }),
103
+ /** 生成建模业务表单数据库表名(mk_model_yyyyMMdd + 随机5位) */
104
+ generateTableName: (client) => client.post(`${MODELING_XFORM_BASE}/generateTableName`),
105
+ /** 检查表名是否可用(true=可用) */
106
+ checkTableNameExist: (client, fdTableName) => client.post(`${MODELING_XFORM_BASE}/checkTableNameExist`, { fdTableName }),
107
+ /** 检查表单名称在应用内是否已存在(true=已存在) */
108
+ checkXFormNameExist: (client, payload) => client.post(`${MODELING_XFORM_BASE}/checkXFormNameExist`, {
109
+ fdApp: { fdId: payload.fdAppId },
110
+ fdName: payload.fdName,
111
+ fdId: payload.fdId,
112
+ }),
113
+ /** 新增业务表单基本信息(表单名称、表单图标、数据库表名) */
114
+ create: (client, payload) => client.post(`${MODELING_XFORM_BASE}/create`, payload),
115
+ /** 保存更新表单基本信息 */
116
+ saveBase: (client, payload) => client.post(`${MODELING_XFORM_BASE}/saveBase`, payload),
117
+ /** 保存更新表单设计器配置 */
118
+ saveDesign: (client, payload) => client.post(`${MODELING_XFORM_BASE}/saveDesign`, payload),
119
+ /** 业务表单发布 */
120
+ publish: (client, payload) => client.post(`${MODELING_XFORM_BASE}/publish`, payload),
121
+ /** 保存设计器配置并同时发布 */
122
+ saveDesignAndPublish: (client, payload) => client.post(`${MODELING_XFORM_BASE}/saveDesignAndPublish`, payload),
123
+ /** 获取业务表单设计器配置信息 */
124
+ loadDesign: (client, fdId) => client.post(`${MODELING_XFORM_BASE}/loadDesign`, { fdId }),
125
+ /** 获取业务表单基本信息 */
126
+ loadBase: (client, fdId) => client.post(`${MODELING_XFORM_BASE}/loadBase`, { fdId }),
127
+ /** 删除业务表单(连同其数据模型) */
128
+ remove: (client, fdId) => client.post(`${MODELING_XFORM_BASE}/delete`, { fdId }),
129
+ /** 删除业务表单的运行时数据(保留表单定义) */
130
+ removeData: (client, fdId) => client.post(`${MODELING_XFORM_BASE}/deleteData`, { fdId }),
131
+ };
132
+ /** 业务建模应用接口 */
133
+ const MODELING_APP_BASE = 'sys-modeling/sysModelingApplication';
134
+ exports.modelingAppApi = {
135
+ /** 应用基本信息列表 */
136
+ list: (client, request = {}) => client.post(`${MODELING_APP_BASE}/list`, request),
137
+ /** 应用列表(不做权限校验) */
138
+ listNotAuth: (client, request = {}) => client.post(`${MODELING_APP_BASE}/listNotAuth`, request),
139
+ /** 查看应用 */
140
+ get: (client, fdId) => client.post(`${MODELING_APP_BASE}/get`, { fdId }),
141
+ /** 编辑页数据(含可访问者/可维护者,提交 update 需回传) */
142
+ edit: (client, fdId) => client.post(`${MODELING_APP_BASE}/edit`, { fdId }),
143
+ /** 新建应用,返回 { fdId }(后端同时创建默认首页) */
144
+ add: (client, vo) => client.post(`${MODELING_APP_BASE}/add`, vo),
145
+ /** 修改应用基本信息(请求体为 DetailVO) */
146
+ update: (client, vo) => client.post(`${MODELING_APP_BASE}/update`, vo),
147
+ /** 删除应用 */
148
+ remove: (client, fdId) => client.post(`${MODELING_APP_BASE}/delete`, { fdId }),
149
+ /** 删除应用业务数据(保留应用) */
150
+ removeData: (client, fdId) => client.post(`${MODELING_APP_BASE}/deleteData`, { fdId }),
151
+ };
152
+ /** 数据集接口 */
153
+ const DATASET_BASE = 'sys-xform/sysXFormDataSetConfig';
154
+ exports.dataSetApi = {
155
+ categoryList: (client, payload = {}) => client.post(`${DATASET_BASE}/categoryList`, payload),
156
+ moduleList: (client, payload = {}) => client.post(`${DATASET_BASE}/moduleList`, payload),
157
+ entityList: (client, payload = {}) => client.post(`${DATASET_BASE}/entityList`, payload),
158
+ fieldList: (client, payload = {}) => client.post(`${DATASET_BASE}/fieldList`, payload),
159
+ subFieldList: (client, payload = {}) => client.post(`${DATASET_BASE}/subFieldList`, payload),
160
+ entitySearch: (client, payload = {}) => client.post(`${DATASET_BASE}/entitySearch`, payload),
161
+ };
package/dist/client.js ADDED
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LcodeClient = void 0;
7
+ exports.rawRequest = rawRequest;
8
+ const http_1 = __importDefault(require("http"));
9
+ const https_1 = __importDefault(require("https"));
10
+ /**
11
+ * 底层 HTTP 请求:基于 `node:http` / `node:https`,`agent: false` 禁用连接复用。
12
+ *
13
+ * 不用全局 `fetch`(undici)的原因:Windows + Node 24 下,进程退出时 undici
14
+ * 的空闲 keep-alive 连接会触发 libuv 断言
15
+ * `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)`,
16
+ * 进程以 0xC0000409 异常退出,脚本无法根据退出码判断成败。
17
+ */
18
+ function rawRequest(options) {
19
+ const { url, method, headers = {}, body, timeout = 30000 } = options;
20
+ const target = new URL(url);
21
+ const transport = target.protocol === 'https:' ? https_1.default : http_1.default;
22
+ return new Promise((resolve, reject) => {
23
+ const request = transport.request({
24
+ protocol: target.protocol,
25
+ hostname: target.hostname,
26
+ port: target.port || (target.protocol === 'https:' ? 443 : 80),
27
+ path: `${target.pathname}${target.search}`,
28
+ method,
29
+ headers: {
30
+ ...headers,
31
+ ...(body !== undefined ? { 'Content-Length': Buffer.byteLength(body) } : {}),
32
+ },
33
+ // 每次请求新建连接,响应结束即关闭,进程可干净退出
34
+ agent: false,
35
+ }, (response) => {
36
+ const chunks = [];
37
+ response.on('data', (chunk) => chunks.push(chunk));
38
+ response.on('end', () => {
39
+ resolve({
40
+ status: response.statusCode ?? 0,
41
+ statusText: response.statusMessage ?? '',
42
+ text: Buffer.concat(chunks).toString('utf8'),
43
+ });
44
+ });
45
+ response.on('error', reject);
46
+ });
47
+ request.setTimeout(timeout, () => {
48
+ request.destroy(new Error(`请求超时(${timeout}ms)`));
49
+ });
50
+ request.on('error', reject);
51
+ if (body !== undefined)
52
+ request.write(body);
53
+ request.end();
54
+ });
55
+ }
56
+ /**
57
+ * HTTP 客户端:零运行时依赖。
58
+ * 鉴权方式:请求头 `X-AUTH-TOKEN`。
59
+ *
60
+ * 完整请求地址 = `${baseUrl}/${relativePath}`
61
+ * 例如 baseUrl = http://host/data,relativePath = sys-xform/generalFormDefine/add
62
+ * => http://host/data/sys-xform/generalFormDefine/add
63
+ */
64
+ class LcodeClient {
65
+ constructor(config) {
66
+ this.baseUrl = config.baseUrl.replace(/\/+$/, '');
67
+ this.token = config.token;
68
+ this.timeout = config.timeout ?? 30000;
69
+ }
70
+ /** 拼装完整 URL */
71
+ resolveUrl(relativePath) {
72
+ const path = relativePath.replace(/^\/+/, '');
73
+ return `${this.baseUrl}/${path}`;
74
+ }
75
+ /** POST 请求(后端接口统一为 POST + JSON body) */
76
+ async post(relativePath, body = {}) {
77
+ return this.request('POST', relativePath, body);
78
+ }
79
+ async request(method, relativePath, body) {
80
+ const url = this.resolveUrl(relativePath);
81
+ let response;
82
+ try {
83
+ response = await rawRequest({
84
+ url,
85
+ method,
86
+ headers: {
87
+ 'Content-Type': 'application/json;charset=UTF-8',
88
+ Accept: 'application/json, text/plain, */*',
89
+ 'X-AUTH-TOKEN': this.token,
90
+ },
91
+ body: method === 'POST' ? JSON.stringify(body ?? {}) : undefined,
92
+ timeout: this.timeout,
93
+ });
94
+ }
95
+ catch (err) {
96
+ const message = err.message || '';
97
+ if (message.includes('请求超时')) {
98
+ throw new Error(`请求超时(${this.timeout}ms):${url}`);
99
+ }
100
+ throw new Error(`请求失败:${url}\n ${message}`);
101
+ }
102
+ const rawText = response.text;
103
+ let payload = null;
104
+ if (rawText) {
105
+ try {
106
+ payload = JSON.parse(rawText);
107
+ }
108
+ catch {
109
+ payload = null;
110
+ }
111
+ }
112
+ if (response.status < 200 || response.status >= 300) {
113
+ const detail = payload?.msg || payload?.data || rawText.slice(0, 500);
114
+ const error = new Error(`HTTP ${response.status} ${response.statusText}:${url}\n ${formatDetail(detail)}`);
115
+ error.status = response.status;
116
+ throw error;
117
+ }
118
+ if (payload && payload.success === false) {
119
+ throw new Error(`接口返回失败:${url}\n ${formatDetail(payload.msg || payload.data)}`);
120
+ }
121
+ // 统一剥离 Response 外层信封,直接返回 data
122
+ if (payload && Object.prototype.hasOwnProperty.call(payload, 'data') && 'success' in payload) {
123
+ return payload.data;
124
+ }
125
+ return (payload ?? rawText);
126
+ }
127
+ }
128
+ exports.LcodeClient = LcodeClient;
129
+ function formatDetail(detail) {
130
+ if (detail === null || detail === undefined)
131
+ return '(无详细信息)';
132
+ if (typeof detail === 'string')
133
+ return detail;
134
+ try {
135
+ return JSON.stringify(detail);
136
+ }
137
+ catch {
138
+ return String(detail);
139
+ }
140
+ }
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAppCommand = registerAppCommand;
4
+ const formDefine_1 = require("../api/formDefine");
5
+ const client_1 = require("../client");
6
+ const config_1 = require("../config");
7
+ const icon_1 = require("../utils/icon");
8
+ const logger_1 = require("../utils/logger");
9
+ function createClient(opts) {
10
+ return new client_1.LcodeClient((0, config_1.resolveRuntime)({ baseUrl: opts.baseUrl, token: opts.token }));
11
+ }
12
+ /** 非空的 fdId(commander 的 requiredOption 不拦截空字符串) */
13
+ function requireId(value, label = 'id') {
14
+ const id = String(value ?? '').trim();
15
+ if (!id) {
16
+ throw new Error(`${label} 不能为空`);
17
+ }
18
+ return id;
19
+ }
20
+ /**
21
+ * 应用编码合法性(后端校验正则 `^[a-zA-Z-0-9]*$`):
22
+ * 只允许字母、数字、连字符,**不含下划线**。
23
+ */
24
+ function normalizeAppCode(input) {
25
+ if (input === undefined)
26
+ return undefined;
27
+ const code = String(input).trim();
28
+ if (!code)
29
+ return undefined;
30
+ if (!/^[a-zA-Z0-9-]+$/.test(code)) {
31
+ throw new Error(`非法的应用编码:${code}(平台要求匹配 ^[a-zA-Z-0-9]*$,只允许字母/数字/连字符,不能使用下划线)`);
32
+ }
33
+ return code;
34
+ }
35
+ /** 删除类操作需显式 --yes 确认,避免误删 */
36
+ function confirmDestructive(opts, what) {
37
+ if (opts.yes || opts.force)
38
+ return true;
39
+ logger_1.logger.warn(`已取消:删除 ${what} 属于不可逆操作,请追加 --yes 确认执行`);
40
+ return false;
41
+ }
42
+ /** 列出应用下的表单(删除前保护:让人先看到会连带删掉什么) */
43
+ async function listAppForms(client, fdId) {
44
+ try {
45
+ const result = await formDefine_1.modelingXFormApi.list(client, { fdAppId: fdId, pageSize: 200 });
46
+ return result?.content ?? [];
47
+ }
48
+ catch (err) {
49
+ logger_1.logger.warn(`列出应用下表单失败(不影响删除,但请自行确认):${err.message}`);
50
+ return null;
51
+ }
52
+ }
53
+ function registerAppCommand(program) {
54
+ const app = program.command('app').description('业务建模应用(低代码应用)相关操作');
55
+ app
56
+ .command('list')
57
+ .description('查询应用列表,获取创建表单所需的 --app-id')
58
+ .option('--keyword <name>', '按应用名称搜索')
59
+ .option('--no-auth', '使用不做权限校验的接口(listNotAuth)')
60
+ .option('--base-url <url>', '服务端基址(覆盖配置)')
61
+ .option('--token <token>', '鉴权 Token(覆盖配置)')
62
+ .option('--json', '以 JSON 输出')
63
+ .action(async (opts) => {
64
+ const client = createClient(opts);
65
+ const request = opts.keyword ? { fdName: String(opts.keyword).trim() } : {};
66
+ const list = opts.auth === false
67
+ ? await formDefine_1.modelingAppApi.listNotAuth(client, request)
68
+ : await formDefine_1.modelingAppApi.list(client, request);
69
+ const apps = Array.isArray(list) ? list : [];
70
+ if (opts.json) {
71
+ logger_1.logger.json(apps);
72
+ return;
73
+ }
74
+ logger_1.logger.table(apps.map((item) => ({
75
+ fdId: item.fdId ?? '',
76
+ 应用名称: item.fdName ?? '',
77
+ 应用编码: item.fdAppCode ?? '',
78
+ 状态: item.fdStatus ?? '',
79
+ })));
80
+ logger_1.logger.info(logger_1.color.gray(`共 ${apps.length} 个应用,fdId 即 form create 的 --app-id`));
81
+ });
82
+ app
83
+ .command('get')
84
+ .description('查看应用详情(含可访问者/可维护者)')
85
+ .requiredOption('--id <fdId>', '应用 id')
86
+ .option('--base-url <url>', '服务端基址(覆盖配置)')
87
+ .option('--token <token>', '鉴权 Token(覆盖配置)')
88
+ .option('--json', '以 JSON 输出')
89
+ .action(async (opts) => {
90
+ const client = createClient(opts);
91
+ const detail = await formDefine_1.modelingAppApi.edit(client, opts.id);
92
+ if (opts.json) {
93
+ logger_1.logger.json(detail);
94
+ return;
95
+ }
96
+ logger_1.logger.title(`应用:${detail.fdName ?? opts.id}`);
97
+ logger_1.logger.info(` fdId = ${detail.fdId}`);
98
+ logger_1.logger.info(` 应用编码 = ${detail.fdAppCode ?? logger_1.color.gray('(无)')}`);
99
+ logger_1.logger.info(` 描述 = ${detail.fdDesc ?? logger_1.color.gray('(无)')}`);
100
+ const visitor = (detail.listVisitor ?? []).map((x) => x.fdName ?? x.fdId);
101
+ const maintainer = (detail.listMaintainer ?? []).map((x) => x.fdName ?? x.fdId);
102
+ logger_1.logger.info(` 可访问者 = ${visitor.length ? visitor.join('、') : logger_1.color.gray('(未设置)')}`);
103
+ logger_1.logger.info(` 可维护者 = ${maintainer.length ? maintainer.join('、') : logger_1.color.gray('(未设置)')}`);
104
+ });
105
+ app
106
+ .command('create')
107
+ .description('新建低代码应用(后端会同时创建默认首页)')
108
+ .requiredOption('--name <name>', '应用名称')
109
+ .option('--code <code>', '应用编码(作为 URL 中的应用标识)')
110
+ .option('--desc <text>', '应用描述')
111
+ .option('--category <fdId>', '业务应用分类 id')
112
+ .option('--icon <icon>', '应用图标(图标名或图标 JSON)')
113
+ .option('--order <n>', '排序号', (v) => Number(v))
114
+ .option('--base-url <url>', '服务端基址(覆盖配置)')
115
+ .option('--token <token>', '鉴权 Token(覆盖配置)')
116
+ .option('--json', '以 JSON 输出')
117
+ .action(async (opts) => {
118
+ const client = createClient(opts);
119
+ const vo = {
120
+ fdName: String(opts.name).trim(),
121
+ ...(normalizeAppCode(opts.code) ? { fdAppCode: normalizeAppCode(opts.code) } : {}),
122
+ ...(opts.desc ? { fdDesc: String(opts.desc) } : {}),
123
+ fdIcon: (0, icon_1.normalizeIcon)(opts.icon),
124
+ ...(opts.category ? { fdCategory: { fdId: String(opts.category).trim() } } : {}),
125
+ ...(opts.order !== undefined ? { fdOrder: opts.order } : {}),
126
+ };
127
+ logger_1.logger.step(`新建应用:${vo.fdName}`);
128
+ const created = await formDefine_1.modelingAppApi.add(client, vo);
129
+ const fdId = created?.fdId ?? created;
130
+ logger_1.logger.success(`应用创建成功,fdId = ${fdId}`);
131
+ logger_1.logger.info(logger_1.color.gray(' 后端已同时创建默认首页;可用 llc form create --app-id ' + fdId + ' 建表单'));
132
+ if (opts.json)
133
+ logger_1.logger.json({ fdId });
134
+ });
135
+ app
136
+ .command('update')
137
+ .description('修改应用基本信息(名称/编码/描述/图标/分类/排序)')
138
+ .requiredOption('--id <fdId>', '应用 id')
139
+ .option('--name <name>', '应用名称')
140
+ .option('--code <code>', '应用编码')
141
+ .option('--desc <text>', '应用描述')
142
+ .option('--category <fdId>', '业务应用分类 id')
143
+ .option('--icon <icon>', '应用图标(图标名或图标 JSON)')
144
+ .option('--order <n>', '排序号', (v) => Number(v))
145
+ .option('--base-url <url>', '服务端基址(覆盖配置)')
146
+ .option('--token <token>', '鉴权 Token(覆盖配置)')
147
+ .action(async (opts) => {
148
+ const client = createClient(opts);
149
+ const fdId = requireId(opts.id, '应用 id');
150
+ // update 的请求体是 DetailVO:先取编辑页数据,避免丢掉可访问者/可维护者
151
+ logger_1.logger.step(`加载应用:${fdId}`);
152
+ const detail = await formDefine_1.modelingAppApi.edit(client, fdId);
153
+ const patch = {
154
+ ...(opts.name !== undefined ? { fdName: String(opts.name).trim() } : {}),
155
+ ...(opts.code !== undefined ? { fdAppCode: String(opts.code).trim() } : {}),
156
+ ...(opts.desc !== undefined ? { fdDesc: String(opts.desc) } : {}),
157
+ ...(opts.icon !== undefined ? { fdIcon: (0, icon_1.normalizeIcon)(opts.icon) } : {}),
158
+ ...(opts.category !== undefined
159
+ ? { fdCategory: opts.category ? { fdId: String(opts.category).trim() } : undefined }
160
+ : {}),
161
+ ...(opts.order !== undefined ? { fdOrder: opts.order } : {}),
162
+ };
163
+ const changed = Object.keys(patch).filter((k) => patch[k] !== undefined);
164
+ if (changed.length === 0) {
165
+ logger_1.logger.warn('未提供任何修改项,用法示例:llc app update --id <fdId> --name 新名称');
166
+ return;
167
+ }
168
+ logger_1.logger.step(`提交修改:${changed.join('、')}`);
169
+ await formDefine_1.modelingAppApi.update(client, { ...detail, ...patch });
170
+ logger_1.logger.success(`应用 ${detail.fdName ?? fdId} 已更新`);
171
+ });
172
+ app
173
+ .command('delete')
174
+ .description('删除应用(不可逆,会连带删除其下表单;需 --yes 确认)')
175
+ .requiredOption('--id <fdId>', '应用 id')
176
+ .option('--yes', '确认执行删除')
177
+ .option('--base-url <url>', '服务端基址(覆盖配置)')
178
+ .option('--token <token>', '鉴权 Token(覆盖配置)')
179
+ .action(async (opts) => {
180
+ const fdId = requireId(opts.id, '应用 id');
181
+ const client = createClient(opts);
182
+ // 删除前保护:先列出应用下的表单
183
+ const forms = await listAppForms(client, fdId);
184
+ if (forms) {
185
+ if (forms.length === 0) {
186
+ logger_1.logger.info('该应用下没有表单');
187
+ }
188
+ else {
189
+ logger_1.logger.title(`该应用下共有 ${forms.length} 个表单,删除应用会一并删除:`);
190
+ logger_1.logger.table(forms.map((f) => ({
191
+ fdId: f.fdId ?? '',
192
+ 表单名称: f.fdName ?? '',
193
+ 状态: String(f.fdStatus ?? ''),
194
+ })));
195
+ }
196
+ }
197
+ if (!confirmDestructive(opts, `应用 ${fdId}`)) {
198
+ logger_1.logger.info(logger_1.color.gray('确认无误后请执行:llc app delete --id ' + fdId + ' --yes'));
199
+ return;
200
+ }
201
+ if (forms?.length) {
202
+ logger_1.logger.warn(`即将删除应用及其下 ${forms.length} 个表单(含全部数据模型)`);
203
+ }
204
+ logger_1.logger.step(`删除应用:${fdId}`);
205
+ await formDefine_1.modelingAppApi.remove(client, fdId);
206
+ logger_1.logger.success(`应用 ${fdId} 已删除`);
207
+ });
208
+ app
209
+ .command('forms')
210
+ .description('列出应用下的表单(删除前预览用)')
211
+ .requiredOption('--id <fdId>', '应用 id')
212
+ .option('--base-url <url>', '服务端基址(覆盖配置)')
213
+ .option('--token <token>', '鉴权 Token(覆盖配置)')
214
+ .option('--json', '以 JSON 输出')
215
+ .action(async (opts) => {
216
+ const fdId = requireId(opts.id, '应用 id');
217
+ const client = createClient(opts);
218
+ const forms = await listAppForms(client, fdId);
219
+ if (forms === null)
220
+ return;
221
+ if (opts.json) {
222
+ logger_1.logger.json(forms);
223
+ return;
224
+ }
225
+ if (forms.length === 0) {
226
+ logger_1.logger.info('该应用下没有表单');
227
+ return;
228
+ }
229
+ logger_1.logger.table(forms.map((f) => ({
230
+ fdId: f.fdId ?? '',
231
+ 表单名称: f.fdName ?? '',
232
+ 状态: String(f.fdStatus ?? ''),
233
+ })));
234
+ logger_1.logger.info(logger_1.color.gray(`共 ${forms.length} 个表单`));
235
+ });
236
+ app
237
+ .command('delete-data')
238
+ .description('删除应用的业务数据(保留应用定义;需 --yes 确认)')
239
+ .requiredOption('--id <fdId>', '应用 id')
240
+ .option('--yes', '确认执行删除')
241
+ .option('--base-url <url>', '服务端基址(覆盖配置)')
242
+ .option('--token <token>', '鉴权 Token(覆盖配置)')
243
+ .action(async (opts) => {
244
+ const fdId = requireId(opts.id, '应用 id');
245
+ if (!confirmDestructive(opts, `应用 ${fdId} 的业务数据`))
246
+ return;
247
+ const client = createClient(opts);
248
+ logger_1.logger.step(`删除应用业务数据:${fdId}`);
249
+ await formDefine_1.modelingAppApi.removeData(client, fdId);
250
+ logger_1.logger.success(`应用 ${fdId} 的业务数据已删除`);
251
+ });
252
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerConfigCommand = registerConfigCommand;
4
+ const config_1 = require("../config");
5
+ const logger_1 = require("../utils/logger");
6
+ function maskToken(token) {
7
+ if (!token)
8
+ return logger_1.color.gray('(未设置)');
9
+ if (token.length <= 8)
10
+ return '****';
11
+ return `${token.slice(0, 4)}****${token.slice(-4)}`;
12
+ }
13
+ function registerConfigCommand(program) {
14
+ const config = program.command('config').description('管理 CLI 配置(服务端地址、Token)');
15
+ config
16
+ .command('set')
17
+ .description('设置服务端地址与鉴权 Token(写入全局配置 ~/.lcode/config.json)')
18
+ .option('--base-url <url>', '服务端基址,例如 http://mkdev01.ywork.me/data')
19
+ .option('--token <token>', '鉴权 Token(请求头 X-AUTH-TOKEN)')
20
+ .option('--timeout <ms>', '请求超时(毫秒)', (v) => Number(v))
21
+ .option('--default-form-type <type>', '默认表单类型(1-6)', (v) => Number(v))
22
+ .action((opts) => {
23
+ const patch = {};
24
+ if (opts.baseUrl !== undefined)
25
+ patch.baseUrl = String(opts.baseUrl).trim().replace(/\/+$/, '');
26
+ if (opts.token !== undefined)
27
+ patch.token = String(opts.token).trim();
28
+ if (opts.timeout !== undefined)
29
+ patch.timeout = opts.timeout;
30
+ if (opts.defaultFormType !== undefined)
31
+ patch.defaultFormType = opts.defaultFormType;
32
+ if (Object.keys(patch).length === 0) {
33
+ logger_1.logger.warn('未提供任何配置项,用法示例:llc config set --base-url http://host/data --token xxx');
34
+ return;
35
+ }
36
+ (0, config_1.saveGlobalConfig)(patch);
37
+ logger_1.logger.success(`配置已写入 ${config_1.GLOBAL_CONFIG_FILE}`);
38
+ logger_1.logger.info(` baseUrl: ${patch.baseUrl ?? logger_1.color.gray('(未变更)')}`);
39
+ if (patch.token !== undefined)
40
+ logger_1.logger.info(` token: ${maskToken(patch.token)}`);
41
+ });
42
+ config
43
+ .command('show')
44
+ .description('查看当前生效的配置(含环境变量与项目级配置)')
45
+ .action(() => {
46
+ const conf = (0, config_1.loadConfig)();
47
+ const envBaseUrl = process.env.LLC_BASE_URL;
48
+ const envToken = process.env.LLC_TOKEN;
49
+ logger_1.logger.title('当前生效配置');
50
+ logger_1.logger.info(JSON.stringify({
51
+ 全局配置文件: config_1.GLOBAL_CONFIG_FILE,
52
+ 项目配置文件: config_1.PROJECT_CONFIG_FILE,
53
+ baseUrl: conf.baseUrl ?? null,
54
+ token: maskToken(conf.token),
55
+ timeout: conf.timeout ?? 30000,
56
+ defaultFormType: conf.defaultFormType ?? null,
57
+ controlMapKeys: Object.keys(conf.controlMap ?? {}),
58
+ 环境变量: {
59
+ LLC_BASE_URL: envBaseUrl ?? null,
60
+ LLC_TOKEN: envToken ? maskToken(envToken) : null,
61
+ },
62
+ }, null, 2));
63
+ });
64
+ config
65
+ .command('clear')
66
+ .description('清除全局配置项')
67
+ .option('--all', '清除全部(baseUrl/token/timeout/defaultFormType)')
68
+ .action((opts) => {
69
+ const keys = opts.all
70
+ ? ['baseUrl', 'token', 'timeout', 'defaultFormType', 'controlMap']
71
+ : ['baseUrl', 'token'];
72
+ (0, config_1.clearGlobalConfig)(keys);
73
+ logger_1.logger.success(`已清除配置项:${keys.join(', ')}`);
74
+ });
75
+ }