@wx-sab/renkei 1.2.5 → 1.3.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.
package/README.md CHANGED
@@ -157,8 +157,174 @@ export default defineConfig([
157
157
  ]);
158
158
  ```
159
159
 
160
+ ### CLI 命令
161
+
162
+ ```bash
163
+ # 生成所有服务的所有接口
164
+ renkei api
165
+
166
+ # 只生成指定服务的接口
167
+ renkei api -s dp-service
168
+
169
+ # 只生成指定文件夹的接口(命令行方式)
170
+ renkei api -f "用户管理,订单管理"
171
+
172
+ # 交互式选择要生成的文件夹
173
+ renkei api -i
174
+
175
+ # 组合使用:交互式选择指定服务的文件夹
176
+ renkei api -s dp-service -i
177
+ ```
178
+
179
+ ### 交互式选择文件夹
180
+
181
+ 当运行 `renkei api -i` 时,工具会:
182
+
183
+ 1. 分析每个服务的 swagger/apifox 文档
184
+ 2. 提取所有文件夹信息(基于 `x-apifox-folder` 字段)
185
+ 3. 显示交互式多选界面,默认选中"全部"选项
186
+ 4. 用户可以选择:
187
+ - 保持选中"全部":生成所有接口
188
+ - 取消"全部",选择具体文件夹:只生成选中的接口
189
+
190
+ 示例输出:
191
+ ```
192
+ 正在分析服务: dp-service
193
+
194
+ 发现以下文件夹:
195
+ 1. 用户管理 (15 个接口)
196
+ 2. 订单管理 (23 个接口)
197
+ 3. 商品管理 (18 个接口)
198
+ 4. 支付管理 (12 个接口)
199
+
200
+ ? 请选择要生成的文件夹 (空格选择,回车确认) (Press <space> to select, <a> to toggle all, <i> to invert selection)
201
+ ❯◉ 全部 (共 68 个接口)
202
+ ── 具体文件夹 ──
203
+ ◯ 用户管理 (15 个接口)
204
+ ◯ 订单管理 (23 个接口)
205
+ ◯ 商品管理 (18 个接口)
206
+ ◯ 支付管理 (12 个接口)
207
+ ```
208
+
209
+ **操作说明:**
210
+ - 默认选中"全部",生成所有接口
211
+ - 按 `空格` 取消"全部",然后选择具体文件夹
212
+ - 按 `a` 切换全选/全不选
213
+ - 按 `i` 反选
214
+ - 按 `回车` 确认选择
215
+
216
+ ### 文件夹过滤功能
217
+
218
+ 支持三种方式指定要生成的文件夹:
219
+
220
+ #### 1. 配置文件方式
221
+
222
+ 在配置文件中通过 `folders` 字段指定:
223
+
224
+ ```typescript
225
+ import { defineConfig } from "@wx-sab/renkei";
226
+
227
+ export default defineConfig([
228
+ {
229
+ serviceName: "dp-service",
230
+ apifoxProjectId: "2954843",
231
+ // 只生成这两个文件夹的接口
232
+ folders: ["用户管理", "订单管理"],
233
+ customRequestUrl: (routeData) => {
234
+ return "/" + routeData["x-apifox-folder"].split("/")[0] + routeData.route;
235
+ },
236
+ }
237
+ ]);
238
+ ```
239
+
240
+ #### 2. 命令行参数方式
241
+
242
+ 使用 `-f` 或 `--folder` 参数:
243
+
244
+ ```bash
245
+ # 只生成指定文件夹的接口
246
+ renkei api -f "用户管理,订单管理"
247
+
248
+ # 指定服务和文件夹
249
+ renkei api -s dp-service -f "用户管理,订单管理"
250
+ ```
251
+
252
+ #### 3. 交互式选择方式(推荐)
253
+
254
+ 使用 `-i` 或 `--interactive` 参数:
255
+
256
+ ```bash
257
+ # 交互式选择所有服务的文件夹
258
+ renkei api -i
259
+
260
+ # 交互式选择指定服务的文件夹
261
+ renkei api -s dp-service -i
262
+ ```
263
+
264
+ #### 注意事项
265
+
266
+ 1. **文件夹名称匹配**:文件夹名称基于 swagger/apifox 文档中的 `x-apifox-folder` 字段,取第一级目录名称
267
+ 2. **优先级**:交互式选择 > 命令行参数 > 配置文件
268
+ 3. **空数组**:如果 `folders` 为空数组或不设置,则生成所有接口
269
+ 4. **多服务**:每个服务可以独立配置不同的文件夹列表
270
+
160
271
  ## Examples
161
272
 
273
+ ### 文件夹过滤示例
274
+
275
+ #### 场景1:只生成特定模块的接口
276
+
277
+ ```typescript
278
+ import { defineConfig } from "@wx-sab/renkei";
279
+
280
+ export default defineConfig([
281
+ {
282
+ serviceName: "dp-service",
283
+ apifoxProjectId: "2954843",
284
+ // 只生成用户管理和订单管理模块的接口
285
+ folders: ["用户管理", "订单管理"],
286
+ customRequestUrl: (routeData) => {
287
+ return "/" + routeData["x-apifox-folder"].split("/")[0] + routeData.route;
288
+ },
289
+ }
290
+ ]);
291
+ ```
292
+
293
+ #### 场景2:多服务配置,每个服务过滤不同文件夹
294
+
295
+ ```typescript
296
+ import { defineConfig } from "@wx-sab/renkei";
297
+
298
+ export default defineConfig([
299
+ {
300
+ serviceName: "dp-service",
301
+ apifoxProjectId: "2954843",
302
+ folders: ["用户管理", "订单管理"],
303
+ customRequestUrl: (routeData) => {
304
+ return "/" + routeData["x-apifox-folder"].split("/")[0] + routeData.route;
305
+ },
306
+ },
307
+ {
308
+ serviceName: "cop-profile",
309
+ apifoxProjectId: "1234567",
310
+ folders: ["个人中心", "权限管理"],
311
+ customRequestUrl: (routeData) => {
312
+ return "/cop-profile" + routeData.route;
313
+ },
314
+ }
315
+ ]);
316
+ ```
317
+
318
+ #### 场景3:命令行快速过滤
319
+
320
+ ```bash
321
+ # 快速生成特定文件夹的接口,无需修改配置文件
322
+ renkei api -s dp-service -f "用户管理,订单管理,商品管理"
323
+
324
+ # 交互式选择,适合不确定有哪些文件夹时使用
325
+ renkei api -i
326
+ ```
327
+
162
328
  ### 自定义接口的 mock 服务
163
329
  ```ts
164
330
  import { request } from '@/utils/request'
@@ -1,37 +1,92 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
13
  };
5
14
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const fs_1 = __importDefault(require("fs"));
15
+ const promises_1 = __importDefault(require("fs/promises"));
7
16
  const path_1 = __importDefault(require("path"));
8
17
  const commander_1 = __importDefault(require("commander"));
9
18
  const generate_1 = require("../core/generate");
10
19
  const constants_1 = require("../core/constants");
11
- /**
12
- * 读取项目中的配置文件
13
- */
14
- const readProjectConfigFile = () => {
20
+ const folder_selector_1 = require("../core/folder-selector");
21
+ const config_utils_1 = require("../core/config-utils");
22
+ const readProjectConfigFile = () => __awaiter(void 0, void 0, void 0, function* () {
15
23
  for (const file of constants_1.CONFIG_FILE_NAMES) {
16
24
  const fullFilePath = path_1.default.resolve(process.cwd(), file);
17
- if (fs_1.default.existsSync(fullFilePath)) {
25
+ try {
26
+ yield promises_1.default.access(fullFilePath);
18
27
  if (fullFilePath.endsWith('.ts')) {
19
28
  return require(fullFilePath).default;
20
29
  }
21
30
  return require(fullFilePath);
22
31
  }
32
+ catch (_a) {
33
+ continue;
34
+ }
23
35
  }
24
36
  return null;
25
- };
26
- const parse = new commander_1.default.Command('api').option('-s, --service <service>', '只获取指定的服务,多个服务用","分隔', ',').action((param, options) => {
27
- var _a;
28
- // 只生成指定服务
37
+ });
38
+ const parseCommandOptions = (param) => {
39
+ var _a, _b;
29
40
  const services = (((_a = param.service) === null || _a === void 0 ? void 0 : _a.split(',')) || []).filter(Boolean);
30
- const config = readProjectConfigFile();
41
+ const folders = (((_b = param.folder) === null || _b === void 0 ? void 0 : _b.split(',')) || []).filter(Boolean);
42
+ return { services, folders };
43
+ };
44
+ const handleInteractiveSelection = (config, services) => __awaiter(void 0, void 0, void 0, function* () {
45
+ const _configs = (0, config_utils_1.collectConfig)(config).filter((conf) => {
46
+ if (services.length > 0) {
47
+ return services.includes(conf.serviceName);
48
+ }
49
+ return true;
50
+ });
51
+ try {
52
+ const folderSelections = yield (0, folder_selector_1.selectFoldersForServices)(_configs);
53
+ (0, config_utils_1.applyFoldersToConfig)(config, folderSelections, services);
54
+ return true;
55
+ }
56
+ catch (error) {
57
+ console.error('交互式选择失败:', error.message);
58
+ return false;
59
+ }
60
+ });
61
+ const handleCommandLineFolders = (config, services, folders) => {
62
+ const folderMap = new Map();
63
+ (0, config_utils_1.collectConfig)(config).forEach((conf) => {
64
+ if (services.length === 0 || services.includes(conf.serviceName)) {
65
+ folderMap.set(conf.serviceName, folders);
66
+ }
67
+ });
68
+ (0, config_utils_1.applyFoldersToConfig)(config, folderMap, services);
69
+ };
70
+ const parse = new commander_1.default.Command('api')
71
+ .option('-s, --service <service>', '只获取指定的服务,多个服务用","分隔', ',')
72
+ .option('-f, --folder <folder>', '只获取指定的文件夹,多个文件夹用","分隔', ',')
73
+ .option('-i, --interactive', '启用交互式选择文件夹', false)
74
+ .action((param) => __awaiter(void 0, void 0, void 0, function* () {
75
+ const { services, folders } = parseCommandOptions(param);
76
+ const config = yield readProjectConfigFile();
31
77
  if (!config) {
32
78
  console.warn('未找到配置文件');
33
79
  return;
34
80
  }
81
+ if (param.interactive) {
82
+ const success = yield handleInteractiveSelection(config, services);
83
+ if (!success) {
84
+ return;
85
+ }
86
+ }
87
+ else if (folders.length > 0) {
88
+ handleCommandLineFolders(config, services, folders);
89
+ }
35
90
  (0, generate_1.generateApis)(config, services);
36
- });
91
+ }));
37
92
  exports.default = parse;
@@ -0,0 +1,8 @@
1
+ import { GenerateConfig, ServiceConfig } from './interface';
2
+ export declare const SKIP_ROUTE_MARKER: "__SKIP__";
3
+ export declare const collectConfig: (conf: GenerateConfig | GenerateConfig[], collects?: GenerateConfig[]) => GenerateConfig[];
4
+ export interface FolderSelectionResult {
5
+ serviceName: string;
6
+ folders: string[];
7
+ }
8
+ export declare const applyFoldersToConfig: (conf: ServiceConfig | ServiceConfig[], folderSelections: Map<string, string[]>, services?: string[]) => void;
@@ -0,0 +1,45 @@
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.applyFoldersToConfig = exports.collectConfig = exports.SKIP_ROUTE_MARKER = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ exports.SKIP_ROUTE_MARKER = '__SKIP__';
9
+ const collectConfig = (conf, collects = []) => {
10
+ if (!conf)
11
+ return collects;
12
+ if (Array.isArray(conf)) {
13
+ conf.forEach((con) => (0, exports.collectConfig)(con, collects));
14
+ }
15
+ else {
16
+ collects.push(lodash_1.default.omit(conf, 'projects'));
17
+ if (Array.isArray(conf.projects)) {
18
+ conf.projects.forEach((project) => (0, exports.collectConfig)(Object.assign(Object.assign({}, lodash_1.default.omit(conf, 'projects')), project), collects));
19
+ }
20
+ }
21
+ return collects;
22
+ };
23
+ exports.collectConfig = collectConfig;
24
+ const applyFoldersToConfig = (conf, folderSelections, services = []) => {
25
+ if (Array.isArray(conf)) {
26
+ conf.forEach((c) => (0, exports.applyFoldersToConfig)(c, folderSelections, services));
27
+ }
28
+ else {
29
+ const serviceName = conf.serviceName;
30
+ const selectedFolders = folderSelections.get(serviceName);
31
+ if (selectedFolders && selectedFolders.length > 0) {
32
+ conf.folders = selectedFolders;
33
+ }
34
+ if (Array.isArray(conf.projects)) {
35
+ conf.projects.forEach((p) => {
36
+ const projectServiceName = p.serviceName || serviceName;
37
+ const projectFolders = folderSelections.get(projectServiceName);
38
+ if (projectFolders && projectFolders.length > 0) {
39
+ p.folders = projectFolders;
40
+ }
41
+ });
42
+ }
43
+ }
44
+ };
45
+ exports.applyFoldersToConfig = applyFoldersToConfig;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const config_utils_1 = require("./config-utils");
5
+ (0, vitest_1.describe)('config-utils', () => {
6
+ (0, vitest_1.describe)('collectConfig', () => {
7
+ (0, vitest_1.it)('should return empty array for null/undefined config', () => {
8
+ // 测试边界情况:null 和 undefined 输入
9
+ (0, vitest_1.expect)((0, config_utils_1.collectConfig)(null)).toEqual([]);
10
+ (0, vitest_1.expect)((0, config_utils_1.collectConfig)(undefined)).toEqual([]);
11
+ });
12
+ (0, vitest_1.it)('should collect single config', () => {
13
+ const config = {
14
+ serviceName: 'test-service',
15
+ sourceType: 'apifox',
16
+ apifoxProjectId: '12345'
17
+ };
18
+ const result = (0, config_utils_1.collectConfig)(config);
19
+ (0, vitest_1.expect)(result).toHaveLength(1);
20
+ (0, vitest_1.expect)(result[0].serviceName).toBe('test-service');
21
+ });
22
+ (0, vitest_1.it)('should collect array of configs', () => {
23
+ const configs = [
24
+ { serviceName: 'service1', sourceType: 'apifox', apifoxProjectId: '123' },
25
+ { serviceName: 'service2', sourceType: 'swagger' }
26
+ ];
27
+ const result = (0, config_utils_1.collectConfig)(configs);
28
+ (0, vitest_1.expect)(result).toHaveLength(2);
29
+ (0, vitest_1.expect)(result[0].serviceName).toBe('service1');
30
+ (0, vitest_1.expect)(result[1].serviceName).toBe('service2');
31
+ });
32
+ (0, vitest_1.it)('should flatten nested projects', () => {
33
+ const config = {
34
+ serviceName: 'parent-service',
35
+ sourceType: 'apifox',
36
+ apifoxProjectId: '123',
37
+ projects: [
38
+ { serviceName: 'child-service-1' },
39
+ { serviceName: 'child-service-2' }
40
+ ]
41
+ };
42
+ const result = (0, config_utils_1.collectConfig)(config);
43
+ (0, vitest_1.expect)(result).toHaveLength(3);
44
+ (0, vitest_1.expect)(result[0].serviceName).toBe('parent-service');
45
+ (0, vitest_1.expect)(result[1].serviceName).toBe('child-service-1');
46
+ (0, vitest_1.expect)(result[2].serviceName).toBe('child-service-2');
47
+ });
48
+ (0, vitest_1.it)('should omit projects field from collected configs', () => {
49
+ const config = {
50
+ serviceName: 'parent-service',
51
+ sourceType: 'apifox',
52
+ apifoxProjectId: '123',
53
+ projects: [
54
+ { serviceName: 'child-service' }
55
+ ]
56
+ };
57
+ const result = (0, config_utils_1.collectConfig)(config);
58
+ (0, vitest_1.expect)(result[0]).not.toHaveProperty('projects');
59
+ (0, vitest_1.expect)(result[1]).not.toHaveProperty('projects');
60
+ });
61
+ (0, vitest_1.it)('should merge parent config with child projects', () => {
62
+ const config = {
63
+ serviceName: 'parent-service',
64
+ sourceType: 'apifox',
65
+ apifoxProjectId: '123',
66
+ output: '/custom/output',
67
+ projects: [
68
+ { serviceName: 'child-service' }
69
+ ]
70
+ };
71
+ const result = (0, config_utils_1.collectConfig)(config);
72
+ (0, vitest_1.expect)(result[1].serviceName).toBe('child-service');
73
+ (0, vitest_1.expect)(result[1].sourceType).toBe('apifox');
74
+ (0, vitest_1.expect)(result[1].apifoxProjectId).toBe('123');
75
+ (0, vitest_1.expect)(result[1].output).toBe('/custom/output');
76
+ });
77
+ (0, vitest_1.it)('should handle deeply nested projects', () => {
78
+ const config = {
79
+ serviceName: 'root-service',
80
+ sourceType: 'swagger',
81
+ projects: [
82
+ {
83
+ serviceName: 'level-1',
84
+ projects: [
85
+ { serviceName: 'level-2' }
86
+ ]
87
+ }
88
+ ]
89
+ };
90
+ const result = (0, config_utils_1.collectConfig)(config);
91
+ (0, vitest_1.expect)(result).toHaveLength(3);
92
+ (0, vitest_1.expect)(result[0].serviceName).toBe('root-service');
93
+ (0, vitest_1.expect)(result[1].serviceName).toBe('level-1');
94
+ (0, vitest_1.expect)(result[2].serviceName).toBe('level-2');
95
+ });
96
+ (0, vitest_1.it)('should preserve existing collects array', () => {
97
+ const config = {
98
+ serviceName: 'new-service'
99
+ };
100
+ const existing = [
101
+ { serviceName: 'existing-service' }
102
+ ];
103
+ const result = (0, config_utils_1.collectConfig)(config, existing);
104
+ (0, vitest_1.expect)(result).toHaveLength(2);
105
+ (0, vitest_1.expect)(result[0].serviceName).toBe('existing-service');
106
+ (0, vitest_1.expect)(result[1].serviceName).toBe('new-service');
107
+ });
108
+ });
109
+ (0, vitest_1.describe)('applyFoldersToConfig', () => {
110
+ (0, vitest_1.it)('should apply folders to single config', () => {
111
+ const config = {
112
+ serviceName: 'test-service'
113
+ };
114
+ const folderSelections = new Map();
115
+ folderSelections.set('test-service', ['folder1', 'folder2']);
116
+ (0, config_utils_1.applyFoldersToConfig)(config, folderSelections);
117
+ (0, vitest_1.expect)(config.folders).toEqual(['folder1', 'folder2']);
118
+ });
119
+ (0, vitest_1.it)('should not modify config if no folders selected', () => {
120
+ const config = {
121
+ serviceName: 'test-service'
122
+ };
123
+ const folderSelections = new Map();
124
+ (0, config_utils_1.applyFoldersToConfig)(config, folderSelections);
125
+ (0, vitest_1.expect)(config.folders).toBeUndefined();
126
+ });
127
+ (0, vitest_1.it)('should apply folders to array of configs', () => {
128
+ const configs = [
129
+ { serviceName: 'service1' },
130
+ { serviceName: 'service2' }
131
+ ];
132
+ const folderSelections = new Map();
133
+ folderSelections.set('service1', ['folder1']);
134
+ folderSelections.set('service2', ['folder2']);
135
+ (0, config_utils_1.applyFoldersToConfig)(configs, folderSelections);
136
+ (0, vitest_1.expect)(configs[0].folders).toEqual(['folder1']);
137
+ (0, vitest_1.expect)(configs[1].folders).toEqual(['folder2']);
138
+ });
139
+ (0, vitest_1.it)('should apply folders to nested projects', () => {
140
+ const config = {
141
+ serviceName: 'parent-service',
142
+ projects: [
143
+ { serviceName: 'child-service' }
144
+ ]
145
+ };
146
+ const folderSelections = new Map();
147
+ folderSelections.set('parent-service', ['parent-folder']);
148
+ folderSelections.set('child-service', ['child-folder']);
149
+ (0, config_utils_1.applyFoldersToConfig)(config, folderSelections);
150
+ (0, vitest_1.expect)(config.folders).toEqual(['parent-folder']);
151
+ (0, vitest_1.expect)(config.projects[0].folders).toEqual(['child-folder']);
152
+ });
153
+ (0, vitest_1.it)('should use parent serviceName for projects without serviceName', () => {
154
+ const config = {
155
+ serviceName: 'parent-service',
156
+ projects: [
157
+ {}
158
+ ]
159
+ };
160
+ const folderSelections = new Map();
161
+ folderSelections.set('parent-service', ['shared-folder']);
162
+ (0, config_utils_1.applyFoldersToConfig)(config, folderSelections);
163
+ (0, vitest_1.expect)(config.folders).toEqual(['shared-folder']);
164
+ (0, vitest_1.expect)(config.projects[0].folders).toEqual(['shared-folder']);
165
+ });
166
+ (0, vitest_1.it)('should not apply empty folder selection to projects', () => {
167
+ const config = {
168
+ serviceName: 'parent-service',
169
+ projects: [
170
+ { serviceName: 'child-service', folders: ['existing-folder'] }
171
+ ]
172
+ };
173
+ const folderSelections = new Map();
174
+ folderSelections.set('child-service', []);
175
+ (0, config_utils_1.applyFoldersToConfig)(config, folderSelections);
176
+ (0, vitest_1.expect)(config.projects[0].folders).toEqual(['existing-folder']);
177
+ });
178
+ (0, vitest_1.it)('should not modify config when folder selection is empty', () => {
179
+ const config = {
180
+ serviceName: 'test-service',
181
+ folders: ['existing-folder']
182
+ };
183
+ const folderSelections = new Map();
184
+ folderSelections.set('test-service', []);
185
+ (0, config_utils_1.applyFoldersToConfig)(config, folderSelections);
186
+ (0, vitest_1.expect)(config.folders).toEqual(['existing-folder']);
187
+ });
188
+ (0, vitest_1.it)('should preserve services array parameter', () => {
189
+ const config = {
190
+ serviceName: 'test-service'
191
+ };
192
+ const folderSelections = new Map();
193
+ folderSelections.set('test-service', ['folder1']);
194
+ const services = [];
195
+ (0, config_utils_1.applyFoldersToConfig)(config, folderSelections, services);
196
+ (0, vitest_1.expect)(config.folders).toEqual(['folder1']);
197
+ });
198
+ });
199
+ (0, vitest_1.describe)('SKIP_ROUTE_MARKER', () => {
200
+ (0, vitest_1.it)('should be defined as constant', () => {
201
+ (0, vitest_1.expect)(config_utils_1.SKIP_ROUTE_MARKER).toBe('__SKIP__');
202
+ });
203
+ });
204
+ });
@@ -0,0 +1,10 @@
1
+ import { GenerateConfig } from './interface';
2
+ export interface FolderInfo {
3
+ name: string;
4
+ path: string;
5
+ count: number;
6
+ }
7
+ export declare const fetchSwaggerDocument: (config: GenerateConfig) => Promise<any>;
8
+ export declare const extractFoldersFromSwagger: (swaggerDoc: any) => FolderInfo[];
9
+ export declare const selectFolders: (folders: FolderInfo[]) => Promise<string[]>;
10
+ export declare const selectFoldersForServices: (configs: GenerateConfig[]) => Promise<Map<string, string[]>>;