@lark-apaas/fullstack-cli 1.1.5 → 1.1.6-alpha.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/dist/index.js CHANGED
@@ -1,54 +1,2157 @@
1
- import { fileURLToPath } from 'node:url';
2
- import path from 'node:path';
3
- import fs from 'node:fs';
4
- import cac from 'cac';
5
- import { run as runGenDbSchema } from './commands/gen-db-schema.js';
6
- import { run as runSync } from './commands/sync.js';
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
- // 读取 package.json
10
- const pkgPath = path.join(__dirname, '../package.json');
11
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
12
- const cli = cac('fullstack-cli');
13
- // 设置全局描述和版本
14
- cli
15
- .version(pkg.version)
16
- .help();
17
- // gen-db-schema 命令
18
- cli
19
- .command('gen-db-schema', 'Generate database schema from existing database')
20
- .option('--output <path>', 'Output path for schema', {
21
- default: 'server/database/schema.ts',
22
- })
23
- .option('--schema-filter <schemas>', 'Schema filter, comma-separated')
24
- .option('--tables-filter <tables>', 'Tables filter, comma-separated')
25
- .option('--enable-nest-module-generate', 'Enable generate NestJS module boilerplate')
26
- .example('fullstack-cli gen-db-schema')
27
- .example('fullstack-cli gen-db-schema --output custom/schema.ts')
28
- .action(async (options) => {
1
+ // src/index.ts
2
+ import fs13 from "fs";
3
+ import path11 from "path";
4
+ import { fileURLToPath as fileURLToPath3 } from "url";
5
+ import { config as dotenvConfig } from "dotenv";
6
+
7
+ // src/cli.ts
8
+ import { Command } from "commander";
9
+
10
+ // src/hooks.ts
11
+ var HooksManager = class {
12
+ constructor() {
13
+ this.beforeHooks = [];
14
+ this.afterHooks = [];
15
+ }
16
+ /** 注册 beforeRun hook */
17
+ beforeRun(hook) {
18
+ this.beforeHooks.push(hook);
19
+ return this;
20
+ }
21
+ /** 注册 afterRun hook */
22
+ afterRun(hook) {
23
+ this.afterHooks.push(hook);
24
+ return this;
25
+ }
26
+ /** 执行 beforeRun hooks */
27
+ async runBeforeHooks(ctx) {
28
+ for (const hook of this.beforeHooks) {
29
+ await hook(ctx);
30
+ }
31
+ }
32
+ /** 执行 afterRun hooks */
33
+ async runAfterHooks(ctx, error) {
34
+ for (const hook of this.afterHooks) {
35
+ await hook(ctx, error);
36
+ }
37
+ }
38
+ /** 包装命令 action,自动执行 hooks */
39
+ wrapAction(commandName, action) {
40
+ return async (...args) => {
41
+ const ctx = {
42
+ commandName,
43
+ args: args.filter((a) => typeof a === "string"),
44
+ options: args.find((a) => typeof a === "object" && a !== null) || {},
45
+ startTime: Date.now()
46
+ };
47
+ let error;
48
+ try {
49
+ await this.runBeforeHooks(ctx);
50
+ await action(...args);
51
+ } catch (e) {
52
+ error = e instanceof Error ? e : new Error(String(e));
53
+ throw error;
54
+ } finally {
55
+ await this.runAfterHooks(ctx, error);
56
+ }
57
+ };
58
+ }
59
+ };
60
+
61
+ // src/cli.ts
62
+ var FullstackCLI = class {
63
+ constructor(version) {
64
+ this.program = new Command();
65
+ this.hooks = new HooksManager();
66
+ this.program.name("fullstack-cli").description("CLI tool for fullstack template management").version(version);
67
+ this.setupDefaultHooks();
68
+ }
69
+ setupDefaultHooks() {
70
+ this.hooks.afterRun((ctx, error) => {
71
+ if (error) {
72
+ console.error(`
73
+ Command "${ctx.commandName}" failed:`, error.message);
74
+ if (process.env.DEBUG) {
75
+ console.error(error.stack);
76
+ }
77
+ process.exitCode = 1;
78
+ }
79
+ });
80
+ this.hooks.afterRun((ctx, error) => {
81
+ if (process.env.DEBUG && !error) {
82
+ const elapsed = Date.now() - ctx.startTime;
83
+ console.log(`
84
+ Command "${ctx.commandName}" completed in ${elapsed}ms`);
85
+ }
86
+ });
87
+ }
88
+ /** 注册单个命令 */
89
+ use(command) {
90
+ command.register(this.program);
91
+ return this;
92
+ }
93
+ /** 注册命令组 */
94
+ useGroup(group) {
95
+ const groupCmd = this.program.command(group.name).description(group.description);
96
+ for (const cmd of group.commands) {
97
+ cmd.register(groupCmd);
98
+ }
99
+ return this;
100
+ }
101
+ /** 批量注册 */
102
+ useAll(items) {
103
+ for (const item of items) {
104
+ if ("commands" in item) {
105
+ this.useGroup(item);
106
+ } else {
107
+ this.use(item);
108
+ }
109
+ }
110
+ return this;
111
+ }
112
+ /** 运行 CLI */
113
+ run(argv = process.argv) {
114
+ this.program.parse(argv);
115
+ }
116
+ };
117
+
118
+ // src/commands/db/schema.handler.ts
119
+ import path from "path";
120
+ import fs from "fs";
121
+ import { fileURLToPath } from "url";
122
+ import { spawnSync } from "child_process";
123
+ import { createRequire } from "module";
124
+ import { config as loadEnv } from "dotenv";
125
+ var require2 = createRequire(import.meta.url);
126
+ async function run(options = {}) {
127
+ let exitCode = 0;
128
+ const envPath2 = path.resolve(process.cwd(), ".env");
129
+ if (fs.existsSync(envPath2)) {
130
+ loadEnv({ path: envPath2 });
131
+ console.log("[gen-db-schema] \u2713 Loaded .env file");
132
+ }
133
+ const databaseUrl = process.env.SUDA_DATABASE_URL;
134
+ if (!databaseUrl) {
135
+ console.error("[gen-db-schema] Error: SUDA_DATABASE_URL environment variable is required");
136
+ process.exit(1);
137
+ }
138
+ const outputPath = options.output || process.env.DB_SCHEMA_OUTPUT || "server/database/schema.ts";
139
+ const OUT_DIR = path.resolve(process.cwd(), "server/database/.introspect");
140
+ const SCHEMA_FILE = path.resolve(process.cwd(), outputPath);
141
+ console.log("[gen-db-schema] Starting...");
142
+ const __filename = fileURLToPath(import.meta.url);
143
+ const __dirname2 = path.dirname(__filename);
144
+ const cliRoot = path.resolve(__dirname2, "../");
145
+ const configPath = path.join(cliRoot, "config", "drizzle.config.js");
146
+ if (!fs.existsSync(configPath)) {
147
+ console.error("[gen-db-schema] Error: drizzle config not found in CLI package");
148
+ process.exit(1);
149
+ }
150
+ try {
151
+ const env = {
152
+ ...process.env,
153
+ __DRIZZLE_OUT_DIR__: OUT_DIR,
154
+ __DRIZZLE_SCHEMA_PATH__: SCHEMA_FILE
155
+ };
156
+ const args = process.argv.slice(3).filter((arg) => !arg.startsWith("--enable-nest-module-generate"));
157
+ const spawnArgs = ["--yes", "drizzle-kit", "introspect", "--config", configPath, ...args];
158
+ const result = spawnSync("npx", spawnArgs, { stdio: "inherit", env });
159
+ if (result.error) {
160
+ console.error("[gen-db-schema] Execution failed:", result.error);
161
+ throw result.error;
162
+ }
163
+ if ((result.status ?? 0) !== 0) {
164
+ throw new Error(`drizzle-kit introspect failed with status ${result.status}`);
165
+ }
166
+ const generatedSchema = path.join(OUT_DIR, "schema.ts");
167
+ if (!fs.existsSync(generatedSchema)) {
168
+ console.error("[gen-db-schema] schema.ts not generated");
169
+ throw new Error("drizzle-kit introspect failed to generate schema.ts");
170
+ }
171
+ fs.mkdirSync(path.dirname(SCHEMA_FILE), { recursive: true });
172
+ fs.copyFileSync(generatedSchema, SCHEMA_FILE);
173
+ console.log(`[gen-db-schema] \u2713 Copied to ${outputPath}`);
174
+ const { postprocessDrizzleSchema } = require2("@lark-apaas/devtool-kits");
175
+ const stats = postprocessDrizzleSchema(SCHEMA_FILE);
176
+ if (stats?.unmatchedUnknown?.length) {
177
+ console.warn("[gen-db-schema] Unmatched custom types detected:", stats.unmatchedUnknown);
178
+ }
179
+ console.log("[gen-db-schema] \u2713 Postprocessed schema");
29
180
  try {
30
- await runGenDbSchema(options);
181
+ if (options.enableNestModuleGenerate) {
182
+ const { parseAndGenerateNestResourceTemplate } = require2("@lark-apaas/devtool-kits");
183
+ const tsConfigFilePath = path.resolve(process.cwd(), "tsconfig.json");
184
+ const schemaFilePath = SCHEMA_FILE;
185
+ await parseAndGenerateNestResourceTemplate({
186
+ tsConfigFilePath,
187
+ schemaFilePath,
188
+ moduleOutputDir: path.resolve(process.cwd(), "server/modules")
189
+ });
190
+ console.log("[gen-db-schema] \u2713 Generate NestJS Module Boilerplate Successfully");
191
+ }
192
+ } catch (error) {
193
+ console.warn("[gen-db-schema] Generate NestJS Module Boilerplate failed:", error instanceof Error ? error.message : String(error));
31
194
  }
32
- catch (error) {
33
- console.error(`Failed to execute command:`, error.message);
34
- console.error(error.stack);
35
- process.exit(1);
195
+ console.log("[gen-db-schema] \u2713 Complete");
196
+ } catch (err) {
197
+ console.error("[gen-db-schema] Failed:", err instanceof Error ? err.message : String(err));
198
+ exitCode = 1;
199
+ } finally {
200
+ if (fs.existsSync(OUT_DIR)) {
201
+ fs.rmSync(OUT_DIR, { recursive: true, force: true });
202
+ }
203
+ process.exit(exitCode);
204
+ }
205
+ }
206
+
207
+ // src/commands/db/index.ts
208
+ var genDbSchemaCommand = {
209
+ name: "gen-db-schema",
210
+ description: "Generate database schema from existing database",
211
+ register(program) {
212
+ program.command(this.name).description(this.description).option("--output <path>", "Output path for schema", "server/database/schema.ts").option("--schema-filter <schemas>", "Schema filter, comma-separated").option("--tables-filter <tables>", "Tables filter, comma-separated").option("--enable-nest-module-generate", "Enable generate NestJS module boilerplate").action(async (options) => {
213
+ await run(options);
214
+ });
215
+ }
216
+ };
217
+
218
+ // src/commands/sync/run.handler.ts
219
+ import path2 from "path";
220
+ import fs2 from "fs";
221
+ import { fileURLToPath as fileURLToPath2 } from "url";
222
+
223
+ // src/config/sync.ts
224
+ var syncConfig = {
225
+ // 派生规则
226
+ sync: [
227
+ // 1. 派生 scripts 目录(总是覆盖)
228
+ {
229
+ from: "templates/scripts",
230
+ to: "scripts",
231
+ type: "directory",
232
+ overwrite: true
233
+ },
234
+ // 2. 覆写 nest-cli.json 配置,禁止用户修改
235
+ {
236
+ from: "templates/nest-cli.json",
237
+ to: "nest-cli.json",
238
+ type: "file",
239
+ overwrite: true
240
+ },
241
+ // // 2. 追加内容到 .gitignore
242
+ // {
243
+ // from: 'templates/.gitignore.append',
244
+ // to: '.gitignore',
245
+ // type: 'append',
246
+ // },
247
+ // 3. 派生 server/type.ts 文件(总是覆盖)
248
+ // {
249
+ // from: 'templates/server/global.d.ts',
250
+ // to: 'server/types/global.d.ts',
251
+ // type: 'file',
252
+ // },
253
+ {
254
+ type: "delete-directory",
255
+ to: ".swc"
256
+ // 删除 .swc 目录(如果存在)
257
+ }
258
+ ],
259
+ // 文件权限设置
260
+ permissions: {
261
+ // 所有 .sh 文件设置为可执行
262
+ // '**/*.sh': 0o755,
263
+ }
264
+ };
265
+ function genSyncConfig(perms = {}) {
266
+ if (!perms.disableGenOpenapi) {
267
+ syncConfig.sync.push({
268
+ from: "templates/helper/gen-openapi.ts",
269
+ to: "scripts/gen-openapi.ts",
270
+ type: "file",
271
+ overwrite: true
272
+ });
273
+ }
274
+ return syncConfig;
275
+ }
276
+
277
+ // src/commands/sync/run.handler.ts
278
+ async function run2(options) {
279
+ const userProjectRoot = process.env.INIT_CWD || process.cwd();
280
+ const __filename = fileURLToPath2(import.meta.url);
281
+ const __dirname2 = path2.dirname(__filename);
282
+ const pluginRoot = path2.resolve(__dirname2, "..");
283
+ if (userProjectRoot === pluginRoot) {
284
+ console.log("[fullstack-cli] Skip syncing (installing plugin itself)");
285
+ process.exit(0);
286
+ }
287
+ const userPackageJson = path2.join(userProjectRoot, "package.json");
288
+ if (!fs2.existsSync(userPackageJson)) {
289
+ console.log("[fullstack-cli] Skip syncing (not a valid npm project)");
290
+ process.exit(0);
291
+ }
292
+ try {
293
+ console.log("[fullstack-cli] Starting sync...");
294
+ const config = genSyncConfig({
295
+ disableGenOpenapi: options.disableGenOpenapi ?? false
296
+ });
297
+ if (!config || !config.sync) {
298
+ console.warn("[fullstack-cli] No sync configuration found");
299
+ process.exit(0);
300
+ }
301
+ for (const rule of config.sync) {
302
+ await syncRule(rule, pluginRoot, userProjectRoot);
303
+ }
304
+ if (config.permissions) {
305
+ setPermissions(config.permissions, userProjectRoot);
306
+ }
307
+ console.log("[fullstack-cli] Sync completed successfully \u2705");
308
+ } catch (error) {
309
+ const message = error instanceof Error ? error.message : String(error);
310
+ console.error("[fullstack-cli] Failed to sync:", message);
311
+ process.exit(1);
312
+ }
313
+ }
314
+ async function syncRule(rule, pluginRoot, userProjectRoot) {
315
+ if (rule.type === "delete-file" || rule.type === "delete-directory") {
316
+ const destPath2 = path2.join(userProjectRoot, rule.to);
317
+ if (rule.type === "delete-file") {
318
+ deleteFile(destPath2);
319
+ } else {
320
+ deleteDirectory(destPath2);
321
+ }
322
+ return;
323
+ }
324
+ if (!("from" in rule)) {
325
+ return;
326
+ }
327
+ const srcPath = path2.join(pluginRoot, rule.from);
328
+ const destPath = path2.join(userProjectRoot, rule.to);
329
+ if (!fs2.existsSync(srcPath)) {
330
+ console.warn(`[fullstack-cli] Source not found: ${rule.from}`);
331
+ return;
332
+ }
333
+ switch (rule.type) {
334
+ case "directory":
335
+ syncDirectory(srcPath, destPath, rule.overwrite ?? true);
336
+ break;
337
+ case "file":
338
+ syncFile(srcPath, destPath, rule.overwrite ?? true);
339
+ break;
340
+ case "append":
341
+ appendToFile(srcPath, destPath);
342
+ break;
343
+ }
344
+ }
345
+ function syncFile(src, dest, overwrite = true) {
346
+ const destDir = path2.dirname(dest);
347
+ if (!fs2.existsSync(destDir)) {
348
+ fs2.mkdirSync(destDir, { recursive: true });
349
+ }
350
+ if (fs2.existsSync(dest) && !overwrite) {
351
+ console.log(`[fullstack-cli] \u25CB ${path2.basename(dest)} (skipped, already exists)`);
352
+ return;
353
+ }
354
+ fs2.copyFileSync(src, dest);
355
+ console.log(`[fullstack-cli] \u2713 ${path2.basename(dest)}`);
356
+ }
357
+ function syncDirectory(src, dest, overwrite = true) {
358
+ if (!fs2.existsSync(dest)) {
359
+ fs2.mkdirSync(dest, { recursive: true });
360
+ }
361
+ const files = fs2.readdirSync(src);
362
+ let count = 0;
363
+ files.forEach((file) => {
364
+ const srcFile = path2.join(src, file);
365
+ const destFile = path2.join(dest, file);
366
+ const stats = fs2.statSync(srcFile);
367
+ if (stats.isDirectory()) {
368
+ syncDirectory(srcFile, destFile, overwrite);
369
+ } else {
370
+ if (overwrite || !fs2.existsSync(destFile)) {
371
+ fs2.copyFileSync(srcFile, destFile);
372
+ console.log(`[fullstack-cli] \u2713 ${path2.relative(dest, destFile)}`);
373
+ count++;
374
+ }
375
+ }
376
+ });
377
+ if (count > 0) {
378
+ console.log(`[fullstack-cli] Synced ${count} files to ${path2.basename(dest)}/`);
379
+ }
380
+ }
381
+ function appendToFile(src, dest) {
382
+ const content = fs2.readFileSync(src, "utf-8");
383
+ let existingContent = "";
384
+ if (fs2.existsSync(dest)) {
385
+ existingContent = fs2.readFileSync(dest, "utf-8");
386
+ }
387
+ if (existingContent.includes(content.trim())) {
388
+ console.log(`[fullstack-cli] \u25CB ${path2.basename(dest)} (already contains content)`);
389
+ return;
390
+ }
391
+ fs2.appendFileSync(dest, content);
392
+ console.log(`[fullstack-cli] \u2713 ${path2.basename(dest)} (appended)`);
393
+ }
394
+ function setPermissions(permissions, projectRoot) {
395
+ for (const [pattern, mode] of Object.entries(permissions)) {
396
+ if (pattern === "**/*.sh") {
397
+ const scriptsDir = path2.join(projectRoot, "scripts");
398
+ if (fs2.existsSync(scriptsDir)) {
399
+ const files = fs2.readdirSync(scriptsDir);
400
+ files.forEach((file) => {
401
+ if (file.endsWith(".sh")) {
402
+ const filePath = path2.join(scriptsDir, file);
403
+ fs2.chmodSync(filePath, mode);
404
+ }
405
+ });
406
+ }
407
+ }
408
+ }
409
+ }
410
+ function deleteFile(filePath) {
411
+ if (fs2.existsSync(filePath)) {
412
+ fs2.unlinkSync(filePath);
413
+ console.log(`[fullstack-cli] \u2713 ${path2.basename(filePath)} (deleted)`);
414
+ } else {
415
+ console.log(`[fullstack-cli] \u25CB ${path2.basename(filePath)} (not found)`);
416
+ }
417
+ }
418
+ function deleteDirectory(dirPath) {
419
+ if (fs2.existsSync(dirPath)) {
420
+ fs2.rmSync(dirPath, { recursive: true });
421
+ console.log(`[fullstack-cli] \u2713 ${path2.basename(dirPath)} (deleted)`);
422
+ } else {
423
+ console.log(`[fullstack-cli] \u25CB ${path2.basename(dirPath)} (not found)`);
424
+ }
425
+ }
426
+
427
+ // src/commands/sync/index.ts
428
+ var syncCommand = {
429
+ name: "sync",
430
+ description: "Sync template files (scripts, configs) to user project",
431
+ register(program) {
432
+ program.command(this.name).description(this.description).option("--disable-gen-openapi", "Disable generating openapi.ts").action(async (options) => {
433
+ await run2(options);
434
+ });
435
+ }
436
+ };
437
+
438
+ // src/commands/action-plugin/utils.ts
439
+ import fs3 from "fs";
440
+ import path3 from "path";
441
+ import { spawnSync as spawnSync2 } from "child_process";
442
+ var CONFIG_FILE_NAME = ".capabilityrc.json";
443
+ function parsePluginName(input) {
444
+ const match = input.match(/^(@[^/]+\/[^@]+)(?:@(.+))?$/);
445
+ if (!match) {
446
+ throw new Error(
447
+ `Invalid plugin name format: ${input}. Expected format: @scope/name or @scope/name@version`
448
+ );
449
+ }
450
+ return {
451
+ name: match[1],
452
+ version: match[2] || "latest"
453
+ };
454
+ }
455
+ function getProjectRoot() {
456
+ return process.cwd();
457
+ }
458
+ function getConfigPath() {
459
+ return path3.join(getProjectRoot(), CONFIG_FILE_NAME);
460
+ }
461
+ function getPluginPath(pluginName) {
462
+ return path3.join(getProjectRoot(), "node_modules", pluginName);
463
+ }
464
+ function readConfig() {
465
+ const configPath = getConfigPath();
466
+ if (!fs3.existsSync(configPath)) {
467
+ return { plugins: {} };
468
+ }
469
+ try {
470
+ const content = fs3.readFileSync(configPath, "utf-8");
471
+ return JSON.parse(content);
472
+ } catch {
473
+ console.warn(`[action-plugin] Warning: Failed to parse ${CONFIG_FILE_NAME}, using empty config`);
474
+ return { plugins: {} };
475
+ }
476
+ }
477
+ function writeConfig(config) {
478
+ const configPath = getConfigPath();
479
+ fs3.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
480
+ }
481
+ function isPluginInstalled(pluginName) {
482
+ const config = readConfig();
483
+ return pluginName in config.plugins;
484
+ }
485
+ function getInstalledPluginInfo(pluginName) {
486
+ const config = readConfig();
487
+ return config.plugins[pluginName] || null;
488
+ }
489
+ function npmInstall(tgzPath) {
490
+ console.log(`[action-plugin] Running npm install ${tgzPath}...`);
491
+ const result = spawnSync2("npm", ["install", tgzPath, "--no-save", "--no-package-lock", "--ignore-scripts"], {
492
+ cwd: getProjectRoot(),
493
+ stdio: "inherit"
494
+ });
495
+ if (result.error) {
496
+ throw new Error(`npm install failed: ${result.error.message}`);
497
+ }
498
+ if (result.status !== 0) {
499
+ throw new Error(`npm install failed with exit code ${result.status}`);
500
+ }
501
+ }
502
+ function npmUninstall(pluginName) {
503
+ console.log(`[action-plugin] Running npm uninstall ${pluginName}...`);
504
+ const result = spawnSync2("npm", ["uninstall", pluginName], {
505
+ cwd: getProjectRoot(),
506
+ stdio: "inherit"
507
+ });
508
+ if (result.error) {
509
+ throw new Error(`npm uninstall failed: ${result.error.message}`);
510
+ }
511
+ if (result.status !== 0) {
512
+ throw new Error(`npm uninstall failed with exit code ${result.status}`);
513
+ }
514
+ }
515
+ function getPackageVersion(pluginName) {
516
+ const pkgJsonPath = path3.join(getPluginPath(pluginName), "package.json");
517
+ if (!fs3.existsSync(pkgJsonPath)) {
518
+ return null;
519
+ }
520
+ try {
521
+ const content = fs3.readFileSync(pkgJsonPath, "utf-8");
522
+ const pkg2 = JSON.parse(content);
523
+ return pkg2.version || null;
524
+ } catch {
525
+ return null;
526
+ }
527
+ }
528
+
529
+ // src/commands/action-plugin/api-client.ts
530
+ import { HttpClient as HttpClient2 } from "@lark-apaas/http-client";
531
+ import fs4 from "fs";
532
+ import path4 from "path";
533
+
534
+ // src/utils/http-client.ts
535
+ import { HttpClient } from "@lark-apaas/http-client";
536
+ var clientInstance = null;
537
+ function getHttpClient() {
538
+ if (!clientInstance) {
539
+ clientInstance = new HttpClient({
540
+ timeout: 3e4,
541
+ platform: {
542
+ enabled: true
543
+ }
544
+ });
545
+ clientInstance.interceptors.request.use((req) => {
546
+ req.headers["x-tt-env"] = "boe_miaoda_plugin";
547
+ return req;
548
+ });
549
+ }
550
+ return clientInstance;
551
+ }
552
+
553
+ // src/commands/action-plugin/api-client.ts
554
+ var PLUGIN_CACHE_DIR = "node_modules/.cache/fullstack-cli/plugins";
555
+ async function getPluginVersions(keys, latestOnly = true) {
556
+ const client = getHttpClient();
557
+ const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(",")}&latest_only=${latestOnly}`);
558
+ if (!response.ok || response.status !== 200) {
559
+ throw new Error(`Failed to get plugin versions: ${response.status} ${response.statusText}`);
560
+ }
561
+ const result = await response.json();
562
+ console.log("getPluginVersions response:", result);
563
+ if (result.status_code !== "0") {
564
+ throw new Error(`API error: ${result.message}`);
565
+ }
566
+ return result.data.pluginVersions;
567
+ }
568
+ async function getPluginVersion(pluginKey, requestedVersion) {
569
+ const isLatest = requestedVersion === "latest";
570
+ const versions = await getPluginVersions([pluginKey], isLatest);
571
+ const pluginVersions = versions[pluginKey];
572
+ if (!pluginVersions || pluginVersions.length === 0) {
573
+ throw new Error(`Plugin not found: ${pluginKey}`);
574
+ }
575
+ if (isLatest) {
576
+ return pluginVersions[0];
577
+ }
578
+ const targetVersion = pluginVersions.find((v) => v.version === requestedVersion);
579
+ if (!targetVersion) {
580
+ throw new Error(
581
+ `Version ${requestedVersion} not found for plugin ${pluginKey}. Available versions: ${pluginVersions.map((v) => v.version).join(", ")}`
582
+ );
583
+ }
584
+ return targetVersion;
585
+ }
586
+ function parsePluginKey(key) {
587
+ const match = key.match(/^(@[^/]+)\/(.+)$/);
588
+ if (!match) {
589
+ throw new Error(`Invalid plugin key format: ${key}`);
590
+ }
591
+ return { scope: match[1], name: match[2] };
592
+ }
593
+ async function downloadFromInner(pluginKey, version) {
594
+ const client = getHttpClient();
595
+ const { scope, name } = parsePluginKey(pluginKey);
596
+ const url = `/api/v1/studio/innerapi/plugins/${scope}/${name}/versions/${version}/package`;
597
+ console.log(`[action-plugin] Downloading plugin from inner API: ${url}`);
598
+ const response = await client.get(url);
599
+ if (!response.ok) {
600
+ throw new Error(`Failed to download plugin: ${response.status} ${response.statusText}`);
601
+ }
602
+ const arrayBuffer = await response.arrayBuffer();
603
+ return Buffer.from(arrayBuffer);
604
+ }
605
+ async function downloadFromPublic(downloadURL) {
606
+ const client = new HttpClient2({
607
+ timeout: 6e4
608
+ });
609
+ const response = await client.get(downloadURL);
610
+ if (!response.ok) {
611
+ throw new Error(`Failed to download plugin from public URL: ${response.status} ${response.statusText}`);
612
+ }
613
+ const arrayBuffer = await response.arrayBuffer();
614
+ return Buffer.from(arrayBuffer);
615
+ }
616
+ function getPluginCacheDir() {
617
+ return path4.join(process.cwd(), PLUGIN_CACHE_DIR);
618
+ }
619
+ function ensureCacheDir() {
620
+ const cacheDir = getPluginCacheDir();
621
+ if (!fs4.existsSync(cacheDir)) {
622
+ fs4.mkdirSync(cacheDir, { recursive: true });
623
+ }
624
+ }
625
+ function getTempFilePath(pluginKey, version) {
626
+ ensureCacheDir();
627
+ const safeKey = pluginKey.replace(/[/@]/g, "_");
628
+ const filename = `${safeKey}-${version}.tgz`;
629
+ return path4.join(getPluginCacheDir(), filename);
630
+ }
631
+ async function downloadPlugin(pluginKey, requestedVersion) {
632
+ console.log(`[action-plugin] Fetching plugin info for ${pluginKey}@${requestedVersion}...`);
633
+ const pluginInfo = await getPluginVersion(pluginKey, requestedVersion);
634
+ console.log(`[action-plugin] Found: ${pluginInfo.name} v${pluginInfo.version}`);
635
+ console.log(`[action-plugin] Download approach: ${pluginInfo.downloadApproach}`);
636
+ let tgzBuffer;
637
+ if (pluginInfo.downloadApproach === "inner") {
638
+ console.log(`[action-plugin] Downloading from inner API...`);
639
+ tgzBuffer = await downloadFromInner(pluginKey, pluginInfo.version);
640
+ } else {
641
+ console.log(`[action-plugin] Downloading from public URL...`);
642
+ tgzBuffer = await downloadFromPublic(pluginInfo.downloadURL);
643
+ }
644
+ const tgzPath = getTempFilePath(pluginKey, pluginInfo.version);
645
+ fs4.writeFileSync(tgzPath, tgzBuffer);
646
+ console.log(`[action-plugin] Downloaded to ${tgzPath} (${(tgzBuffer.length / 1024).toFixed(2)} KB)`);
647
+ return {
648
+ tgzPath,
649
+ version: pluginInfo.version,
650
+ pluginInfo
651
+ };
652
+ }
653
+ function cleanupTempFile(tgzPath) {
654
+ try {
655
+ if (fs4.existsSync(tgzPath)) {
656
+ fs4.unlinkSync(tgzPath);
657
+ }
658
+ } catch {
659
+ }
660
+ }
661
+
662
+ // src/commands/action-plugin/install.handler.ts
663
+ async function installOne(nameWithVersion) {
664
+ let tgzPath;
665
+ const { name, version: requestedVersion } = parsePluginName(nameWithVersion);
666
+ try {
667
+ console.log(`[action-plugin] Installing ${name}@${requestedVersion}...`);
668
+ if (isPluginInstalled(name)) {
669
+ const info = getInstalledPluginInfo(name);
670
+ if (info) {
671
+ const latestInfo = await getPluginVersion(name, "latest");
672
+ const latestVersion = latestInfo.version;
673
+ if (info.version === latestVersion) {
674
+ console.log(`[action-plugin] Plugin ${name} is already up to date (version: ${info.version})`);
675
+ return { name, version: info.version, success: true, skipped: true };
676
+ }
677
+ console.log(`[action-plugin] Found newer version: ${latestVersion} (installed: ${info.version})`);
678
+ console.log(`[action-plugin] Updating to ${latestVersion}...`);
679
+ }
680
+ }
681
+ const downloadResult = await downloadPlugin(name, requestedVersion);
682
+ tgzPath = downloadResult.tgzPath;
683
+ npmInstall(tgzPath);
684
+ const installedVersion = getPackageVersion(name) || downloadResult.version;
685
+ const config = readConfig();
686
+ config.plugins[name] = {
687
+ version: installedVersion,
688
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
689
+ };
690
+ writeConfig(config);
691
+ console.log(`[action-plugin] Successfully installed ${name}@${installedVersion}`);
692
+ return { name, version: installedVersion, success: true };
693
+ } catch (error) {
694
+ const message = error instanceof Error ? error.message : String(error);
695
+ console.error(`[action-plugin] Failed to install ${name}: ${message}`);
696
+ return { name, version: requestedVersion, success: false, error: message };
697
+ } finally {
698
+ if (tgzPath) {
699
+ cleanupTempFile(tgzPath);
700
+ }
701
+ }
702
+ }
703
+ async function install(namesWithVersion) {
704
+ if (namesWithVersion.length === 0) {
705
+ console.error("[action-plugin] No plugin names provided");
706
+ process.exit(1);
707
+ }
708
+ const results = [];
709
+ for (const nameWithVersion of namesWithVersion) {
710
+ const result = await installOne(nameWithVersion);
711
+ results.push(result);
712
+ }
713
+ if (namesWithVersion.length > 1) {
714
+ console.log("\n[action-plugin] Installation summary:");
715
+ const successful = results.filter((r) => r.success && !r.skipped);
716
+ const skipped = results.filter((r) => r.skipped);
717
+ const failed = results.filter((r) => !r.success);
718
+ if (successful.length > 0) {
719
+ console.log(` \u2713 Installed: ${successful.map((r) => `${r.name}@${r.version}`).join(", ")}`);
720
+ }
721
+ if (skipped.length > 0) {
722
+ console.log(` \u25CB Skipped (up to date): ${skipped.map((r) => r.name).join(", ")}`);
723
+ }
724
+ if (failed.length > 0) {
725
+ console.log(` \u2717 Failed: ${failed.map((r) => r.name).join(", ")}`);
726
+ }
727
+ }
728
+ if (results.some((r) => !r.success)) {
729
+ process.exit(1);
730
+ }
731
+ }
732
+
733
+ // src/commands/action-plugin/update.handler.ts
734
+ async function updateOne(nameWithVersion) {
735
+ let tgzPath;
736
+ const { name } = parsePluginName(nameWithVersion);
737
+ try {
738
+ console.log(`[action-plugin] Updating ${name}...`);
739
+ if (!isPluginInstalled(name)) {
740
+ console.error(`[action-plugin] Plugin ${name} is not installed`);
741
+ return { name, success: false, notInstalled: true };
742
+ }
743
+ const currentInfo = getInstalledPluginInfo(name);
744
+ const oldVersion = currentInfo?.version || "unknown";
745
+ console.log(`[action-plugin] Current version: ${oldVersion}`);
746
+ const downloadResult = await downloadPlugin(name, "latest");
747
+ tgzPath = downloadResult.tgzPath;
748
+ if (currentInfo && currentInfo.version === downloadResult.version) {
749
+ console.log(`[action-plugin] Plugin ${name} is already up to date (version: ${downloadResult.version})`);
750
+ return { name, oldVersion, newVersion: downloadResult.version, success: true, skipped: true };
751
+ }
752
+ npmInstall(tgzPath);
753
+ const installedVersion = getPackageVersion(name) || downloadResult.version;
754
+ const config = readConfig();
755
+ config.plugins[name] = {
756
+ version: installedVersion,
757
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
758
+ };
759
+ writeConfig(config);
760
+ console.log(`[action-plugin] Successfully updated ${name} to ${installedVersion}`);
761
+ return { name, oldVersion, newVersion: installedVersion, success: true };
762
+ } catch (error) {
763
+ const message = error instanceof Error ? error.message : String(error);
764
+ console.error(`[action-plugin] Failed to update ${name}: ${message}`);
765
+ return { name, success: false, error: message };
766
+ } finally {
767
+ if (tgzPath) {
768
+ cleanupTempFile(tgzPath);
769
+ }
770
+ }
771
+ }
772
+ async function update(names) {
773
+ if (names.length === 0) {
774
+ console.error("[action-plugin] No plugin names provided");
775
+ process.exit(1);
776
+ }
777
+ const results = [];
778
+ for (const name of names) {
779
+ const result = await updateOne(name);
780
+ results.push(result);
781
+ }
782
+ if (names.length > 1) {
783
+ console.log("\n[action-plugin] Update summary:");
784
+ const updated = results.filter((r) => r.success && !r.skipped);
785
+ const skipped = results.filter((r) => r.skipped);
786
+ const notInstalled = results.filter((r) => r.notInstalled);
787
+ const failed = results.filter((r) => !r.success && !r.notInstalled);
788
+ if (updated.length > 0) {
789
+ console.log(` \u2713 Updated: ${updated.map((r) => `${r.name} (${r.oldVersion} \u2192 ${r.newVersion})`).join(", ")}`);
790
+ }
791
+ if (skipped.length > 0) {
792
+ console.log(` \u25CB Skipped (up to date): ${skipped.map((r) => r.name).join(", ")}`);
793
+ }
794
+ if (notInstalled.length > 0) {
795
+ console.log(` \u26A0 Not installed: ${notInstalled.map((r) => r.name).join(", ")}`);
796
+ }
797
+ if (failed.length > 0) {
798
+ console.log(` \u2717 Failed: ${failed.map((r) => r.name).join(", ")}`);
799
+ }
800
+ }
801
+ if (results.some((r) => !r.success && !r.notInstalled)) {
802
+ process.exit(1);
803
+ }
804
+ }
805
+
806
+ // src/commands/action-plugin/remove.handler.ts
807
+ async function remove(nameWithVersion) {
808
+ try {
809
+ const { name } = parsePluginName(nameWithVersion);
810
+ console.log(`[action-plugin] Removing ${name}...`);
811
+ if (!isPluginInstalled(name)) {
812
+ console.error(`[action-plugin] Plugin ${name} is not installed`);
813
+ process.exit(1);
814
+ }
815
+ npmUninstall(name);
816
+ const config = readConfig();
817
+ delete config.plugins[name];
818
+ writeConfig(config);
819
+ console.log(`[action-plugin] Successfully removed ${name}`);
820
+ } catch (error) {
821
+ const message = error instanceof Error ? error.message : String(error);
822
+ console.error(`[action-plugin] Failed to remove: ${message}`);
823
+ process.exit(1);
824
+ }
825
+ }
826
+
827
+ // src/commands/action-plugin/list.handler.ts
828
+ async function list() {
829
+ try {
830
+ const config = readConfig();
831
+ const plugins = Object.entries(config.plugins);
832
+ if (plugins.length === 0) {
833
+ console.log("[action-plugin] No plugins installed");
834
+ console.log('[action-plugin] Use "action-plugin install <name>" to install a plugin');
835
+ return;
836
+ }
837
+ console.log("[action-plugin] Installed plugins:\n");
838
+ const nameWidth = Math.max(30, ...plugins.map(([name]) => name.length + 2));
839
+ const versionWidth = 15;
840
+ const dateWidth = 25;
841
+ console.log(
842
+ " " + "Plugin".padEnd(nameWidth) + "Version".padEnd(versionWidth) + "Installed At"
843
+ );
844
+ console.log(" " + "-".repeat(nameWidth + versionWidth + dateWidth));
845
+ for (const [name, info] of plugins) {
846
+ const installedAt = new Date(info.installedAt).toLocaleString();
847
+ console.log(
848
+ " " + name.padEnd(nameWidth) + info.version.padEnd(versionWidth) + installedAt
849
+ );
850
+ }
851
+ console.log(`
852
+ Total: ${plugins.length} plugin(s)`);
853
+ } catch (error) {
854
+ const message = error instanceof Error ? error.message : String(error);
855
+ console.error(`[action-plugin] Failed to list plugins: ${message}`);
856
+ process.exit(1);
857
+ }
858
+ }
859
+
860
+ // src/commands/action-plugin/index.ts
861
+ var installCommand = {
862
+ name: "install",
863
+ description: "Install action plugin(s) (default: latest version)",
864
+ aliases: ["i"],
865
+ register(program) {
866
+ program.command(this.name).alias("i").description(this.description).argument("<names...>", "Plugin name(s) (e.g., @office/feishu-create-group or @office/feishu-create-group@1.0.0)").action(async (names) => {
867
+ await install(names);
868
+ });
869
+ }
870
+ };
871
+ var updateCommand = {
872
+ name: "update",
873
+ description: "Update action plugin(s) to the latest version",
874
+ aliases: ["up"],
875
+ register(program) {
876
+ program.command(this.name).alias("up").description(this.description).argument("<names...>", "Plugin name(s) (e.g., @office/feishu-create-group)").action(async (names) => {
877
+ await update(names);
878
+ });
879
+ }
880
+ };
881
+ var removeCommand = {
882
+ name: "remove",
883
+ description: "Remove an installed action plugin",
884
+ aliases: ["rm"],
885
+ register(program) {
886
+ program.command(this.name).alias("rm").description(this.description).argument("<name>", "Plugin name (e.g., @office/feishu-create-group)").action(async (name) => {
887
+ await remove(name);
888
+ });
889
+ }
890
+ };
891
+ var listCommand = {
892
+ name: "list",
893
+ description: "List all installed action plugins",
894
+ aliases: ["ls"],
895
+ register(program) {
896
+ program.command(this.name).alias("ls").description(this.description).action(async () => {
897
+ await list();
898
+ });
899
+ }
900
+ };
901
+ var actionPluginCommandGroup = {
902
+ name: "action-plugin",
903
+ description: "Manage action plugins",
904
+ commands: [installCommand, updateCommand, removeCommand, listCommand]
905
+ };
906
+
907
+ // src/commands/capability/utils.ts
908
+ import fs5 from "fs";
909
+ import path5 from "path";
910
+ var CAPABILITIES_DIR = "server/capabilities";
911
+ function getProjectRoot2() {
912
+ return process.cwd();
913
+ }
914
+ function getCapabilitiesDir() {
915
+ return path5.join(getProjectRoot2(), CAPABILITIES_DIR);
916
+ }
917
+ function getCapabilityPath(id) {
918
+ return path5.join(getCapabilitiesDir(), `${id}.json`);
919
+ }
920
+ function getPluginManifestPath(pluginID) {
921
+ return path5.join(getProjectRoot2(), "node_modules", pluginID, "manifest.json");
922
+ }
923
+ function capabilitiesDirExists() {
924
+ return fs5.existsSync(getCapabilitiesDir());
925
+ }
926
+ function listCapabilityIds() {
927
+ const dir = getCapabilitiesDir();
928
+ if (!fs5.existsSync(dir)) {
929
+ return [];
930
+ }
931
+ const files = fs5.readdirSync(dir);
932
+ return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
933
+ }
934
+ function readCapability(id) {
935
+ const filePath = getCapabilityPath(id);
936
+ if (!fs5.existsSync(filePath)) {
937
+ throw new Error(`Capability not found: ${id}`);
938
+ }
939
+ try {
940
+ const content = fs5.readFileSync(filePath, "utf-8");
941
+ return JSON.parse(content);
942
+ } catch (error) {
943
+ if (error instanceof SyntaxError) {
944
+ throw new Error(`Invalid JSON in capability file: ${id}.json`);
945
+ }
946
+ throw error;
947
+ }
948
+ }
949
+ function readAllCapabilities() {
950
+ const ids = listCapabilityIds();
951
+ return ids.map((id) => readCapability(id));
952
+ }
953
+ function readPluginManifest(pluginID) {
954
+ const manifestPath = getPluginManifestPath(pluginID);
955
+ if (!fs5.existsSync(manifestPath)) {
956
+ throw new Error(`Plugin not installed: ${pluginID} (manifest.json not found)`);
957
+ }
958
+ try {
959
+ const content = fs5.readFileSync(manifestPath, "utf-8");
960
+ return JSON.parse(content);
961
+ } catch (error) {
962
+ if (error instanceof SyntaxError) {
963
+ throw new Error(`Invalid JSON in plugin manifest: ${pluginID}/manifest.json`);
964
+ }
965
+ throw error;
966
+ }
967
+ }
968
+ function isDynamicSchema(schema) {
969
+ return schema !== void 0 && typeof schema === "object" && "dynamic" in schema && schema.dynamic === true;
970
+ }
971
+ function hasValidParamsSchema(paramsSchema) {
972
+ return paramsSchema !== void 0 && Object.keys(paramsSchema).length > 0;
973
+ }
974
+ async function loadPlugin(pluginID) {
975
+ try {
976
+ const pluginPackage = (await import(pluginID)).default;
977
+ if (!pluginPackage || typeof pluginPackage.create !== "function") {
978
+ throw new Error(`Plugin ${pluginID} does not export a valid create function`);
979
+ }
980
+ return pluginPackage;
981
+ } catch (error) {
982
+ if (error.code === "MODULE_NOT_FOUND") {
983
+ throw new Error(`Plugin not installed: ${pluginID}`);
984
+ }
985
+ throw new Error(
986
+ `Failed to load plugin ${pluginID}: ${error instanceof Error ? error.message : String(error)}`
987
+ );
988
+ }
989
+ }
990
+ async function zodToJsonSchema(zodSchema) {
991
+ try {
992
+ const { zodToJsonSchema: convert } = await import("zod-to-json-schema");
993
+ return convert(zodSchema);
994
+ } catch {
995
+ throw new Error("Failed to convert Zod schema to JSON Schema. Make sure zod-to-json-schema is installed.");
996
+ }
997
+ }
998
+ async function hydrateCapability(capability) {
999
+ try {
1000
+ const manifest = readPluginManifest(capability.pluginID);
1001
+ if (!manifest.actions || manifest.actions.length === 0) {
1002
+ throw new Error(`Plugin ${capability.pluginID} has no actions defined`);
1003
+ }
1004
+ const hasDynamicSchema = manifest.actions.some(
1005
+ (action) => isDynamicSchema(action.inputSchema) || isDynamicSchema(action.outputSchema)
1006
+ );
1007
+ let pluginInstance = null;
1008
+ if (hasDynamicSchema) {
1009
+ const plugin = await loadPlugin(capability.pluginID);
1010
+ pluginInstance = plugin.create(capability.formValue || {});
1011
+ }
1012
+ const actions = [];
1013
+ for (let index = 0; index < manifest.actions.length; index++) {
1014
+ const manifestAction = manifest.actions[index];
1015
+ let inputSchema;
1016
+ if (index === 0 && hasValidParamsSchema(capability.paramsSchema)) {
1017
+ inputSchema = capability.paramsSchema;
1018
+ } else if (isDynamicSchema(manifestAction.inputSchema)) {
1019
+ if (!pluginInstance) {
1020
+ throw new Error(`Plugin instance not available for dynamic schema`);
1021
+ }
1022
+ const zodSchema = pluginInstance.getInputSchema(manifestAction.key);
1023
+ if (!zodSchema) {
1024
+ throw new Error(`Failed to get input schema for action: ${manifestAction.key}`);
1025
+ }
1026
+ inputSchema = await zodToJsonSchema(zodSchema);
1027
+ } else {
1028
+ inputSchema = manifestAction.inputSchema;
1029
+ }
1030
+ let outputSchema;
1031
+ if (isDynamicSchema(manifestAction.outputSchema)) {
1032
+ if (!pluginInstance) {
1033
+ throw new Error(`Plugin instance not available for dynamic schema`);
1034
+ }
1035
+ const zodSchema = pluginInstance.getOutputSchema(manifestAction.key, {});
1036
+ if (!zodSchema) {
1037
+ throw new Error(`Failed to get output schema for action: ${manifestAction.key}`);
1038
+ }
1039
+ outputSchema = await zodToJsonSchema(zodSchema);
1040
+ } else {
1041
+ outputSchema = manifestAction.outputSchema;
1042
+ }
1043
+ actions.push({
1044
+ key: manifestAction.key,
1045
+ inputSchema,
1046
+ outputSchema,
1047
+ outputMode: manifestAction.outputMode || ""
1048
+ });
36
1049
  }
37
- });
38
- // sync 命令
39
- cli
40
- .command('sync', 'Sync template files (scripts, configs) to user project')
41
- .option('--disable-gen-openapi', 'Disable generating openapi.ts')
42
- .example('fullstack-cli sync')
43
- .action(async (options) => {
1050
+ return {
1051
+ id: capability.id,
1052
+ pluginID: capability.pluginID,
1053
+ pluginVersion: capability.pluginVersion,
1054
+ name: capability.name,
1055
+ description: capability.description,
1056
+ actions,
1057
+ createdAt: capability.createdAt,
1058
+ updatedAt: capability.updatedAt
1059
+ };
1060
+ } catch (error) {
1061
+ const message = error instanceof Error ? error.message : String(error);
1062
+ return {
1063
+ ...capability,
1064
+ _hydrateError: message
1065
+ };
1066
+ }
1067
+ }
1068
+ function formatJsonOutput(data, compact = false) {
1069
+ return compact ? JSON.stringify(data) : JSON.stringify(data, null, 2);
1070
+ }
1071
+ function printJson(data, compact = false) {
1072
+ console.log(formatJsonOutput(data, compact));
1073
+ }
1074
+ function logError(message) {
1075
+ console.error(`[capability] Error: ${message}`);
1076
+ }
1077
+
1078
+ // src/commands/capability/list.handler.ts
1079
+ async function list2(options) {
1080
+ try {
1081
+ if (!capabilitiesDirExists()) {
1082
+ logError("server/capabilities directory not found");
1083
+ process.exit(1);
1084
+ }
1085
+ if (options.id) {
1086
+ await listSingle(options.id, options.summary);
1087
+ } else {
1088
+ await listAll(options.summary);
1089
+ }
1090
+ } catch (error) {
1091
+ const message = error instanceof Error ? error.message : String(error);
1092
+ logError(message);
1093
+ process.exit(1);
1094
+ }
1095
+ }
1096
+ async function listSingle(id, summary) {
1097
+ const capability = readCapability(id);
1098
+ if (summary) {
1099
+ printJson(capability);
1100
+ } else {
1101
+ const hydrated = await hydrateCapability(capability);
1102
+ printJson(hydrated);
1103
+ }
1104
+ }
1105
+ async function listAll(summary) {
1106
+ const capabilities = readAllCapabilities();
1107
+ if (capabilities.length === 0) {
1108
+ printJson([]);
1109
+ return;
1110
+ }
1111
+ if (summary) {
1112
+ printJson(capabilities);
1113
+ } else {
1114
+ const hydrated = [];
1115
+ for (const capability of capabilities) {
1116
+ const result = await hydrateCapability(capability);
1117
+ hydrated.push(result);
1118
+ }
1119
+ printJson(hydrated);
1120
+ }
1121
+ }
1122
+
1123
+ // src/commands/capability/migration.handler.ts
1124
+ import fs12 from "fs";
1125
+
1126
+ // src/commands/capability/migration/mapping.ts
1127
+ var DEFAULT_MAPPING = {
1128
+ // 飞书相关
1129
+ "official_feishu/create_group": "@official_feishu/create_group",
1130
+ "official_feishu/send_message": "@official_feishu/send_message",
1131
+ "official_feishu/send_card": "@official_feishu/send_card",
1132
+ // AI 相关
1133
+ "ai/generate_text": "@official_ai/text_generation",
1134
+ "ai/chat_completion": "@official_ai/chat_completion"
1135
+ // 其他
1136
+ // 根据实际需要添加更多映射
1137
+ };
1138
+ function mergeMapping(customMapping) {
1139
+ if (!customMapping) {
1140
+ return { ...DEFAULT_MAPPING };
1141
+ }
1142
+ return { ...DEFAULT_MAPPING, ...customMapping };
1143
+ }
1144
+ function getPluginID(sourceActionID, mapping) {
1145
+ const pluginID = mapping[sourceActionID];
1146
+ if (!pluginID) {
1147
+ throw new Error(
1148
+ `Unknown sourceActionID: "${sourceActionID}". Please provide mapping via --mapping option or add to default mapping.`
1149
+ );
1150
+ }
1151
+ return pluginID;
1152
+ }
1153
+
1154
+ // src/commands/capability/migration/json-migrator/index.ts
1155
+ import fs7 from "fs";
1156
+ import path7 from "path";
1157
+
1158
+ // src/commands/capability/migration/json-migrator/detector.ts
1159
+ import fs6 from "fs";
1160
+ import path6 from "path";
1161
+ function detectJsonMigration() {
1162
+ const capabilitiesDir = getCapabilitiesDir();
1163
+ const oldFilePath = path6.join(capabilitiesDir, "capabilities.json");
1164
+ if (!fs6.existsSync(capabilitiesDir)) {
1165
+ return {
1166
+ needsMigration: false,
1167
+ reason: "capabilities directory does not exist"
1168
+ };
1169
+ }
1170
+ if (!fs6.existsSync(oldFilePath)) {
1171
+ return {
1172
+ needsMigration: false,
1173
+ reason: "capabilities.json not found (may already be migrated)"
1174
+ };
1175
+ }
1176
+ try {
1177
+ const content = fs6.readFileSync(oldFilePath, "utf-8");
1178
+ const parsed = JSON.parse(content);
1179
+ if (!Array.isArray(parsed)) {
1180
+ return {
1181
+ needsMigration: false,
1182
+ reason: "capabilities.json is not array format"
1183
+ };
1184
+ }
1185
+ if (parsed.length === 0) {
1186
+ return {
1187
+ needsMigration: false,
1188
+ reason: "capabilities.json is empty array"
1189
+ };
1190
+ }
1191
+ const firstItem = parsed[0];
1192
+ if (!firstItem.sourceActionID) {
1193
+ return {
1194
+ needsMigration: false,
1195
+ reason: "capabilities.json does not contain old format (missing sourceActionID)"
1196
+ };
1197
+ }
1198
+ return {
1199
+ needsMigration: true,
1200
+ oldCapabilities: parsed,
1201
+ oldFilePath
1202
+ };
1203
+ } catch (error) {
1204
+ if (error instanceof SyntaxError) {
1205
+ return {
1206
+ needsMigration: false,
1207
+ reason: "capabilities.json contains invalid JSON"
1208
+ };
1209
+ }
1210
+ throw error;
1211
+ }
1212
+ }
1213
+
1214
+ // src/commands/capability/migration/json-migrator/transformer.ts
1215
+ function transformCapability(old, mapping) {
1216
+ const pluginID = getPluginID(old.sourceActionID, mapping);
1217
+ return {
1218
+ id: old.id,
1219
+ pluginID,
1220
+ pluginVersion: "latest",
1221
+ name: old.name,
1222
+ description: old.desc,
1223
+ paramsSchema: old.inputSchema,
1224
+ formValue: old.actionInput,
1225
+ createdAt: old.createdAt,
1226
+ updatedAt: old.updatedAt
1227
+ };
1228
+ }
1229
+ function transformCapabilities(oldCapabilities, mapping) {
1230
+ return oldCapabilities.map((old) => transformCapability(old, mapping));
1231
+ }
1232
+
1233
+ // src/commands/capability/migration/json-migrator/index.ts
1234
+ async function migrateJsonFiles(options) {
1235
+ const detection = detectJsonMigration();
1236
+ if (!detection.needsMigration) {
1237
+ return {
1238
+ success: true,
1239
+ skipped: true,
1240
+ reason: detection.reason,
1241
+ count: 0,
1242
+ capabilities: []
1243
+ };
1244
+ }
1245
+ const { oldCapabilities, oldFilePath } = detection;
1246
+ if (!oldCapabilities || !oldFilePath) {
1247
+ return {
1248
+ success: false,
1249
+ skipped: false,
1250
+ reason: "Detection error: missing data",
1251
+ count: 0,
1252
+ capabilities: []
1253
+ };
1254
+ }
1255
+ let newCapabilities;
1256
+ try {
1257
+ newCapabilities = transformCapabilities(oldCapabilities, options.mapping);
1258
+ } catch (error) {
1259
+ return {
1260
+ success: false,
1261
+ skipped: false,
1262
+ reason: error instanceof Error ? error.message : String(error),
1263
+ count: 0,
1264
+ capabilities: []
1265
+ };
1266
+ }
1267
+ if (options.dryRun) {
1268
+ console.log(` Would create ${newCapabilities.length} capability files:`);
1269
+ for (const cap of newCapabilities) {
1270
+ console.log(` - ${cap.id}.json (pluginID: ${cap.pluginID})`);
1271
+ }
1272
+ return {
1273
+ success: true,
1274
+ skipped: false,
1275
+ count: newCapabilities.length,
1276
+ capabilities: newCapabilities
1277
+ };
1278
+ }
1279
+ const capabilitiesDir = getCapabilitiesDir();
1280
+ for (const cap of newCapabilities) {
1281
+ const filePath = path7.join(capabilitiesDir, `${cap.id}.json`);
1282
+ const content = JSON.stringify(cap, null, 2);
1283
+ fs7.writeFileSync(filePath, content, "utf-8");
1284
+ console.log(` \u2713 Created: ${cap.id}.json`);
1285
+ }
1286
+ const backupPath = oldFilePath + ".backup";
1287
+ fs7.renameSync(oldFilePath, backupPath);
1288
+ return {
1289
+ success: true,
1290
+ skipped: false,
1291
+ count: newCapabilities.length,
1292
+ capabilities: newCapabilities,
1293
+ backupPath
1294
+ };
1295
+ }
1296
+
1297
+ // src/commands/capability/migration/plugin-installer/detector.ts
1298
+ import fs8 from "fs";
1299
+ function isPluginInstalled2(pluginID) {
1300
+ const manifestPath = getPluginManifestPath(pluginID);
1301
+ return fs8.existsSync(manifestPath);
1302
+ }
1303
+ function detectPluginsToInstall(capabilities) {
1304
+ const pluginIDs = /* @__PURE__ */ new Set();
1305
+ for (const cap of capabilities) {
1306
+ pluginIDs.add(cap.pluginID);
1307
+ }
1308
+ const toInstall = [];
1309
+ const alreadyInstalled = [];
1310
+ for (const pluginID of pluginIDs) {
1311
+ if (isPluginInstalled2(pluginID)) {
1312
+ alreadyInstalled.push(pluginID);
1313
+ } else {
1314
+ toInstall.push(pluginID);
1315
+ }
1316
+ }
1317
+ return { toInstall, alreadyInstalled };
1318
+ }
1319
+
1320
+ // src/commands/capability/migration/plugin-installer/index.ts
1321
+ async function installPlugins(capabilities, options) {
1322
+ const detection = detectPluginsToInstall(capabilities);
1323
+ const result = {
1324
+ installed: [],
1325
+ alreadyInstalled: detection.alreadyInstalled,
1326
+ failed: []
1327
+ };
1328
+ if (detection.toInstall.length === 0) {
1329
+ return result;
1330
+ }
1331
+ if (options.dryRun) {
1332
+ console.log(` Would install ${detection.toInstall.length} plugins:`);
1333
+ for (const pluginID of detection.toInstall) {
1334
+ console.log(` - ${pluginID}`);
1335
+ }
1336
+ result.installed = detection.toInstall;
1337
+ return result;
1338
+ }
1339
+ console.log(` \u2B07 Installing ${detection.toInstall.length} plugins...`);
1340
+ for (const pluginID of detection.toInstall) {
1341
+ console.log(` - ${pluginID}`);
1342
+ }
1343
+ try {
1344
+ const originalExit = process.exit;
1345
+ let exitCalled = false;
1346
+ process.exit = ((code) => {
1347
+ exitCalled = true;
1348
+ if (code !== 0) {
1349
+ throw new Error(`Plugin installation failed with exit code ${code}`);
1350
+ }
1351
+ });
44
1352
  try {
45
- await runSync(options);
1353
+ await install(detection.toInstall);
1354
+ result.installed = detection.toInstall;
1355
+ } catch (error) {
1356
+ for (const pluginID of detection.toInstall) {
1357
+ result.failed.push({
1358
+ pluginID,
1359
+ error: error instanceof Error ? error.message : String(error)
1360
+ });
1361
+ }
1362
+ } finally {
1363
+ process.exit = originalExit;
1364
+ }
1365
+ } catch (error) {
1366
+ for (const pluginID of detection.toInstall) {
1367
+ result.failed.push({
1368
+ pluginID,
1369
+ error: error instanceof Error ? error.message : String(error)
1370
+ });
1371
+ }
1372
+ }
1373
+ return result;
1374
+ }
1375
+
1376
+ // src/commands/capability/migration/code-migrator/index.ts
1377
+ import fs10 from "fs";
1378
+ import path9 from "path";
1379
+ import * as ts4 from "typescript";
1380
+
1381
+ // src/commands/capability/migration/code-migrator/scanner.ts
1382
+ import fs9 from "fs";
1383
+ import path8 from "path";
1384
+ var EXCLUDED_DIRS = [
1385
+ "node_modules",
1386
+ "dist",
1387
+ "build",
1388
+ ".git",
1389
+ "__test__",
1390
+ "__tests__"
1391
+ ];
1392
+ var EXCLUDED_PATTERNS = [
1393
+ /\.spec\.ts$/,
1394
+ /\.test\.ts$/,
1395
+ /\.d\.ts$/
1396
+ ];
1397
+ function scanDirectory(dir, files = []) {
1398
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
1399
+ for (const entry of entries) {
1400
+ const fullPath = path8.join(dir, entry.name);
1401
+ if (entry.isDirectory()) {
1402
+ if (EXCLUDED_DIRS.includes(entry.name)) {
1403
+ continue;
1404
+ }
1405
+ scanDirectory(fullPath, files);
1406
+ } else if (entry.isFile() && entry.name.endsWith(".ts")) {
1407
+ const shouldExclude = EXCLUDED_PATTERNS.some((pattern) => pattern.test(entry.name));
1408
+ if (!shouldExclude) {
1409
+ files.push(fullPath);
1410
+ }
1411
+ }
1412
+ }
1413
+ return files;
1414
+ }
1415
+ function scanServerFiles() {
1416
+ const serverDir = path8.join(getProjectRoot2(), "server");
1417
+ if (!fs9.existsSync(serverDir)) {
1418
+ return [];
1419
+ }
1420
+ return scanDirectory(serverDir);
1421
+ }
1422
+ function hasCapabilityImport(filePath) {
1423
+ const content = fs9.readFileSync(filePath, "utf-8");
1424
+ return /import\s+.*from\s+['"][^'"]*capabilities[^'"]*['"]/.test(content);
1425
+ }
1426
+ function scanFilesToMigrate() {
1427
+ const allFiles = scanServerFiles();
1428
+ return allFiles.filter(hasCapabilityImport);
1429
+ }
1430
+
1431
+ // src/commands/capability/migration/code-migrator/analyzers/import-analyzer.ts
1432
+ import * as ts from "typescript";
1433
+ function extractCapabilityId(importPath) {
1434
+ const match = importPath.match(/capabilities\/([^/]+)$/);
1435
+ if (match) {
1436
+ return match[1];
1437
+ }
1438
+ return null;
1439
+ }
1440
+ function isCapabilityImport(importPath) {
1441
+ return importPath.includes("capabilities/") || importPath.includes("capabilities\\");
1442
+ }
1443
+ function analyzeImports(sourceFile) {
1444
+ const imports = [];
1445
+ function visit(node) {
1446
+ if (ts.isImportDeclaration(node)) {
1447
+ const moduleSpecifier = node.moduleSpecifier;
1448
+ if (ts.isStringLiteral(moduleSpecifier)) {
1449
+ const importPath = moduleSpecifier.text;
1450
+ if (isCapabilityImport(importPath)) {
1451
+ const capabilityId = extractCapabilityId(importPath);
1452
+ if (capabilityId && node.importClause) {
1453
+ const importClause = node.importClause;
1454
+ let importName = null;
1455
+ if (importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) {
1456
+ for (const element of importClause.namedBindings.elements) {
1457
+ importName = element.name.text;
1458
+ imports.push({
1459
+ importName,
1460
+ capabilityId,
1461
+ start: node.getStart(),
1462
+ end: node.getEnd(),
1463
+ text: node.getText()
1464
+ });
1465
+ }
1466
+ } else if (importClause.namedBindings && ts.isNamespaceImport(importClause.namedBindings)) {
1467
+ importName = importClause.namedBindings.name.text;
1468
+ imports.push({
1469
+ importName,
1470
+ capabilityId,
1471
+ start: node.getStart(),
1472
+ end: node.getEnd(),
1473
+ text: node.getText()
1474
+ });
1475
+ } else if (importClause.name) {
1476
+ importName = importClause.name.text;
1477
+ imports.push({
1478
+ importName,
1479
+ capabilityId,
1480
+ start: node.getStart(),
1481
+ end: node.getEnd(),
1482
+ text: node.getText()
1483
+ });
1484
+ }
1485
+ }
1486
+ }
1487
+ }
1488
+ }
1489
+ ts.forEachChild(node, visit);
1490
+ }
1491
+ visit(sourceFile);
1492
+ return imports;
1493
+ }
1494
+
1495
+ // src/commands/capability/migration/code-migrator/analyzers/call-site-analyzer.ts
1496
+ import * as ts2 from "typescript";
1497
+ function analyzeCallSites(sourceFile, imports) {
1498
+ const callSites = [];
1499
+ const importMap = /* @__PURE__ */ new Map();
1500
+ for (const imp of imports) {
1501
+ importMap.set(imp.importName, imp.capabilityId);
1502
+ }
1503
+ function visit(node) {
1504
+ if (ts2.isCallExpression(node)) {
1505
+ const expression = node.expression;
1506
+ if (ts2.isIdentifier(expression)) {
1507
+ const functionName = expression.text;
1508
+ const capabilityId = importMap.get(functionName);
1509
+ if (capabilityId) {
1510
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1511
+ callSites.push({
1512
+ functionName,
1513
+ capabilityId,
1514
+ start: node.getStart(),
1515
+ end: node.getEnd(),
1516
+ line: line + 1,
1517
+ // 转为 1-based
1518
+ text: node.getText()
1519
+ });
1520
+ }
1521
+ } else if (ts2.isPropertyAccessExpression(expression)) {
1522
+ const objectName = expression.expression;
1523
+ if (ts2.isIdentifier(objectName)) {
1524
+ const capabilityId = importMap.get(objectName.text);
1525
+ if (capabilityId) {
1526
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1527
+ callSites.push({
1528
+ functionName: `${objectName.text}.${expression.name.text}`,
1529
+ capabilityId,
1530
+ start: node.getStart(),
1531
+ end: node.getEnd(),
1532
+ line: line + 1,
1533
+ text: node.getText()
1534
+ });
1535
+ }
1536
+ }
1537
+ }
1538
+ }
1539
+ ts2.forEachChild(node, visit);
1540
+ }
1541
+ visit(sourceFile);
1542
+ return callSites;
1543
+ }
1544
+
1545
+ // src/commands/capability/migration/code-migrator/analyzers/class-analyzer.ts
1546
+ import * as ts3 from "typescript";
1547
+ function hasDecorator(node, decoratorName) {
1548
+ const decorators = ts3.getDecorators(node);
1549
+ if (!decorators) {
1550
+ return false;
1551
+ }
1552
+ return decorators.some((decorator) => {
1553
+ const expression = decorator.expression;
1554
+ if (ts3.isIdentifier(expression)) {
1555
+ return expression.text === decoratorName;
1556
+ }
1557
+ if (ts3.isCallExpression(expression) && ts3.isIdentifier(expression.expression)) {
1558
+ return expression.expression.text === decoratorName;
1559
+ }
1560
+ return false;
1561
+ });
1562
+ }
1563
+ function analyzeClass(sourceFile) {
1564
+ let classInfo;
1565
+ function visit(node) {
1566
+ if (ts3.isClassDeclaration(node) && node.name) {
1567
+ const isInjectable = hasDecorator(node, "Injectable");
1568
+ const isController = hasDecorator(node, "Controller");
1569
+ if (classInfo && classInfo.isInjectable && !isInjectable && !isController) {
1570
+ return;
1571
+ }
1572
+ const info = {
1573
+ name: node.name.text,
1574
+ isInjectable,
1575
+ isController,
1576
+ constructorParamCount: 0
1577
+ };
1578
+ for (const member of node.members) {
1579
+ if (ts3.isConstructorDeclaration(member)) {
1580
+ info.constructorParamCount = member.parameters.length;
1581
+ info.constructorStart = member.getStart();
1582
+ info.constructorEnd = member.getEnd();
1583
+ if (member.parameters.length > 0) {
1584
+ const lastParam = member.parameters[member.parameters.length - 1];
1585
+ info.constructorParamsEnd = lastParam.getEnd();
1586
+ } else {
1587
+ const constructorText = member.getText();
1588
+ const parenIndex = constructorText.indexOf("(");
1589
+ if (parenIndex !== -1) {
1590
+ info.constructorParamsEnd = member.getStart() + parenIndex + 1;
1591
+ }
1592
+ }
1593
+ break;
1594
+ }
1595
+ }
1596
+ if (isInjectable || isController || !classInfo) {
1597
+ classInfo = info;
1598
+ }
1599
+ }
1600
+ ts3.forEachChild(node, visit);
1601
+ }
1602
+ visit(sourceFile);
1603
+ return classInfo;
1604
+ }
1605
+ function canAutoMigrate(classInfo) {
1606
+ if (!classInfo) {
1607
+ return {
1608
+ canMigrate: false,
1609
+ reason: "No class found in file"
1610
+ };
1611
+ }
1612
+ if (!classInfo.isInjectable && !classInfo.isController) {
1613
+ return {
1614
+ canMigrate: false,
1615
+ reason: `Class "${classInfo.name}" is not @Injectable or @Controller`
1616
+ };
1617
+ }
1618
+ return { canMigrate: true };
1619
+ }
1620
+
1621
+ // src/commands/capability/migration/code-migrator/transformers/import-transformer.ts
1622
+ var CAPABILITY_SERVICE_IMPORT = `import { CapabilityService } from '@lark-apaas/nestjs-core';`;
1623
+ function hasCapabilityServiceImport(content) {
1624
+ return /import\s+.*CapabilityService.*from\s+['"]@lark-apaas\/nestjs-core['"]/.test(content);
1625
+ }
1626
+ function findImportInsertPosition(content) {
1627
+ const importRegex = /^import\s+.*?from\s+['"][^'"]+['"];?\s*$/gm;
1628
+ let lastMatch = null;
1629
+ let match;
1630
+ while ((match = importRegex.exec(content)) !== null) {
1631
+ lastMatch = match;
1632
+ }
1633
+ if (lastMatch) {
1634
+ return lastMatch.index + lastMatch[0].length;
1635
+ }
1636
+ return 0;
1637
+ }
1638
+ function transformImports(content, imports) {
1639
+ const sortedImports = [...imports].sort((a, b) => b.start - a.start);
1640
+ let result = content;
1641
+ for (const imp of sortedImports) {
1642
+ const before = result.substring(0, imp.start);
1643
+ const after = result.substring(imp.end);
1644
+ const trimmedAfter = after.replace(/^\r?\n/, "");
1645
+ result = before + trimmedAfter;
1646
+ }
1647
+ let importAdded = false;
1648
+ if (!hasCapabilityServiceImport(result)) {
1649
+ const insertPos = findImportInsertPosition(result);
1650
+ if (insertPos > 0) {
1651
+ result = result.substring(0, insertPos) + "\n" + CAPABILITY_SERVICE_IMPORT + result.substring(insertPos);
1652
+ } else {
1653
+ result = CAPABILITY_SERVICE_IMPORT + "\n" + result;
1654
+ }
1655
+ importAdded = true;
1656
+ }
1657
+ return { content: result, importAdded };
1658
+ }
1659
+
1660
+ // src/commands/capability/migration/code-migrator/transformers/injection-transformer.ts
1661
+ var INJECTION_PARAM = "private readonly capabilityService: CapabilityService";
1662
+ function hasCapabilityServiceInjection(content) {
1663
+ return /capabilityService\s*:\s*CapabilityService/.test(content);
1664
+ }
1665
+ function addInjection(content, classInfo) {
1666
+ if (hasCapabilityServiceInjection(content)) {
1667
+ return { content, injectionAdded: false };
1668
+ }
1669
+ if (classInfo.constructorParamsEnd === void 0) {
1670
+ return { content, injectionAdded: false };
1671
+ }
1672
+ let result = content;
1673
+ const insertPos = classInfo.constructorParamsEnd;
1674
+ if (classInfo.constructorParamCount > 0) {
1675
+ const afterParams = content.substring(insertPos);
1676
+ const beforeParams = content.substring(0, insertPos);
1677
+ result = beforeParams + ",\n " + INJECTION_PARAM + afterParams;
1678
+ } else {
1679
+ const constructorMatch = content.match(/constructor\s*\(\s*/);
1680
+ if (constructorMatch && constructorMatch.index !== void 0) {
1681
+ const paramStart = constructorMatch.index + constructorMatch[0].length;
1682
+ const before = content.substring(0, paramStart);
1683
+ const after = content.substring(paramStart);
1684
+ result = before + INJECTION_PARAM + after;
1685
+ }
1686
+ }
1687
+ return { content: result, injectionAdded: result !== content };
1688
+ }
1689
+
1690
+ // src/commands/capability/migration/code-migrator/transformers/call-site-transformer.ts
1691
+ function generateNewCall(capabilityId, originalCall) {
1692
+ const parenIndex = originalCall.indexOf("(");
1693
+ if (parenIndex === -1) {
1694
+ return originalCall;
1695
+ }
1696
+ const argsStart = parenIndex + 1;
1697
+ const argsEnd = originalCall.lastIndexOf(")");
1698
+ if (argsEnd === -1 || argsEnd <= argsStart) {
1699
+ return `this.capabilityService.load('${capabilityId}').call('run', {})`;
1700
+ }
1701
+ const args = originalCall.substring(argsStart, argsEnd).trim();
1702
+ if (!args) {
1703
+ return `this.capabilityService.load('${capabilityId}').call('run', {})`;
1704
+ }
1705
+ return `this.capabilityService.load('${capabilityId}').call('run', ${args})`;
1706
+ }
1707
+ function transformCallSites(content, callSites) {
1708
+ const sortedCallSites = [...callSites].sort((a, b) => b.start - a.start);
1709
+ let result = content;
1710
+ let replacedCount = 0;
1711
+ for (const callSite of sortedCallSites) {
1712
+ const newCall = generateNewCall(callSite.capabilityId, callSite.text);
1713
+ const before = result.substring(0, callSite.start);
1714
+ const after = result.substring(callSite.end);
1715
+ result = before + newCall + after;
1716
+ replacedCount++;
1717
+ }
1718
+ return { content: result, replacedCount };
1719
+ }
1720
+
1721
+ // src/commands/capability/migration/code-migrator/index.ts
1722
+ function analyzeFile(filePath) {
1723
+ const content = fs10.readFileSync(filePath, "utf-8");
1724
+ const sourceFile = ts4.createSourceFile(
1725
+ filePath,
1726
+ content,
1727
+ ts4.ScriptTarget.Latest,
1728
+ true
1729
+ );
1730
+ const imports = analyzeImports(sourceFile);
1731
+ const callSites = analyzeCallSites(sourceFile, imports);
1732
+ const classInfo = analyzeClass(sourceFile);
1733
+ const { canMigrate, reason } = canAutoMigrate(classInfo);
1734
+ const relativePath = path9.relative(getProjectRoot2(), filePath);
1735
+ return {
1736
+ filePath: relativePath,
1737
+ imports,
1738
+ callSites,
1739
+ classInfo,
1740
+ canAutoMigrate: canMigrate,
1741
+ manualMigrationReason: reason
1742
+ };
1743
+ }
1744
+ function migrateFile(analysis, dryRun) {
1745
+ const absolutePath = path9.join(getProjectRoot2(), analysis.filePath);
1746
+ if (!analysis.canAutoMigrate) {
1747
+ return {
1748
+ filePath: analysis.filePath,
1749
+ success: false,
1750
+ importsRemoved: 0,
1751
+ callSitesReplaced: 0,
1752
+ injectionAdded: false,
1753
+ error: analysis.manualMigrationReason
1754
+ };
1755
+ }
1756
+ try {
1757
+ let content = fs10.readFileSync(absolutePath, "utf-8");
1758
+ const callResult = transformCallSites(content, analysis.callSites);
1759
+ content = callResult.content;
1760
+ const sourceFile = ts4.createSourceFile(
1761
+ analysis.filePath,
1762
+ content,
1763
+ ts4.ScriptTarget.Latest,
1764
+ true
1765
+ );
1766
+ const newImports = analyzeImports(sourceFile);
1767
+ const importResult = transformImports(content, newImports);
1768
+ content = importResult.content;
1769
+ let injectionAdded = false;
1770
+ if (analysis.classInfo) {
1771
+ const newSourceFile = ts4.createSourceFile(
1772
+ analysis.filePath,
1773
+ content,
1774
+ ts4.ScriptTarget.Latest,
1775
+ true
1776
+ );
1777
+ const newClassInfo = analyzeClass(newSourceFile);
1778
+ if (newClassInfo) {
1779
+ const injectionResult = addInjection(content, newClassInfo);
1780
+ content = injectionResult.content;
1781
+ injectionAdded = injectionResult.injectionAdded;
1782
+ }
46
1783
  }
47
- catch (error) {
48
- console.error(`Failed to execute command:`, error.message);
49
- console.error(error.stack);
1784
+ if (!dryRun) {
1785
+ fs10.writeFileSync(absolutePath, content, "utf-8");
1786
+ }
1787
+ return {
1788
+ filePath: analysis.filePath,
1789
+ success: true,
1790
+ importsRemoved: analysis.imports.length,
1791
+ callSitesReplaced: callResult.replacedCount,
1792
+ injectionAdded
1793
+ };
1794
+ } catch (error) {
1795
+ return {
1796
+ filePath: analysis.filePath,
1797
+ success: false,
1798
+ importsRemoved: 0,
1799
+ callSitesReplaced: 0,
1800
+ injectionAdded: false,
1801
+ error: error instanceof Error ? error.message : String(error)
1802
+ };
1803
+ }
1804
+ }
1805
+ async function migrateCode(options) {
1806
+ const result = {
1807
+ autoMigrated: [],
1808
+ manualRequired: []
1809
+ };
1810
+ const filesToMigrate = scanFilesToMigrate();
1811
+ if (filesToMigrate.length === 0) {
1812
+ console.log(" No files need code migration.\n");
1813
+ return result;
1814
+ }
1815
+ const analyses = [];
1816
+ for (const filePath of filesToMigrate) {
1817
+ try {
1818
+ const analysis = analyzeFile(filePath);
1819
+ analyses.push(analysis);
1820
+ } catch (error) {
1821
+ console.error(` \u2717 Failed to analyze ${filePath}: ${error}`);
1822
+ }
1823
+ }
1824
+ const autoMigratable = analyses.filter((a) => a.canAutoMigrate);
1825
+ const manualRequired = analyses.filter((a) => !a.canAutoMigrate);
1826
+ for (const analysis of manualRequired) {
1827
+ const capabilityIds = [...new Set(analysis.imports.map((i) => i.capabilityId))];
1828
+ result.manualRequired.push({
1829
+ filePath: analysis.filePath,
1830
+ reason: analysis.manualMigrationReason || "Unknown reason",
1831
+ capabilities: capabilityIds,
1832
+ suggestion: getSuggestion(analysis)
1833
+ });
1834
+ }
1835
+ for (const analysis of autoMigratable) {
1836
+ const migrationResult = migrateFile(analysis, options.dryRun);
1837
+ result.autoMigrated.push(migrationResult);
1838
+ }
1839
+ return result;
1840
+ }
1841
+ function getSuggestion(analysis) {
1842
+ if (!analysis.classInfo) {
1843
+ return "Consider moving capability usage to an @Injectable service";
1844
+ }
1845
+ if (!analysis.classInfo.isInjectable && !analysis.classInfo.isController) {
1846
+ return `Add @Injectable() decorator to class "${analysis.classInfo.name}" or move capability usage to an injectable service`;
1847
+ }
1848
+ return "Manual review required";
1849
+ }
1850
+
1851
+ // src/commands/capability/migration/report-generator.ts
1852
+ import fs11 from "fs";
1853
+ import path10 from "path";
1854
+ var REPORT_FILE = "capability-migration-report.md";
1855
+ function printSummary(result) {
1856
+ const { jsonMigration, pluginInstallation, codeMigration } = result;
1857
+ console.log("\u{1F4CA} Migration Summary");
1858
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1859
+ if (!jsonMigration.skipped) {
1860
+ console.log(` JSON files migrated: ${jsonMigration.count}`);
1861
+ }
1862
+ const totalPlugins = pluginInstallation.installed.length + pluginInstallation.alreadyInstalled.length;
1863
+ if (totalPlugins > 0) {
1864
+ console.log(` Plugins installed: ${pluginInstallation.installed.length}`);
1865
+ console.log(` Plugins already present: ${pluginInstallation.alreadyInstalled.length}`);
1866
+ if (pluginInstallation.failed.length > 0) {
1867
+ console.log(` Plugins failed: ${pluginInstallation.failed.length}`);
1868
+ }
1869
+ }
1870
+ const successfulMigrations = codeMigration.autoMigrated.filter((f) => f.success);
1871
+ const failedMigrations = codeMigration.autoMigrated.filter((f) => !f.success);
1872
+ const totalCallSites = successfulMigrations.reduce((sum, f) => sum + f.callSitesReplaced, 0);
1873
+ console.log(` Files auto-migrated: ${successfulMigrations.length}`);
1874
+ if (failedMigrations.length > 0) {
1875
+ console.log(` Files failed: ${failedMigrations.length}`);
1876
+ }
1877
+ if (codeMigration.manualRequired.length > 0) {
1878
+ console.log(` Files need manual work: ${codeMigration.manualRequired.length}`);
1879
+ }
1880
+ console.log(` Total call sites fixed: ${totalCallSites}`);
1881
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
1882
+ const hasErrors = pluginInstallation.failed.length > 0 || failedMigrations.length > 0;
1883
+ const hasManual = codeMigration.manualRequired.length > 0;
1884
+ if (hasErrors) {
1885
+ console.log("\u26A0\uFE0F Migration completed with errors!\n");
1886
+ } else if (hasManual) {
1887
+ console.log("\u2705 Migration completed! (some files need manual work)\n");
1888
+ } else {
1889
+ console.log("\u2705 Migration completed!\n");
1890
+ }
1891
+ console.log("\u{1F4DD} Next steps:");
1892
+ console.log(" 1. Review changes: git diff");
1893
+ console.log(" 2. Run TypeScript check: npm run typecheck");
1894
+ console.log(" 3. Run tests: npm test");
1895
+ if (hasManual) {
1896
+ console.log(" 4. Handle manual migrations (see report)");
1897
+ }
1898
+ console.log("");
1899
+ }
1900
+ async function generateReport(result) {
1901
+ const { jsonMigration, pluginInstallation, codeMigration } = result;
1902
+ const lines = [];
1903
+ lines.push("# Capability \u8FC1\u79FB\u62A5\u544A");
1904
+ lines.push("");
1905
+ lines.push(`\u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
1906
+ lines.push("");
1907
+ lines.push("## 1. \u914D\u7F6E\u8FC1\u79FB");
1908
+ lines.push("");
1909
+ if (jsonMigration.skipped) {
1910
+ lines.push(`\u8DF3\u8FC7: ${jsonMigration.reason}`);
1911
+ } else {
1912
+ lines.push("### 1.1 \u5DF2\u8FC1\u79FB\u7684 Capabilities");
1913
+ lines.push("");
1914
+ lines.push("| ID | pluginID |");
1915
+ lines.push("|----|----------|");
1916
+ for (const cap of jsonMigration.capabilities) {
1917
+ lines.push(`| ${cap.id} | ${cap.pluginID} |`);
1918
+ }
1919
+ lines.push("");
1920
+ if (jsonMigration.backupPath) {
1921
+ lines.push("### 1.2 \u5907\u4EFD\u6587\u4EF6");
1922
+ lines.push("");
1923
+ lines.push(`- \`${jsonMigration.backupPath}\``);
1924
+ lines.push("");
1925
+ }
1926
+ }
1927
+ lines.push("## 2. \u63D2\u4EF6\u5B89\u88C5");
1928
+ lines.push("");
1929
+ if (pluginInstallation.installed.length === 0 && pluginInstallation.alreadyInstalled.length === 0) {
1930
+ lines.push("\u65E0\u63D2\u4EF6\u9700\u8981\u5B89\u88C5\u3002");
1931
+ } else {
1932
+ lines.push("| pluginID | \u72B6\u6001 |");
1933
+ lines.push("|----------|------|");
1934
+ for (const pluginID of pluginInstallation.alreadyInstalled) {
1935
+ lines.push(`| ${pluginID} | \u5DF2\u5B89\u88C5 |`);
1936
+ }
1937
+ for (const pluginID of pluginInstallation.installed) {
1938
+ lines.push(`| ${pluginID} | \u65B0\u5B89\u88C5 |`);
1939
+ }
1940
+ for (const failed of pluginInstallation.failed) {
1941
+ lines.push(`| ${failed.pluginID} | \u274C \u5931\u8D25: ${failed.error} |`);
1942
+ }
1943
+ }
1944
+ lines.push("");
1945
+ lines.push("## 3. \u4EE3\u7801\u8FC1\u79FB");
1946
+ lines.push("");
1947
+ const successfulMigrations = codeMigration.autoMigrated.filter((f) => f.success);
1948
+ const failedMigrations = codeMigration.autoMigrated.filter((f) => !f.success);
1949
+ if (successfulMigrations.length > 0) {
1950
+ lines.push(`### 3.1 \u81EA\u52A8\u8FC1\u79FB\u7684\u6587\u4EF6 (${successfulMigrations.length} \u4E2A)`);
1951
+ lines.push("");
1952
+ for (const file of successfulMigrations) {
1953
+ lines.push(`#### ${file.filePath}`);
1954
+ lines.push("");
1955
+ lines.push("**\u53D8\u66F4\uFF1A**");
1956
+ lines.push(`- \u5220\u9664 ${file.importsRemoved} \u4E2A capability import`);
1957
+ if (file.injectionAdded) {
1958
+ lines.push("- \u6DFB\u52A0 CapabilityService \u4F9D\u8D56\u6CE8\u5165");
1959
+ }
1960
+ lines.push(`- \u66FF\u6362 ${file.callSitesReplaced} \u4E2A\u8C03\u7528\u70B9`);
1961
+ lines.push("");
1962
+ }
1963
+ }
1964
+ if (failedMigrations.length > 0) {
1965
+ lines.push(`### 3.2 \u8FC1\u79FB\u5931\u8D25\u7684\u6587\u4EF6 (${failedMigrations.length} \u4E2A)`);
1966
+ lines.push("");
1967
+ for (const file of failedMigrations) {
1968
+ lines.push(`#### ${file.filePath}`);
1969
+ lines.push("");
1970
+ lines.push(`**\u9519\u8BEF\uFF1A** ${file.error}`);
1971
+ lines.push("");
1972
+ }
1973
+ }
1974
+ if (codeMigration.manualRequired.length > 0) {
1975
+ lines.push(`### 3.3 \u9700\u8981\u624B\u52A8\u8FC1\u79FB\u7684\u6587\u4EF6 (${codeMigration.manualRequired.length} \u4E2A)`);
1976
+ lines.push("");
1977
+ for (const item of codeMigration.manualRequired) {
1978
+ lines.push(`#### ${item.filePath}`);
1979
+ lines.push("");
1980
+ lines.push(`- **\u539F\u56E0\uFF1A** ${item.reason}`);
1981
+ lines.push(`- **\u6D89\u53CA\u7684 Capabilities\uFF1A** ${item.capabilities.join(", ")}`);
1982
+ lines.push(`- **\u5EFA\u8BAE\uFF1A** ${item.suggestion}`);
1983
+ lines.push("");
1984
+ }
1985
+ }
1986
+ lines.push("## 4. \u9A8C\u8BC1\u6E05\u5355");
1987
+ lines.push("");
1988
+ lines.push("- [ ] \u8FD0\u884C `git diff` \u68C0\u67E5\u4EE3\u7801\u53D8\u66F4");
1989
+ lines.push("- [ ] \u8FD0\u884C `npm run typecheck` \u68C0\u67E5\u7C7B\u578B");
1990
+ lines.push("- [ ] \u8FD0\u884C `npm test` \u9A8C\u8BC1\u529F\u80FD");
1991
+ if (codeMigration.manualRequired.length > 0) {
1992
+ lines.push("- [ ] \u5904\u7406\u624B\u52A8\u8FC1\u79FB\u9879");
1993
+ }
1994
+ lines.push("- [ ] \u5220\u9664\u5907\u4EFD\u6587\u4EF6\uFF08\u786E\u8BA4\u65E0\u8BEF\u540E\uFF09");
1995
+ lines.push("");
1996
+ const reportPath = path10.join(getProjectRoot2(), REPORT_FILE);
1997
+ fs11.writeFileSync(reportPath, lines.join("\n"), "utf-8");
1998
+ console.log(`\u{1F4C4} Report generated: ${REPORT_FILE}`);
1999
+ }
2000
+
2001
+ // src/commands/capability/migration/index.ts
2002
+ async function runMigration(options = {}) {
2003
+ const fullOptions = {
2004
+ dryRun: options.dryRun ?? false,
2005
+ skipInstall: options.skipInstall ?? false,
2006
+ skipCode: options.skipCode ?? false,
2007
+ mapping: mergeMapping(options.mapping)
2008
+ };
2009
+ console.log("\u{1F50D} Analyzing project...\n");
2010
+ if (fullOptions.dryRun) {
2011
+ console.log("\u{1F4CB} Running in dry-run mode (no files will be modified)\n");
2012
+ }
2013
+ console.log("\u{1F4C1} Step 1: JSON File Migration");
2014
+ const jsonResult = await migrateJsonFiles(fullOptions);
2015
+ if (jsonResult.skipped) {
2016
+ console.log(` \u25CB Skipped: ${jsonResult.reason}
2017
+ `);
2018
+ } else if (jsonResult.success) {
2019
+ console.log(` \u2713 Migrated ${jsonResult.count} capabilities`);
2020
+ if (jsonResult.backupPath) {
2021
+ console.log(` \u2713 Backed up: capabilities.json \u2192 capabilities.json.backup`);
2022
+ }
2023
+ console.log("");
2024
+ }
2025
+ let pluginResult = { installed: [], alreadyInstalled: [], failed: [] };
2026
+ if (!fullOptions.skipInstall) {
2027
+ console.log("\u{1F4E6} Step 2: Plugin Installation");
2028
+ pluginResult = await installPlugins(jsonResult.capabilities, fullOptions);
2029
+ if (pluginResult.alreadyInstalled.length > 0) {
2030
+ console.log(` \u2713 Already installed: ${pluginResult.alreadyInstalled.join(", ")}`);
2031
+ }
2032
+ if (pluginResult.installed.length > 0) {
2033
+ console.log(` \u2713 Installed: ${pluginResult.installed.join(", ")}`);
2034
+ }
2035
+ if (pluginResult.failed.length > 0) {
2036
+ console.log(` \u2717 Failed: ${pluginResult.failed.map((f) => f.pluginID).join(", ")}`);
2037
+ }
2038
+ console.log("");
2039
+ } else {
2040
+ console.log("\u{1F4E6} Step 2: Plugin Installation");
2041
+ console.log(" \u25CB Skipped (--skip-install)\n");
2042
+ }
2043
+ let codeResult = { autoMigrated: [], manualRequired: [] };
2044
+ if (!fullOptions.skipCode) {
2045
+ console.log("\u{1F527} Step 3: Code Migration");
2046
+ console.log(" Scanning server/ directory...\n");
2047
+ codeResult = await migrateCode(fullOptions);
2048
+ for (const file of codeResult.autoMigrated) {
2049
+ if (file.success) {
2050
+ console.log(` \u2713 ${file.filePath}`);
2051
+ console.log(` - Removed ${file.importsRemoved} capability imports`);
2052
+ if (file.injectionAdded) {
2053
+ console.log(` - Added CapabilityService injection`);
2054
+ }
2055
+ console.log(` - Replaced ${file.callSitesReplaced} call sites`);
2056
+ } else {
2057
+ console.log(` \u2717 ${file.filePath}: ${file.error}`);
2058
+ }
2059
+ }
2060
+ if (codeResult.manualRequired.length > 0) {
2061
+ console.log("");
2062
+ for (const item of codeResult.manualRequired) {
2063
+ console.log(` \u26A0 ${item.filePath} (manual migration required)`);
2064
+ console.log(` - Reason: ${item.reason}`);
2065
+ }
2066
+ }
2067
+ console.log("");
2068
+ } else {
2069
+ console.log("\u{1F527} Step 3: Code Migration");
2070
+ console.log(" \u25CB Skipped (--skip-code)\n");
2071
+ }
2072
+ const result = {
2073
+ jsonMigration: jsonResult,
2074
+ pluginInstallation: pluginResult,
2075
+ codeMigration: codeResult
2076
+ };
2077
+ printSummary(result);
2078
+ if (!fullOptions.dryRun) {
2079
+ await generateReport(result);
2080
+ }
2081
+ return result;
2082
+ }
2083
+
2084
+ // src/commands/capability/migration.handler.ts
2085
+ async function migration(options = {}) {
2086
+ try {
2087
+ let customMapping;
2088
+ if (options.mapping) {
2089
+ if (!fs12.existsSync(options.mapping)) {
2090
+ console.error(`[capability] Mapping file not found: ${options.mapping}`);
2091
+ process.exit(1);
2092
+ }
2093
+ try {
2094
+ const content = fs12.readFileSync(options.mapping, "utf-8");
2095
+ customMapping = JSON.parse(content);
2096
+ } catch (error) {
2097
+ console.error(`[capability] Failed to parse mapping file: ${error}`);
50
2098
  process.exit(1);
2099
+ }
51
2100
  }
52
- });
53
- // 解析命令行参数
54
- cli.parse();
2101
+ const migrationOptions = {
2102
+ dryRun: options.dryRun,
2103
+ skipInstall: options.skipInstall,
2104
+ skipCode: options.skipCode,
2105
+ mapping: customMapping
2106
+ };
2107
+ await runMigration(migrationOptions);
2108
+ } catch (error) {
2109
+ console.error(`[capability] Migration failed: ${error}`);
2110
+ process.exit(1);
2111
+ }
2112
+ }
2113
+
2114
+ // src/commands/capability/index.ts
2115
+ var listCommand2 = {
2116
+ name: "list",
2117
+ description: "List capability configurations",
2118
+ aliases: ["ls"],
2119
+ register(program) {
2120
+ program.command(this.name).alias("ls").description(this.description).option("--summary", "Return raw capability config (without hydration)").option("--id <id>", "Specify capability ID").action(async (options) => {
2121
+ await list2(options);
2122
+ });
2123
+ }
2124
+ };
2125
+ var migrationCommand = {
2126
+ name: "migration",
2127
+ description: "Execute capability migration",
2128
+ register(program) {
2129
+ program.command(this.name).description(this.description).option("--dry-run", "Preview mode, do not modify files").option("--skip-install", "Skip plugin installation step").option("--skip-code", "Skip code migration step").option("--mapping <file>", "Custom sourceActionID to pluginID mapping file").action(async (options) => {
2130
+ await migration(options);
2131
+ });
2132
+ }
2133
+ };
2134
+ var capabilityCommandGroup = {
2135
+ name: "capability",
2136
+ description: "Manage capability configurations",
2137
+ commands: [listCommand2, migrationCommand]
2138
+ };
2139
+
2140
+ // src/commands/index.ts
2141
+ var commands = [
2142
+ genDbSchemaCommand,
2143
+ syncCommand,
2144
+ actionPluginCommandGroup,
2145
+ capabilityCommandGroup
2146
+ ];
2147
+
2148
+ // src/index.ts
2149
+ var envPath = path11.join(process.cwd(), ".env");
2150
+ if (fs13.existsSync(envPath)) {
2151
+ dotenvConfig({ path: envPath });
2152
+ }
2153
+ var __dirname = path11.dirname(fileURLToPath3(import.meta.url));
2154
+ var pkg = JSON.parse(fs13.readFileSync(path11.join(__dirname, "../package.json"), "utf-8"));
2155
+ var cli = new FullstackCLI(pkg.version);
2156
+ cli.useAll(commands);
2157
+ cli.run();