@lark-apaas/fullstack-cli 1.1.16-beta.0 → 1.1.16-beta.10

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,7 +1,7 @@
1
1
  // src/index.ts
2
- import fs21 from "fs";
3
- import path17 from "path";
4
- import { fileURLToPath as fileURLToPath4 } from "url";
2
+ import fs26 from "fs";
3
+ import path22 from "path";
4
+ import { fileURLToPath as fileURLToPath5 } from "url";
5
5
  import { config as dotenvConfig } from "dotenv";
6
6
 
7
7
  // src/cli.ts
@@ -472,11 +472,16 @@ var KNOWN_TYPE_FACTORIES = {
472
472
  user_profile: "userProfile",
473
473
  file_attachment: "fileAttachment"
474
474
  };
475
+ var KNOWN_ARRAY_TYPE_FACTORIES = {
476
+ user_profile: "userProfileArray",
477
+ file_attachment: "fileAttachmentArray"
478
+ };
475
479
  var replaceUnknownTransform = {
476
480
  name: "replace-unknown",
477
481
  transform(ctx) {
478
482
  const { sourceFile, stats } = ctx;
479
483
  const fullText = sourceFile.getFullText();
484
+ const replacements = [];
480
485
  sourceFile.forEachDescendant((node) => {
481
486
  if (!Node5.isCallExpression(node)) {
482
487
  return;
@@ -490,25 +495,49 @@ var replaceUnknownTransform = {
490
495
  const lines = textBefore.split("\n");
491
496
  let factoryName = "text";
492
497
  let foundKnownType = false;
498
+ let isArrayType = false;
493
499
  for (let i = lines.length - 1; i >= Math.max(0, lines.length - 5); i--) {
494
500
  const line = lines[i];
495
501
  const todoMatch = line.match(/\/\/ TODO: failed to parse database type '(?:\w+\.)?([\w_]+)(\[\])?'/);
496
502
  if (todoMatch) {
497
503
  const typeName = todoMatch[1];
498
- if (KNOWN_TYPE_FACTORIES[typeName]) {
504
+ isArrayType = todoMatch[2] === "[]";
505
+ if (isArrayType && KNOWN_ARRAY_TYPE_FACTORIES[typeName]) {
506
+ factoryName = KNOWN_ARRAY_TYPE_FACTORIES[typeName];
507
+ foundKnownType = true;
508
+ } else if (KNOWN_TYPE_FACTORIES[typeName]) {
499
509
  factoryName = KNOWN_TYPE_FACTORIES[typeName];
500
510
  foundKnownType = true;
501
511
  }
502
512
  break;
503
513
  }
504
514
  }
515
+ replacements.push({
516
+ expression,
517
+ factoryName,
518
+ foundKnownType,
519
+ isArrayType,
520
+ node
521
+ });
522
+ });
523
+ for (const { expression, factoryName, foundKnownType, isArrayType, node } of replacements) {
505
524
  expression.replaceWithText(factoryName);
525
+ if (isArrayType && foundKnownType) {
526
+ const parent = node.getParent();
527
+ if (Node5.isPropertyAccessExpression(parent) && parent.getName() === "array") {
528
+ const grandParent = parent.getParent();
529
+ if (Node5.isCallExpression(grandParent)) {
530
+ const nodeText = node.getText();
531
+ grandParent.replaceWithText(nodeText);
532
+ }
533
+ }
534
+ }
506
535
  if (foundKnownType) {
507
536
  stats.replacedUnknown++;
508
537
  } else {
509
538
  stats.fallbackToText++;
510
539
  }
511
- });
540
+ }
512
541
  const todoCommentRegex = /\/\/ TODO: failed to parse database type '[^']+'\s*\n/g;
513
542
  const currentText = sourceFile.getFullText();
514
543
  const cleanedText = currentText.replace(todoCommentRegex, "");
@@ -1005,6 +1034,215 @@ function resolveTemplateTypesPath() {
1005
1034
  return void 0;
1006
1035
  }
1007
1036
 
1037
+ // src/commands/db/gen-dbschema/utils/fetch-column-comments.ts
1038
+ import postgres from "postgres";
1039
+ var DEFAULT_TIMEOUT_MS = 1e4;
1040
+ async function fetchColumnComments(connectionString, options = {}) {
1041
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1042
+ const url = new URL(connectionString);
1043
+ const schemaName = url.searchParams.get("schema") ?? "public";
1044
+ const start = Date.now();
1045
+ console.log(`[fetchColumnComments] \u2192 Querying pg_description for schema=${schemaName} (timeout=${timeoutMs}ms)`);
1046
+ const sql = postgres(connectionString, {
1047
+ connect_timeout: Math.ceil(timeoutMs / 1e3),
1048
+ idle_timeout: Math.ceil(timeoutMs / 1e3)
1049
+ });
1050
+ try {
1051
+ const queryPromise = sql`
1052
+ SELECT
1053
+ c.table_name AS "tableName",
1054
+ c.column_name AS "columnName",
1055
+ pgd.description AS "comment"
1056
+ FROM information_schema.columns c
1057
+ JOIN pg_catalog.pg_statio_all_tables st
1058
+ ON c.table_name = st.relname AND c.table_schema = st.schemaname
1059
+ JOIN pg_catalog.pg_description pgd
1060
+ ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
1061
+ WHERE c.table_schema = ${schemaName}
1062
+ AND pgd.description IS NOT NULL
1063
+ AND pgd.description != ''
1064
+ `;
1065
+ const timeoutPromise = new Promise((_, reject) => {
1066
+ setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
1067
+ });
1068
+ const result = await Promise.race([queryPromise, timeoutPromise]);
1069
+ const commentMap = /* @__PURE__ */ new Map();
1070
+ for (const row of result) {
1071
+ const key = `${row.tableName}.${row.columnName}`;
1072
+ commentMap.set(key, row.comment);
1073
+ }
1074
+ console.log(`[fetchColumnComments] \u2190 Fetched ${commentMap.size} column comments (${Date.now() - start}ms)`);
1075
+ return commentMap;
1076
+ } finally {
1077
+ await sql.end().catch(() => {
1078
+ });
1079
+ }
1080
+ }
1081
+ function extractTypeAnnotation(comment) {
1082
+ const typeStart = comment.indexOf("@type");
1083
+ if (typeStart === -1) return null;
1084
+ const afterType = comment.slice(typeStart + 5).trimStart();
1085
+ if (!afterType.startsWith("{")) return null;
1086
+ let depth = 0;
1087
+ let endIndex = 0;
1088
+ for (let i = 0; i < afterType.length; i++) {
1089
+ if (afterType[i] === "{") depth++;
1090
+ if (afterType[i] === "}") depth--;
1091
+ if (depth === 0) {
1092
+ endIndex = i + 1;
1093
+ break;
1094
+ }
1095
+ }
1096
+ if (endIndex === 0) return null;
1097
+ return afterType.slice(0, endIndex);
1098
+ }
1099
+ function parseColumnComment(comment) {
1100
+ const typeValue = extractTypeAnnotation(comment);
1101
+ if (typeValue) {
1102
+ const descMatch = comment.match(/@description\s+([^@]+)/);
1103
+ return {
1104
+ type: typeValue,
1105
+ description: descMatch?.[1]?.trim()
1106
+ };
1107
+ }
1108
+ return { description: comment.trim() };
1109
+ }
1110
+
1111
+ // src/commands/db/gen-dbschema/transforms/text/jsonb-comments.ts
1112
+ function addJsonbTypeComments(source, columnComments) {
1113
+ if (!columnComments || columnComments.size === 0) {
1114
+ return { text: source, added: 0 };
1115
+ }
1116
+ const lines = source.split("\n");
1117
+ const result = [];
1118
+ let added = 0;
1119
+ let currentTableName = null;
1120
+ const tableDefRegex = /export const\s+\w+\s*=\s*pgTable\(\s*["'`]([^"'`]+)["'`]/;
1121
+ const jsonFieldWithNameRegex = /^\s*(\w+):\s*(?:json|jsonb)\(\s*["'`]([^"'`]+)["'`]\)/;
1122
+ const jsonFieldNoNameRegex = /^\s*(\w+):\s*(?:json|jsonb)\(\s*\)/;
1123
+ for (let i = 0; i < lines.length; i++) {
1124
+ const line = lines[i];
1125
+ const tableMatch = line.match(tableDefRegex);
1126
+ if (tableMatch) {
1127
+ currentTableName = tableMatch[1];
1128
+ }
1129
+ let columnName = null;
1130
+ const jsonMatchWithName = line.match(jsonFieldWithNameRegex);
1131
+ const jsonMatchNoName = line.match(jsonFieldNoNameRegex);
1132
+ if (jsonMatchWithName) {
1133
+ columnName = jsonMatchWithName[2];
1134
+ } else if (jsonMatchNoName) {
1135
+ columnName = jsonMatchNoName[1];
1136
+ }
1137
+ if (columnName && currentTableName) {
1138
+ const commentKey = `${currentTableName}.${columnName}`;
1139
+ const comment = columnComments.get(commentKey);
1140
+ if (comment) {
1141
+ const parsed = parseColumnComment(comment);
1142
+ const indentMatch = line.match(/^\s*/);
1143
+ const indent = indentMatch ? indentMatch[0] : "";
1144
+ const prevLine = result[result.length - 1]?.trim() ?? "";
1145
+ if (!prevLine.startsWith("/**") && !prevLine.startsWith("*") && !prevLine.startsWith("//")) {
1146
+ const commentLines = [];
1147
+ commentLines.push(`${indent}/**`);
1148
+ if (parsed.description) {
1149
+ const safeDesc = parsed.description.replace(/[\r\n]+/g, " ").trim();
1150
+ commentLines.push(`${indent} * ${safeDesc}`);
1151
+ }
1152
+ if (parsed.type) {
1153
+ if (parsed.description) {
1154
+ commentLines.push(`${indent} *`);
1155
+ }
1156
+ const safeType = parsed.type.replace(/[\r\n]+/g, " ").trim();
1157
+ commentLines.push(`${indent} * @type ${safeType}`);
1158
+ }
1159
+ commentLines.push(`${indent} */`);
1160
+ result.push(...commentLines);
1161
+ added++;
1162
+ }
1163
+ }
1164
+ }
1165
+ if (line.match(/^\}\);?\s*$/) || line.match(/^}\s*,\s*\{/)) {
1166
+ currentTableName = null;
1167
+ }
1168
+ result.push(line);
1169
+ }
1170
+ return { text: result.join("\n"), added };
1171
+ }
1172
+
1173
+ // src/commands/db/gen-dbschema/transforms/text/synced-table-comments.ts
1174
+ var TABLE_COMMENT = "Synced table: data is auto-synced from external source. Do not rename or delete this table.";
1175
+ var FIELD_COMMENT = "Synced field: auto-synced, do not modify or delete";
1176
+ var TABLE_DEF_REGEX = /^(export const\s+\w+\s*=\s*(?:pgTable|pgView|pgMaterializedView)\(\s*["'`])([^"'`]+)(["'`])/;
1177
+ var FIELD_WITH_NAME_REGEX = /^\s*[\w"']+\s*:\s*\w+\(\s*["'`]([^"'`]+)["'`]/;
1178
+ var FIELD_PROP_NAME_REGEX = /^\s*([\w]+)\s*:/;
1179
+ function addSyncedTableComments(source, syncedTableMap) {
1180
+ if (!syncedTableMap || syncedTableMap.size === 0) {
1181
+ return { text: source, added: 0 };
1182
+ }
1183
+ const lines = source.split("\n");
1184
+ const result = [];
1185
+ let added = 0;
1186
+ let currentSyncedFields = null;
1187
+ let insideTableBody = false;
1188
+ let braceDepth = 0;
1189
+ for (let i = 0; i < lines.length; i++) {
1190
+ const line = lines[i];
1191
+ const tableMatch = line.match(TABLE_DEF_REGEX);
1192
+ if (tableMatch) {
1193
+ const tableName = tableMatch[2];
1194
+ const syncedFields = syncedTableMap.get(tableName);
1195
+ if (syncedFields) {
1196
+ currentSyncedFields = syncedFields;
1197
+ insideTableBody = true;
1198
+ braceDepth = 0;
1199
+ const prevLine = result[result.length - 1]?.trim() ?? "";
1200
+ if (!prevLine.includes("Synced table")) {
1201
+ const indentMatch = line.match(/^\s*/);
1202
+ const indent = indentMatch ? indentMatch[0] : "";
1203
+ result.push(`${indent}// ${TABLE_COMMENT}`);
1204
+ added++;
1205
+ }
1206
+ }
1207
+ }
1208
+ if (insideTableBody) {
1209
+ for (const ch of line) {
1210
+ if (ch === "{") braceDepth++;
1211
+ if (ch === "}") braceDepth--;
1212
+ }
1213
+ if (braceDepth <= 0) {
1214
+ insideTableBody = false;
1215
+ currentSyncedFields = null;
1216
+ }
1217
+ if (currentSyncedFields && braceDepth >= 1 && !tableMatch) {
1218
+ const columnName = extractColumnName2(line);
1219
+ if (columnName && currentSyncedFields.has(columnName)) {
1220
+ const prevLine = result[result.length - 1]?.trim() ?? "";
1221
+ if (!prevLine.includes("Synced field")) {
1222
+ const indentMatch = line.match(/^\s*/);
1223
+ const indent = indentMatch ? indentMatch[0] : "";
1224
+ result.push(`${indent}// ${FIELD_COMMENT}`);
1225
+ added++;
1226
+ }
1227
+ }
1228
+ }
1229
+ }
1230
+ result.push(line);
1231
+ }
1232
+ return { text: result.join("\n"), added };
1233
+ }
1234
+ function extractColumnName2(line) {
1235
+ const withNameMatch = line.match(FIELD_WITH_NAME_REGEX);
1236
+ if (withNameMatch) {
1237
+ return withNameMatch[1];
1238
+ }
1239
+ const propMatch = line.match(FIELD_PROP_NAME_REGEX);
1240
+ if (propMatch) {
1241
+ return propMatch[1];
1242
+ }
1243
+ return null;
1244
+ }
1245
+
1008
1246
  // src/commands/db/gen-dbschema/transforms/text/table-aliases.ts
1009
1247
  var TABLE_ALIAS_MARKER = "// table aliases";
1010
1248
  function generateTableAliases(source) {
@@ -1041,7 +1279,7 @@ function formatSource(source) {
1041
1279
  }
1042
1280
 
1043
1281
  // src/commands/db/gen-dbschema/postprocess.ts
1044
- function postprocessSchema(rawSource) {
1282
+ function postprocessSchema(rawSource, options = {}) {
1045
1283
  const patchResult = patchDefects(rawSource);
1046
1284
  let source = patchResult.text;
1047
1285
  const { sourceFile } = parseSource(source);
@@ -1051,12 +1289,18 @@ function postprocessSchema(rawSource) {
1051
1289
  source = ensureHeader(source);
1052
1290
  source = addSystemFieldComments(source);
1053
1291
  source = inlineCustomTypes(source);
1292
+ const jsonbCommentsResult = addJsonbTypeComments(source, options.columnComments);
1293
+ source = jsonbCommentsResult.text;
1294
+ const syncedCommentsResult = addSyncedTableComments(source, options.syncedTableMap);
1295
+ source = syncedCommentsResult.text;
1054
1296
  source = generateTableAliases(source);
1055
1297
  source = formatSource(source);
1056
1298
  return {
1057
1299
  source,
1058
1300
  astStats,
1059
- patchedDefects: patchResult.fixed
1301
+ patchedDefects: patchResult.fixed,
1302
+ addedJsonbComments: jsonbCommentsResult.added,
1303
+ addedSyncedComments: syncedCommentsResult.added
1060
1304
  };
1061
1305
  }
1062
1306
  function logStats(result, prefix = "[postprocess]") {
@@ -1103,17 +1347,26 @@ function logStats(result, prefix = "[postprocess]") {
1103
1347
  if (astStats.removedImports.length > 0) {
1104
1348
  console.info(`${prefix} Removed imports: ${astStats.removedImports.join(", ")}`);
1105
1349
  }
1350
+ if (result.addedJsonbComments > 0) {
1351
+ console.info(`${prefix} Added ${result.addedJsonbComments} JSDoc comments for jsonb fields`);
1352
+ }
1353
+ if (result.addedSyncedComments > 0) {
1354
+ console.info(`${prefix} Added ${result.addedSyncedComments} comments for synced tables/fields`);
1355
+ }
1106
1356
  }
1107
1357
 
1108
1358
  // src/commands/db/gen-dbschema/index.ts
1109
- function postprocessDrizzleSchema(targetPath) {
1359
+ async function postprocessDrizzleSchema(targetPath, options = {}) {
1110
1360
  const resolvedPath = path.resolve(targetPath);
1111
1361
  if (!fs3.existsSync(resolvedPath)) {
1112
1362
  console.warn(`[postprocess-drizzle-schema] File not found: ${resolvedPath}`);
1113
1363
  return void 0;
1114
1364
  }
1115
1365
  const rawSource = fs3.readFileSync(resolvedPath, "utf8");
1116
- const result = postprocessSchema(rawSource);
1366
+ const result = postprocessSchema(rawSource, {
1367
+ columnComments: options.columnComments,
1368
+ syncedTableMap: options.syncedTableMap
1369
+ });
1117
1370
  fs3.writeFileSync(resolvedPath, result.source, "utf8");
1118
1371
  logStats(result, "[postprocess-drizzle-schema]");
1119
1372
  return {
@@ -1122,10 +1375,114 @@ function postprocessDrizzleSchema(targetPath) {
1122
1375
  unmatchedUnknown: result.astStats.unmatchedUnknown,
1123
1376
  patchedDefects: result.patchedDefects,
1124
1377
  replacedTimestamps: result.astStats.replacedTimestamp,
1125
- replacedDefaultNow: result.astStats.replacedDefaultNow
1378
+ replacedDefaultNow: result.astStats.replacedDefaultNow,
1379
+ addedJsonbComments: result.addedJsonbComments,
1380
+ addedSyncedComments: result.addedSyncedComments
1126
1381
  };
1127
1382
  }
1128
1383
 
1384
+ // src/utils/http-client.ts
1385
+ import { HttpClient } from "@lark-apaas/http-client";
1386
+ var clientInstance = null;
1387
+ function getHttpClient() {
1388
+ if (!clientInstance) {
1389
+ clientInstance = new HttpClient({
1390
+ timeout: 3e4,
1391
+ platform: {
1392
+ enabled: true
1393
+ }
1394
+ });
1395
+ const canaryEnv = process.env.FORCE_FRAMEWORK_CLI_CANARY_ENV;
1396
+ if (canaryEnv) {
1397
+ clientInstance.interceptors.request.use((req) => {
1398
+ req.headers["x-tt-env"] = canaryEnv;
1399
+ return req;
1400
+ });
1401
+ }
1402
+ }
1403
+ return clientInstance;
1404
+ }
1405
+
1406
+ // src/commands/db/gen-dbschema/utils/fetch-synced-tables.ts
1407
+ var DEFAULT_TIMEOUT_MS2 = 1e4;
1408
+ async function fetchSyncedTables(appId, workspace) {
1409
+ try {
1410
+ const client = getHttpClient();
1411
+ const timeoutPromise = new Promise((_, reject) => {
1412
+ setTimeout(
1413
+ () => reject(new Error(`Timeout after ${DEFAULT_TIMEOUT_MS2}ms`)),
1414
+ DEFAULT_TIMEOUT_MS2
1415
+ );
1416
+ });
1417
+ const dbBranch = process.env.FORCE_DB_BRANCH || "main";
1418
+ const start = Date.now();
1419
+ console.log(
1420
+ `[fetchSyncedTables] \u2192 GET listTableView (dbBranch=${dbBranch}) appId=${appId ? "set" : "unset"} workspace=${workspace ? "set" : "unset"}`
1421
+ );
1422
+ const response = await Promise.race([
1423
+ client.get(
1424
+ `/api/v1/dataloom/inner/app/${appId}/workspaces/${workspace}/listTableView`,
1425
+ { params: { dbBranch }, headers: { "x-supaas-bizsource": "miaoda" } }
1426
+ ),
1427
+ timeoutPromise
1428
+ ]);
1429
+ console.log(
1430
+ `[fetchSyncedTables] \u2190 listTableView response: ${response.status} ${response.statusText} (${Date.now() - start}ms) with logId=${response.headers.get("x-tt-logid")}`
1431
+ );
1432
+ if (!response.ok) {
1433
+ throw new Error(
1434
+ `listTableView API failed: ${response.status} ${response.statusText}`
1435
+ );
1436
+ }
1437
+ let json;
1438
+ try {
1439
+ json = await response.json();
1440
+ } catch (error) {
1441
+ console.warn(
1442
+ "[fetchSyncedTables] \u26A0 Failed to parse listTableView response JSON, returning empty map:",
1443
+ error instanceof Error ? error.message : String(error)
1444
+ );
1445
+ return /* @__PURE__ */ new Map();
1446
+ }
1447
+ const tableView = json?.data?.data;
1448
+ if (!tableView) {
1449
+ console.warn(
1450
+ "[fetchSyncedTables] \u26A0 listTableView response missing data.data, returning empty map"
1451
+ );
1452
+ return /* @__PURE__ */ new Map();
1453
+ }
1454
+ const syncedMap = extractSyncedTableMap(tableView);
1455
+ const totalCount = (tableView.table?.data?.length ?? 0) + (tableView.view?.data?.length ?? 0) + (tableView.materializedView?.data?.length ?? 0);
1456
+ console.log(
1457
+ `[fetchSyncedTables] \u2713 Extracted synced tables: ${syncedMap.size}/${totalCount} (elapsed ${Date.now() - start}ms)`
1458
+ );
1459
+ return syncedMap;
1460
+ } catch (error) {
1461
+ console.error(
1462
+ "[fetchSyncedTables] \u274C Error fetching synced tables:",
1463
+ error
1464
+ );
1465
+ return /* @__PURE__ */ new Map();
1466
+ }
1467
+ }
1468
+ function extractSyncedTableMap(tableView) {
1469
+ const syncedMap = /* @__PURE__ */ new Map();
1470
+ const allTables = [
1471
+ ...tableView.table?.data ?? [],
1472
+ ...tableView.view?.data ?? [],
1473
+ ...tableView.materializedView?.data ?? []
1474
+ ];
1475
+ for (const table of allTables) {
1476
+ if (table.bitableSyncTask && table.bitableSyncTask.fieldApiNameList?.length > 0) {
1477
+ syncedMap.set(
1478
+ table.tableName,
1479
+ new Set(table.bitableSyncTask.fieldApiNameList)
1480
+ );
1481
+ }
1482
+ }
1483
+ return syncedMap;
1484
+ }
1485
+
1129
1486
  // src/commands/db/gen-nest-resource/generator.ts
1130
1487
  import { pluralize } from "inflection";
1131
1488
 
@@ -1804,7 +2161,9 @@ async function run(options = {}) {
1804
2161
  }
1805
2162
  const databaseUrl = process.env.SUDA_DATABASE_URL;
1806
2163
  if (!databaseUrl) {
1807
- console.error("[gen-db-schema] Error: SUDA_DATABASE_URL environment variable is required");
2164
+ console.error(
2165
+ "[gen-db-schema] Error: SUDA_DATABASE_URL environment variable is required"
2166
+ );
1808
2167
  process.exit(1);
1809
2168
  }
1810
2169
  const outputPath = options.output || process.env.DB_SCHEMA_OUTPUT || "server/database/schema.ts";
@@ -1819,9 +2178,14 @@ async function run(options = {}) {
1819
2178
  path2.resolve(__dirname2, "../../../dist/config/drizzle.config.js")
1820
2179
  ];
1821
2180
  const configPath = configPathCandidates.find((p) => fs4.existsSync(p));
1822
- console.log("[gen-db-schema] Using drizzle config from:", configPath ?? "(not found)");
2181
+ console.log(
2182
+ "[gen-db-schema] Using drizzle config from:",
2183
+ configPath ?? "(not found)"
2184
+ );
1823
2185
  if (!configPath) {
1824
- console.error("[gen-db-schema] Error: drizzle config not found in CLI package");
2186
+ console.error(
2187
+ "[gen-db-schema] Error: drizzle config not found in CLI package"
2188
+ );
1825
2189
  process.exit(1);
1826
2190
  }
1827
2191
  const resolveDrizzleKitBin = () => {
@@ -1837,7 +2201,9 @@ async function run(options = {}) {
1837
2201
  const binField = pkgJson.bin;
1838
2202
  const binRelPath = typeof binField === "string" ? binField : binField?.["drizzle-kit"];
1839
2203
  if (!binRelPath) {
1840
- throw new Error("Unable to resolve drizzle-kit binary from package.json");
2204
+ throw new Error(
2205
+ "Unable to resolve drizzle-kit binary from package.json"
2206
+ );
1841
2207
  }
1842
2208
  return path2.resolve(currentDir, binRelPath);
1843
2209
  }
@@ -1847,6 +2213,67 @@ async function run(options = {}) {
1847
2213
  }
1848
2214
  throw new Error("Unable to locate drizzle-kit package root");
1849
2215
  };
2216
+ let columnComments;
2217
+ let syncedTableMap;
2218
+ const appId = process.env.app_id;
2219
+ const workspace = process.env.suda_workspace_id;
2220
+ console.log(
2221
+ `[gen-db-schema] Pre-fetch info: columnComments=enabled, syncedTables=${appId && workspace ? "enabled" : "skipped"} (app_id=${appId ? "set" : "unset"}, suda_workspace_id=${workspace ? "set" : "unset"})`
2222
+ );
2223
+ const columnCommentsTask = (async () => {
2224
+ const start = Date.now();
2225
+ console.log("[gen-db-schema] \u2192 Fetching column comments...");
2226
+ const res = await fetchColumnComments(databaseUrl, { timeoutMs: 1e4 });
2227
+ console.log(
2228
+ `[gen-db-schema] \u2190 Fetched column comments: ${res.size} items (${Date.now() - start}ms)`
2229
+ );
2230
+ return res;
2231
+ })();
2232
+ const syncedTablesTask = appId && workspace ? (async () => {
2233
+ const start = Date.now();
2234
+ console.log(
2235
+ "[gen-db-schema] \u2192 Fetching synced tables from listTableView..."
2236
+ );
2237
+ const res = await fetchSyncedTables(appId, workspace);
2238
+ console.log(
2239
+ `[gen-db-schema] \u2190 Fetched synced tables: ${res.size} tables (${Date.now() - start}ms)`
2240
+ );
2241
+ return res;
2242
+ })() : void 0;
2243
+ const fetchTasks = await Promise.allSettled([
2244
+ columnCommentsTask,
2245
+ ...syncedTablesTask ? [syncedTablesTask] : []
2246
+ ]);
2247
+ if (fetchTasks[0].status === "fulfilled") {
2248
+ columnComments = fetchTasks[0].value;
2249
+ console.log(
2250
+ `[gen-db-schema] \u2713 Column comments ready: ${columnComments.size}`
2251
+ );
2252
+ } else {
2253
+ console.warn(
2254
+ "[gen-db-schema] \u26A0 Failed to fetch column comments (skipping):",
2255
+ fetchTasks[0].reason instanceof Error ? fetchTasks[0].reason.message : String(fetchTasks[0].reason)
2256
+ );
2257
+ }
2258
+ if (appId && workspace) {
2259
+ if (fetchTasks[1]?.status === "fulfilled") {
2260
+ syncedTableMap = fetchTasks[1].value;
2261
+ console.log(
2262
+ `[gen-db-schema] \u2713 Synced tables ready: ${syncedTableMap.size}`
2263
+ );
2264
+ } else if (fetchTasks[1]?.status === "rejected") {
2265
+ console.warn(
2266
+ "[gen-db-schema] \u26A0 Failed to fetch synced tables (skipping):",
2267
+ fetchTasks[1].reason instanceof Error ? fetchTasks[1].reason.message : String(fetchTasks[1].reason)
2268
+ );
2269
+ syncedTableMap = /* @__PURE__ */ new Map();
2270
+ }
2271
+ } else {
2272
+ console.info(
2273
+ "[gen-db-schema] \u2139 Skipping synced table detection (app_id or suda_workspace_id not set)"
2274
+ );
2275
+ syncedTableMap = /* @__PURE__ */ new Map();
2276
+ }
1850
2277
  try {
1851
2278
  const env = {
1852
2279
  ...process.env,
@@ -1857,22 +2284,34 @@ async function run(options = {}) {
1857
2284
  };
1858
2285
  const drizzleKitBin = resolveDrizzleKitBin();
1859
2286
  const spawnArgs = [drizzleKitBin, "introspect", "--config", configPath];
1860
- const result = spawnSync(process.execPath, spawnArgs, { stdio: "inherit", env, cwd: process.cwd() });
2287
+ const result = spawnSync(process.execPath, spawnArgs, {
2288
+ stdio: "inherit",
2289
+ env,
2290
+ cwd: process.cwd()
2291
+ });
1861
2292
  if (result.error) {
1862
2293
  console.error("[gen-db-schema] Execution failed:", result.error);
1863
2294
  throw result.error;
1864
2295
  }
1865
2296
  if ((result.status ?? 0) !== 0) {
1866
- throw new Error(`drizzle-kit introspect failed with status ${result.status}`);
2297
+ throw new Error(
2298
+ `drizzle-kit introspect failed with status ${result.status}`
2299
+ );
1867
2300
  }
1868
2301
  const generatedSchema = path2.join(OUT_DIR, "schema.ts");
1869
2302
  if (!fs4.existsSync(generatedSchema)) {
1870
2303
  console.error("[gen-db-schema] schema.ts not generated");
1871
2304
  throw new Error("drizzle-kit introspect failed to generate schema.ts");
1872
2305
  }
1873
- const stats = postprocessDrizzleSchema(generatedSchema);
2306
+ const stats = await postprocessDrizzleSchema(generatedSchema, {
2307
+ columnComments,
2308
+ syncedTableMap
2309
+ });
1874
2310
  if (stats?.unmatchedUnknown?.length) {
1875
- console.warn("[gen-db-schema] Unmatched custom types detected:", stats.unmatchedUnknown);
2311
+ console.warn(
2312
+ "[gen-db-schema] Unmatched custom types detected:",
2313
+ stats.unmatchedUnknown
2314
+ );
1876
2315
  }
1877
2316
  console.log("[gen-db-schema] \u2713 Postprocessed schema");
1878
2317
  fs4.mkdirSync(path2.dirname(SCHEMA_FILE), { recursive: true });
@@ -1887,14 +2326,22 @@ async function run(options = {}) {
1887
2326
  schemaFilePath,
1888
2327
  moduleOutputDir: path2.resolve(process.cwd(), "server/modules")
1889
2328
  });
1890
- console.log("[gen-db-schema] \u2713 Generate NestJS Module Boilerplate Successfully");
2329
+ console.log(
2330
+ "[gen-db-schema] \u2713 Generate NestJS Module Boilerplate Successfully"
2331
+ );
1891
2332
  }
1892
2333
  } catch (error) {
1893
- console.warn("[gen-db-schema] Generate NestJS Module Boilerplate failed:", error instanceof Error ? error.message : String(error));
2334
+ console.warn(
2335
+ "[gen-db-schema] Generate NestJS Module Boilerplate failed:",
2336
+ error instanceof Error ? error.message : String(error)
2337
+ );
1894
2338
  }
1895
2339
  console.log("[gen-db-schema] \u2713 Complete");
1896
2340
  } catch (err) {
1897
- console.error("[gen-db-schema] Failed:", err instanceof Error ? err.message : String(err));
2341
+ console.error(
2342
+ "[gen-db-schema] Failed:",
2343
+ err instanceof Error ? err.message : String(err)
2344
+ );
1898
2345
  exitCode = 1;
1899
2346
  } finally {
1900
2347
  if (fs4.existsSync(OUT_DIR)) {
@@ -1931,12 +2378,15 @@ var syncConfig = {
1931
2378
  type: "directory",
1932
2379
  overwrite: true
1933
2380
  },
1934
- // 2. 覆写 nest-cli.json 配置,禁止用户修改
2381
+ // 2. 智能合并 nest-cli.json 配置(保留用户自定义的 assets、plugins 等)
1935
2382
  {
1936
2383
  from: "templates/nest-cli.json",
1937
2384
  to: "nest-cli.json",
1938
- type: "file",
1939
- overwrite: true
2385
+ type: "merge-json",
2386
+ arrayMerge: {
2387
+ "compilerOptions.assets": { key: "include" },
2388
+ "compilerOptions.plugins": { key: "name" }
2389
+ }
1940
2390
  },
1941
2391
  // // 2. 追加内容到 .gitignore
1942
2392
  // {
@@ -1960,6 +2410,34 @@ var syncConfig = {
1960
2410
  type: "remove-line",
1961
2411
  to: ".gitignore",
1962
2412
  pattern: "package-lock.json"
2413
+ },
2414
+ // 5. 注册 postinstall 脚本,自动恢复 action plugins
2415
+ {
2416
+ type: "add-script",
2417
+ name: "postinstall",
2418
+ command: "fullstack-cli action-plugin init",
2419
+ overwrite: false
2420
+ },
2421
+ // 6. 替换 drizzle.config.ts(仅当文件存在时)
2422
+ {
2423
+ from: "templates/drizzle.config.ts",
2424
+ to: "drizzle.config.ts",
2425
+ type: "file",
2426
+ overwrite: true,
2427
+ onlyIfExists: true
2428
+ },
2429
+ // 7. 确保 .gitignore 包含 .agent/ 目录
2430
+ {
2431
+ type: "add-line",
2432
+ to: ".gitignore",
2433
+ line: ".agent/"
2434
+ },
2435
+ // 8. 同步 .spark_project 配置文件(总是覆盖)
2436
+ {
2437
+ from: "templates/.spark_project",
2438
+ to: ".spark_project",
2439
+ type: "file",
2440
+ overwrite: true
1963
2441
  }
1964
2442
  ],
1965
2443
  // 文件权限设置
@@ -2001,6 +2479,52 @@ function removeLineFromFile(filePath, pattern) {
2001
2479
  return true;
2002
2480
  }
2003
2481
 
2482
+ // src/utils/merge-json.ts
2483
+ function isPlainObject(value) {
2484
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2485
+ }
2486
+ function mergeArrayByKey(userArr, templateArr, key) {
2487
+ const result = [...userArr];
2488
+ for (const templateItem of templateArr) {
2489
+ if (!isPlainObject(templateItem)) continue;
2490
+ const templateKey = templateItem[key];
2491
+ const existingIndex = result.findIndex(
2492
+ (item) => isPlainObject(item) && item[key] === templateKey
2493
+ );
2494
+ if (existingIndex >= 0) {
2495
+ result[existingIndex] = templateItem;
2496
+ } else {
2497
+ result.push(templateItem);
2498
+ }
2499
+ }
2500
+ return result;
2501
+ }
2502
+ function deepMergeJson(user, template, arrayMerge = {}, currentPath = "") {
2503
+ const result = { ...user };
2504
+ for (const key of Object.keys(template)) {
2505
+ const fullPath = currentPath ? `${currentPath}.${key}` : key;
2506
+ const templateValue = template[key];
2507
+ const userValue = user[key];
2508
+ if (Array.isArray(templateValue)) {
2509
+ const mergeConfig = arrayMerge[fullPath];
2510
+ if (mergeConfig && Array.isArray(userValue)) {
2511
+ result[key] = mergeArrayByKey(userValue, templateValue, mergeConfig.key);
2512
+ } else {
2513
+ result[key] = templateValue;
2514
+ }
2515
+ } else if (isPlainObject(templateValue)) {
2516
+ if (isPlainObject(userValue)) {
2517
+ result[key] = deepMergeJson(userValue, templateValue, arrayMerge, fullPath);
2518
+ } else {
2519
+ result[key] = templateValue;
2520
+ }
2521
+ } else {
2522
+ result[key] = templateValue;
2523
+ }
2524
+ }
2525
+ return result;
2526
+ }
2527
+
2004
2528
  // src/commands/sync/run.handler.ts
2005
2529
  async function run2(options) {
2006
2530
  const userProjectRoot = process.env.INIT_CWD || process.cwd();
@@ -2053,6 +2577,22 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
2053
2577
  removeLineFromFile(destPath2, rule.pattern);
2054
2578
  return;
2055
2579
  }
2580
+ if (rule.type === "add-script") {
2581
+ const packageJsonPath = path4.join(userProjectRoot, "package.json");
2582
+ addScript(packageJsonPath, rule.name, rule.command, rule.overwrite ?? false);
2583
+ return;
2584
+ }
2585
+ if (rule.type === "add-line") {
2586
+ const destPath2 = path4.join(userProjectRoot, rule.to);
2587
+ addLineToFile(destPath2, rule.line);
2588
+ return;
2589
+ }
2590
+ if (rule.type === "merge-json") {
2591
+ const srcPath2 = path4.join(pluginRoot, rule.from);
2592
+ const destPath2 = path4.join(userProjectRoot, rule.to);
2593
+ mergeJsonFile(srcPath2, destPath2, rule.arrayMerge);
2594
+ return;
2595
+ }
2056
2596
  if (!("from" in rule)) {
2057
2597
  return;
2058
2598
  }
@@ -2067,14 +2607,18 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
2067
2607
  syncDirectory(srcPath, destPath, rule.overwrite ?? true);
2068
2608
  break;
2069
2609
  case "file":
2070
- syncFile(srcPath, destPath, rule.overwrite ?? true);
2610
+ syncFile(srcPath, destPath, rule.overwrite ?? true, rule.onlyIfExists ?? false);
2071
2611
  break;
2072
2612
  case "append":
2073
2613
  appendToFile(srcPath, destPath);
2074
2614
  break;
2075
2615
  }
2076
2616
  }
2077
- function syncFile(src, dest, overwrite = true) {
2617
+ function syncFile(src, dest, overwrite = true, onlyIfExists = false) {
2618
+ if (onlyIfExists && !fs6.existsSync(dest)) {
2619
+ console.log(`[fullstack-cli] \u25CB ${path4.basename(dest)} (skipped, target not exists)`);
2620
+ return;
2621
+ }
2078
2622
  const destDir = path4.dirname(dest);
2079
2623
  if (!fs6.existsSync(destDir)) {
2080
2624
  fs6.mkdirSync(destDir, { recursive: true });
@@ -2155,6 +2699,69 @@ function deleteDirectory(dirPath) {
2155
2699
  console.log(`[fullstack-cli] \u25CB ${path4.basename(dirPath)} (not found)`);
2156
2700
  }
2157
2701
  }
2702
+ function addScript(packageJsonPath, name, command, overwrite) {
2703
+ if (!fs6.existsSync(packageJsonPath)) {
2704
+ console.log(`[fullstack-cli] \u25CB package.json (not found)`);
2705
+ return;
2706
+ }
2707
+ const content = fs6.readFileSync(packageJsonPath, "utf-8");
2708
+ const pkg2 = JSON.parse(content);
2709
+ if (!pkg2.scripts) {
2710
+ pkg2.scripts = {};
2711
+ }
2712
+ if (pkg2.scripts[name]) {
2713
+ if (!overwrite) {
2714
+ console.log(`[fullstack-cli] \u25CB scripts.${name} (already exists)`);
2715
+ return;
2716
+ }
2717
+ }
2718
+ pkg2.scripts[name] = command;
2719
+ fs6.writeFileSync(packageJsonPath, JSON.stringify(pkg2, null, 2) + "\n");
2720
+ console.log(`[fullstack-cli] \u2713 scripts.${name}`);
2721
+ }
2722
+ function addLineToFile(filePath, line) {
2723
+ const fileName = path4.basename(filePath);
2724
+ if (!fs6.existsSync(filePath)) {
2725
+ console.log(`[fullstack-cli] \u25CB ${fileName} (not found, skipped)`);
2726
+ return;
2727
+ }
2728
+ const content = fs6.readFileSync(filePath, "utf-8");
2729
+ const lines = content.split("\n").map((l) => l.trim());
2730
+ if (lines.includes(line)) {
2731
+ console.log(`[fullstack-cli] \u25CB ${fileName} (line already exists: ${line})`);
2732
+ return;
2733
+ }
2734
+ const appendContent = (content.endsWith("\n") ? "" : "\n") + line + "\n";
2735
+ fs6.appendFileSync(filePath, appendContent);
2736
+ console.log(`[fullstack-cli] \u2713 ${fileName} (added: ${line})`);
2737
+ }
2738
+ function mergeJsonFile(src, dest, arrayMerge) {
2739
+ const fileName = path4.basename(dest);
2740
+ if (!fs6.existsSync(src)) {
2741
+ console.warn(`[fullstack-cli] Source not found: ${src}`);
2742
+ return;
2743
+ }
2744
+ const templateContent = JSON.parse(fs6.readFileSync(src, "utf-8"));
2745
+ if (!fs6.existsSync(dest)) {
2746
+ const destDir = path4.dirname(dest);
2747
+ if (!fs6.existsSync(destDir)) {
2748
+ fs6.mkdirSync(destDir, { recursive: true });
2749
+ }
2750
+ fs6.writeFileSync(dest, JSON.stringify(templateContent, null, 2) + "\n");
2751
+ console.log(`[fullstack-cli] \u2713 ${fileName} (created)`);
2752
+ return;
2753
+ }
2754
+ const userContent = JSON.parse(fs6.readFileSync(dest, "utf-8"));
2755
+ const merged = deepMergeJson(userContent, templateContent, arrayMerge ?? {});
2756
+ const userStr = JSON.stringify(userContent, null, 2);
2757
+ const mergedStr = JSON.stringify(merged, null, 2);
2758
+ if (userStr === mergedStr) {
2759
+ console.log(`[fullstack-cli] \u25CB ${fileName} (already up to date)`);
2760
+ return;
2761
+ }
2762
+ fs6.writeFileSync(dest, mergedStr + "\n");
2763
+ console.log(`[fullstack-cli] \u2713 ${fileName} (merged)`);
2764
+ }
2158
2765
 
2159
2766
  // src/commands/sync/index.ts
2160
2767
  var syncCommand = {
@@ -2167,55 +2774,443 @@ var syncCommand = {
2167
2774
  }
2168
2775
  };
2169
2776
 
2170
- // src/commands/action-plugin/utils.ts
2171
- import fs7 from "fs";
2172
- import path5 from "path";
2173
- import { spawnSync as spawnSync2, execSync } from "child_process";
2174
- function parsePluginName(input) {
2175
- const match = input.match(/^(@[^/]+\/[^@]+)(?:@(.+))?$/);
2176
- if (!match) {
2177
- throw new Error(
2178
- `Invalid plugin name format: ${input}. Expected format: @scope/name or @scope/name@version`
2179
- );
2777
+ // src/utils/telemetry.ts
2778
+ async function reportEvents(events) {
2779
+ if (events.length === 0) {
2780
+ return true;
2781
+ }
2782
+ try {
2783
+ const client = getHttpClient();
2784
+ const response = await client.post("/api/v1/studio/innerapi/resource_events", { events });
2785
+ if (!response.ok) {
2786
+ console.warn(`[telemetry] Failed to report events: ${response.status} ${response.statusText}`);
2787
+ return false;
2788
+ }
2789
+ const result = await response.json();
2790
+ if (result.status_code !== "0") {
2791
+ console.warn(`[telemetry] API error: ${result.message}`);
2792
+ return false;
2793
+ }
2794
+ return true;
2795
+ } catch (error) {
2796
+ console.warn(`[telemetry] Failed to report events: ${error instanceof Error ? error.message : error}`);
2797
+ return false;
2180
2798
  }
2181
- return {
2182
- name: match[1],
2183
- version: match[2] || "latest"
2184
- };
2185
- }
2186
- function getProjectRoot() {
2187
- return process.cwd();
2188
2799
  }
2189
- function getPackageJsonPath() {
2190
- return path5.join(getProjectRoot(), "package.json");
2800
+ async function reportInstallEvent(pluginKey, version) {
2801
+ await reportEvents([
2802
+ {
2803
+ resourceType: "plugin",
2804
+ resourceKey: pluginKey,
2805
+ eventType: "install",
2806
+ details: { version }
2807
+ }
2808
+ ]);
2191
2809
  }
2192
- function getPluginPath(pluginName) {
2193
- return path5.join(getProjectRoot(), "node_modules", pluginName);
2810
+ async function reportCreateInstanceEvent(pluginKey, version) {
2811
+ await reportEvents([
2812
+ {
2813
+ resourceType: "plugin",
2814
+ resourceKey: pluginKey,
2815
+ eventType: "create_instance",
2816
+ details: { version }
2817
+ }
2818
+ ]);
2194
2819
  }
2195
- function readPackageJson() {
2196
- const pkgPath = getPackageJsonPath();
2197
- if (!fs7.existsSync(pkgPath)) {
2198
- throw new Error("package.json not found in current directory");
2199
- }
2820
+
2821
+ // src/utils/git.ts
2822
+ import { execSync, spawnSync as spawnSync2 } from "child_process";
2823
+ import fs7 from "fs";
2824
+ import path5 from "path";
2825
+ function isGitRepository(cwd = process.cwd()) {
2200
2826
  try {
2201
- const content = fs7.readFileSync(pkgPath, "utf-8");
2202
- return JSON.parse(content);
2827
+ const gitDir = path5.join(cwd, ".git");
2828
+ if (fs7.existsSync(gitDir)) {
2829
+ return true;
2830
+ }
2831
+ const result = spawnSync2("git", ["rev-parse", "--git-dir"], {
2832
+ cwd,
2833
+ stdio: "pipe",
2834
+ encoding: "utf-8"
2835
+ });
2836
+ return result.status === 0;
2203
2837
  } catch {
2204
- throw new Error("Failed to parse package.json");
2838
+ return false;
2205
2839
  }
2206
2840
  }
2207
- function writePackageJson(pkg2) {
2208
- const pkgPath = getPackageJsonPath();
2209
- fs7.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
2210
- }
2211
- function readActionPlugins() {
2212
- const pkg2 = readPackageJson();
2213
- return pkg2.actionPlugins || {};
2841
+ function getChangedFiles(cwd = process.cwd()) {
2842
+ try {
2843
+ const output = execSync("git status --porcelain", {
2844
+ cwd,
2845
+ stdio: "pipe",
2846
+ encoding: "utf-8"
2847
+ });
2848
+ return output.split("\n").filter((line) => line.trim()).map((line) => line.substring(3));
2849
+ } catch (error) {
2850
+ const message = error instanceof Error ? error.message : String(error);
2851
+ throw new Error(`Failed to get changed files: ${message}`);
2852
+ }
2853
+ }
2854
+ function gitAddUpgradeFiles(cwd = process.cwd(), filesToStage) {
2855
+ const filteredFiles = [];
2856
+ for (const filePath of filesToStage) {
2857
+ if (fs7.existsSync(path5.join(cwd, filePath))) {
2858
+ filteredFiles.push(filePath);
2859
+ continue;
2860
+ }
2861
+ const tracked = spawnSync2("git", ["ls-files", "--error-unmatch", "--", filePath], {
2862
+ cwd,
2863
+ stdio: "pipe",
2864
+ encoding: "utf-8"
2865
+ });
2866
+ if (tracked.status === 0) {
2867
+ filteredFiles.push(filePath);
2868
+ }
2869
+ }
2870
+ if (filteredFiles.length === 0) {
2871
+ return;
2872
+ }
2873
+ const result = spawnSync2("git", ["add", "--", ...filteredFiles], {
2874
+ cwd,
2875
+ stdio: "pipe",
2876
+ encoding: "utf-8"
2877
+ });
2878
+ if (result.error || result.status !== 0) {
2879
+ const errorMsg = result.stderr || result.error?.message || "Unknown error";
2880
+ throw new Error(`git add failed: ${errorMsg}`);
2881
+ }
2882
+ }
2883
+ function hasStagedChanges(cwd = process.cwd()) {
2884
+ const result = spawnSync2("git", ["diff", "--cached", "--quiet"], {
2885
+ cwd,
2886
+ stdio: "pipe",
2887
+ encoding: "utf-8"
2888
+ });
2889
+ if (result.status === 0) {
2890
+ return false;
2891
+ }
2892
+ if (result.status === 1) {
2893
+ return true;
2894
+ }
2895
+ const errorMsg = result.stderr || result.error?.message || "Unknown error";
2896
+ throw new Error(`Failed to check staged changes: ${errorMsg}`);
2897
+ }
2898
+ function gitCommit(message, cwd = process.cwd()) {
2899
+ const result = spawnSync2("git", ["commit", "-m", message], {
2900
+ cwd,
2901
+ stdio: "pipe",
2902
+ encoding: "utf-8"
2903
+ });
2904
+ if (result.error || result.status !== 0) {
2905
+ const errorMsg = result.stderr || result.error?.message || "Unknown error";
2906
+ throw new Error(`git commit failed: ${errorMsg}`);
2907
+ }
2908
+ }
2909
+ function autoCommitUpgradeChanges(version, cwd, filesToStage, commitMessage) {
2910
+ if (!isGitRepository(cwd)) {
2911
+ console.log("[fullstack-cli] \u26A0 Not a git repository, skipping auto-commit");
2912
+ return false;
2913
+ }
2914
+ const changedFiles = getChangedFiles(cwd);
2915
+ if (changedFiles.length === 0) {
2916
+ console.log("[fullstack-cli] No changes to commit");
2917
+ return false;
2918
+ }
2919
+ try {
2920
+ gitAddUpgradeFiles(cwd, filesToStage);
2921
+ if (!hasStagedChanges(cwd)) {
2922
+ console.log("[fullstack-cli] No upgrade changes to commit");
2923
+ return false;
2924
+ }
2925
+ const message = commitMessage || `chore(upgrade): auto-upgrade by fullstack-cli
2926
+
2927
+ - Sync template files
2928
+ - Cleanup package.json config
2929
+ - Upgrade @lark-apaas dependencies (if any)
2930
+
2931
+ Auto-committed by fullstack-cli`;
2932
+ gitCommit(message, cwd);
2933
+ console.log(`[fullstack-cli] \u2713 Auto-committed ${changedFiles.length} changed file(s)`);
2934
+ return true;
2935
+ } catch (error) {
2936
+ const message = error instanceof Error ? error.message : String(error);
2937
+ throw new Error(`Failed to auto-commit changes: ${message}`);
2938
+ }
2939
+ }
2940
+
2941
+ // src/utils/package-json.ts
2942
+ import fs8 from "fs";
2943
+ import path6 from "path";
2944
+ function readPackageJson(cwd = process.cwd()) {
2945
+ const pkgPath = path6.join(cwd, "package.json");
2946
+ if (!fs8.existsSync(pkgPath)) {
2947
+ throw new Error(`package.json not found at ${pkgPath}`);
2948
+ }
2949
+ const content = fs8.readFileSync(pkgPath, "utf-8");
2950
+ return JSON.parse(content);
2951
+ }
2952
+ function writePackageJson(pkg2, cwd = process.cwd()) {
2953
+ const pkgPath = path6.join(cwd, "package.json");
2954
+ const content = JSON.stringify(pkg2, null, 2) + "\n";
2955
+ fs8.writeFileSync(pkgPath, content, "utf-8");
2956
+ }
2957
+ function removeUpgradeScript(pkg2) {
2958
+ if (!pkg2.scripts?.upgrade) {
2959
+ return false;
2960
+ }
2961
+ delete pkg2.scripts.upgrade;
2962
+ return true;
2963
+ }
2964
+ function cleanDevScript(pkg2) {
2965
+ if (!pkg2.scripts?.dev) {
2966
+ return false;
2967
+ }
2968
+ const originalDev = pkg2.scripts.dev;
2969
+ const cleanedDev = originalDev.replace(/npm\s+run\s+upgrade\s*&&\s*/g, "").replace(/npm\s+run\s+upgrade\s*$/g, "").trim();
2970
+ if (cleanedDev !== originalDev) {
2971
+ pkg2.scripts.dev = cleanedDev;
2972
+ return true;
2973
+ }
2974
+ return false;
2975
+ }
2976
+ function cleanupPackageJson(cwd = process.cwd()) {
2977
+ try {
2978
+ const pkg2 = readPackageJson(cwd);
2979
+ let changed = false;
2980
+ if (removeUpgradeScript(pkg2)) {
2981
+ console.log("[fullstack-cli] \u2713 Removed scripts.upgrade");
2982
+ changed = true;
2983
+ }
2984
+ if (cleanDevScript(pkg2)) {
2985
+ console.log("[fullstack-cli] \u2713 Cleaned scripts.dev (removed npm run upgrade)");
2986
+ changed = true;
2987
+ }
2988
+ if (changed) {
2989
+ writePackageJson(pkg2, cwd);
2990
+ }
2991
+ return changed;
2992
+ } catch (error) {
2993
+ const message = error instanceof Error ? error.message : String(error);
2994
+ console.log(`[fullstack-cli] \u26A0 Could not cleanup package.json: ${message}`);
2995
+ return false;
2996
+ }
2997
+ }
2998
+
2999
+ // src/commands/upgrade/shared/utils.ts
3000
+ import path7 from "path";
3001
+ import fs9 from "fs";
3002
+ import { fileURLToPath as fileURLToPath4 } from "url";
3003
+ function getCliVersion() {
3004
+ try {
3005
+ const __filename = fileURLToPath4(import.meta.url);
3006
+ const __dirname2 = path7.dirname(__filename);
3007
+ const pkgPath = path7.resolve(__dirname2, "../../../package.json");
3008
+ const pkgContent = fs9.readFileSync(pkgPath, "utf-8");
3009
+ const pkg2 = JSON.parse(pkgContent);
3010
+ return pkg2.version || "unknown";
3011
+ } catch {
3012
+ return "unknown";
3013
+ }
3014
+ }
3015
+
3016
+ // src/commands/upgrade/get-upgrade-files.ts
3017
+ function getUpgradeFilesToStage(disableGenOpenapi = true) {
3018
+ const syncConfig2 = genSyncConfig({ disableGenOpenapi });
3019
+ const filesToStage = /* @__PURE__ */ new Set();
3020
+ syncConfig2.sync.forEach((rule) => {
3021
+ if (rule.type === "file" || rule.type === "directory" || rule.type === "merge-json") {
3022
+ filesToStage.add(rule.to);
3023
+ } else if (rule.type === "remove-line" || rule.type === "add-line") {
3024
+ filesToStage.add(rule.to);
3025
+ } else if (rule.type === "add-script") {
3026
+ filesToStage.add("package.json");
3027
+ } else if (rule.type === "delete-file" || rule.type === "delete-directory") {
3028
+ filesToStage.add(rule.to);
3029
+ }
3030
+ });
3031
+ filesToStage.add("package.json");
3032
+ filesToStage.add("package-lock.json");
3033
+ return Array.from(filesToStage);
3034
+ }
3035
+
3036
+ // src/commands/upgrade/run.handler.ts
3037
+ async function run3(options = {}) {
3038
+ const userProjectRoot = process.env.INIT_CWD || process.cwd();
3039
+ console.log("[fullstack-cli] Starting upgrade...");
3040
+ try {
3041
+ console.log("[fullstack-cli] Step 1/3: Syncing template files...");
3042
+ await run2({ disableGenOpenapi: options.disableGenOpenapi ?? true });
3043
+ console.log("[fullstack-cli] Step 2/3: Cleaning up package.json...");
3044
+ const cleaned = cleanupPackageJson(userProjectRoot);
3045
+ if (!cleaned) {
3046
+ console.log("[fullstack-cli] \u25CB No cleanup needed");
3047
+ }
3048
+ const shouldCommit = options.commit ?? true;
3049
+ if (shouldCommit) {
3050
+ console.log("[fullstack-cli] Step 3/3: Committing changes...");
3051
+ const version = getCliVersion();
3052
+ const filesToStage = getUpgradeFilesToStage(options.disableGenOpenapi ?? true);
3053
+ autoCommitUpgradeChanges(version, userProjectRoot, filesToStage);
3054
+ } else {
3055
+ console.log("[fullstack-cli] Step 3/3: Skipping commit (--no-commit flag)");
3056
+ }
3057
+ console.log("[fullstack-cli] Upgrade completed successfully \u2705");
3058
+ } catch (error) {
3059
+ const message = error instanceof Error ? error.message : String(error);
3060
+ console.error("[fullstack-cli] Failed to upgrade:", message);
3061
+ process.exit(1);
3062
+ }
3063
+ }
3064
+
3065
+ // src/commands/upgrade/deps/run.handler.ts
3066
+ import { spawnSync as spawnSync3 } from "child_process";
3067
+ function findLarkAapaasPackages(cwd, filterPackages) {
3068
+ const pkg2 = readPackageJson(cwd);
3069
+ const allPackages = /* @__PURE__ */ new Set();
3070
+ if (pkg2.dependencies) {
3071
+ Object.keys(pkg2.dependencies).forEach((name) => {
3072
+ if (name.startsWith("@lark-apaas/")) {
3073
+ allPackages.add(name);
3074
+ }
3075
+ });
3076
+ }
3077
+ if (pkg2.devDependencies) {
3078
+ Object.keys(pkg2.devDependencies).forEach((name) => {
3079
+ if (name.startsWith("@lark-apaas/")) {
3080
+ allPackages.add(name);
3081
+ }
3082
+ });
3083
+ }
3084
+ const packages = Array.from(allPackages);
3085
+ if (filterPackages) {
3086
+ const filter = new Set(filterPackages.split(",").map((p) => p.trim()));
3087
+ return packages.filter((p) => filter.has(p));
3088
+ }
3089
+ return packages;
3090
+ }
3091
+ function upgradePackages(packages, version, cwd) {
3092
+ if (version) {
3093
+ console.log(`[fullstack-cli] Upgrading to version ${version}...`);
3094
+ packages.forEach((pkg2) => {
3095
+ const target = `${pkg2}@${version}`;
3096
+ console.log(`[fullstack-cli] Installing ${target}...`);
3097
+ const result = spawnSync3("npm", ["install", target], {
3098
+ cwd,
3099
+ stdio: "inherit"
3100
+ });
3101
+ if (result.error || result.status !== 0) {
3102
+ throw new Error(`Failed to install ${target}`);
3103
+ }
3104
+ });
3105
+ } else {
3106
+ console.log("[fullstack-cli] Upgrading to latest compatible versions...");
3107
+ packages.forEach((pkg2) => {
3108
+ console.log(`[fullstack-cli] Updating ${pkg2}...`);
3109
+ const result = spawnSync3("npm", ["update", pkg2], {
3110
+ cwd,
3111
+ stdio: "inherit"
3112
+ });
3113
+ if (result.error || result.status !== 0) {
3114
+ throw new Error(`Failed to update ${pkg2}`);
3115
+ }
3116
+ });
3117
+ }
3118
+ }
3119
+ async function run4(options = {}) {
3120
+ const cwd = process.env.INIT_CWD || process.cwd();
3121
+ console.log("[fullstack-cli] Starting dependencies upgrade...");
3122
+ try {
3123
+ console.log("[fullstack-cli] Step 1/2: Scanning @lark-apaas dependencies...");
3124
+ const packages = findLarkAapaasPackages(cwd, options.packages);
3125
+ if (packages.length === 0) {
3126
+ console.log("[fullstack-cli] No @lark-apaas packages found");
3127
+ return;
3128
+ }
3129
+ console.log(`[fullstack-cli] Found ${packages.length} @lark-apaas package(s):`);
3130
+ packages.forEach((p) => console.log(` - ${p}`));
3131
+ console.log("[fullstack-cli] Step 2/2: Upgrading packages...");
3132
+ upgradePackages(packages, options.version, cwd);
3133
+ console.log(`[fullstack-cli] \u2713 Successfully upgraded ${packages.length} package(s)`);
3134
+ console.log("[fullstack-cli] Dependencies upgrade completed successfully \u2705");
3135
+ } catch (error) {
3136
+ const message = error instanceof Error ? error.message : String(error);
3137
+ console.error("[fullstack-cli] Failed to upgrade dependencies:", message);
3138
+ process.exit(1);
3139
+ }
3140
+ }
3141
+
3142
+ // src/commands/upgrade/deps/index.ts
3143
+ var depsCommand = {
3144
+ name: "deps",
3145
+ description: "Upgrade @lark-apaas dependencies",
3146
+ register(parentCommand) {
3147
+ parentCommand.command(this.name).description(this.description).option("--version <version>", "Upgrade to specific version").option("--packages <packages>", "Only upgrade specific packages (comma-separated)").action(async (options) => {
3148
+ await run4(options);
3149
+ });
3150
+ }
3151
+ };
3152
+
3153
+ // src/commands/upgrade/index.ts
3154
+ var upgradeCommand = {
3155
+ name: "upgrade",
3156
+ description: "Upgrade template files and auto-commit changes",
3157
+ register(program) {
3158
+ const upgradeCmd = program.command(this.name).description(this.description).option("--disable-gen-openapi", "Disable generating openapi.ts").option("--no-commit", "Skip auto-commit changes").action(async (options) => {
3159
+ await run3(options);
3160
+ });
3161
+ depsCommand.register(upgradeCmd);
3162
+ }
3163
+ };
3164
+
3165
+ // src/commands/action-plugin/utils.ts
3166
+ import fs10 from "fs";
3167
+ import path8 from "path";
3168
+ import { spawnSync as spawnSync4, execSync as execSync2 } from "child_process";
3169
+ function parsePluginName(input) {
3170
+ const match = input.match(/^(@[^/]+\/[^@]+)(?:@(.+))?$/);
3171
+ if (!match) {
3172
+ throw new Error(
3173
+ `Invalid plugin name format: ${input}. Expected format: @scope/name or @scope/name@version`
3174
+ );
3175
+ }
3176
+ return {
3177
+ name: match[1],
3178
+ version: match[2] || "latest"
3179
+ };
3180
+ }
3181
+ function getProjectRoot() {
3182
+ return process.cwd();
3183
+ }
3184
+ function getPackageJsonPath() {
3185
+ return path8.join(getProjectRoot(), "package.json");
3186
+ }
3187
+ function getPluginPath(pluginName) {
3188
+ return path8.join(getProjectRoot(), "node_modules", pluginName);
3189
+ }
3190
+ function readPackageJson2() {
3191
+ const pkgPath = getPackageJsonPath();
3192
+ if (!fs10.existsSync(pkgPath)) {
3193
+ throw new Error("package.json not found in current directory");
3194
+ }
3195
+ try {
3196
+ const content = fs10.readFileSync(pkgPath, "utf-8");
3197
+ return JSON.parse(content);
3198
+ } catch {
3199
+ throw new Error("Failed to parse package.json");
3200
+ }
3201
+ }
3202
+ function writePackageJson2(pkg2) {
3203
+ const pkgPath = getPackageJsonPath();
3204
+ fs10.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
3205
+ }
3206
+ function readActionPlugins() {
3207
+ const pkg2 = readPackageJson2();
3208
+ return pkg2.actionPlugins || {};
2214
3209
  }
2215
3210
  function writeActionPlugins(plugins) {
2216
- const pkg2 = readPackageJson();
3211
+ const pkg2 = readPackageJson2();
2217
3212
  pkg2.actionPlugins = plugins;
2218
- writePackageJson(pkg2);
3213
+ writePackageJson2(pkg2);
2219
3214
  }
2220
3215
  function isPluginInstalled(pluginName) {
2221
3216
  const plugins = readActionPlugins();
@@ -2227,7 +3222,7 @@ function getInstalledPluginVersion(pluginName) {
2227
3222
  }
2228
3223
  function npmInstall(tgzPath) {
2229
3224
  console.log(`[action-plugin] Running npm install ${tgzPath}...`);
2230
- const result = spawnSync2("npm", ["install", tgzPath, "--no-save", "--no-package-lock", "--ignore-scripts"], {
3225
+ const result = spawnSync4("npm", ["install", tgzPath, "--no-save", "--no-package-lock", "--ignore-scripts"], {
2231
3226
  cwd: getProjectRoot(),
2232
3227
  stdio: "inherit"
2233
3228
  });
@@ -2239,12 +3234,12 @@ function npmInstall(tgzPath) {
2239
3234
  }
2240
3235
  }
2241
3236
  function getPackageVersion(pluginName) {
2242
- const pkgJsonPath = path5.join(getPluginPath(pluginName), "package.json");
2243
- if (!fs7.existsSync(pkgJsonPath)) {
3237
+ const pkgJsonPath = path8.join(getPluginPath(pluginName), "package.json");
3238
+ if (!fs10.existsSync(pkgJsonPath)) {
2244
3239
  return null;
2245
3240
  }
2246
3241
  try {
2247
- const content = fs7.readFileSync(pkgJsonPath, "utf-8");
3242
+ const content = fs10.readFileSync(pkgJsonPath, "utf-8");
2248
3243
  const pkg2 = JSON.parse(content);
2249
3244
  return pkg2.version || null;
2250
3245
  } catch {
@@ -2252,49 +3247,49 @@ function getPackageVersion(pluginName) {
2252
3247
  }
2253
3248
  }
2254
3249
  function readPluginPackageJson(pluginPath) {
2255
- const pkgJsonPath = path5.join(pluginPath, "package.json");
2256
- if (!fs7.existsSync(pkgJsonPath)) {
3250
+ const pkgJsonPath = path8.join(pluginPath, "package.json");
3251
+ if (!fs10.existsSync(pkgJsonPath)) {
2257
3252
  return null;
2258
3253
  }
2259
3254
  try {
2260
- const content = fs7.readFileSync(pkgJsonPath, "utf-8");
3255
+ const content = fs10.readFileSync(pkgJsonPath, "utf-8");
2261
3256
  return JSON.parse(content);
2262
3257
  } catch {
2263
3258
  return null;
2264
3259
  }
2265
3260
  }
2266
3261
  function extractTgzToNodeModules(tgzPath, pluginName) {
2267
- const nodeModulesPath = path5.join(getProjectRoot(), "node_modules");
2268
- const targetDir = path5.join(nodeModulesPath, pluginName);
2269
- const scopeDir = path5.dirname(targetDir);
2270
- if (!fs7.existsSync(scopeDir)) {
2271
- fs7.mkdirSync(scopeDir, { recursive: true });
3262
+ const nodeModulesPath = path8.join(getProjectRoot(), "node_modules");
3263
+ const targetDir = path8.join(nodeModulesPath, pluginName);
3264
+ const scopeDir = path8.dirname(targetDir);
3265
+ if (!fs10.existsSync(scopeDir)) {
3266
+ fs10.mkdirSync(scopeDir, { recursive: true });
2272
3267
  }
2273
- if (fs7.existsSync(targetDir)) {
2274
- fs7.rmSync(targetDir, { recursive: true });
3268
+ if (fs10.existsSync(targetDir)) {
3269
+ fs10.rmSync(targetDir, { recursive: true });
2275
3270
  }
2276
- const tempDir = path5.join(nodeModulesPath, ".cache", "fullstack-cli", "extract-temp");
2277
- if (fs7.existsSync(tempDir)) {
2278
- fs7.rmSync(tempDir, { recursive: true });
3271
+ const tempDir = path8.join(nodeModulesPath, ".cache", "fullstack-cli", "extract-temp");
3272
+ if (fs10.existsSync(tempDir)) {
3273
+ fs10.rmSync(tempDir, { recursive: true });
2279
3274
  }
2280
- fs7.mkdirSync(tempDir, { recursive: true });
3275
+ fs10.mkdirSync(tempDir, { recursive: true });
2281
3276
  try {
2282
- execSync(`tar -xzf "${tgzPath}" -C "${tempDir}"`, { stdio: "pipe" });
2283
- const extractedDir = path5.join(tempDir, "package");
2284
- if (fs7.existsSync(extractedDir)) {
2285
- fs7.renameSync(extractedDir, targetDir);
3277
+ execSync2(`tar -xzf "${tgzPath}" -C "${tempDir}"`, { stdio: "pipe" });
3278
+ const extractedDir = path8.join(tempDir, "package");
3279
+ if (fs10.existsSync(extractedDir)) {
3280
+ fs10.renameSync(extractedDir, targetDir);
2286
3281
  } else {
2287
- const files = fs7.readdirSync(tempDir);
3282
+ const files = fs10.readdirSync(tempDir);
2288
3283
  if (files.length === 1) {
2289
- fs7.renameSync(path5.join(tempDir, files[0]), targetDir);
3284
+ fs10.renameSync(path8.join(tempDir, files[0]), targetDir);
2290
3285
  } else {
2291
3286
  throw new Error("Unexpected tgz structure");
2292
3287
  }
2293
3288
  }
2294
3289
  return targetDir;
2295
3290
  } finally {
2296
- if (fs7.existsSync(tempDir)) {
2297
- fs7.rmSync(tempDir, { recursive: true });
3291
+ if (fs10.existsSync(tempDir)) {
3292
+ fs10.rmSync(tempDir, { recursive: true });
2298
3293
  }
2299
3294
  }
2300
3295
  }
@@ -2303,10 +3298,10 @@ function checkMissingPeerDeps(peerDeps) {
2303
3298
  return [];
2304
3299
  }
2305
3300
  const missing = [];
2306
- const nodeModulesPath = path5.join(getProjectRoot(), "node_modules");
3301
+ const nodeModulesPath = path8.join(getProjectRoot(), "node_modules");
2307
3302
  for (const [depName, _version] of Object.entries(peerDeps)) {
2308
- const depPath = path5.join(nodeModulesPath, depName);
2309
- if (!fs7.existsSync(depPath)) {
3303
+ const depPath = path8.join(nodeModulesPath, depName);
3304
+ if (!fs10.existsSync(depPath)) {
2310
3305
  missing.push(depName);
2311
3306
  }
2312
3307
  }
@@ -2317,7 +3312,7 @@ function installMissingDeps(deps) {
2317
3312
  return;
2318
3313
  }
2319
3314
  console.log(`[action-plugin] Installing missing dependencies: ${deps.join(", ")}`);
2320
- const result = spawnSync2("npm", ["install", ...deps, "--no-save", "--no-package-lock"], {
3315
+ const result = spawnSync4("npm", ["install", ...deps, "--no-save", "--no-package-lock"], {
2321
3316
  cwd: getProjectRoot(),
2322
3317
  stdio: "inherit"
2323
3318
  });
@@ -2330,40 +3325,16 @@ function installMissingDeps(deps) {
2330
3325
  }
2331
3326
  function removePluginDirectory(pluginName) {
2332
3327
  const pluginPath = getPluginPath(pluginName);
2333
- if (fs7.existsSync(pluginPath)) {
2334
- fs7.rmSync(pluginPath, { recursive: true });
3328
+ if (fs10.existsSync(pluginPath)) {
3329
+ fs10.rmSync(pluginPath, { recursive: true });
2335
3330
  console.log(`[action-plugin] Removed ${pluginName}`);
2336
3331
  }
2337
3332
  }
2338
3333
 
2339
3334
  // src/commands/action-plugin/api-client.ts
2340
3335
  import { HttpClient as HttpClient2 } from "@lark-apaas/http-client";
2341
- import fs8 from "fs";
2342
- import path6 from "path";
2343
-
2344
- // src/utils/http-client.ts
2345
- import { HttpClient } from "@lark-apaas/http-client";
2346
- var clientInstance = null;
2347
- function getHttpClient() {
2348
- if (!clientInstance) {
2349
- clientInstance = new HttpClient({
2350
- timeout: 3e4,
2351
- platform: {
2352
- enabled: true
2353
- }
2354
- });
2355
- const canaryEnv = process.env.FORCE_FRAMEWORK_CLI_CANARY_ENV;
2356
- if (canaryEnv) {
2357
- clientInstance.interceptors.request.use((req) => {
2358
- req.headers["x-tt-env"] = canaryEnv;
2359
- return req;
2360
- });
2361
- }
2362
- }
2363
- return clientInstance;
2364
- }
2365
-
2366
- // src/commands/action-plugin/api-client.ts
3336
+ import fs11 from "fs";
3337
+ import path9 from "path";
2367
3338
  var PLUGIN_CACHE_DIR = "node_modules/.cache/fullstack-cli/plugins";
2368
3339
  async function getPluginVersions(keys, latestOnly = true) {
2369
3340
  const client = getHttpClient();
@@ -2427,19 +3398,34 @@ async function downloadFromPublic(downloadURL) {
2427
3398
  return Buffer.from(arrayBuffer);
2428
3399
  }
2429
3400
  function getPluginCacheDir() {
2430
- return path6.join(process.cwd(), PLUGIN_CACHE_DIR);
3401
+ return path9.join(process.cwd(), PLUGIN_CACHE_DIR);
2431
3402
  }
2432
3403
  function ensureCacheDir() {
2433
3404
  const cacheDir = getPluginCacheDir();
2434
- if (!fs8.existsSync(cacheDir)) {
2435
- fs8.mkdirSync(cacheDir, { recursive: true });
3405
+ if (!fs11.existsSync(cacheDir)) {
3406
+ fs11.mkdirSync(cacheDir, { recursive: true });
2436
3407
  }
2437
3408
  }
2438
3409
  function getTempFilePath(pluginKey, version) {
2439
3410
  ensureCacheDir();
2440
3411
  const safeKey = pluginKey.replace(/[/@]/g, "_");
2441
- const filename = `${safeKey}-${version}.tgz`;
2442
- return path6.join(getPluginCacheDir(), filename);
3412
+ const filename = `${safeKey}@${version}.tgz`;
3413
+ return path9.join(getPluginCacheDir(), filename);
3414
+ }
3415
+ var MAX_RETRIES = 2;
3416
+ async function withRetry(operation, description, maxRetries = MAX_RETRIES) {
3417
+ let lastError;
3418
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
3419
+ try {
3420
+ return await operation();
3421
+ } catch (error) {
3422
+ lastError = error instanceof Error ? error : new Error(String(error));
3423
+ if (attempt < maxRetries) {
3424
+ console.log(`[action-plugin] ${description} failed, retrying (${attempt + 1}/${maxRetries})...`);
3425
+ }
3426
+ }
3427
+ }
3428
+ throw lastError;
2443
3429
  }
2444
3430
  async function downloadPlugin(pluginKey, requestedVersion) {
2445
3431
  console.log(`[action-plugin] Fetching plugin info for ${pluginKey}@${requestedVersion}...`);
@@ -2449,13 +3435,19 @@ async function downloadPlugin(pluginKey, requestedVersion) {
2449
3435
  let tgzBuffer;
2450
3436
  if (pluginInfo.downloadApproach === "inner") {
2451
3437
  console.log(`[action-plugin] Downloading from inner API...`);
2452
- tgzBuffer = await downloadFromInner(pluginKey, pluginInfo.version);
3438
+ tgzBuffer = await withRetry(
3439
+ () => downloadFromInner(pluginKey, pluginInfo.version),
3440
+ "Download"
3441
+ );
2453
3442
  } else {
2454
3443
  console.log(`[action-plugin] Downloading from public URL...`);
2455
- tgzBuffer = await downloadFromPublic(pluginInfo.downloadURL);
3444
+ tgzBuffer = await withRetry(
3445
+ () => downloadFromPublic(pluginInfo.downloadURL),
3446
+ "Download"
3447
+ );
2456
3448
  }
2457
3449
  const tgzPath = getTempFilePath(pluginKey, pluginInfo.version);
2458
- fs8.writeFileSync(tgzPath, tgzBuffer);
3450
+ fs11.writeFileSync(tgzPath, tgzBuffer);
2459
3451
  console.log(`[action-plugin] Downloaded to ${tgzPath} (${(tgzBuffer.length / 1024).toFixed(2)} KB)`);
2460
3452
  return {
2461
3453
  tgzPath,
@@ -2464,25 +3456,99 @@ async function downloadPlugin(pluginKey, requestedVersion) {
2464
3456
  };
2465
3457
  }
2466
3458
  function cleanupTempFile(tgzPath) {
2467
- try {
2468
- if (fs8.existsSync(tgzPath)) {
2469
- fs8.unlinkSync(tgzPath);
3459
+ }
3460
+ function getCachePath(pluginKey, version) {
3461
+ ensureCacheDir();
3462
+ const safeKey = pluginKey.replace(/[/@]/g, "_");
3463
+ const filename = `${safeKey}@${version}.tgz`;
3464
+ return path9.join(getPluginCacheDir(), filename);
3465
+ }
3466
+ function hasCachedPlugin(pluginKey, version) {
3467
+ const cachePath = getCachePath(pluginKey, version);
3468
+ return fs11.existsSync(cachePath);
3469
+ }
3470
+ function listCachedPlugins() {
3471
+ const cacheDir = getPluginCacheDir();
3472
+ if (!fs11.existsSync(cacheDir)) {
3473
+ return [];
3474
+ }
3475
+ const files = fs11.readdirSync(cacheDir);
3476
+ const result = [];
3477
+ for (const file of files) {
3478
+ if (!file.endsWith(".tgz")) continue;
3479
+ const match = file.match(/^(.+)@(.+)\.tgz$/);
3480
+ if (!match) continue;
3481
+ const [, rawName, version] = match;
3482
+ const name = rawName.replace(/^_/, "@").replace(/_/, "/");
3483
+ const filePath = path9.join(cacheDir, file);
3484
+ const stat = fs11.statSync(filePath);
3485
+ result.push({
3486
+ name,
3487
+ version,
3488
+ filePath,
3489
+ size: stat.size,
3490
+ mtime: stat.mtime
3491
+ });
3492
+ }
3493
+ return result;
3494
+ }
3495
+ function cleanAllCache() {
3496
+ const cacheDir = getPluginCacheDir();
3497
+ if (!fs11.existsSync(cacheDir)) {
3498
+ return 0;
3499
+ }
3500
+ const files = fs11.readdirSync(cacheDir);
3501
+ let count = 0;
3502
+ for (const file of files) {
3503
+ if (file.endsWith(".tgz")) {
3504
+ fs11.unlinkSync(path9.join(cacheDir, file));
3505
+ count++;
2470
3506
  }
2471
- } catch {
2472
3507
  }
3508
+ return count;
3509
+ }
3510
+ function cleanPluginCache(pluginKey, version) {
3511
+ const cacheDir = getPluginCacheDir();
3512
+ if (!fs11.existsSync(cacheDir)) {
3513
+ return 0;
3514
+ }
3515
+ const safeKey = pluginKey.replace(/[/@]/g, "_");
3516
+ const files = fs11.readdirSync(cacheDir);
3517
+ let count = 0;
3518
+ for (const file of files) {
3519
+ if (version) {
3520
+ if (file === `${safeKey}@${version}.tgz`) {
3521
+ fs11.unlinkSync(path9.join(cacheDir, file));
3522
+ count++;
3523
+ }
3524
+ } else {
3525
+ if (file.startsWith(`${safeKey}@`) && file.endsWith(".tgz")) {
3526
+ fs11.unlinkSync(path9.join(cacheDir, file));
3527
+ count++;
3528
+ }
3529
+ }
3530
+ }
3531
+ return count;
2473
3532
  }
2474
3533
 
2475
3534
  // src/commands/action-plugin/init.handler.ts
2476
3535
  async function installOneForInit(name, version) {
2477
- let tgzPath;
2478
3536
  try {
2479
3537
  const installedVersion = getPackageVersion(name);
2480
3538
  if (installedVersion === version) {
2481
3539
  return { name, version, success: true, skipped: true };
2482
3540
  }
2483
- console.log(`[action-plugin] Installing ${name}@${version}...`);
2484
- const downloadResult = await downloadPlugin(name, version);
2485
- tgzPath = downloadResult.tgzPath;
3541
+ let tgzPath;
3542
+ let fromCache = false;
3543
+ if (hasCachedPlugin(name, version)) {
3544
+ console.log(`[action-plugin] \u21BB Restoring ${name}@${version} from cache...`);
3545
+ tgzPath = getCachePath(name, version);
3546
+ fromCache = true;
3547
+ } else {
3548
+ console.log(`[action-plugin] \u2193 Downloading ${name}@${version}...`);
3549
+ const downloadResult = await downloadPlugin(name, version);
3550
+ tgzPath = downloadResult.tgzPath;
3551
+ }
2486
3552
  const pluginDir = extractTgzToNodeModules(tgzPath, name);
2487
3553
  const pluginPkg = readPluginPackageJson(pluginDir);
2488
3554
  if (pluginPkg?.peerDependencies) {
@@ -2491,16 +3557,13 @@ async function installOneForInit(name, version) {
2491
3557
  installMissingDeps(missingDeps);
2492
3558
  }
2493
3559
  }
2494
- console.log(`[action-plugin] \u2713 Installed ${name}@${version}`);
3560
+ const source = fromCache ? "from cache" : "downloaded";
3561
+ console.log(`[action-plugin] \u2713 Installed ${name}@${version} (${source})`);
2495
3562
  return { name, version, success: true };
2496
3563
  } catch (error) {
2497
3564
  const message = error instanceof Error ? error.message : String(error);
2498
3565
  console.error(`[action-plugin] \u2717 Failed to install ${name}: ${message}`);
2499
3566
  return { name, version, success: false, error: message };
2500
- } finally {
2501
- if (tgzPath) {
2502
- cleanupTempFile(tgzPath);
2503
- }
2504
3567
  }
2505
3568
  }
2506
3569
  async function init() {
@@ -2546,7 +3609,6 @@ function syncActionPluginsRecord(name, version) {
2546
3609
  }
2547
3610
  }
2548
3611
  async function installOne(nameWithVersion) {
2549
- let tgzPath;
2550
3612
  const { name, version: requestedVersion } = parsePluginName(nameWithVersion);
2551
3613
  try {
2552
3614
  console.log(`[action-plugin] Installing ${name}@${requestedVersion}...`);
@@ -2555,20 +3617,35 @@ async function installOne(nameWithVersion) {
2555
3617
  if (actualVersion === requestedVersion) {
2556
3618
  console.log(`[action-plugin] Plugin ${name}@${requestedVersion} is already installed`);
2557
3619
  syncActionPluginsRecord(name, actualVersion);
3620
+ reportCreateInstanceEvent(name, actualVersion).catch(() => {
3621
+ });
2558
3622
  return { name, version: actualVersion, success: true, skipped: true };
2559
3623
  }
2560
3624
  }
2561
- if (actualVersion && requestedVersion === "latest") {
3625
+ let targetVersion = requestedVersion;
3626
+ if (requestedVersion === "latest") {
2562
3627
  const latestInfo = await getPluginVersion(name, "latest");
2563
- if (actualVersion === latestInfo.version) {
3628
+ targetVersion = latestInfo.version;
3629
+ if (actualVersion === targetVersion) {
2564
3630
  console.log(`[action-plugin] Plugin ${name} is already up to date (version: ${actualVersion})`);
2565
3631
  syncActionPluginsRecord(name, actualVersion);
3632
+ reportCreateInstanceEvent(name, actualVersion).catch(() => {
3633
+ });
2566
3634
  return { name, version: actualVersion, success: true, skipped: true };
2567
3635
  }
2568
- console.log(`[action-plugin] Found newer version: ${latestInfo.version} (installed: ${actualVersion})`);
3636
+ console.log(`[action-plugin] Found newer version: ${targetVersion} (installed: ${actualVersion || "none"})`);
3637
+ }
3638
+ let tgzPath;
3639
+ let fromCache = false;
3640
+ if (hasCachedPlugin(name, targetVersion)) {
3641
+ console.log(`[action-plugin] \u21BB Using cached ${name}@${targetVersion}...`);
3642
+ tgzPath = getCachePath(name, targetVersion);
3643
+ fromCache = true;
3644
+ } else {
3645
+ console.log(`[action-plugin] \u2193 Downloading ${name}@${targetVersion}...`);
3646
+ const downloadResult = await downloadPlugin(name, requestedVersion);
3647
+ tgzPath = downloadResult.tgzPath;
2569
3648
  }
2570
- const downloadResult = await downloadPlugin(name, requestedVersion);
2571
- tgzPath = downloadResult.tgzPath;
2572
3649
  console.log(`[action-plugin] Extracting to node_modules...`);
2573
3650
  const pluginDir = extractTgzToNodeModules(tgzPath, name);
2574
3651
  const pluginPkg = readPluginPackageJson(pluginDir);
@@ -2578,20 +3655,21 @@ async function installOne(nameWithVersion) {
2578
3655
  installMissingDeps(missingDeps);
2579
3656
  }
2580
3657
  }
2581
- const installedVersion = getPackageVersion(name) || downloadResult.version;
3658
+ const installedVersion = getPackageVersion(name) || targetVersion;
2582
3659
  const plugins = readActionPlugins();
2583
3660
  plugins[name] = installedVersion;
2584
3661
  writeActionPlugins(plugins);
2585
- console.log(`[action-plugin] Successfully installed ${name}@${installedVersion}`);
3662
+ const source = fromCache ? "from cache" : "downloaded";
3663
+ console.log(`[action-plugin] Successfully installed ${name}@${installedVersion} (${source})`);
3664
+ reportInstallEvent(name, installedVersion).catch(() => {
3665
+ });
3666
+ reportCreateInstanceEvent(name, installedVersion).catch(() => {
3667
+ });
2586
3668
  return { name, version: installedVersion, success: true };
2587
3669
  } catch (error) {
2588
3670
  const message = error instanceof Error ? error.message : String(error);
2589
3671
  console.error(`[action-plugin] Failed to install ${name}: ${message}`);
2590
3672
  return { name, version: requestedVersion, success: false, error: message };
2591
- } finally {
2592
- if (tgzPath) {
2593
- cleanupTempFile(tgzPath);
2594
- }
2595
3673
  }
2596
3674
  }
2597
3675
  async function install(namesWithVersion) {
@@ -2745,6 +3823,58 @@ async function list() {
2745
3823
  }
2746
3824
  }
2747
3825
 
3826
+ // src/commands/action-plugin/cache.handler.ts
3827
+ async function cacheList() {
3828
+ const cached = listCachedPlugins();
3829
+ if (cached.length === 0) {
3830
+ console.log("[action-plugin] No cached plugins found");
3831
+ return;
3832
+ }
3833
+ console.log("[action-plugin] Cached plugins:\n");
3834
+ console.log(" Name Version Size Modified");
3835
+ 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\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
3836
+ let totalSize = 0;
3837
+ for (const plugin of cached) {
3838
+ const name = plugin.name.padEnd(38);
3839
+ const version = plugin.version.padEnd(10);
3840
+ const size = formatSize(plugin.size).padEnd(9);
3841
+ const mtime = plugin.mtime.toISOString().replace("T", " ").slice(0, 19);
3842
+ console.log(` ${name} ${version} ${size} ${mtime}`);
3843
+ totalSize += plugin.size;
3844
+ }
3845
+ 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\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
3846
+ console.log(` Total: ${cached.length} cached file(s), ${formatSize(totalSize)}`);
3847
+ }
3848
+ async function cacheClean(pluginName, version) {
3849
+ let count;
3850
+ if (pluginName) {
3851
+ console.log(`[action-plugin] Cleaning cache for ${pluginName}${version ? `@${version}` : ""}...`);
3852
+ count = cleanPluginCache(pluginName, version);
3853
+ if (count === 0) {
3854
+ console.log(`[action-plugin] No cached files found for ${pluginName}${version ? `@${version}` : ""}`);
3855
+ } else {
3856
+ console.log(`[action-plugin] Cleaned ${count} cached file(s)`);
3857
+ }
3858
+ } else {
3859
+ console.log("[action-plugin] Cleaning all cached plugins...");
3860
+ count = cleanAllCache();
3861
+ if (count === 0) {
3862
+ console.log("[action-plugin] No cached files found");
3863
+ } else {
3864
+ console.log(`[action-plugin] Cleaned ${count} cached file(s)`);
3865
+ }
3866
+ }
3867
+ }
3868
+ function formatSize(bytes) {
3869
+ if (bytes < 1024) {
3870
+ return `${bytes} B`;
3871
+ } else if (bytes < 1024 * 1024) {
3872
+ return `${(bytes / 1024).toFixed(1)} KB`;
3873
+ } else {
3874
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3875
+ }
3876
+ }
3877
+
2748
3878
  // src/commands/action-plugin/index.ts
2749
3879
  var initCommand = {
2750
3880
  name: "init",
@@ -2795,46 +3925,65 @@ var listCommand = {
2795
3925
  });
2796
3926
  }
2797
3927
  };
3928
+ var cacheListCommand = {
3929
+ name: "cache-list",
3930
+ description: "List all cached plugin packages",
3931
+ register(program) {
3932
+ program.command(this.name).description(this.description).action(async () => {
3933
+ await cacheList();
3934
+ });
3935
+ }
3936
+ };
3937
+ var cacheCleanCommand = {
3938
+ name: "cache-clean",
3939
+ description: "Clean cached plugin packages",
3940
+ register(program) {
3941
+ program.command(this.name).description(this.description).argument("[name]", "Plugin name to clean (e.g., @office/feishu-create-group). If not provided, clean all cache.").option("-v, --version <version>", "Specific version to clean").action(async (name, options) => {
3942
+ await cacheClean(name, options.version);
3943
+ });
3944
+ }
3945
+ };
2798
3946
  var actionPluginCommandGroup = {
2799
3947
  name: "action-plugin",
2800
3948
  description: "Manage action plugins",
2801
- commands: [initCommand, installCommand, updateCommand, removeCommand, listCommand]
3949
+ commands: [initCommand, installCommand, updateCommand, removeCommand, listCommand, cacheListCommand, cacheCleanCommand]
2802
3950
  };
2803
3951
 
2804
3952
  // src/commands/capability/utils.ts
2805
- import fs9 from "fs";
2806
- import path7 from "path";
3953
+ import fs12 from "fs";
3954
+ import { createRequire as createRequire2 } from "module";
3955
+ import path10 from "path";
2807
3956
  var CAPABILITIES_DIR = "server/capabilities";
2808
3957
  function getProjectRoot2() {
2809
3958
  return process.cwd();
2810
3959
  }
2811
3960
  function getCapabilitiesDir() {
2812
- return path7.join(getProjectRoot2(), CAPABILITIES_DIR);
3961
+ return path10.join(getProjectRoot2(), CAPABILITIES_DIR);
2813
3962
  }
2814
3963
  function getCapabilityPath(id) {
2815
- return path7.join(getCapabilitiesDir(), `${id}.json`);
3964
+ return path10.join(getCapabilitiesDir(), `${id}.json`);
2816
3965
  }
2817
3966
  function getPluginManifestPath(pluginKey) {
2818
- return path7.join(getProjectRoot2(), "node_modules", pluginKey, "manifest.json");
3967
+ return path10.join(getProjectRoot2(), "node_modules", pluginKey, "manifest.json");
2819
3968
  }
2820
3969
  function capabilitiesDirExists() {
2821
- return fs9.existsSync(getCapabilitiesDir());
3970
+ return fs12.existsSync(getCapabilitiesDir());
2822
3971
  }
2823
3972
  function listCapabilityIds() {
2824
3973
  const dir = getCapabilitiesDir();
2825
- if (!fs9.existsSync(dir)) {
3974
+ if (!fs12.existsSync(dir)) {
2826
3975
  return [];
2827
3976
  }
2828
- const files = fs9.readdirSync(dir);
3977
+ const files = fs12.readdirSync(dir);
2829
3978
  return files.filter((f) => f.endsWith(".json") && f !== "capabilities.json").map((f) => f.replace(/\.json$/, ""));
2830
3979
  }
2831
3980
  function readCapability(id) {
2832
3981
  const filePath = getCapabilityPath(id);
2833
- if (!fs9.existsSync(filePath)) {
3982
+ if (!fs12.existsSync(filePath)) {
2834
3983
  throw new Error(`Capability not found: ${id}`);
2835
3984
  }
2836
3985
  try {
2837
- const content = fs9.readFileSync(filePath, "utf-8");
3986
+ const content = fs12.readFileSync(filePath, "utf-8");
2838
3987
  return JSON.parse(content);
2839
3988
  } catch (error) {
2840
3989
  if (error instanceof SyntaxError) {
@@ -2861,11 +4010,11 @@ function readAllCapabilities() {
2861
4010
  }
2862
4011
  function readPluginManifest(pluginKey) {
2863
4012
  const manifestPath = getPluginManifestPath(pluginKey);
2864
- if (!fs9.existsSync(manifestPath)) {
4013
+ if (!fs12.existsSync(manifestPath)) {
2865
4014
  throw new Error(`Plugin not installed: ${pluginKey} (manifest.json not found)`);
2866
4015
  }
2867
4016
  try {
2868
- const content = fs9.readFileSync(manifestPath, "utf-8");
4017
+ const content = fs12.readFileSync(manifestPath, "utf-8");
2869
4018
  return JSON.parse(content);
2870
4019
  } catch (error) {
2871
4020
  if (error instanceof SyntaxError) {
@@ -2882,7 +4031,10 @@ function hasValidParamsSchema(paramsSchema) {
2882
4031
  }
2883
4032
  async function loadPlugin(pluginKey) {
2884
4033
  try {
2885
- const pluginPackage = (await import(pluginKey)).default;
4034
+ const userRequire = createRequire2(path10.join(getProjectRoot2(), "package.json"));
4035
+ const resolvedPath = userRequire.resolve(pluginKey);
4036
+ const pluginModule = await import(resolvedPath);
4037
+ const pluginPackage = pluginModule.default ?? pluginModule;
2886
4038
  if (!pluginPackage || typeof pluginPackage.create !== "function") {
2887
4039
  throw new Error(`Plugin ${pluginKey} does not export a valid create function`);
2888
4040
  }
@@ -2976,121 +4128,420 @@ function logError(message) {
2976
4128
  console.error(`[capability] Error: ${message}`);
2977
4129
  }
2978
4130
 
2979
- // src/commands/capability/list.handler.ts
2980
- async function list2(options) {
2981
- try {
2982
- if (!capabilitiesDirExists()) {
2983
- logError("server/capabilities directory not found");
2984
- process.exit(1);
4131
+ // src/commands/capability/list.handler.ts
4132
+ async function list2(options) {
4133
+ try {
4134
+ if (!capabilitiesDirExists()) {
4135
+ logError("server/capabilities directory not found");
4136
+ process.exit(1);
4137
+ }
4138
+ if (options.id) {
4139
+ await listSingle(options.id, options.summary);
4140
+ } else {
4141
+ await listAll(options.summary);
4142
+ }
4143
+ } catch (error) {
4144
+ const message = error instanceof Error ? error.message : String(error);
4145
+ logError(message);
4146
+ process.exit(1);
4147
+ }
4148
+ }
4149
+ async function listSingle(id, summary) {
4150
+ const capability = readCapability(id);
4151
+ if (summary) {
4152
+ printJson(capability);
4153
+ } else {
4154
+ const hydrated = await hydrateCapability(capability);
4155
+ printJson(hydrated);
4156
+ }
4157
+ }
4158
+ async function listAll(summary) {
4159
+ const capabilities = readAllCapabilities();
4160
+ if (capabilities.length === 0) {
4161
+ printJson([]);
4162
+ return;
4163
+ }
4164
+ if (summary) {
4165
+ printJson(capabilities);
4166
+ } else {
4167
+ const hydrated = [];
4168
+ for (const capability of capabilities) {
4169
+ const result = await hydrateCapability(capability);
4170
+ hydrated.push(result);
4171
+ }
4172
+ printJson(hydrated);
4173
+ }
4174
+ }
4175
+
4176
+ // src/commands/capability/index.ts
4177
+ var listCommand2 = {
4178
+ name: "list",
4179
+ description: "List capability configurations",
4180
+ aliases: ["ls"],
4181
+ register(program) {
4182
+ 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) => {
4183
+ await list2(options);
4184
+ });
4185
+ }
4186
+ };
4187
+ var capabilityCommandGroup = {
4188
+ name: "capability",
4189
+ description: "Manage capability configurations",
4190
+ commands: [listCommand2]
4191
+ };
4192
+
4193
+ // src/commands/component/add.handler.ts
4194
+ import { execFile } from "child_process";
4195
+
4196
+ // src/commands/component/registry-preparer.ts
4197
+ import fs13 from "fs";
4198
+ import path11 from "path";
4199
+ import os from "os";
4200
+
4201
+ // src/commands/component/service.ts
4202
+ import { mapValues } from "es-toolkit";
4203
+
4204
+ // src/commands/component/utils.ts
4205
+ import createDebug from "debug";
4206
+ var debug = createDebug("component");
4207
+
4208
+ // src/commands/component/service.ts
4209
+ async function getComponents(keys) {
4210
+ const client = getHttpClient();
4211
+ debug("\u8C03\u7528 /components/batch_get %o", keys);
4212
+ const response = await client.post(
4213
+ `/api/v1/studio/innerapi/components/batch_get?keys=${keys.join(",")}`
4214
+ );
4215
+ if (response.status !== 200) {
4216
+ throw new Error(
4217
+ `\u83B7\u53D6\u7EC4\u4EF6\u4FE1\u606F\u5931\u8D25\uFF1A${response.status} ${response.statusText}`
4218
+ );
4219
+ }
4220
+ const result = await response.json();
4221
+ if (result.status_code !== "0") {
4222
+ debug("\u63A5\u53E3\u8FD4\u56DE\u9519\u8BEF\uFF1A%o", result);
4223
+ throw new Error(`\u83B7\u53D6\u7EC4\u4EF6\u4FE1\u606F\u5931\u8D25\uFF1A${result.error_msg}`);
4224
+ }
4225
+ return mapValues(result.data.components, ([component]) => component);
4226
+ }
4227
+ async function getRegistryItem(url) {
4228
+ const client = getHttpClient();
4229
+ debug("\u4E0B\u8F7D registry-item.json\uFF1A%s", url);
4230
+ const response = await client.get(url);
4231
+ if (!response.ok) {
4232
+ throw new Error(
4233
+ `\u4E0B\u8F7D registry-item.json \u5931\u8D25\uFF1A${response.status} ${response.statusText}`
4234
+ );
4235
+ }
4236
+ const item = await response.json();
4237
+ return item;
4238
+ }
4239
+ async function sendInstallEvent(key) {
4240
+ const client = getHttpClient();
4241
+ await client.post("/api/v1/studio/innerapi/resource_events", {
4242
+ events: [
4243
+ {
4244
+ resourceType: "component",
4245
+ resourceKey: key,
4246
+ eventType: "install",
4247
+ details: {}
4248
+ }
4249
+ ]
4250
+ });
4251
+ }
4252
+
4253
+ // src/commands/component/registry-preparer.ts
4254
+ var REGISTRY_TEMP_DIR = path11.join(os.tmpdir(), "miaoda-registry");
4255
+ function parseComponentKey(key) {
4256
+ const match = key.match(/^@([^/]+)\/(.+)$/);
4257
+ if (!match) {
4258
+ throw new Error(
4259
+ `Invalid component key format: ${key}. Expected format: @scope/name`
4260
+ );
4261
+ }
4262
+ return { scope: match[1], name: match[2] };
4263
+ }
4264
+ function getLocalRegistryPath(key) {
4265
+ const { scope, name } = parseComponentKey(key);
4266
+ return path11.join(REGISTRY_TEMP_DIR, scope, `${name}.json`);
4267
+ }
4268
+ function ensureDir(dirPath) {
4269
+ if (!fs13.existsSync(dirPath)) {
4270
+ fs13.mkdirSync(dirPath, { recursive: true });
4271
+ }
4272
+ }
4273
+ async function prepareRecursive(key, visited) {
4274
+ if (visited.has(key)) {
4275
+ debug("\u8DF3\u8FC7\u5DF2\u5904\u7406\u7684\u7EC4\u4EF6: %s", key);
4276
+ return;
4277
+ }
4278
+ visited.add(key);
4279
+ debug("\u5904\u7406\u7EC4\u4EF6: %s", key);
4280
+ debug("\u83B7\u53D6\u7EC4\u4EF6\u4E0B\u8F7D\u4FE1\u606F...");
4281
+ const infoMap = await getComponents([key]);
4282
+ const info = infoMap[key];
4283
+ debug("\u7EC4\u4EF6\u4FE1\u606F: %o", info);
4284
+ if (!info) {
4285
+ throw new Error(`Component not found: ${key}`);
4286
+ }
4287
+ if (info.status !== "active") {
4288
+ throw new Error(`Component is not active: ${key}`);
4289
+ }
4290
+ debug("\u4E0B\u8F7D registry item: %s", info.downloadURL);
4291
+ const registryItem = await getRegistryItem(info.downloadURL);
4292
+ debug("registry item \u5185\u5BB9: %o", registryItem);
4293
+ const deps = registryItem.registryDependencies || [];
4294
+ debug("\u4F9D\u8D56\u5217\u8868: %o", deps);
4295
+ for (const dep of deps) {
4296
+ await prepareRecursive(dep, visited);
4297
+ }
4298
+ const rewrittenItem = {
4299
+ ...registryItem,
4300
+ registryDependencies: deps.map((dep) => getLocalRegistryPath(dep))
4301
+ };
4302
+ const localPath = getLocalRegistryPath(key);
4303
+ ensureDir(path11.dirname(localPath));
4304
+ fs13.writeFileSync(localPath, JSON.stringify(rewrittenItem, null, 2), "utf-8");
4305
+ debug("\u4FDD\u5B58\u5230: %s", localPath);
4306
+ }
4307
+ async function prepareComponentRegistryItems(id) {
4308
+ const visited = /* @__PURE__ */ new Set();
4309
+ await prepareRecursive(id, visited);
4310
+ return getLocalRegistryPath(id);
4311
+ }
4312
+ function cleanupTempDir() {
4313
+ try {
4314
+ if (fs13.existsSync(REGISTRY_TEMP_DIR)) {
4315
+ fs13.rmSync(REGISTRY_TEMP_DIR, { recursive: true, force: true });
4316
+ }
4317
+ } catch {
4318
+ }
4319
+ }
4320
+ function getDownloadedRegistryItem(itemId) {
4321
+ const localPath = getLocalRegistryPath(itemId);
4322
+ if (!fs13.existsSync(localPath)) {
4323
+ return null;
4324
+ }
4325
+ const content = fs13.readFileSync(localPath, "utf-8");
4326
+ return JSON.parse(content);
4327
+ }
4328
+
4329
+ // src/commands/component/shadcn-executor.ts
4330
+ import * as pty from "@lydell/node-pty";
4331
+ function parseOutput(output) {
4332
+ const state = {
4333
+ currentSection: null,
4334
+ files: /* @__PURE__ */ new Set()
4335
+ };
4336
+ const lines = output.split("\n");
4337
+ for (const line of lines) {
4338
+ const trimmedLine = line.trim();
4339
+ if (/Created \d+ files?:/.test(trimmedLine)) {
4340
+ state.currentSection = "created";
4341
+ continue;
2985
4342
  }
2986
- if (options.id) {
2987
- await listSingle(options.id, options.summary);
2988
- } else {
2989
- await listAll(options.summary);
4343
+ if (/Updated \d+ files?:/.test(trimmedLine)) {
4344
+ state.currentSection = "updated";
4345
+ continue;
4346
+ }
4347
+ if (/Skipped \d+ files?:/.test(trimmedLine)) {
4348
+ state.currentSection = "skipped";
4349
+ continue;
4350
+ }
4351
+ if (state.currentSection && trimmedLine.startsWith("- ")) {
4352
+ const filePath = trimmedLine.slice(2).trim();
4353
+ if (filePath && filePath.includes("/")) {
4354
+ state.files.add(filePath);
4355
+ }
4356
+ }
4357
+ if (state.currentSection && trimmedLine.length > 1 && !trimmedLine.startsWith("- ")) {
4358
+ state.currentSection = null;
2990
4359
  }
2991
- } catch (error) {
2992
- const message = error instanceof Error ? error.message : String(error);
2993
- logError(message);
2994
- process.exit(1);
2995
4360
  }
4361
+ return Array.from(state.files);
2996
4362
  }
2997
- async function listSingle(id, summary) {
2998
- const capability = readCapability(id);
2999
- if (summary) {
3000
- printJson(capability);
3001
- } else {
3002
- const hydrated = await hydrateCapability(capability);
3003
- printJson(hydrated);
3004
- }
4363
+ function toFileInfo(filePath) {
4364
+ const name = filePath.split("/").pop() || filePath;
4365
+ return { name, path: filePath };
3005
4366
  }
3006
- async function listAll(summary) {
3007
- const capabilities = readAllCapabilities();
3008
- if (capabilities.length === 0) {
3009
- printJson([]);
3010
- return;
4367
+ var PROMPT_PATTERNS = [
4368
+ // 文件覆盖确认 - 回答 n(不覆盖)
4369
+ { pattern: /overwrite/i, answer: "n\n" },
4370
+ // 主题/样式选择 - 回答 n(不安装额外主题)
4371
+ { pattern: /theme/i, answer: "n\n" },
4372
+ { pattern: /style/i, answer: "n\n" },
4373
+ // 继续确认 - 回答 y
4374
+ { pattern: /continue\?/i, answer: "y\n" },
4375
+ { pattern: /proceed\?/i, answer: "y\n" }
4376
+ ];
4377
+ async function executeShadcnAdd(registryItemPath) {
4378
+ return new Promise((resolve2) => {
4379
+ let output = "";
4380
+ const args = ["--yes", "shadcn@3.8.2", "add", registryItemPath];
4381
+ const ptyProcess = pty.spawn("npx", args, {
4382
+ name: "xterm-color",
4383
+ cols: 120,
4384
+ rows: 30,
4385
+ cwd: process.cwd(),
4386
+ env: {
4387
+ ...process.env,
4388
+ // 禁用颜色输出以便解析
4389
+ NO_COLOR: "1",
4390
+ FORCE_COLOR: "0"
4391
+ }
4392
+ });
4393
+ ptyProcess.onData((data) => {
4394
+ output += data;
4395
+ for (const { pattern, answer } of PROMPT_PATTERNS) {
4396
+ if (pattern.test(data)) {
4397
+ ptyProcess.write(answer);
4398
+ return;
4399
+ }
4400
+ }
4401
+ });
4402
+ const timeoutId = setTimeout(() => {
4403
+ ptyProcess.kill();
4404
+ resolve2({
4405
+ success: false,
4406
+ files: [],
4407
+ error: "\u6267\u884C\u8D85\u65F6"
4408
+ });
4409
+ }, 3 * 60 * 1e3);
4410
+ ptyProcess.onExit(({ exitCode }) => {
4411
+ clearTimeout(timeoutId);
4412
+ const success = exitCode === 0;
4413
+ const filePaths = parseOutput(output);
4414
+ const files = filePaths.map(toFileInfo);
4415
+ resolve2({
4416
+ success,
4417
+ files,
4418
+ error: success ? void 0 : output || `Process exited with code ${exitCode}`
4419
+ });
4420
+ });
4421
+ });
4422
+ }
4423
+
4424
+ // src/commands/component/add.handler.ts
4425
+ function runActionPluginInit() {
4426
+ return new Promise((resolve2) => {
4427
+ execFile("fullstack-cli", ["action-plugin", "init"], { cwd: process.cwd(), stdio: "ignore" }, (error) => {
4428
+ if (error) {
4429
+ debug("action-plugin init \u5931\u8D25: %s", error.message);
4430
+ }
4431
+ resolve2();
4432
+ });
4433
+ });
4434
+ }
4435
+ function printResult(result) {
4436
+ console.log(JSON.stringify(result, null, 2));
4437
+ }
4438
+ async function addComponent(key) {
4439
+ debug("\u5F00\u59CB\u5B89\u88C5\u7EC4\u4EF6: %s", key);
4440
+ debug("\u51C6\u5907 registry items...");
4441
+ const registryItemPath = await prepareComponentRegistryItems(key);
4442
+ debug("registry item \u8DEF\u5F84: %s", registryItemPath);
4443
+ const registryItem = getDownloadedRegistryItem(key);
4444
+ debug("\u83B7\u53D6\u5230 registry item: %o", registryItem);
4445
+ debug("\u6267\u884C shadcn add...");
4446
+ const executeResult = await executeShadcnAdd(registryItemPath);
4447
+ debug("shadcn \u6267\u884C\u7ED3\u679C: %o", executeResult);
4448
+ if (!executeResult.success) {
4449
+ throw new Error(executeResult.error || "\u5B89\u88C5\u5931\u8D25\uFF0C\u672A\u77E5\u539F\u56E0");
3011
4450
  }
3012
- if (summary) {
3013
- printJson(capabilities);
3014
- } else {
3015
- const hydrated = [];
3016
- for (const capability of capabilities) {
3017
- const result = await hydrateCapability(capability);
3018
- hydrated.push(result);
3019
- }
3020
- printJson(hydrated);
4451
+ return {
4452
+ success: true,
4453
+ name: key,
4454
+ description: registryItem?.description || "",
4455
+ files: executeResult.files,
4456
+ docs: registryItem?.docs || ""
4457
+ };
4458
+ }
4459
+ async function add(key) {
4460
+ try {
4461
+ const result = await addComponent(key);
4462
+ printResult(result);
4463
+ void sendInstallEvent(key);
4464
+ } catch (error) {
4465
+ const errorMessage = error instanceof Error ? error.message : "\u5B89\u88C5\u5931\u8D25\uFF0C\u539F\u56E0\u672A\u77E5";
4466
+ printResult({
4467
+ success: false,
4468
+ errors: [{ message: errorMessage }]
4469
+ });
4470
+ } finally {
4471
+ await runActionPluginInit();
4472
+ cleanupTempDir();
3021
4473
  }
3022
4474
  }
3023
4475
 
3024
- // src/commands/capability/index.ts
3025
- var listCommand2 = {
3026
- name: "list",
3027
- description: "List capability configurations",
3028
- aliases: ["ls"],
4476
+ // src/commands/component/index.ts
4477
+ var addCommand = {
4478
+ name: "add",
4479
+ description: "\u5B89\u88C5\u5999\u642D\u7EC4\u4EF6\u5E02\u573A\u4E2D\u7684\u7EC4\u4EF6",
3029
4480
  register(program) {
3030
- 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) => {
3031
- await list2(options);
4481
+ program.command(this.name).description(this.description).argument("<component>", "\u7EC4\u4EF6 ID (\u4F8B\u5982 @miaoda/button)").action(async (component) => {
4482
+ await add(component);
3032
4483
  });
3033
4484
  }
3034
4485
  };
3035
- var capabilityCommandGroup = {
3036
- name: "capability",
3037
- description: "Manage capability configurations",
3038
- commands: [listCommand2]
4486
+ var componentCommandGroup = {
4487
+ name: "component",
4488
+ description: "\u7EC4\u4EF6\u76F8\u5173\u547D\u4EE4",
4489
+ commands: [addCommand]
3039
4490
  };
3040
4491
 
3041
4492
  // src/commands/migration/version-manager.ts
3042
- import fs10 from "fs";
3043
- import path8 from "path";
4493
+ import fs14 from "fs";
4494
+ import path12 from "path";
3044
4495
  var PACKAGE_JSON = "package.json";
3045
4496
  var VERSION_FIELD = "migrationVersion";
3046
4497
  function getPackageJsonPath2() {
3047
- return path8.join(process.cwd(), PACKAGE_JSON);
4498
+ return path12.join(process.cwd(), PACKAGE_JSON);
3048
4499
  }
3049
4500
  function getCurrentVersion() {
3050
4501
  const pkgPath = getPackageJsonPath2();
3051
- if (!fs10.existsSync(pkgPath)) {
4502
+ if (!fs14.existsSync(pkgPath)) {
3052
4503
  throw new Error("package.json not found");
3053
4504
  }
3054
- const pkg2 = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
4505
+ const pkg2 = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
3055
4506
  return pkg2[VERSION_FIELD] ?? 0;
3056
4507
  }
3057
4508
  function setCurrentVersion(version) {
3058
4509
  const pkgPath = getPackageJsonPath2();
3059
- const pkg2 = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
4510
+ const pkg2 = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
3060
4511
  pkg2[VERSION_FIELD] = version;
3061
- fs10.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
4512
+ fs14.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
3062
4513
  }
3063
4514
 
3064
4515
  // src/commands/migration/versions/v001_capability/json-migrator/detector.ts
3065
- import fs12 from "fs";
3066
- import path10 from "path";
4516
+ import fs16 from "fs";
4517
+ import path14 from "path";
3067
4518
 
3068
4519
  // src/commands/migration/versions/v001_capability/utils.ts
3069
- import fs11 from "fs";
3070
- import path9 from "path";
4520
+ import fs15 from "fs";
4521
+ import path13 from "path";
3071
4522
  var CAPABILITIES_DIR2 = "server/capabilities";
3072
4523
  function getProjectRoot3() {
3073
4524
  return process.cwd();
3074
4525
  }
3075
4526
  function getCapabilitiesDir2() {
3076
- return path9.join(getProjectRoot3(), CAPABILITIES_DIR2);
4527
+ return path13.join(getProjectRoot3(), CAPABILITIES_DIR2);
3077
4528
  }
3078
4529
  function getPluginManifestPath2(pluginKey) {
3079
- return path9.join(getProjectRoot3(), "node_modules", pluginKey, "manifest.json");
4530
+ return path13.join(getProjectRoot3(), "node_modules", pluginKey, "manifest.json");
3080
4531
  }
3081
4532
 
3082
4533
  // src/commands/migration/versions/v001_capability/json-migrator/detector.ts
3083
4534
  function detectJsonMigration() {
3084
4535
  const capabilitiesDir = getCapabilitiesDir2();
3085
- const oldFilePath = path10.join(capabilitiesDir, "capabilities.json");
3086
- if (!fs12.existsSync(oldFilePath)) {
4536
+ const oldFilePath = path14.join(capabilitiesDir, "capabilities.json");
4537
+ if (!fs16.existsSync(oldFilePath)) {
3087
4538
  return {
3088
4539
  needsMigration: false,
3089
4540
  reason: "capabilities.json not found"
3090
4541
  };
3091
4542
  }
3092
4543
  try {
3093
- const content = fs12.readFileSync(oldFilePath, "utf-8");
4544
+ const content = fs16.readFileSync(oldFilePath, "utf-8");
3094
4545
  const parsed = JSON.parse(content);
3095
4546
  if (!Array.isArray(parsed)) {
3096
4547
  return {
@@ -3141,8 +4592,8 @@ async function check(options) {
3141
4592
  }
3142
4593
 
3143
4594
  // src/commands/migration/versions/v001_capability/json-migrator/index.ts
3144
- import fs13 from "fs";
3145
- import path11 from "path";
4595
+ import fs17 from "fs";
4596
+ import path15 from "path";
3146
4597
 
3147
4598
  // src/commands/migration/versions/v001_capability/mapping.ts
3148
4599
  var DEFAULT_PLUGIN_VERSION = "1.0.0";
@@ -3372,18 +4823,18 @@ function transformCapabilities(oldCapabilities) {
3372
4823
  // src/commands/migration/versions/v001_capability/json-migrator/index.ts
3373
4824
  function loadExistingCapabilities() {
3374
4825
  const capabilitiesDir = getCapabilitiesDir2();
3375
- if (!fs13.existsSync(capabilitiesDir)) {
4826
+ if (!fs17.existsSync(capabilitiesDir)) {
3376
4827
  return [];
3377
4828
  }
3378
- const files = fs13.readdirSync(capabilitiesDir);
4829
+ const files = fs17.readdirSync(capabilitiesDir);
3379
4830
  const capabilities = [];
3380
4831
  for (const file of files) {
3381
4832
  if (file === "capabilities.json" || !file.endsWith(".json")) {
3382
4833
  continue;
3383
4834
  }
3384
4835
  try {
3385
- const filePath = path11.join(capabilitiesDir, file);
3386
- const content = fs13.readFileSync(filePath, "utf-8");
4836
+ const filePath = path15.join(capabilitiesDir, file);
4837
+ const content = fs17.readFileSync(filePath, "utf-8");
3387
4838
  const capability = JSON.parse(content);
3388
4839
  if (capability.id && capability.pluginKey) {
3389
4840
  capabilities.push(capability);
@@ -3441,9 +4892,9 @@ async function migrateJsonFiles(options) {
3441
4892
  }
3442
4893
  const capabilitiesDir = getCapabilitiesDir2();
3443
4894
  for (const cap of newCapabilities) {
3444
- const filePath = path11.join(capabilitiesDir, `${cap.id}.json`);
4895
+ const filePath = path15.join(capabilitiesDir, `${cap.id}.json`);
3445
4896
  const content = JSON.stringify(cap, null, 2);
3446
- fs13.writeFileSync(filePath, content, "utf-8");
4897
+ fs17.writeFileSync(filePath, content, "utf-8");
3447
4898
  console.log(` \u2713 Created: ${cap.id}.json`);
3448
4899
  }
3449
4900
  return {
@@ -3455,11 +4906,11 @@ async function migrateJsonFiles(options) {
3455
4906
  }
3456
4907
 
3457
4908
  // src/commands/migration/versions/v001_capability/plugin-installer/detector.ts
3458
- import fs14 from "fs";
4909
+ import fs18 from "fs";
3459
4910
  function isPluginInstalled2(pluginKey) {
3460
4911
  const actionPlugins = readActionPlugins();
3461
4912
  const manifestPath = getPluginManifestPath2(pluginKey);
3462
- return fs14.existsSync(manifestPath) && !!actionPlugins[pluginKey];
4913
+ return fs18.existsSync(manifestPath) && !!actionPlugins[pluginKey];
3463
4914
  }
3464
4915
  function detectPluginsToInstall(capabilities) {
3465
4916
  const pluginKeys = /* @__PURE__ */ new Set();
@@ -3535,12 +4986,12 @@ async function installPlugins(capabilities, options) {
3535
4986
  }
3536
4987
 
3537
4988
  // src/commands/migration/versions/v001_capability/code-migrator/index.ts
3538
- import path13 from "path";
4989
+ import path17 from "path";
3539
4990
  import { Project as Project3 } from "ts-morph";
3540
4991
 
3541
4992
  // src/commands/migration/versions/v001_capability/code-migrator/scanner.ts
3542
- import fs15 from "fs";
3543
- import path12 from "path";
4993
+ import fs19 from "fs";
4994
+ import path16 from "path";
3544
4995
  var EXCLUDED_DIRS = [
3545
4996
  "node_modules",
3546
4997
  "dist",
@@ -3555,9 +5006,9 @@ var EXCLUDED_PATTERNS = [
3555
5006
  /\.d\.ts$/
3556
5007
  ];
3557
5008
  function scanDirectory(dir, files = []) {
3558
- const entries = fs15.readdirSync(dir, { withFileTypes: true });
5009
+ const entries = fs19.readdirSync(dir, { withFileTypes: true });
3559
5010
  for (const entry of entries) {
3560
- const fullPath = path12.join(dir, entry.name);
5011
+ const fullPath = path16.join(dir, entry.name);
3561
5012
  if (entry.isDirectory()) {
3562
5013
  if (EXCLUDED_DIRS.includes(entry.name)) {
3563
5014
  continue;
@@ -3573,14 +5024,14 @@ function scanDirectory(dir, files = []) {
3573
5024
  return files;
3574
5025
  }
3575
5026
  function scanServerFiles() {
3576
- const serverDir = path12.join(getProjectRoot3(), "server");
3577
- if (!fs15.existsSync(serverDir)) {
5027
+ const serverDir = path16.join(getProjectRoot3(), "server");
5028
+ if (!fs19.existsSync(serverDir)) {
3578
5029
  return [];
3579
5030
  }
3580
5031
  return scanDirectory(serverDir);
3581
5032
  }
3582
5033
  function hasCapabilityImport(filePath) {
3583
- const content = fs15.readFileSync(filePath, "utf-8");
5034
+ const content = fs19.readFileSync(filePath, "utf-8");
3584
5035
  return /import\s+.*from\s+['"][^'"]*capabilities[^'"]*['"]/.test(content);
3585
5036
  }
3586
5037
  function scanFilesToMigrate() {
@@ -3957,7 +5408,7 @@ function analyzeFile(project, filePath, actionNameMap) {
3957
5408
  const callSites = analyzeCallSites(sourceFile, imports);
3958
5409
  const classInfo = analyzeClass(sourceFile);
3959
5410
  const { canMigrate, reason } = canAutoMigrate(classInfo);
3960
- const relativePath = path13.relative(getProjectRoot3(), filePath);
5411
+ const relativePath = path17.relative(getProjectRoot3(), filePath);
3961
5412
  return {
3962
5413
  filePath: relativePath,
3963
5414
  imports,
@@ -3968,7 +5419,7 @@ function analyzeFile(project, filePath, actionNameMap) {
3968
5419
  };
3969
5420
  }
3970
5421
  function migrateFile(project, analysis, dryRun) {
3971
- const absolutePath = path13.join(getProjectRoot3(), analysis.filePath);
5422
+ const absolutePath = path17.join(getProjectRoot3(), analysis.filePath);
3972
5423
  if (!analysis.canAutoMigrate) {
3973
5424
  return {
3974
5425
  filePath: analysis.filePath,
@@ -4071,17 +5522,17 @@ function getSuggestion(analysis) {
4071
5522
  }
4072
5523
 
4073
5524
  // src/commands/migration/versions/v001_capability/cleanup.ts
4074
- import fs16 from "fs";
4075
- import path14 from "path";
5525
+ import fs20 from "fs";
5526
+ import path18 from "path";
4076
5527
  function cleanupOldFiles(capabilities, dryRun) {
4077
5528
  const deletedFiles = [];
4078
5529
  const errors = [];
4079
5530
  const capabilitiesDir = getCapabilitiesDir2();
4080
- const oldJsonPath = path14.join(capabilitiesDir, "capabilities.json");
4081
- if (fs16.existsSync(oldJsonPath)) {
5531
+ const oldJsonPath = path18.join(capabilitiesDir, "capabilities.json");
5532
+ if (fs20.existsSync(oldJsonPath)) {
4082
5533
  try {
4083
5534
  if (!dryRun) {
4084
- fs16.unlinkSync(oldJsonPath);
5535
+ fs20.unlinkSync(oldJsonPath);
4085
5536
  }
4086
5537
  deletedFiles.push("capabilities.json");
4087
5538
  } catch (error) {
@@ -4089,11 +5540,11 @@ function cleanupOldFiles(capabilities, dryRun) {
4089
5540
  }
4090
5541
  }
4091
5542
  for (const cap of capabilities) {
4092
- const tsFilePath = path14.join(capabilitiesDir, `${cap.id}.ts`);
4093
- if (fs16.existsSync(tsFilePath)) {
5543
+ const tsFilePath = path18.join(capabilitiesDir, `${cap.id}.ts`);
5544
+ if (fs20.existsSync(tsFilePath)) {
4094
5545
  try {
4095
5546
  if (!dryRun) {
4096
- fs16.unlinkSync(tsFilePath);
5547
+ fs20.unlinkSync(tsFilePath);
4097
5548
  }
4098
5549
  deletedFiles.push(`${cap.id}.ts`);
4099
5550
  } catch (error) {
@@ -4109,8 +5560,8 @@ function cleanupOldFiles(capabilities, dryRun) {
4109
5560
  }
4110
5561
 
4111
5562
  // src/commands/migration/versions/v001_capability/report-generator.ts
4112
- import fs17 from "fs";
4113
- import path15 from "path";
5563
+ import fs21 from "fs";
5564
+ import path19 from "path";
4114
5565
  var REPORT_FILE = "capability-migration-report.md";
4115
5566
  function printSummary(result) {
4116
5567
  const { jsonMigration, pluginInstallation, codeMigration, cleanup } = result;
@@ -4273,15 +5724,15 @@ async function generateReport(result) {
4273
5724
  }
4274
5725
  lines.push("");
4275
5726
  const logDir = process.env.LOG_DIR || "logs";
4276
- if (!fs17.existsSync(logDir)) {
5727
+ if (!fs21.existsSync(logDir)) {
4277
5728
  return;
4278
5729
  }
4279
- const reportDir = path15.join(logDir, "migration");
4280
- if (!fs17.existsSync(reportDir)) {
4281
- fs17.mkdirSync(reportDir, { recursive: true });
5730
+ const reportDir = path19.join(logDir, "migration");
5731
+ if (!fs21.existsSync(reportDir)) {
5732
+ fs21.mkdirSync(reportDir, { recursive: true });
4282
5733
  }
4283
- const reportPath = path15.join(reportDir, REPORT_FILE);
4284
- fs17.writeFileSync(reportPath, lines.join("\n"), "utf-8");
5734
+ const reportPath = path19.join(reportDir, REPORT_FILE);
5735
+ fs21.writeFileSync(reportPath, lines.join("\n"), "utf-8");
4285
5736
  console.log(`\u{1F4C4} Report generated: ${reportPath}`);
4286
5737
  }
4287
5738
 
@@ -4458,7 +5909,7 @@ function buildResult(jsonMigration, pluginInstallation, codeMigration, cleanup)
4458
5909
  }
4459
5910
 
4460
5911
  // src/commands/migration/versions/v001_capability/run.ts
4461
- async function run3(options) {
5912
+ async function run5(options) {
4462
5913
  try {
4463
5914
  const migrationOptions = {
4464
5915
  dryRun: options.dryRun ?? false
@@ -4523,7 +5974,7 @@ var v001CapabilityMigration = {
4523
5974
  name: "capability",
4524
5975
  description: "Migrate capability configurations from old format (capabilities.json array) to new format (individual JSON files)",
4525
5976
  check,
4526
- run: run3
5977
+ run: run5
4527
5978
  };
4528
5979
 
4529
5980
  // src/commands/migration/versions/index.ts
@@ -4813,10 +6264,10 @@ var migrationCommand = {
4813
6264
  };
4814
6265
 
4815
6266
  // src/commands/read-logs/index.ts
4816
- import path16 from "path";
6267
+ import path20 from "path";
4817
6268
 
4818
6269
  // src/commands/read-logs/std-utils.ts
4819
- import fs18 from "fs";
6270
+ import fs22 from "fs";
4820
6271
  function formatStdPrefixTime(localTime) {
4821
6272
  const match = localTime.match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
4822
6273
  if (!match) return localTime;
@@ -4846,11 +6297,11 @@ function stripPrefixFromStdLine(line) {
4846
6297
  return `[${time}] ${content}`;
4847
6298
  }
4848
6299
  function readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, isMarker) {
4849
- const stat = fs18.statSync(filePath);
6300
+ const stat = fs22.statSync(filePath);
4850
6301
  if (stat.size === 0) {
4851
6302
  return { lines: [], markerFound: false, totalLinesCount: 0 };
4852
6303
  }
4853
- const fd = fs18.openSync(filePath, "r");
6304
+ const fd = fs22.openSync(filePath, "r");
4854
6305
  const chunkSize = 64 * 1024;
4855
6306
  let position = stat.size;
4856
6307
  let remainder = "";
@@ -4864,7 +6315,7 @@ function readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, isMarke
4864
6315
  const length = Math.min(chunkSize, position);
4865
6316
  position -= length;
4866
6317
  const buffer = Buffer.alloc(length);
4867
- fs18.readSync(fd, buffer, 0, length, position);
6318
+ fs22.readSync(fd, buffer, 0, length, position);
4868
6319
  let chunk = buffer.toString("utf8");
4869
6320
  if (remainder) {
4870
6321
  chunk += remainder;
@@ -4906,7 +6357,7 @@ function readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, isMarke
4906
6357
  }
4907
6358
  }
4908
6359
  } finally {
4909
- fs18.closeSync(fd);
6360
+ fs22.closeSync(fd);
4910
6361
  }
4911
6362
  return { lines: collected.reverse(), markerFound, totalLinesCount };
4912
6363
  }
@@ -4927,21 +6378,21 @@ function readServerStdSegment(filePath, maxLines, offset) {
4927
6378
  }
4928
6379
 
4929
6380
  // src/commands/read-logs/tail.ts
4930
- import fs19 from "fs";
6381
+ import fs23 from "fs";
4931
6382
  function fileExists(filePath) {
4932
6383
  try {
4933
- fs19.accessSync(filePath, fs19.constants.F_OK | fs19.constants.R_OK);
6384
+ fs23.accessSync(filePath, fs23.constants.F_OK | fs23.constants.R_OK);
4934
6385
  return true;
4935
6386
  } catch {
4936
6387
  return false;
4937
6388
  }
4938
6389
  }
4939
6390
  function readFileTailLines(filePath, maxLines) {
4940
- const stat = fs19.statSync(filePath);
6391
+ const stat = fs23.statSync(filePath);
4941
6392
  if (stat.size === 0) {
4942
6393
  return [];
4943
6394
  }
4944
- const fd = fs19.openSync(filePath, "r");
6395
+ const fd = fs23.openSync(filePath, "r");
4945
6396
  const chunkSize = 64 * 1024;
4946
6397
  const chunks = [];
4947
6398
  let position = stat.size;
@@ -4951,13 +6402,13 @@ function readFileTailLines(filePath, maxLines) {
4951
6402
  const length = Math.min(chunkSize, position);
4952
6403
  position -= length;
4953
6404
  const buffer = Buffer.alloc(length);
4954
- fs19.readSync(fd, buffer, 0, length, position);
6405
+ fs23.readSync(fd, buffer, 0, length, position);
4955
6406
  chunks.unshift(buffer.toString("utf8"));
4956
6407
  const chunkLines = buffer.toString("utf8").split("\n").length - 1;
4957
6408
  collectedLines += chunkLines;
4958
6409
  }
4959
6410
  } finally {
4960
- fs19.closeSync(fd);
6411
+ fs23.closeSync(fd);
4961
6412
  }
4962
6413
  const content = chunks.join("");
4963
6414
  const allLines = content.split("\n");
@@ -4973,11 +6424,11 @@ function readFileTailLines(filePath, maxLines) {
4973
6424
  return allLines.slice(allLines.length - maxLines);
4974
6425
  }
4975
6426
  function readFileTailNonEmptyLinesWithOffset(filePath, maxLines, offset) {
4976
- const stat = fs19.statSync(filePath);
6427
+ const stat = fs23.statSync(filePath);
4977
6428
  if (stat.size === 0) {
4978
6429
  return { lines: [], totalLinesCount: 0 };
4979
6430
  }
4980
- const fd = fs19.openSync(filePath, "r");
6431
+ const fd = fs23.openSync(filePath, "r");
4981
6432
  const chunkSize = 64 * 1024;
4982
6433
  let position = stat.size;
4983
6434
  let remainder = "";
@@ -4989,7 +6440,7 @@ function readFileTailNonEmptyLinesWithOffset(filePath, maxLines, offset) {
4989
6440
  const length = Math.min(chunkSize, position);
4990
6441
  position -= length;
4991
6442
  const buffer = Buffer.alloc(length);
4992
- fs19.readSync(fd, buffer, 0, length, position);
6443
+ fs23.readSync(fd, buffer, 0, length, position);
4993
6444
  let chunk = buffer.toString("utf8");
4994
6445
  if (remainder) {
4995
6446
  chunk += remainder;
@@ -5020,7 +6471,7 @@ function readFileTailNonEmptyLinesWithOffset(filePath, maxLines, offset) {
5020
6471
  }
5021
6472
  }
5022
6473
  } finally {
5023
- fs19.closeSync(fd);
6474
+ fs23.closeSync(fd);
5024
6475
  }
5025
6476
  return { lines: collected.reverse(), totalLinesCount };
5026
6477
  }
@@ -5033,13 +6484,19 @@ function readClientStdSegment(filePath, maxLines, offset) {
5033
6484
  function extractClientStdSegment(lines, maxLines, offset) {
5034
6485
  const bodyLines = lines.map(stripPrefixFromStdLine);
5035
6486
  const hotStartMarkers = [
6487
+ // Webpack markers
5036
6488
  /file change detected\..*incremental compilation/i,
5037
6489
  /starting incremental compilation/i,
5038
6490
  /starting compilation/i,
5039
6491
  /\bcompiling\b/i,
5040
- /\brecompil/i
6492
+ /\brecompil/i,
6493
+ // Vite markers - format: "3:11:46 PM [vite] (client) hmr update ..."
6494
+ /VITE v[\d.]+.*ready/i,
6495
+ /\[vite\].*page reload/i,
6496
+ /\[vite\].*hmr update/i
5041
6497
  ];
5042
6498
  const hotEndMarkers = [
6499
+ // Webpack markers
5043
6500
  /file change detected\..*incremental compilation/i,
5044
6501
  /\bwebpack compiled\b/i,
5045
6502
  /compiled successfully/i,
@@ -5050,14 +6507,25 @@ function extractClientStdSegment(lines, maxLines, offset) {
5050
6507
  /\bhmr\b/i,
5051
6508
  /hot update/i,
5052
6509
  /\bhot reload\b/i,
5053
- /\bhmr update\b/i
6510
+ /\bhmr update\b/i,
6511
+ // Vite markers
6512
+ /VITE v[\d.]+.*ready/i,
6513
+ /\[vite\].*page reload/i,
6514
+ /\[vite\].*hmr update/i,
6515
+ /\[vite\].*hmr invalidate/i,
6516
+ /\[vite\].*internal server error/i,
6517
+ /\[vite\].*pre-transform error/i
5054
6518
  ];
5055
6519
  const compiledMarkers = [
6520
+ // Webpack markers
5056
6521
  /\bwebpack compiled\b/i,
5057
6522
  /compiled successfully/i,
5058
6523
  /compiled with warnings/i,
5059
6524
  /compiled with errors/i,
5060
- /failed to compile/i
6525
+ /failed to compile/i,
6526
+ // Vite markers
6527
+ /VITE v[\d.]+.*ready/i,
6528
+ /\[vite\].*hmr update/i
5061
6529
  ];
5062
6530
  let startIndex = -1;
5063
6531
  for (let i = bodyLines.length - 1; i >= 0; i -= 1) {
@@ -5110,19 +6578,42 @@ function extractClientStdSegment(lines, maxLines, offset) {
5110
6578
  }
5111
6579
  const segment = startIndex === -1 ? bodyLines : bodyLines.slice(startIndex);
5112
6580
  if (segment.length === 0) {
5113
- return { lines: [], totalLinesCount: 0 };
6581
+ return { lines: [], totalLinesCount: 0, allLines: [] };
5114
6582
  }
5115
6583
  const totalLinesCount = segment.length;
5116
6584
  const endExclusive = Math.max(0, segment.length - offset);
5117
6585
  const start = Math.max(0, endExclusive - maxLines);
5118
6586
  return {
5119
6587
  lines: segment.slice(start, endExclusive),
5120
- totalLinesCount
6588
+ totalLinesCount,
6589
+ allLines: segment
6590
+ };
6591
+ }
6592
+
6593
+ // src/commands/read-logs/dev.ts
6594
+ function readDevSegment(filePath, maxLines, offset) {
6595
+ const marker = (line) => {
6596
+ if (!line) return false;
6597
+ if (line.includes("Dev session started")) return true;
6598
+ return false;
6599
+ };
6600
+ const segment = readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, marker);
6601
+ return { lines: segment.lines, totalLinesCount: segment.totalLinesCount };
6602
+ }
6603
+
6604
+ // src/commands/read-logs/dev-std.ts
6605
+ function readDevStdSegment(filePath, maxLines, offset) {
6606
+ const marker = (line) => {
6607
+ if (!line) return false;
6608
+ if (line.includes("Dev session started")) return true;
6609
+ return false;
5121
6610
  };
6611
+ const segment = readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, marker);
6612
+ return { lines: segment.lines, totalLinesCount: segment.totalLinesCount };
5122
6613
  }
5123
6614
 
5124
6615
  // src/commands/read-logs/json-lines.ts
5125
- import fs20 from "fs";
6616
+ import fs24 from "fs";
5126
6617
  function normalizePid(value) {
5127
6618
  if (typeof value === "number") {
5128
6619
  return String(value);
@@ -5173,11 +6664,11 @@ function buildWantedLevelSet(levels) {
5173
6664
  return set.size > 0 ? set : null;
5174
6665
  }
5175
6666
  function readJsonLinesLastPid(filePath, maxLines, offset, levels) {
5176
- const stat = fs20.statSync(filePath);
6667
+ const stat = fs24.statSync(filePath);
5177
6668
  if (stat.size === 0) {
5178
6669
  return { lines: [], totalLinesCount: 0 };
5179
6670
  }
5180
- const fd = fs20.openSync(filePath, "r");
6671
+ const fd = fs24.openSync(filePath, "r");
5181
6672
  const chunkSize = 64 * 1024;
5182
6673
  let position = stat.size;
5183
6674
  let remainder = "";
@@ -5192,7 +6683,7 @@ function readJsonLinesLastPid(filePath, maxLines, offset, levels) {
5192
6683
  const length = Math.min(chunkSize, position);
5193
6684
  position -= length;
5194
6685
  const buffer = Buffer.alloc(length);
5195
- fs20.readSync(fd, buffer, 0, length, position);
6686
+ fs24.readSync(fd, buffer, 0, length, position);
5196
6687
  let chunk = buffer.toString("utf8");
5197
6688
  if (remainder) {
5198
6689
  chunk += remainder;
@@ -5254,7 +6745,7 @@ function readJsonLinesLastPid(filePath, maxLines, offset, levels) {
5254
6745
  }
5255
6746
  }
5256
6747
  } finally {
5257
- fs20.closeSync(fd);
6748
+ fs24.closeSync(fd);
5258
6749
  }
5259
6750
  return { lines: collected.reverse(), totalLinesCount };
5260
6751
  }
@@ -5297,11 +6788,11 @@ function extractTraceId(obj) {
5297
6788
  function readJsonLinesByTraceId(filePath, traceId, maxLines, offset, levels) {
5298
6789
  const wanted = traceId.trim();
5299
6790
  if (!wanted) return { lines: [], totalLinesCount: 0 };
5300
- const stat = fs20.statSync(filePath);
6791
+ const stat = fs24.statSync(filePath);
5301
6792
  if (stat.size === 0) {
5302
6793
  return { lines: [], totalLinesCount: 0 };
5303
6794
  }
5304
- const fd = fs20.openSync(filePath, "r");
6795
+ const fd = fs24.openSync(filePath, "r");
5305
6796
  const chunkSize = 64 * 1024;
5306
6797
  let position = stat.size;
5307
6798
  let remainder = "";
@@ -5314,7 +6805,7 @@ function readJsonLinesByTraceId(filePath, traceId, maxLines, offset, levels) {
5314
6805
  const length = Math.min(chunkSize, position);
5315
6806
  position -= length;
5316
6807
  const buffer = Buffer.alloc(length);
5317
- fs20.readSync(fd, buffer, 0, length, position);
6808
+ fs24.readSync(fd, buffer, 0, length, position);
5318
6809
  let chunk = buffer.toString("utf8");
5319
6810
  if (remainder) {
5320
6811
  chunk += remainder;
@@ -5367,7 +6858,7 @@ function readJsonLinesByTraceId(filePath, traceId, maxLines, offset, levels) {
5367
6858
  }
5368
6859
  }
5369
6860
  } finally {
5370
- fs20.closeSync(fd);
6861
+ fs24.closeSync(fd);
5371
6862
  }
5372
6863
  return { lines: collected.reverse(), totalLinesCount };
5373
6864
  }
@@ -5376,11 +6867,11 @@ function readJsonLinesTailByLevel(filePath, maxLines, offset, levels) {
5376
6867
  if (!wantedLevelSet) {
5377
6868
  return { lines: [], totalLinesCount: 0 };
5378
6869
  }
5379
- const stat = fs20.statSync(filePath);
6870
+ const stat = fs24.statSync(filePath);
5380
6871
  if (stat.size === 0) {
5381
6872
  return { lines: [], totalLinesCount: 0 };
5382
6873
  }
5383
- const fd = fs20.openSync(filePath, "r");
6874
+ const fd = fs24.openSync(filePath, "r");
5384
6875
  const chunkSize = 64 * 1024;
5385
6876
  let position = stat.size;
5386
6877
  let remainder = "";
@@ -5392,7 +6883,7 @@ function readJsonLinesTailByLevel(filePath, maxLines, offset, levels) {
5392
6883
  const length = Math.min(chunkSize, position);
5393
6884
  position -= length;
5394
6885
  const buffer = Buffer.alloc(length);
5395
- fs20.readSync(fd, buffer, 0, length, position);
6886
+ fs24.readSync(fd, buffer, 0, length, position);
5396
6887
  let chunk = buffer.toString("utf8");
5397
6888
  if (remainder) {
5398
6889
  chunk += remainder;
@@ -5439,13 +6930,13 @@ function readJsonLinesTailByLevel(filePath, maxLines, offset, levels) {
5439
6930
  }
5440
6931
  }
5441
6932
  } finally {
5442
- fs20.closeSync(fd);
6933
+ fs24.closeSync(fd);
5443
6934
  }
5444
6935
  return { lines: collected.reverse(), totalLinesCount };
5445
6936
  }
5446
6937
 
5447
6938
  // src/commands/read-logs/index.ts
5448
- var LOG_TYPES = ["server", "trace", "server-std", "client-std", "browser"];
6939
+ var LOG_TYPES = ["server", "trace", "server-std", "client-std", "dev", "dev-std", "install-dep-std", "browser"];
5449
6940
  function normalizeObjectKey(key) {
5450
6941
  return key.toLowerCase().replace(/_/g, "");
5451
6942
  }
@@ -5607,6 +7098,15 @@ async function readLatestLogLinesMeta(options) {
5607
7098
  if (options.type === "client-std") {
5608
7099
  return readClientStdSegment(filePath, maxLines, offset);
5609
7100
  }
7101
+ if (options.type === "dev") {
7102
+ return readDevSegment(filePath, maxLines, offset);
7103
+ }
7104
+ if (options.type === "dev-std") {
7105
+ return readDevStdSegment(filePath, maxLines, offset);
7106
+ }
7107
+ if (options.type === "install-dep-std") {
7108
+ return readFileTailNonEmptyLinesWithOffset(filePath, maxLines, offset);
7109
+ }
5610
7110
  const traceId = typeof options.traceId === "string" ? options.traceId.trim() : "";
5611
7111
  if (traceId) {
5612
7112
  return readJsonLinesByTraceId(filePath, traceId, maxLines, offset, levels);
@@ -5623,12 +7123,21 @@ async function readLatestLogLinesMeta(options) {
5623
7123
  return { lines: [], totalLinesCount: 0 };
5624
7124
  }
5625
7125
  async function readLogsJsonResult(options) {
5626
- const { lines, totalLinesCount } = await readLatestLogLinesMeta(options);
5627
- if (options.type === "server-std" || options.type === "client-std") {
7126
+ const { lines, totalLinesCount, allLines } = await readLatestLogLinesMeta(options);
7127
+ if (options.type === "server-std" || options.type === "client-std" || options.type === "dev" || options.type === "dev-std" || options.type === "install-dep-std") {
7128
+ const linesForErrorCheck = allLines ?? lines;
7129
+ const stackTracePattern = /\bat\s+\S+\s+\([^)]+:\d+:\d+\)/;
7130
+ const linesToFilter = allLines ?? lines;
7131
+ const filteredLines = linesToFilter.filter((line) => !stackTracePattern.test(line));
7132
+ const maxLines = options.maxLines ?? 30;
7133
+ const offset = options.offset ?? 0;
7134
+ const endExclusive = Math.max(0, filteredLines.length - offset);
7135
+ const start = Math.max(0, endExclusive - maxLines);
7136
+ const paginatedLogs = filteredLines.slice(start, endExclusive);
5628
7137
  return {
5629
- hasError: hasErrorInStdLines(lines),
5630
- totalLinesCount,
5631
- logs: lines
7138
+ hasError: hasErrorInStdLines(linesForErrorCheck),
7139
+ totalLinesCount: filteredLines.length,
7140
+ logs: paginatedLogs
5632
7141
  };
5633
7142
  }
5634
7143
  const logs = [];
@@ -5655,25 +7164,34 @@ async function readLogsJsonResult(options) {
5655
7164
  };
5656
7165
  }
5657
7166
  function resolveLogFilePath(logDir, type) {
5658
- const base = path16.isAbsolute(logDir) ? logDir : path16.join(process.cwd(), logDir);
7167
+ const base = path20.isAbsolute(logDir) ? logDir : path20.join(process.cwd(), logDir);
5659
7168
  if (type === "server") {
5660
- return path16.join(base, "server.log");
7169
+ return path20.join(base, "server.log");
5661
7170
  }
5662
7171
  if (type === "trace") {
5663
- return path16.join(base, "trace.log");
7172
+ return path20.join(base, "trace.log");
5664
7173
  }
5665
7174
  if (type === "server-std") {
5666
- return path16.join(base, "server.std.log");
7175
+ return path20.join(base, "server.std.log");
5667
7176
  }
5668
7177
  if (type === "client-std") {
5669
- return path16.join(base, "client.std.log");
7178
+ return path20.join(base, "client.std.log");
7179
+ }
7180
+ if (type === "dev") {
7181
+ return path20.join(base, "dev.log");
7182
+ }
7183
+ if (type === "dev-std") {
7184
+ return path20.join(base, "dev.std.log");
7185
+ }
7186
+ if (type === "install-dep-std") {
7187
+ return path20.join(base, "install-dep.std.log");
5670
7188
  }
5671
7189
  if (type === "browser") {
5672
- return path16.join(base, "browser.log");
7190
+ return path20.join(base, "browser.log");
5673
7191
  }
5674
7192
  throw new Error(`Unsupported log type: ${type}`);
5675
7193
  }
5676
- async function run4(options) {
7194
+ async function run6(options) {
5677
7195
  const result = await readLogsJsonResult(options);
5678
7196
  process.stdout.write(JSON.stringify(result) + "\n");
5679
7197
  }
@@ -5715,7 +7233,7 @@ var readLogsCommand = {
5715
7233
  const offset = parseNonNegativeInt(rawOptions.offset, "--offset");
5716
7234
  const traceId = typeof rawOptions.traceId === "string" ? rawOptions.traceId : void 0;
5717
7235
  const levels = parseCommaSeparatedList(rawOptions.level);
5718
- await run4({ logDir, type, maxLines, offset, traceId, levels });
7236
+ await run6({ logDir, type, maxLines, offset, traceId, levels });
5719
7237
  } catch (error) {
5720
7238
  const message = error instanceof Error ? error.message : String(error);
5721
7239
  process.stderr.write(message + "\n");
@@ -5725,23 +7243,342 @@ var readLogsCommand = {
5725
7243
  }
5726
7244
  };
5727
7245
 
7246
+ // src/commands/build/types.ts
7247
+ var SCENE_CONFIGS = {
7248
+ pipeline: {
7249
+ name: "pipeline",
7250
+ requiredOptions: ["commitId"],
7251
+ buildRequestBody: (opts) => ({ commitID: opts.commitId })
7252
+ },
7253
+ static: {
7254
+ name: "static",
7255
+ requiredOptions: [],
7256
+ buildRequestBody: () => ({ commitID: "" })
7257
+ }
7258
+ };
7259
+ var UPLOAD_STATIC_DEFAULTS = {
7260
+ staticDir: "shared/static",
7261
+ tosutilPath: "tosutil",
7262
+ endpoint: "tos-cn-beijing.volces.com",
7263
+ region: "cn-beijing"
7264
+ };
7265
+
7266
+ // src/commands/build/api-client.ts
7267
+ async function genArtifactUploadCredential(appId, body) {
7268
+ const client = getHttpClient();
7269
+ const url = `/v1/app/${appId}/pipeline/gen_artifact_upload_credential`;
7270
+ const response = await client.post(url, body);
7271
+ if (!response.ok || response.status !== 200) {
7272
+ throw new Error(
7273
+ `gen_artifact_upload_credential \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
7274
+ );
7275
+ }
7276
+ return response.json();
7277
+ }
7278
+ async function getDefaultBucketId(appId) {
7279
+ const client = getHttpClient();
7280
+ const url = `/v1/app/${appId}/storage/inner/staticBucket`;
7281
+ const response = await client.post(url, {});
7282
+ if (!response.ok || response.status !== 200) {
7283
+ throw new Error(
7284
+ `getOrCreateStaticBucket \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
7285
+ );
7286
+ }
7287
+ const data = await response.json();
7288
+ if (data.status_code !== "0") {
7289
+ throw new Error(`getOrCreateStaticBucket \u8FD4\u56DE\u5F02\u5E38, status_code: ${data.status_code}`);
7290
+ }
7291
+ const bucketId = data?.data?.bucketID;
7292
+ if (!bucketId) {
7293
+ throw new Error(`\u672A\u627E\u5230\u5E94\u7528 ${appId} \u7684\u9759\u6001\u8D44\u6E90\u5B58\u50A8\u6876`);
7294
+ }
7295
+ return bucketId;
7296
+ }
7297
+ async function preUploadStaticAttachment(appId, bucketId) {
7298
+ const client = getHttpClient();
7299
+ const url = `/v1/app/${appId}/storage/bucket/${bucketId}/preUploadStatic`;
7300
+ const response = await client.post(url, {});
7301
+ if (!response.ok || response.status !== 200) {
7302
+ throw new Error(
7303
+ `preUploadStatic \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
7304
+ );
7305
+ }
7306
+ return response.json();
7307
+ }
7308
+ async function uploadStaticAttachmentCallback(appId, bucketId, body) {
7309
+ const client = getHttpClient();
7310
+ const url = `/v1/app/${appId}/storage/bucket/${bucketId}/object/callbackStatic`;
7311
+ const response = await client.post(url, body);
7312
+ if (!response.ok || response.status !== 200) {
7313
+ throw new Error(
7314
+ `callbackStatic \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
7315
+ );
7316
+ }
7317
+ return response.json();
7318
+ }
7319
+
7320
+ // src/commands/build/get-token.handler.ts
7321
+ async function getToken(options) {
7322
+ try {
7323
+ const sceneConfig = SCENE_CONFIGS[options.scene];
7324
+ if (!sceneConfig) {
7325
+ const available = Object.keys(SCENE_CONFIGS).join(", ");
7326
+ console.error(
7327
+ `[build] Error: invalid scene "${options.scene}". Available scenes: ${available}`
7328
+ );
7329
+ process.exit(1);
7330
+ }
7331
+ for (const key of sceneConfig.requiredOptions) {
7332
+ if (!options[key]) {
7333
+ console.error(
7334
+ `[build] Error: --${camelToKebab(key)} is required for scene "${options.scene}"`
7335
+ );
7336
+ process.exit(1);
7337
+ }
7338
+ }
7339
+ const body = sceneConfig.buildRequestBody(options);
7340
+ const response = await genArtifactUploadCredential(options.appId, body);
7341
+ console.log(JSON.stringify(response));
7342
+ } catch (error) {
7343
+ const message = error instanceof Error ? error.message : String(error);
7344
+ console.error(`[build] Error: ${message}`);
7345
+ process.exit(1);
7346
+ }
7347
+ }
7348
+ function camelToKebab(str) {
7349
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
7350
+ }
7351
+
7352
+ // src/commands/build/upload-static.handler.ts
7353
+ import * as fs25 from "fs";
7354
+ import * as path21 from "path";
7355
+ import { execFileSync } from "child_process";
7356
+ function readCredentialsFromEnv() {
7357
+ const uploadPrefix = process.env.STATIC_UPLOAD_PREFIX;
7358
+ const uploadID = process.env.STATIC_UPLOAD_ID;
7359
+ const bucketId = process.env.STATIC_UPLOAD_BUCKET_ID;
7360
+ const accessKeyID = process.env.STATIC_UPLOAD_AK;
7361
+ const secretAccessKey = process.env.STATIC_UPLOAD_SK;
7362
+ const sessionToken = process.env.STATIC_UPLOAD_TOKEN;
7363
+ if (!uploadPrefix || !uploadID || !bucketId || !accessKeyID || !secretAccessKey || !sessionToken) {
7364
+ return null;
7365
+ }
7366
+ return { uploadPrefix, uploadID, bucketId, accessKeyID, secretAccessKey, sessionToken };
7367
+ }
7368
+ var LOG_PREFIX = "[upload-static]";
7369
+ async function uploadStatic(options) {
7370
+ try {
7371
+ const {
7372
+ appId,
7373
+ staticDir = UPLOAD_STATIC_DEFAULTS.staticDir,
7374
+ tosutilPath = UPLOAD_STATIC_DEFAULTS.tosutilPath,
7375
+ endpoint = UPLOAD_STATIC_DEFAULTS.endpoint,
7376
+ region = UPLOAD_STATIC_DEFAULTS.region
7377
+ } = options;
7378
+ const resolvedStaticDir = path21.resolve(staticDir);
7379
+ if (!fs25.existsSync(resolvedStaticDir)) {
7380
+ console.error(`${LOG_PREFIX} \u76EE\u5F55\u4E0D\u5B58\u5728: ${resolvedStaticDir}\uFF0C\u8DF3\u8FC7\u4E0A\u4F20`);
7381
+ return;
7382
+ }
7383
+ if (isDirEmpty(resolvedStaticDir)) {
7384
+ console.error(`${LOG_PREFIX} \u76EE\u5F55\u4E3A\u7A7A: ${resolvedStaticDir}\uFF0C\u8DF3\u8FC7\u4E0A\u4F20`);
7385
+ return;
7386
+ }
7387
+ const resolvedTosutil = resolveTosutilPath(tosutilPath);
7388
+ if (!resolvedTosutil) {
7389
+ throw new Error(
7390
+ `tosutil \u4E0D\u5B58\u5728: ${tosutilPath}\u3002\u8BF7\u786E\u4FDD tosutil \u5DF2\u5B89\u88C5\u5728\u7CFB\u7EDF PATH \u4E2D\u6216\u901A\u8FC7 --tosutil-path \u6307\u5B9A\u8DEF\u5F84\u3002`
7391
+ );
7392
+ }
7393
+ let uploadPrefix;
7394
+ let uploadID;
7395
+ let bucketId;
7396
+ let accessKeyID;
7397
+ let secretAccessKey;
7398
+ let sessionToken;
7399
+ const envCredentials = readCredentialsFromEnv();
7400
+ if (envCredentials) {
7401
+ console.error(`${LOG_PREFIX} \u4F7F\u7528\u73AF\u5883\u53D8\u91CF\u4E2D\u7684\u4E0A\u4F20\u51ED\u8BC1`);
7402
+ ({ uploadPrefix, uploadID, bucketId, accessKeyID, secretAccessKey, sessionToken } = envCredentials);
7403
+ } else {
7404
+ console.error(`${LOG_PREFIX} \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E\uFF0C\u8C03\u7528 preUploadStatic...`);
7405
+ bucketId = await resolveBucketId(appId);
7406
+ const preUploadResp = await fetchPreUpload(appId, bucketId);
7407
+ const { uploadCredential } = preUploadResp.data;
7408
+ ({ uploadPrefix, uploadID } = preUploadResp.data);
7409
+ ({ AccessKeyID: accessKeyID, SecretAccessKey: secretAccessKey, SessionToken: sessionToken } = uploadCredential);
7410
+ }
7411
+ console.error(`${LOG_PREFIX} \u4E0A\u4F20\u76EE\u6807: ${uploadPrefix}`);
7412
+ console.error(`${LOG_PREFIX} \u914D\u7F6E tosutil...`);
7413
+ configureTosutil(resolvedTosutil, {
7414
+ endpoint,
7415
+ region,
7416
+ accessKeyID,
7417
+ secretAccessKey,
7418
+ sessionToken
7419
+ });
7420
+ console.error(`${LOG_PREFIX} \u4E0A\u4F20 ${resolvedStaticDir} -> ${uploadPrefix}`);
7421
+ uploadToTos(resolvedTosutil, resolvedStaticDir, uploadPrefix);
7422
+ console.error(`${LOG_PREFIX} tosutil \u4E0A\u4F20\u5B8C\u6210`);
7423
+ console.error(`${LOG_PREFIX} \u8C03\u7528 callbackStatic (uploadID: ${uploadID})...`);
7424
+ const callbackResp = await uploadStaticAttachmentCallback(appId, bucketId, { uploadID });
7425
+ if (callbackResp.status_code !== "0") {
7426
+ throw new Error(`callbackStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${callbackResp.status_code}`);
7427
+ }
7428
+ const attachments = callbackResp.data?.attachments || [];
7429
+ console.error(`${LOG_PREFIX} \u4E0A\u4F20\u5B8C\u6210\uFF0C\u5171 ${attachments.length} \u4E2A\u6587\u4EF6`);
7430
+ console.log(JSON.stringify(callbackResp));
7431
+ } catch (error) {
7432
+ const message = error instanceof Error ? error.message : String(error);
7433
+ console.error(`${LOG_PREFIX} Error: ${message}`);
7434
+ process.exit(1);
7435
+ }
7436
+ }
7437
+ function resolveTosutilPath(tosutilPath) {
7438
+ if (path21.isAbsolute(tosutilPath)) {
7439
+ return fs25.existsSync(tosutilPath) ? tosutilPath : null;
7440
+ }
7441
+ try {
7442
+ const resolved = execFileSync("which", [tosutilPath], { encoding: "utf-8" }).trim();
7443
+ return resolved || null;
7444
+ } catch {
7445
+ return null;
7446
+ }
7447
+ }
7448
+ async function fetchPreUpload(appId, bucketId) {
7449
+ const response = await preUploadStaticAttachment(appId, bucketId);
7450
+ if (response.status_code !== "0") {
7451
+ throw new Error(`preUploadStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${response.status_code}`);
7452
+ }
7453
+ const { uploadPrefix, uploadID, uploadCredential } = response.data || {};
7454
+ if (!uploadPrefix || !uploadID) {
7455
+ throw new Error("preUploadStatic \u8FD4\u56DE\u6570\u636E\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11 uploadPrefix \u6216 uploadID");
7456
+ }
7457
+ if (!uploadCredential?.AccessKeyID || !uploadCredential?.SecretAccessKey || !uploadCredential?.SessionToken) {
7458
+ throw new Error("preUploadStatic \u8FD4\u56DE\u7684\u51ED\u8BC1\u5B57\u6BB5\u4E0D\u5B8C\u6574");
7459
+ }
7460
+ return response;
7461
+ }
7462
+ function configureTosutil(tosutilPath, config) {
7463
+ const { endpoint, region, accessKeyID, secretAccessKey, sessionToken } = config;
7464
+ execFileSync(
7465
+ tosutilPath,
7466
+ ["config", "-e", endpoint, "-i", accessKeyID, "-k", secretAccessKey, "-t", sessionToken, "-re", region],
7467
+ { stdio: "pipe" }
7468
+ );
7469
+ }
7470
+ function uploadToTos(tosutilPath, sourceDir, destUrl) {
7471
+ execFileSync(
7472
+ tosutilPath,
7473
+ ["cp", sourceDir, destUrl, "-r", "-flat", "-j", "5", "-p", "3", "-ps", "10485760", "-f"],
7474
+ { stdio: "inherit" }
7475
+ );
7476
+ }
7477
+ async function resolveBucketId(appId) {
7478
+ console.error(`${LOG_PREFIX} \u83B7\u53D6\u9ED8\u8BA4\u5B58\u50A8\u6876...`);
7479
+ const bucketId = await getDefaultBucketId(appId);
7480
+ console.error(`${LOG_PREFIX} \u9ED8\u8BA4\u5B58\u50A8\u6876: ${bucketId}`);
7481
+ return bucketId;
7482
+ }
7483
+ function isDirEmpty(dirPath) {
7484
+ const entries = fs25.readdirSync(dirPath);
7485
+ return entries.length === 0;
7486
+ }
7487
+
7488
+ // src/commands/build/pre-upload-static.handler.ts
7489
+ var LOG_PREFIX2 = "[pre-upload-static]";
7490
+ function shellEscape(value) {
7491
+ return value.replace(/["\\`$]/g, "\\$&");
7492
+ }
7493
+ async function preUploadStatic(options) {
7494
+ try {
7495
+ const { appId } = options;
7496
+ console.error(`${LOG_PREFIX2} \u83B7\u53D6\u9ED8\u8BA4\u5B58\u50A8\u6876...`);
7497
+ const bucketId = await getDefaultBucketId(appId);
7498
+ console.error(`${LOG_PREFIX2} \u9ED8\u8BA4\u5B58\u50A8\u6876: ${bucketId}`);
7499
+ console.error(`${LOG_PREFIX2} \u8C03\u7528 preUploadStatic...`);
7500
+ const response = await preUploadStaticAttachment(appId, bucketId);
7501
+ if (response.status_code !== "0") {
7502
+ console.error(`${LOG_PREFIX2} preUploadStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${response.status_code}`);
7503
+ return;
7504
+ }
7505
+ const { downloadURLPrefix, uploadPrefix, uploadID, uploadCredential } = response.data || {};
7506
+ if (!downloadURLPrefix || !uploadPrefix || !uploadID) {
7507
+ console.error(`${LOG_PREFIX2} preUploadStatic \u8FD4\u56DE\u6570\u636E\u4E0D\u5B8C\u6574`);
7508
+ return;
7509
+ }
7510
+ if (!uploadCredential?.AccessKeyID || !uploadCredential?.SecretAccessKey || !uploadCredential?.SessionToken) {
7511
+ console.error(`${LOG_PREFIX2} preUploadStatic \u8FD4\u56DE\u7684\u51ED\u8BC1\u5B57\u6BB5\u4E0D\u5B8C\u6574`);
7512
+ return;
7513
+ }
7514
+ console.log(`export STATIC_ASSETS_BASE_URL="${shellEscape(downloadURLPrefix)}"`);
7515
+ console.log(`export STATIC_UPLOAD_PREFIX="${shellEscape(uploadPrefix)}"`);
7516
+ console.log(`export STATIC_UPLOAD_ID="${shellEscape(uploadID)}"`);
7517
+ console.log(`export STATIC_UPLOAD_AK="${shellEscape(uploadCredential.AccessKeyID)}"`);
7518
+ console.log(`export STATIC_UPLOAD_SK="${shellEscape(uploadCredential.SecretAccessKey)}"`);
7519
+ console.log(`export STATIC_UPLOAD_TOKEN="${shellEscape(uploadCredential.SessionToken)}"`);
7520
+ console.log(`export STATIC_UPLOAD_BUCKET_ID="${shellEscape(bucketId)}"`);
7521
+ console.error(`${LOG_PREFIX2} \u73AF\u5883\u53D8\u91CF\u5DF2\u8F93\u51FA`);
7522
+ } catch (error) {
7523
+ const message = error instanceof Error ? error.message : String(error);
7524
+ console.error(`${LOG_PREFIX2} Warning: ${message}`);
7525
+ }
7526
+ }
7527
+
7528
+ // src/commands/build/index.ts
7529
+ var getTokenCommand = {
7530
+ name: "get-token",
7531
+ description: "Get artifact upload credential (STI token)",
7532
+ register(program) {
7533
+ program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").requiredOption("--scene <scene>", "Build scene (pipeline, static)").option("--commit-id <id>", "Git commit ID (required for pipeline scene)").action(async (options) => {
7534
+ await getToken(options);
7535
+ });
7536
+ }
7537
+ };
7538
+ var uploadStaticCommand = {
7539
+ name: "upload-static",
7540
+ description: "Upload shared/static files to TOS",
7541
+ register(program) {
7542
+ program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").option("--static-dir <dir>", "Static files directory", UPLOAD_STATIC_DEFAULTS.staticDir).option("--tosutil-path <path>", "Path to tosutil binary", UPLOAD_STATIC_DEFAULTS.tosutilPath).option("--endpoint <endpoint>", "TOS endpoint", UPLOAD_STATIC_DEFAULTS.endpoint).option("--region <region>", "TOS region", UPLOAD_STATIC_DEFAULTS.region).action(async (options) => {
7543
+ await uploadStatic(options);
7544
+ });
7545
+ }
7546
+ };
7547
+ var preUploadStaticCommand = {
7548
+ name: "pre-upload-static",
7549
+ description: "Get TOS upload info and output as env vars for build.sh eval",
7550
+ register(program) {
7551
+ program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").action(async (options) => {
7552
+ await preUploadStatic(options);
7553
+ });
7554
+ }
7555
+ };
7556
+ var buildCommandGroup = {
7557
+ name: "build",
7558
+ description: "Build related commands",
7559
+ commands: [getTokenCommand, uploadStaticCommand, preUploadStaticCommand]
7560
+ };
7561
+
5728
7562
  // src/commands/index.ts
5729
7563
  var commands = [
5730
7564
  genDbSchemaCommand,
5731
7565
  syncCommand,
7566
+ upgradeCommand,
5732
7567
  actionPluginCommandGroup,
5733
7568
  capabilityCommandGroup,
7569
+ componentCommandGroup,
5734
7570
  migrationCommand,
5735
- readLogsCommand
7571
+ readLogsCommand,
7572
+ buildCommandGroup
5736
7573
  ];
5737
7574
 
5738
7575
  // src/index.ts
5739
- var envPath = path17.join(process.cwd(), ".env");
5740
- if (fs21.existsSync(envPath)) {
7576
+ var envPath = path22.join(process.cwd(), ".env");
7577
+ if (fs26.existsSync(envPath)) {
5741
7578
  dotenvConfig({ path: envPath });
5742
7579
  }
5743
- var __dirname = path17.dirname(fileURLToPath4(import.meta.url));
5744
- var pkg = JSON.parse(fs21.readFileSync(path17.join(__dirname, "../package.json"), "utf-8"));
7580
+ var __dirname = path22.dirname(fileURLToPath5(import.meta.url));
7581
+ var pkg = JSON.parse(fs26.readFileSync(path22.join(__dirname, "../package.json"), "utf-8"));
5745
7582
  var cli = new FullstackCLI(pkg.version);
5746
7583
  cli.useAll(commands);
5747
7584
  cli.run();