@guanwenai/high-tech-project-cli 0.1.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.
@@ -0,0 +1,145 @@
1
+ import { CliError } from '../core/errors.mjs';
2
+ import {
3
+ itemsInput as sharedItemsInput,
4
+ objectInput as sharedObjectInput,
5
+ valueOf,
6
+ withJavaMethod,
7
+ } from './shared.mjs';
8
+
9
+ const REQUIRED_CONTEXT = ['gatewayUrl', 'uuid'];
10
+
11
+ function objectInput(input) {
12
+ return sharedObjectInput(input);
13
+ }
14
+
15
+ function itemsInput(input) {
16
+ return sharedItemsInput(input);
17
+ }
18
+
19
+ function listCommand(name, path, javaMethod, body = (_input, context) => ({ uuid: context.uuid })) {
20
+ return withJavaMethod({
21
+ name, method: 'POST', path, idempotent: true, multipart: false,
22
+ requiredContext: REQUIRED_CONTEXT, validate: objectInput,
23
+ handler: async ({ client, context, input }) => await client.request({
24
+ method: 'POST', path, body: body(input, context), idempotent: true,
25
+ }),
26
+ }, javaMethod);
27
+ }
28
+
29
+ export const commands = [
30
+ listCommand('finance.list-fees', '/api/project-writing/fee-detail/list'),
31
+ listCommand('finance.list-income', '/api/project-writing/product-income/list'),
32
+ listCommand('finance.list-rd-funds', '/api/project-writing/rd-fund-situation/list', 'project_writing_rd_fund_situation_list'),
33
+ {
34
+ name: 'finance.save-operation-status',
35
+ method: 'POST',
36
+ path: '/api/project-writing/operation-status/create',
37
+ idempotent: false,
38
+ multipart: false,
39
+ requiredContext: REQUIRED_CONTEXT,
40
+ validate: itemsInput,
41
+ handler: async ({ client, context, input }) => {
42
+ const results = [];
43
+ for (const item of input.items) {
44
+ results.push(await client.request({
45
+ method: 'POST',
46
+ path: item.id
47
+ ? '/api/project-writing/operation-status/update'
48
+ : '/api/project-writing/operation-status/create',
49
+ body: { ...item, uuid: item.uuid ?? context.uuid },
50
+ idempotent: false,
51
+ }));
52
+ }
53
+ return results;
54
+ },
55
+ },
56
+ ...[
57
+ ['fee', 'fee-detail', 'project_writing_fee_detail'],
58
+ ['operation', 'operation-status', 'project_writing_operation_status'],
59
+ ['income', 'product-income', 'project_writing_product_income'],
60
+ ].flatMap(([name, resource, javaPrefix]) => [
61
+ {
62
+ name: `finance.${name}-create`, method: 'POST', path: `/api/project-writing/${resource}/create`,
63
+ idempotent: false, multipart: false, requiredContext: REQUIRED_CONTEXT,
64
+ validate: (input) => {
65
+ const value = objectInput(input);
66
+ if (value.data === null || Array.isArray(value.data) || typeof value.data !== 'object') {
67
+ throw new CliError('ARGUMENT_INVALID', 'data 必须是对象');
68
+ }
69
+ return value;
70
+ },
71
+ javaMethod: `${javaPrefix}_create`,
72
+ handler: async ({ client, context, input }) => await client.request({
73
+ method: 'POST', path: `/api/project-writing/${resource}/create`, idempotent: false,
74
+ body: { ...input.data, uuid: context.uuid },
75
+ }),
76
+ },
77
+ {
78
+ name: `finance.${name}-update`, method: 'POST', path: `/api/project-writing/${resource}/update`,
79
+ idempotent: false, multipart: false, requiredContext: REQUIRED_CONTEXT,
80
+ validate: (input) => {
81
+ const value = objectInput(input);
82
+ if (value.data === null || Array.isArray(value.data) || typeof value.data !== 'object'
83
+ || !Number.isSafeInteger(value.data.id) || value.data.id <= 0) {
84
+ throw new CliError('ARGUMENT_INVALID', 'data 必须是包含正整数 id 的对象');
85
+ }
86
+ return value;
87
+ },
88
+ javaMethod: `${javaPrefix}_update`,
89
+ handler: async ({ client, context, input }) => await client.request({
90
+ method: 'POST', path: `/api/project-writing/${resource}/update`, idempotent: false,
91
+ body: { ...input.data, uuid: context.uuid },
92
+ }),
93
+ },
94
+ {
95
+ name: `finance.${name}-batch-save`, method: 'POST', path: `/api/project-writing/${resource}/batch-save`,
96
+ idempotent: false, multipart: false, requiredContext: REQUIRED_CONTEXT, validate: itemsInput,
97
+ javaMethod: `${javaPrefix}_batch_save`,
98
+ handler: async ({ client, context, input }) => await client.request({
99
+ method: 'POST', path: `/api/project-writing/${resource}/batch-save`, idempotent: false,
100
+ body: input.items.map((item) => ({ ...item, uuid: item.uuid ?? context.uuid })),
101
+ }),
102
+ },
103
+ ]).map((definition) => withJavaMethod(definition, definition.javaMethod)),
104
+ withJavaMethod({
105
+ name: 'finance.fee-list', method: 'POST', path: '/api/project-writing/fee-detail/list',
106
+ idempotent: true, multipart: false, requiredContext: REQUIRED_CONTEXT,
107
+ validate: objectInput, javaMethod: 'project_writing_fee_detail_list',
108
+ handler: async ({ client, context, input }) => await client.request({
109
+ method: 'POST', path: '/api/project-writing/fee-detail/list', idempotent: true,
110
+ body: { uuid: context.uuid, ...(input.year !== undefined ? { year: input.year } : {}) },
111
+ }),
112
+ }, 'project_writing_fee_detail_list'),
113
+ withJavaMethod({
114
+ name: 'finance.operation-list', method: 'POST', path: '/api/project-writing/operation-status/list',
115
+ idempotent: true, multipart: false, requiredContext: REQUIRED_CONTEXT,
116
+ validate: objectInput, javaMethod: 'project_writing_operation_status_list',
117
+ handler: async ({ client, context, input }) => await client.request({
118
+ method: 'POST', path: '/api/project-writing/operation-status/list', idempotent: true,
119
+ body: { uuid: context.uuid, ...(input.year !== undefined ? { year: input.year } : {}) },
120
+ }),
121
+ }, 'project_writing_operation_status_list'),
122
+ withJavaMethod({
123
+ name: 'finance.income-list', method: 'POST', path: '/api/project-writing/product-income/list',
124
+ idempotent: true, multipart: false, requiredContext: REQUIRED_CONTEXT,
125
+ validate: objectInput, javaMethod: 'project_writing_product_income_list',
126
+ handler: async ({ client, context, input }) => await client.request({
127
+ method: 'POST', path: '/api/project-writing/product-income/list', idempotent: true,
128
+ body: {
129
+ uuid: context.uuid,
130
+ ...(valueOf(input, 'product_code', 'productCode') !== undefined
131
+ ? { productCode: valueOf(input, 'product_code', 'productCode') } : {}),
132
+ ...(input.year !== undefined ? { year: input.year } : {}),
133
+ },
134
+ }),
135
+ }, 'project_writing_product_income_list'),
136
+ withJavaMethod({
137
+ name: 'finance.rd-fund-batch-save', method: 'POST', path: '/api/project-writing/rd-fund-situation/batch-save',
138
+ idempotent: false, multipart: false, requiredContext: REQUIRED_CONTEXT, validate: itemsInput,
139
+ javaMethod: 'project_writing_rd_fund_situation_batch_save',
140
+ handler: async ({ client, context, input }) => await client.request({
141
+ method: 'POST', path: '/api/project-writing/rd-fund-situation/batch-save', idempotent: false,
142
+ body: input.items.map((item) => ({ ...item, uuid: item.uuid ?? context.uuid })),
143
+ }),
144
+ }, 'project_writing_rd_fund_situation_batch_save'),
145
+ ];
@@ -0,0 +1,121 @@
1
+ import { CliError } from '../core/errors.mjs';
2
+ import {
3
+ PROJECT_CONTEXT,
4
+ contentInput,
5
+ createFileCommand,
6
+ idsInput,
7
+ itemsInput,
8
+ objectInput as sharedObjectInput,
9
+ pageInput,
10
+ positiveId,
11
+ requiredString,
12
+ resolveSmartImportFiles,
13
+ valueOf,
14
+ withJavaMethod,
15
+ } from './shared.mjs';
16
+
17
+ function objectInput(input) {
18
+ return sharedObjectInput(input);
19
+ }
20
+
21
+ function fileCreateInput(input) {
22
+ const value = objectInput(input);
23
+ requiredString(valueOf(value, 'file_name', 'fileName'), 'file_name');
24
+ requiredString(valueOf(value, 'file_path', 'filePath'), 'file_path');
25
+ return value;
26
+ }
27
+
28
+ function smartImportInput(input) {
29
+ const value = objectInput(input);
30
+ if (!Array.isArray(value.items) || value.items.length === 0
31
+ || value.items.some((item) => item === null || Array.isArray(item) || typeof item !== 'object')) {
32
+ throw new CliError('ARGUMENT_INVALID', 'items 必须是非空对象数组');
33
+ }
34
+ value.items.forEach((item) => {
35
+ positiveId(valueOf(item, 'ipr_id', 'iprId'), 'ipr_id');
36
+ requiredString(valueOf(item, 'file_name', 'fileName'), 'file_name');
37
+ });
38
+ return value;
39
+ }
40
+
41
+ function command({ name, path, idempotent, validate = objectInput, body, javaMethod, handler }) {
42
+ return withJavaMethod({
43
+ name, method: 'POST', path, idempotent, requiredContext: PROJECT_CONTEXT, validate,
44
+ handler: handler ?? (async ({ client, context, input }) => await client.request({
45
+ method: 'POST', path, body: body(input, context), idempotent,
46
+ })),
47
+ }, javaMethod);
48
+ }
49
+
50
+ const saveCommand = {
51
+ name: 'ipr.save', method: 'POST', path: '/api/project-writing/ipr/create', idempotent: false,
52
+ requiredContext: PROJECT_CONTEXT, validate: objectInput,
53
+ handler: async ({ client, context, input }) => await client.request({
54
+ method: 'POST',
55
+ path: input.id ? '/api/project-writing/ipr/update' : '/api/project-writing/ipr/create',
56
+ body: { ...input, uuid: context.uuid }, idempotent: false,
57
+ }),
58
+ };
59
+
60
+ export const commands = [
61
+ command({
62
+ name: 'ipr.list', path: '/api/project-writing/ipr/page', idempotent: true, validate: pageInput,
63
+ body: (input, context) => ({
64
+ uuid: context.uuid, pageNumber: input.pageNumber ?? input.page_number ?? 1,
65
+ pageSize: input.pageSize ?? input.page_size ?? 20, ...input,
66
+ }),
67
+ }),
68
+ saveCommand,
69
+ command({
70
+ name: 'ipr.save-batch', path: '/api/project-writing/ipr/batch-save', idempotent: false,
71
+ validate: itemsInput, javaMethod: 'project_writing_ipr_batch_save',
72
+ body: (input, context) => input.items.map((item) => ({ ...item, uuid: item.uuid ?? context.uuid })),
73
+ }),
74
+ command({
75
+ name: 'ipr.list-all', path: '/api/project-writing/ipr/list', idempotent: true,
76
+ javaMethod: 'project_writing_ipr_list', body: (input, context) => ({ ...input, uuid: context.uuid }),
77
+ }),
78
+ command({
79
+ name: 'ipr.files', path: '/api/project-writing/ipr-file/list', idempotent: true,
80
+ body: (_input, context) => ({ uuid: context.uuid }),
81
+ }),
82
+ command({
83
+ name: 'ipr.submissions', path: '/api/project-writing/ipr-submit/list', idempotent: true,
84
+ body: (_input, context) => ({ uuid: context.uuid }),
85
+ }),
86
+ command({
87
+ name: 'ipr.delete-by-uuid', path: '/api/project-writing/ipr/batch-delete-by-uuid', idempotent: false,
88
+ javaMethod: 'project_writing_ipr_batch_delete_by_uuid', validate: idsInput,
89
+ body: (input, context) => input.ids.map((id) => ({ uuid: context.uuid, items: [{ id }] })),
90
+ }),
91
+ withJavaMethod(createFileCommand({
92
+ name: 'ipr.file-create', path: '/api/project-writing/ipr-file/create', validate: fileCreateInput,
93
+ }), 'project_writing_ipr_file_create'),
94
+ command({
95
+ name: 'ipr.file-smart-import', path: '/api/project-writing/ipr-file/smart-import', idempotent: false,
96
+ javaMethod: 'project_writing_ipr_file_smart_import', validate: smartImportInput,
97
+ handler: async ({ client, context, input, baseDir }) => {
98
+ const names = input.items.map((item) => valueOf(item, 'file_name', 'fileName'));
99
+ const resolved = await resolveSmartImportFiles({ client, context, baseDir, fileNames: names });
100
+ const byName = new Map(resolved.files.map((file) => [file.fileName, file]));
101
+ const items = input.items.map((item) => {
102
+ const fileName = valueOf(item, 'file_name', 'fileName');
103
+ const reference = byName.get(fileName);
104
+ return { iprId: valueOf(item, 'ipr_id', 'iprId'), fileName, ...(reference ? { filePath: reference.filePath } : {}) };
105
+ });
106
+ return await client.request({
107
+ method: 'POST', path: '/api/project-writing/ipr-file/smart-import', idempotent: false,
108
+ body: { uuid: context.uuid, items },
109
+ });
110
+ },
111
+ }),
112
+ command({
113
+ name: 'ipr.situation-get', path: '/api/project-writing/ipr-situation/detail', idempotent: true,
114
+ javaMethod: 'project_writing_ipr_situation_get', body: (_input, context) => ({ uuid: context.uuid }),
115
+ }),
116
+ command({
117
+ name: 'ipr.situation-save', path: '/api/project-writing/ipr-situation/batch-save', idempotent: false,
118
+ javaMethod: 'project_writing_ipr_situation_batch_save', validate: contentInput,
119
+ body: (input, context) => ({ uuid: context.uuid, content: valueOf(input, 'content') }),
120
+ }),
121
+ ];
@@ -0,0 +1,205 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { basename, isAbsolute } from 'node:path';
3
+
4
+ import { CliError } from '../core/errors.mjs';
5
+ import { resolveWithinBase } from '../core/files.mjs';
6
+
7
+ const REQUIRED_CONTEXT = ['gatewayUrl'];
8
+
9
+ function objectInput(input) {
10
+ if (input === null || Array.isArray(input) || typeof input !== 'object') {
11
+ throw new CliError('ARGUMENT_INVALID', '命令输入必须是对象');
12
+ }
13
+ return input;
14
+ }
15
+
16
+ function identifier(value, field) {
17
+ if ((typeof value !== 'string' && typeof value !== 'number') || !String(value).trim()) {
18
+ throw new CliError('ARGUMENT_INVALID', `${field} 不能为空`);
19
+ }
20
+ return value;
21
+ }
22
+
23
+ function encoded(value, field) {
24
+ return encodeURIComponent(String(identifier(value, field)));
25
+ }
26
+
27
+ function kbInput(input) {
28
+ const value = objectInput(input);
29
+ identifier(value.kbId, 'kbId');
30
+ return value;
31
+ }
32
+
33
+ function folderInput(input) {
34
+ const value = kbInput(input);
35
+ identifier(value.folderId, 'folderId');
36
+ return value;
37
+ }
38
+
39
+ function documentInput(input) {
40
+ const value = kbInput(input);
41
+ identifier(value.documentId, 'documentId');
42
+ return value;
43
+ }
44
+
45
+ function tagsInput(input) {
46
+ const value = documentInput(input);
47
+ if (!Array.isArray(value.tags) || value.tags.some((tag) => typeof tag !== 'string')) {
48
+ throw new CliError('ARGUMENT_INVALID', 'tags 必须是字符串数组');
49
+ }
50
+ return value;
51
+ }
52
+
53
+ function projectBindInput(input) {
54
+ const value = objectInput(input);
55
+ const projectId = Number(value.workId);
56
+ if (!Number.isSafeInteger(projectId) || projectId <= 0
57
+ || typeof value.workName !== 'string' || !value.workName.trim()
58
+ || typeof value.customerName !== 'string' || !value.customerName.trim()) {
59
+ throw new CliError('ARGUMENT_INVALID', '项目绑定参数无效');
60
+ }
61
+ return value;
62
+ }
63
+
64
+ function uploadInput(input) {
65
+ const value = kbInput(input);
66
+ if (typeof value.uuid !== 'string' || !value.uuid.trim()) {
67
+ throw new CliError('ARGUMENT_INVALID', 'uuid 不能为空');
68
+ }
69
+ if (typeof value.file !== 'string' || !value.file.trim() || isAbsolute(value.file)
70
+ || value.file.split(/[\\/]/u).includes('..')) {
71
+ throw new CliError('ARGUMENT_INVALID', 'file 必须是工作目录内的相对路径');
72
+ }
73
+ if (value.tags !== undefined
74
+ && (!Array.isArray(value.tags) || value.tags.some((tag) => typeof tag !== 'string'))) {
75
+ throw new CliError('ARGUMENT_INVALID', 'tags 必须是字符串数组');
76
+ }
77
+ if (value.tagTemplateId !== undefined
78
+ && (!Number.isSafeInteger(value.tagTemplateId) || value.tagTemplateId <= 0)) {
79
+ throw new CliError('ARGUMENT_INVALID', 'tagTemplateId 必须是正整数');
80
+ }
81
+ return value;
82
+ }
83
+
84
+ function definition(name, method, path, idempotent, validate, handler) {
85
+ return {
86
+ name, method, path, idempotent, multipart: false,
87
+ requiredContext: REQUIRED_CONTEXT, validate, handler,
88
+ };
89
+ }
90
+
91
+ function bodyCommand(name, path, idempotent, validate = objectInput) {
92
+ return definition(
93
+ name, 'POST', path, idempotent, validate,
94
+ async ({ client, input }) => await client.request({
95
+ method: 'POST', path, body: input, idempotent,
96
+ }),
97
+ );
98
+ }
99
+
100
+ function documentAction(name, action, validate = documentInput) {
101
+ const declaredPath = `/api/knowledge/{kbId}/documents/{documentId}/${action}`;
102
+ return definition(
103
+ name, 'POST', declaredPath, false, validate,
104
+ async ({ client, input }) => {
105
+ const { kbId, documentId, ...body } = input;
106
+ const path = `/api/knowledge/${encoded(kbId, 'kbId')}/documents/${encoded(documentId, 'documentId')}/${action}`;
107
+ const request = { method: 'POST', path, idempotent: false };
108
+ if (Object.keys(body).length > 0) request.body = body;
109
+ return await client.request(request);
110
+ },
111
+ );
112
+ }
113
+
114
+ export const commands = [
115
+ definition(
116
+ 'knowledge.documents-page', 'GET', '/api/knowledge/{kbId}/documents/page', true, kbInput,
117
+ async ({ client, input }) => {
118
+ const { kbId, ...query } = input;
119
+ return await client.request({
120
+ method: 'GET', path: `/api/knowledge/${encoded(kbId, 'kbId')}/documents/page`,
121
+ query, idempotent: true,
122
+ });
123
+ },
124
+ ),
125
+ definition(
126
+ 'knowledge.folders-tree', 'GET', '/api/knowledge/{kbId}/folders/tree', true, kbInput,
127
+ async ({ client, input }) => await client.request({
128
+ method: 'GET', path: `/api/knowledge/${encoded(input.kbId, 'kbId')}/folders/tree`,
129
+ idempotent: true,
130
+ }),
131
+ ),
132
+ definition(
133
+ 'knowledge.folder-create', 'POST', '/api/knowledge/{kbId}/folders/create', false, kbInput,
134
+ async ({ client, input }) => {
135
+ const { kbId, ...body } = input;
136
+ return await client.request({
137
+ method: 'POST', path: `/api/knowledge/${encoded(kbId, 'kbId')}/folders/create`,
138
+ body, idempotent: false,
139
+ });
140
+ },
141
+ ),
142
+ definition(
143
+ 'knowledge.folder-delete', 'POST', '/api/knowledge/{kbId}/folders/{folderId}/delete', false, folderInput,
144
+ async ({ client, input }) => await client.request({
145
+ method: 'POST',
146
+ path: `/api/knowledge/${encoded(input.kbId, 'kbId')}/folders/${encoded(input.folderId, 'folderId')}/delete`,
147
+ idempotent: false,
148
+ }),
149
+ ),
150
+ documentAction('knowledge.document-retry', 'retry'),
151
+ documentAction('knowledge.document-delete', 'delete'),
152
+ documentAction('knowledge.document-move', 'move'),
153
+ documentAction('knowledge.document-update-tags', 'tags/update', tagsInput),
154
+ bodyCommand('knowledge.tag-template-list', '/api/project-writing/kb-tag-template/list', true),
155
+ bodyCommand('knowledge.tag-template-create', '/api/project-writing/kb-tag-template/create', false),
156
+ bodyCommand('knowledge.tag-template-update', '/api/project-writing/kb-tag-template/update', false),
157
+ bodyCommand('knowledge.tag-template-delete', '/api/project-writing/kb-tag-template/delete', false),
158
+ bodyCommand('knowledge.tag-template-default', '/api/project-writing/kb-tag-template/get-default', true),
159
+ definition(
160
+ 'knowledge.project-bind', 'POST', '/api/project-writing/bind/create', false, projectBindInput,
161
+ async ({ client, input }) => await client.request({
162
+ method: 'POST', path: '/api/project-writing/bind/create', idempotent: false,
163
+ body: {
164
+ projectId: Number(input.workId),
165
+ projectName: input.workName,
166
+ customerName: input.customerName,
167
+ },
168
+ }),
169
+ ),
170
+ {
171
+ name: 'knowledge.upload', method: 'POST', path: '/api/project-writing/file/upload',
172
+ idempotent: false, multipart: true, requiredContext: REQUIRED_CONTEXT, validate: uploadInput,
173
+ handler: async ({ client, input, baseDir }) => {
174
+ if (typeof baseDir !== 'string' || !baseDir) {
175
+ throw new CliError('CONFIG_MISSING', '知识库上传缺少 baseDir');
176
+ }
177
+ const resolvedPath = resolveWithinBase(input.file, baseDir);
178
+ let content;
179
+ try {
180
+ content = await readFile(resolvedPath);
181
+ } catch {
182
+ throw new CliError('FILE_NOT_FOUND', '知识库上传文件不存在', {
183
+ relativePath: input.file, baseDir, resolvedPath,
184
+ });
185
+ }
186
+ const fileName = basename(resolvedPath);
187
+ const formData = new FormData();
188
+ formData.append('uuid', input.uuid);
189
+ formData.append('file', new Blob([content]), fileName);
190
+ const uploaded = await client.upload({ path: '/api/project-writing/file/upload', formData });
191
+ const url = typeof uploaded === 'string' ? uploaded : uploaded?.filePath ?? uploaded?.url;
192
+ if (!url) throw new CliError('BUSINESS_ERROR', '文件上传未返回地址');
193
+ return await client.request({
194
+ method: 'POST', path: '/api/project-writing/file/upload-to-kb', idempotent: false,
195
+ body: {
196
+ kbId: input.kbId,
197
+ fileName,
198
+ url,
199
+ tags: input.tags ?? [],
200
+ ...(input.tagTemplateId === undefined ? {} : { tagTemplateId: input.tagTemplateId }),
201
+ },
202
+ });
203
+ },
204
+ },
205
+ ];