@lark-apaas/fullstack-cli 1.1.16-beta.1 → 1.1.16-beta.11

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
@@ -481,6 +481,7 @@ var replaceUnknownTransform = {
481
481
  transform(ctx) {
482
482
  const { sourceFile, stats } = ctx;
483
483
  const fullText = sourceFile.getFullText();
484
+ const replacements = [];
484
485
  sourceFile.forEachDescendant((node) => {
485
486
  if (!Node5.isCallExpression(node)) {
486
487
  return;
@@ -511,13 +512,23 @@ var replaceUnknownTransform = {
511
512
  break;
512
513
  }
513
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) {
514
524
  expression.replaceWithText(factoryName);
515
525
  if (isArrayType && foundKnownType) {
516
526
  const parent = node.getParent();
517
527
  if (Node5.isPropertyAccessExpression(parent) && parent.getName() === "array") {
518
528
  const grandParent = parent.getParent();
519
529
  if (Node5.isCallExpression(grandParent)) {
520
- grandParent.replaceWithText(node.getText());
530
+ const nodeText = node.getText();
531
+ grandParent.replaceWithText(nodeText);
521
532
  }
522
533
  }
523
534
  }
@@ -526,7 +537,7 @@ var replaceUnknownTransform = {
526
537
  } else {
527
538
  stats.fallbackToText++;
528
539
  }
529
- });
540
+ }
530
541
  const todoCommentRegex = /\/\/ TODO: failed to parse database type '[^']+'\s*\n/g;
531
542
  const currentText = sourceFile.getFullText();
532
543
  const cleanedText = currentText.replace(todoCommentRegex, "");
@@ -1030,6 +1041,8 @@ async function fetchColumnComments(connectionString, options = {}) {
1030
1041
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1031
1042
  const url = new URL(connectionString);
1032
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)`);
1033
1046
  const sql = postgres(connectionString, {
1034
1047
  connect_timeout: Math.ceil(timeoutMs / 1e3),
1035
1048
  idle_timeout: Math.ceil(timeoutMs / 1e3)
@@ -1058,6 +1071,7 @@ async function fetchColumnComments(connectionString, options = {}) {
1058
1071
  const key = `${row.tableName}.${row.columnName}`;
1059
1072
  commentMap.set(key, row.comment);
1060
1073
  }
1074
+ console.log(`[fetchColumnComments] \u2190 Fetched ${commentMap.size} column comments (${Date.now() - start}ms)`);
1061
1075
  return commentMap;
1062
1076
  } finally {
1063
1077
  await sql.end().catch(() => {
@@ -1156,6 +1170,79 @@ function addJsonbTypeComments(source, columnComments) {
1156
1170
  return { text: result.join("\n"), added };
1157
1171
  }
1158
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
+
1159
1246
  // src/commands/db/gen-dbschema/transforms/text/table-aliases.ts
1160
1247
  var TABLE_ALIAS_MARKER = "// table aliases";
1161
1248
  function generateTableAliases(source) {
@@ -1204,13 +1291,16 @@ function postprocessSchema(rawSource, options = {}) {
1204
1291
  source = inlineCustomTypes(source);
1205
1292
  const jsonbCommentsResult = addJsonbTypeComments(source, options.columnComments);
1206
1293
  source = jsonbCommentsResult.text;
1294
+ const syncedCommentsResult = addSyncedTableComments(source, options.syncedTableMap);
1295
+ source = syncedCommentsResult.text;
1207
1296
  source = generateTableAliases(source);
1208
1297
  source = formatSource(source);
1209
1298
  return {
1210
1299
  source,
1211
1300
  astStats,
1212
1301
  patchedDefects: patchResult.fixed,
1213
- addedJsonbComments: jsonbCommentsResult.added
1302
+ addedJsonbComments: jsonbCommentsResult.added,
1303
+ addedSyncedComments: syncedCommentsResult.added
1214
1304
  };
1215
1305
  }
1216
1306
  function logStats(result, prefix = "[postprocess]") {
@@ -1260,6 +1350,9 @@ function logStats(result, prefix = "[postprocess]") {
1260
1350
  if (result.addedJsonbComments > 0) {
1261
1351
  console.info(`${prefix} Added ${result.addedJsonbComments} JSDoc comments for jsonb fields`);
1262
1352
  }
1353
+ if (result.addedSyncedComments > 0) {
1354
+ console.info(`${prefix} Added ${result.addedSyncedComments} comments for synced tables/fields`);
1355
+ }
1263
1356
  }
1264
1357
 
1265
1358
  // src/commands/db/gen-dbschema/index.ts
@@ -1270,7 +1363,10 @@ async function postprocessDrizzleSchema(targetPath, options = {}) {
1270
1363
  return void 0;
1271
1364
  }
1272
1365
  const rawSource = fs3.readFileSync(resolvedPath, "utf8");
1273
- const result = postprocessSchema(rawSource, { columnComments: options.columnComments });
1366
+ const result = postprocessSchema(rawSource, {
1367
+ columnComments: options.columnComments,
1368
+ syncedTableMap: options.syncedTableMap
1369
+ });
1274
1370
  fs3.writeFileSync(resolvedPath, result.source, "utf8");
1275
1371
  logStats(result, "[postprocess-drizzle-schema]");
1276
1372
  return {
@@ -1280,10 +1376,113 @@ async function postprocessDrizzleSchema(targetPath, options = {}) {
1280
1376
  patchedDefects: result.patchedDefects,
1281
1377
  replacedTimestamps: result.astStats.replacedTimestamp,
1282
1378
  replacedDefaultNow: result.astStats.replacedDefaultNow,
1283
- addedJsonbComments: result.addedJsonbComments
1379
+ addedJsonbComments: result.addedJsonbComments,
1380
+ addedSyncedComments: result.addedSyncedComments
1284
1381
  };
1285
1382
  }
1286
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
+
1287
1486
  // src/commands/db/gen-nest-resource/generator.ts
1288
1487
  import { pluralize } from "inflection";
1289
1488
 
@@ -1962,7 +2161,9 @@ async function run(options = {}) {
1962
2161
  }
1963
2162
  const databaseUrl = process.env.SUDA_DATABASE_URL;
1964
2163
  if (!databaseUrl) {
1965
- 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
+ );
1966
2167
  process.exit(1);
1967
2168
  }
1968
2169
  const outputPath = options.output || process.env.DB_SCHEMA_OUTPUT || "server/database/schema.ts";
@@ -1977,9 +2178,14 @@ async function run(options = {}) {
1977
2178
  path2.resolve(__dirname2, "../../../dist/config/drizzle.config.js")
1978
2179
  ];
1979
2180
  const configPath = configPathCandidates.find((p) => fs4.existsSync(p));
1980
- 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
+ );
1981
2185
  if (!configPath) {
1982
- 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
+ );
1983
2189
  process.exit(1);
1984
2190
  }
1985
2191
  const resolveDrizzleKitBin = () => {
@@ -1995,7 +2201,9 @@ async function run(options = {}) {
1995
2201
  const binField = pkgJson.bin;
1996
2202
  const binRelPath = typeof binField === "string" ? binField : binField?.["drizzle-kit"];
1997
2203
  if (!binRelPath) {
1998
- 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
+ );
1999
2207
  }
2000
2208
  return path2.resolve(currentDir, binRelPath);
2001
2209
  }
@@ -2006,15 +2214,66 @@ async function run(options = {}) {
2006
2214
  throw new Error("Unable to locate drizzle-kit package root");
2007
2215
  };
2008
2216
  let columnComments;
2009
- try {
2010
- columnComments = await fetchColumnComments(databaseUrl, { timeoutMs: 1e4 });
2011
- console.log(`[gen-db-schema] \u2713 Fetched ${columnComments.size} column comments`);
2012
- } catch (err) {
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 {
2013
2253
  console.warn(
2014
2254
  "[gen-db-schema] \u26A0 Failed to fetch column comments (skipping):",
2015
- err instanceof Error ? err.message : String(err)
2255
+ fetchTasks[0].reason instanceof Error ? fetchTasks[0].reason.message : String(fetchTasks[0].reason)
2016
2256
  );
2017
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
+ }
2018
2277
  try {
2019
2278
  const env = {
2020
2279
  ...process.env,
@@ -2025,13 +2284,19 @@ async function run(options = {}) {
2025
2284
  };
2026
2285
  const drizzleKitBin = resolveDrizzleKitBin();
2027
2286
  const spawnArgs = [drizzleKitBin, "introspect", "--config", configPath];
2028
- 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
+ });
2029
2292
  if (result.error) {
2030
2293
  console.error("[gen-db-schema] Execution failed:", result.error);
2031
2294
  throw result.error;
2032
2295
  }
2033
2296
  if ((result.status ?? 0) !== 0) {
2034
- 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
+ );
2035
2300
  }
2036
2301
  const generatedSchema = path2.join(OUT_DIR, "schema.ts");
2037
2302
  if (!fs4.existsSync(generatedSchema)) {
@@ -2039,10 +2304,14 @@ async function run(options = {}) {
2039
2304
  throw new Error("drizzle-kit introspect failed to generate schema.ts");
2040
2305
  }
2041
2306
  const stats = await postprocessDrizzleSchema(generatedSchema, {
2042
- columnComments
2307
+ columnComments,
2308
+ syncedTableMap
2043
2309
  });
2044
2310
  if (stats?.unmatchedUnknown?.length) {
2045
- 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
+ );
2046
2315
  }
2047
2316
  console.log("[gen-db-schema] \u2713 Postprocessed schema");
2048
2317
  fs4.mkdirSync(path2.dirname(SCHEMA_FILE), { recursive: true });
@@ -2057,14 +2326,22 @@ async function run(options = {}) {
2057
2326
  schemaFilePath,
2058
2327
  moduleOutputDir: path2.resolve(process.cwd(), "server/modules")
2059
2328
  });
2060
- 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
+ );
2061
2332
  }
2062
2333
  } catch (error) {
2063
- 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
+ );
2064
2338
  }
2065
2339
  console.log("[gen-db-schema] \u2713 Complete");
2066
2340
  } catch (err) {
2067
- 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
+ );
2068
2345
  exitCode = 1;
2069
2346
  } finally {
2070
2347
  if (fs4.existsSync(OUT_DIR)) {
@@ -2101,12 +2378,15 @@ var syncConfig = {
2101
2378
  type: "directory",
2102
2379
  overwrite: true
2103
2380
  },
2104
- // 2. 覆写 nest-cli.json 配置,禁止用户修改
2381
+ // 2. 智能合并 nest-cli.json 配置(保留用户自定义的 assets、plugins 等)
2105
2382
  {
2106
2383
  from: "templates/nest-cli.json",
2107
2384
  to: "nest-cli.json",
2108
- type: "file",
2109
- overwrite: true
2385
+ type: "merge-json",
2386
+ arrayMerge: {
2387
+ "compilerOptions.assets": { key: "include" },
2388
+ "compilerOptions.plugins": { key: "name" }
2389
+ }
2110
2390
  },
2111
2391
  // // 2. 追加内容到 .gitignore
2112
2392
  // {
@@ -2151,6 +2431,13 @@ var syncConfig = {
2151
2431
  type: "add-line",
2152
2432
  to: ".gitignore",
2153
2433
  line: ".agent/"
2434
+ },
2435
+ // 8. 同步 .spark_project 配置文件(总是覆盖)
2436
+ {
2437
+ from: "templates/.spark_project",
2438
+ to: ".spark_project",
2439
+ type: "file",
2440
+ overwrite: true
2154
2441
  }
2155
2442
  ],
2156
2443
  // 文件权限设置
@@ -2192,6 +2479,52 @@ function removeLineFromFile(filePath, pattern) {
2192
2479
  return true;
2193
2480
  }
2194
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
+
2195
2528
  // src/commands/sync/run.handler.ts
2196
2529
  async function run2(options) {
2197
2530
  const userProjectRoot = process.env.INIT_CWD || process.cwd();
@@ -2254,6 +2587,12 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
2254
2587
  addLineToFile(destPath2, rule.line);
2255
2588
  return;
2256
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
+ }
2257
2596
  if (!("from" in rule)) {
2258
2597
  return;
2259
2598
  }
@@ -2396,6 +2735,33 @@ function addLineToFile(filePath, line) {
2396
2735
  fs6.appendFileSync(filePath, appendContent);
2397
2736
  console.log(`[fullstack-cli] \u2713 ${fileName} (added: ${line})`);
2398
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
+ }
2399
2765
 
2400
2766
  // src/commands/sync/index.ts
2401
2767
  var syncCommand = {
@@ -2408,10 +2774,398 @@ var syncCommand = {
2408
2774
  }
2409
2775
  };
2410
2776
 
2411
- // src/commands/action-plugin/utils.ts
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;
2798
+ }
2799
+ }
2800
+ async function reportInstallEvent(pluginKey, version) {
2801
+ await reportEvents([
2802
+ {
2803
+ resourceType: "plugin",
2804
+ resourceKey: pluginKey,
2805
+ eventType: "install",
2806
+ details: { version }
2807
+ }
2808
+ ]);
2809
+ }
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
+ ]);
2819
+ }
2820
+
2821
+ // src/utils/git.ts
2822
+ import { execSync, spawnSync as spawnSync2 } from "child_process";
2412
2823
  import fs7 from "fs";
2413
2824
  import path5 from "path";
2414
- import { spawnSync as spawnSync2, execSync } from "child_process";
2825
+ function isGitRepository(cwd = process.cwd()) {
2826
+ try {
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;
2837
+ } catch {
2838
+ return false;
2839
+ }
2840
+ }
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";
2415
3169
  function parsePluginName(input) {
2416
3170
  const match = input.match(/^(@[^/]+\/[^@]+)(?:@(.+))?$/);
2417
3171
  if (!match) {
@@ -2428,35 +3182,35 @@ function getProjectRoot() {
2428
3182
  return process.cwd();
2429
3183
  }
2430
3184
  function getPackageJsonPath() {
2431
- return path5.join(getProjectRoot(), "package.json");
3185
+ return path8.join(getProjectRoot(), "package.json");
2432
3186
  }
2433
3187
  function getPluginPath(pluginName) {
2434
- return path5.join(getProjectRoot(), "node_modules", pluginName);
3188
+ return path8.join(getProjectRoot(), "node_modules", pluginName);
2435
3189
  }
2436
- function readPackageJson() {
3190
+ function readPackageJson2() {
2437
3191
  const pkgPath = getPackageJsonPath();
2438
- if (!fs7.existsSync(pkgPath)) {
3192
+ if (!fs10.existsSync(pkgPath)) {
2439
3193
  throw new Error("package.json not found in current directory");
2440
3194
  }
2441
3195
  try {
2442
- const content = fs7.readFileSync(pkgPath, "utf-8");
3196
+ const content = fs10.readFileSync(pkgPath, "utf-8");
2443
3197
  return JSON.parse(content);
2444
3198
  } catch {
2445
3199
  throw new Error("Failed to parse package.json");
2446
3200
  }
2447
3201
  }
2448
- function writePackageJson(pkg2) {
3202
+ function writePackageJson2(pkg2) {
2449
3203
  const pkgPath = getPackageJsonPath();
2450
- fs7.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
3204
+ fs10.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
2451
3205
  }
2452
3206
  function readActionPlugins() {
2453
- const pkg2 = readPackageJson();
3207
+ const pkg2 = readPackageJson2();
2454
3208
  return pkg2.actionPlugins || {};
2455
3209
  }
2456
3210
  function writeActionPlugins(plugins) {
2457
- const pkg2 = readPackageJson();
3211
+ const pkg2 = readPackageJson2();
2458
3212
  pkg2.actionPlugins = plugins;
2459
- writePackageJson(pkg2);
3213
+ writePackageJson2(pkg2);
2460
3214
  }
2461
3215
  function isPluginInstalled(pluginName) {
2462
3216
  const plugins = readActionPlugins();
@@ -2468,7 +3222,7 @@ function getInstalledPluginVersion(pluginName) {
2468
3222
  }
2469
3223
  function npmInstall(tgzPath) {
2470
3224
  console.log(`[action-plugin] Running npm install ${tgzPath}...`);
2471
- 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"], {
2472
3226
  cwd: getProjectRoot(),
2473
3227
  stdio: "inherit"
2474
3228
  });
@@ -2480,12 +3234,12 @@ function npmInstall(tgzPath) {
2480
3234
  }
2481
3235
  }
2482
3236
  function getPackageVersion(pluginName) {
2483
- const pkgJsonPath = path5.join(getPluginPath(pluginName), "package.json");
2484
- if (!fs7.existsSync(pkgJsonPath)) {
3237
+ const pkgJsonPath = path8.join(getPluginPath(pluginName), "package.json");
3238
+ if (!fs10.existsSync(pkgJsonPath)) {
2485
3239
  return null;
2486
3240
  }
2487
3241
  try {
2488
- const content = fs7.readFileSync(pkgJsonPath, "utf-8");
3242
+ const content = fs10.readFileSync(pkgJsonPath, "utf-8");
2489
3243
  const pkg2 = JSON.parse(content);
2490
3244
  return pkg2.version || null;
2491
3245
  } catch {
@@ -2493,49 +3247,49 @@ function getPackageVersion(pluginName) {
2493
3247
  }
2494
3248
  }
2495
3249
  function readPluginPackageJson(pluginPath) {
2496
- const pkgJsonPath = path5.join(pluginPath, "package.json");
2497
- if (!fs7.existsSync(pkgJsonPath)) {
3250
+ const pkgJsonPath = path8.join(pluginPath, "package.json");
3251
+ if (!fs10.existsSync(pkgJsonPath)) {
2498
3252
  return null;
2499
3253
  }
2500
3254
  try {
2501
- const content = fs7.readFileSync(pkgJsonPath, "utf-8");
3255
+ const content = fs10.readFileSync(pkgJsonPath, "utf-8");
2502
3256
  return JSON.parse(content);
2503
3257
  } catch {
2504
3258
  return null;
2505
3259
  }
2506
3260
  }
2507
3261
  function extractTgzToNodeModules(tgzPath, pluginName) {
2508
- const nodeModulesPath = path5.join(getProjectRoot(), "node_modules");
2509
- const targetDir = path5.join(nodeModulesPath, pluginName);
2510
- const scopeDir = path5.dirname(targetDir);
2511
- if (!fs7.existsSync(scopeDir)) {
2512
- 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 });
2513
3267
  }
2514
- if (fs7.existsSync(targetDir)) {
2515
- fs7.rmSync(targetDir, { recursive: true });
3268
+ if (fs10.existsSync(targetDir)) {
3269
+ fs10.rmSync(targetDir, { recursive: true });
2516
3270
  }
2517
- const tempDir = path5.join(nodeModulesPath, ".cache", "fullstack-cli", "extract-temp");
2518
- if (fs7.existsSync(tempDir)) {
2519
- 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 });
2520
3274
  }
2521
- fs7.mkdirSync(tempDir, { recursive: true });
3275
+ fs10.mkdirSync(tempDir, { recursive: true });
2522
3276
  try {
2523
- execSync(`tar -xzf "${tgzPath}" -C "${tempDir}"`, { stdio: "pipe" });
2524
- const extractedDir = path5.join(tempDir, "package");
2525
- if (fs7.existsSync(extractedDir)) {
2526
- 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);
2527
3281
  } else {
2528
- const files = fs7.readdirSync(tempDir);
3282
+ const files = fs10.readdirSync(tempDir);
2529
3283
  if (files.length === 1) {
2530
- fs7.renameSync(path5.join(tempDir, files[0]), targetDir);
3284
+ fs10.renameSync(path8.join(tempDir, files[0]), targetDir);
2531
3285
  } else {
2532
3286
  throw new Error("Unexpected tgz structure");
2533
3287
  }
2534
3288
  }
2535
3289
  return targetDir;
2536
3290
  } finally {
2537
- if (fs7.existsSync(tempDir)) {
2538
- fs7.rmSync(tempDir, { recursive: true });
3291
+ if (fs10.existsSync(tempDir)) {
3292
+ fs10.rmSync(tempDir, { recursive: true });
2539
3293
  }
2540
3294
  }
2541
3295
  }
@@ -2544,10 +3298,10 @@ function checkMissingPeerDeps(peerDeps) {
2544
3298
  return [];
2545
3299
  }
2546
3300
  const missing = [];
2547
- const nodeModulesPath = path5.join(getProjectRoot(), "node_modules");
3301
+ const nodeModulesPath = path8.join(getProjectRoot(), "node_modules");
2548
3302
  for (const [depName, _version] of Object.entries(peerDeps)) {
2549
- const depPath = path5.join(nodeModulesPath, depName);
2550
- if (!fs7.existsSync(depPath)) {
3303
+ const depPath = path8.join(nodeModulesPath, depName);
3304
+ if (!fs10.existsSync(depPath)) {
2551
3305
  missing.push(depName);
2552
3306
  }
2553
3307
  }
@@ -2558,7 +3312,7 @@ function installMissingDeps(deps) {
2558
3312
  return;
2559
3313
  }
2560
3314
  console.log(`[action-plugin] Installing missing dependencies: ${deps.join(", ")}`);
2561
- const result = spawnSync2("npm", ["install", ...deps, "--no-save", "--no-package-lock"], {
3315
+ const result = spawnSync4("npm", ["install", ...deps, "--no-save", "--no-package-lock"], {
2562
3316
  cwd: getProjectRoot(),
2563
3317
  stdio: "inherit"
2564
3318
  });
@@ -2571,40 +3325,16 @@ function installMissingDeps(deps) {
2571
3325
  }
2572
3326
  function removePluginDirectory(pluginName) {
2573
3327
  const pluginPath = getPluginPath(pluginName);
2574
- if (fs7.existsSync(pluginPath)) {
2575
- fs7.rmSync(pluginPath, { recursive: true });
3328
+ if (fs10.existsSync(pluginPath)) {
3329
+ fs10.rmSync(pluginPath, { recursive: true });
2576
3330
  console.log(`[action-plugin] Removed ${pluginName}`);
2577
3331
  }
2578
3332
  }
2579
3333
 
2580
3334
  // src/commands/action-plugin/api-client.ts
2581
3335
  import { HttpClient as HttpClient2 } from "@lark-apaas/http-client";
2582
- import fs8 from "fs";
2583
- import path6 from "path";
2584
-
2585
- // src/utils/http-client.ts
2586
- import { HttpClient } from "@lark-apaas/http-client";
2587
- var clientInstance = null;
2588
- function getHttpClient() {
2589
- if (!clientInstance) {
2590
- clientInstance = new HttpClient({
2591
- timeout: 3e4,
2592
- platform: {
2593
- enabled: true
2594
- }
2595
- });
2596
- const canaryEnv = process.env.FORCE_FRAMEWORK_CLI_CANARY_ENV;
2597
- if (canaryEnv) {
2598
- clientInstance.interceptors.request.use((req) => {
2599
- req.headers["x-tt-env"] = canaryEnv;
2600
- return req;
2601
- });
2602
- }
2603
- }
2604
- return clientInstance;
2605
- }
2606
-
2607
- // src/commands/action-plugin/api-client.ts
3336
+ import fs11 from "fs";
3337
+ import path9 from "path";
2608
3338
  var PLUGIN_CACHE_DIR = "node_modules/.cache/fullstack-cli/plugins";
2609
3339
  async function getPluginVersions(keys, latestOnly = true) {
2610
3340
  const client = getHttpClient();
@@ -2668,19 +3398,19 @@ async function downloadFromPublic(downloadURL) {
2668
3398
  return Buffer.from(arrayBuffer);
2669
3399
  }
2670
3400
  function getPluginCacheDir() {
2671
- return path6.join(process.cwd(), PLUGIN_CACHE_DIR);
3401
+ return path9.join(process.cwd(), PLUGIN_CACHE_DIR);
2672
3402
  }
2673
3403
  function ensureCacheDir() {
2674
3404
  const cacheDir = getPluginCacheDir();
2675
- if (!fs8.existsSync(cacheDir)) {
2676
- fs8.mkdirSync(cacheDir, { recursive: true });
3405
+ if (!fs11.existsSync(cacheDir)) {
3406
+ fs11.mkdirSync(cacheDir, { recursive: true });
2677
3407
  }
2678
3408
  }
2679
3409
  function getTempFilePath(pluginKey, version) {
2680
3410
  ensureCacheDir();
2681
3411
  const safeKey = pluginKey.replace(/[/@]/g, "_");
2682
3412
  const filename = `${safeKey}@${version}.tgz`;
2683
- return path6.join(getPluginCacheDir(), filename);
3413
+ return path9.join(getPluginCacheDir(), filename);
2684
3414
  }
2685
3415
  var MAX_RETRIES = 2;
2686
3416
  async function withRetry(operation, description, maxRetries = MAX_RETRIES) {
@@ -2717,7 +3447,7 @@ async function downloadPlugin(pluginKey, requestedVersion) {
2717
3447
  );
2718
3448
  }
2719
3449
  const tgzPath = getTempFilePath(pluginKey, pluginInfo.version);
2720
- fs8.writeFileSync(tgzPath, tgzBuffer);
3450
+ fs11.writeFileSync(tgzPath, tgzBuffer);
2721
3451
  console.log(`[action-plugin] Downloaded to ${tgzPath} (${(tgzBuffer.length / 1024).toFixed(2)} KB)`);
2722
3452
  return {
2723
3453
  tgzPath,
@@ -2731,18 +3461,18 @@ function getCachePath(pluginKey, version) {
2731
3461
  ensureCacheDir();
2732
3462
  const safeKey = pluginKey.replace(/[/@]/g, "_");
2733
3463
  const filename = `${safeKey}@${version}.tgz`;
2734
- return path6.join(getPluginCacheDir(), filename);
3464
+ return path9.join(getPluginCacheDir(), filename);
2735
3465
  }
2736
3466
  function hasCachedPlugin(pluginKey, version) {
2737
3467
  const cachePath = getCachePath(pluginKey, version);
2738
- return fs8.existsSync(cachePath);
3468
+ return fs11.existsSync(cachePath);
2739
3469
  }
2740
3470
  function listCachedPlugins() {
2741
3471
  const cacheDir = getPluginCacheDir();
2742
- if (!fs8.existsSync(cacheDir)) {
3472
+ if (!fs11.existsSync(cacheDir)) {
2743
3473
  return [];
2744
3474
  }
2745
- const files = fs8.readdirSync(cacheDir);
3475
+ const files = fs11.readdirSync(cacheDir);
2746
3476
  const result = [];
2747
3477
  for (const file of files) {
2748
3478
  if (!file.endsWith(".tgz")) continue;
@@ -2750,8 +3480,8 @@ function listCachedPlugins() {
2750
3480
  if (!match) continue;
2751
3481
  const [, rawName, version] = match;
2752
3482
  const name = rawName.replace(/^_/, "@").replace(/_/, "/");
2753
- const filePath = path6.join(cacheDir, file);
2754
- const stat = fs8.statSync(filePath);
3483
+ const filePath = path9.join(cacheDir, file);
3484
+ const stat = fs11.statSync(filePath);
2755
3485
  result.push({
2756
3486
  name,
2757
3487
  version,
@@ -2764,14 +3494,14 @@ function listCachedPlugins() {
2764
3494
  }
2765
3495
  function cleanAllCache() {
2766
3496
  const cacheDir = getPluginCacheDir();
2767
- if (!fs8.existsSync(cacheDir)) {
3497
+ if (!fs11.existsSync(cacheDir)) {
2768
3498
  return 0;
2769
3499
  }
2770
- const files = fs8.readdirSync(cacheDir);
3500
+ const files = fs11.readdirSync(cacheDir);
2771
3501
  let count = 0;
2772
3502
  for (const file of files) {
2773
3503
  if (file.endsWith(".tgz")) {
2774
- fs8.unlinkSync(path6.join(cacheDir, file));
3504
+ fs11.unlinkSync(path9.join(cacheDir, file));
2775
3505
  count++;
2776
3506
  }
2777
3507
  }
@@ -2779,21 +3509,21 @@ function cleanAllCache() {
2779
3509
  }
2780
3510
  function cleanPluginCache(pluginKey, version) {
2781
3511
  const cacheDir = getPluginCacheDir();
2782
- if (!fs8.existsSync(cacheDir)) {
3512
+ if (!fs11.existsSync(cacheDir)) {
2783
3513
  return 0;
2784
3514
  }
2785
3515
  const safeKey = pluginKey.replace(/[/@]/g, "_");
2786
- const files = fs8.readdirSync(cacheDir);
3516
+ const files = fs11.readdirSync(cacheDir);
2787
3517
  let count = 0;
2788
3518
  for (const file of files) {
2789
3519
  if (version) {
2790
3520
  if (file === `${safeKey}@${version}.tgz`) {
2791
- fs8.unlinkSync(path6.join(cacheDir, file));
3521
+ fs11.unlinkSync(path9.join(cacheDir, file));
2792
3522
  count++;
2793
3523
  }
2794
3524
  } else {
2795
3525
  if (file.startsWith(`${safeKey}@`) && file.endsWith(".tgz")) {
2796
- fs8.unlinkSync(path6.join(cacheDir, file));
3526
+ fs11.unlinkSync(path9.join(cacheDir, file));
2797
3527
  count++;
2798
3528
  }
2799
3529
  }
@@ -2887,6 +3617,8 @@ async function installOne(nameWithVersion) {
2887
3617
  if (actualVersion === requestedVersion) {
2888
3618
  console.log(`[action-plugin] Plugin ${name}@${requestedVersion} is already installed`);
2889
3619
  syncActionPluginsRecord(name, actualVersion);
3620
+ reportCreateInstanceEvent(name, actualVersion).catch(() => {
3621
+ });
2890
3622
  return { name, version: actualVersion, success: true, skipped: true };
2891
3623
  }
2892
3624
  }
@@ -2897,6 +3629,8 @@ async function installOne(nameWithVersion) {
2897
3629
  if (actualVersion === targetVersion) {
2898
3630
  console.log(`[action-plugin] Plugin ${name} is already up to date (version: ${actualVersion})`);
2899
3631
  syncActionPluginsRecord(name, actualVersion);
3632
+ reportCreateInstanceEvent(name, actualVersion).catch(() => {
3633
+ });
2900
3634
  return { name, version: actualVersion, success: true, skipped: true };
2901
3635
  }
2902
3636
  console.log(`[action-plugin] Found newer version: ${targetVersion} (installed: ${actualVersion || "none"})`);
@@ -2927,6 +3661,10 @@ async function installOne(nameWithVersion) {
2927
3661
  writeActionPlugins(plugins);
2928
3662
  const source = fromCache ? "from cache" : "downloaded";
2929
3663
  console.log(`[action-plugin] Successfully installed ${name}@${installedVersion} (${source})`);
3664
+ reportInstallEvent(name, installedVersion).catch(() => {
3665
+ });
3666
+ reportCreateInstanceEvent(name, installedVersion).catch(() => {
3667
+ });
2930
3668
  return { name, version: installedVersion, success: true };
2931
3669
  } catch (error) {
2932
3670
  const message = error instanceof Error ? error.message : String(error);
@@ -3212,40 +3950,40 @@ var actionPluginCommandGroup = {
3212
3950
  };
3213
3951
 
3214
3952
  // src/commands/capability/utils.ts
3215
- import fs9 from "fs";
3953
+ import fs12 from "fs";
3216
3954
  import { createRequire as createRequire2 } from "module";
3217
- import path7 from "path";
3955
+ import path10 from "path";
3218
3956
  var CAPABILITIES_DIR = "server/capabilities";
3219
3957
  function getProjectRoot2() {
3220
3958
  return process.cwd();
3221
3959
  }
3222
3960
  function getCapabilitiesDir() {
3223
- return path7.join(getProjectRoot2(), CAPABILITIES_DIR);
3961
+ return path10.join(getProjectRoot2(), CAPABILITIES_DIR);
3224
3962
  }
3225
3963
  function getCapabilityPath(id) {
3226
- return path7.join(getCapabilitiesDir(), `${id}.json`);
3964
+ return path10.join(getCapabilitiesDir(), `${id}.json`);
3227
3965
  }
3228
3966
  function getPluginManifestPath(pluginKey) {
3229
- return path7.join(getProjectRoot2(), "node_modules", pluginKey, "manifest.json");
3967
+ return path10.join(getProjectRoot2(), "node_modules", pluginKey, "manifest.json");
3230
3968
  }
3231
3969
  function capabilitiesDirExists() {
3232
- return fs9.existsSync(getCapabilitiesDir());
3970
+ return fs12.existsSync(getCapabilitiesDir());
3233
3971
  }
3234
3972
  function listCapabilityIds() {
3235
3973
  const dir = getCapabilitiesDir();
3236
- if (!fs9.existsSync(dir)) {
3974
+ if (!fs12.existsSync(dir)) {
3237
3975
  return [];
3238
3976
  }
3239
- const files = fs9.readdirSync(dir);
3977
+ const files = fs12.readdirSync(dir);
3240
3978
  return files.filter((f) => f.endsWith(".json") && f !== "capabilities.json").map((f) => f.replace(/\.json$/, ""));
3241
3979
  }
3242
3980
  function readCapability(id) {
3243
3981
  const filePath = getCapabilityPath(id);
3244
- if (!fs9.existsSync(filePath)) {
3982
+ if (!fs12.existsSync(filePath)) {
3245
3983
  throw new Error(`Capability not found: ${id}`);
3246
3984
  }
3247
3985
  try {
3248
- const content = fs9.readFileSync(filePath, "utf-8");
3986
+ const content = fs12.readFileSync(filePath, "utf-8");
3249
3987
  return JSON.parse(content);
3250
3988
  } catch (error) {
3251
3989
  if (error instanceof SyntaxError) {
@@ -3272,11 +4010,11 @@ function readAllCapabilities() {
3272
4010
  }
3273
4011
  function readPluginManifest(pluginKey) {
3274
4012
  const manifestPath = getPluginManifestPath(pluginKey);
3275
- if (!fs9.existsSync(manifestPath)) {
4013
+ if (!fs12.existsSync(manifestPath)) {
3276
4014
  throw new Error(`Plugin not installed: ${pluginKey} (manifest.json not found)`);
3277
4015
  }
3278
4016
  try {
3279
- const content = fs9.readFileSync(manifestPath, "utf-8");
4017
+ const content = fs12.readFileSync(manifestPath, "utf-8");
3280
4018
  return JSON.parse(content);
3281
4019
  } catch (error) {
3282
4020
  if (error instanceof SyntaxError) {
@@ -3293,7 +4031,7 @@ function hasValidParamsSchema(paramsSchema) {
3293
4031
  }
3294
4032
  async function loadPlugin(pluginKey) {
3295
4033
  try {
3296
- const userRequire = createRequire2(path7.join(getProjectRoot2(), "package.json"));
4034
+ const userRequire = createRequire2(path10.join(getProjectRoot2(), "package.json"));
3297
4035
  const resolvedPath = userRequire.resolve(pluginKey);
3298
4036
  const pluginModule = await import(resolvedPath);
3299
4037
  const pluginPackage = pluginModule.default ?? pluginModule;
@@ -3452,59 +4190,358 @@ var capabilityCommandGroup = {
3452
4190
  commands: [listCommand2]
3453
4191
  };
3454
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;
4342
+ }
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;
4359
+ }
4360
+ }
4361
+ return Array.from(state.files);
4362
+ }
4363
+ function toFileInfo(filePath) {
4364
+ const name = filePath.split("/").pop() || filePath;
4365
+ return { name, path: filePath };
4366
+ }
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");
4450
+ }
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();
4473
+ }
4474
+ }
4475
+
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",
4480
+ register(program) {
4481
+ program.command(this.name).description(this.description).argument("<component>", "\u7EC4\u4EF6 ID (\u4F8B\u5982 @miaoda/button)").action(async (component) => {
4482
+ await add(component);
4483
+ });
4484
+ }
4485
+ };
4486
+ var componentCommandGroup = {
4487
+ name: "component",
4488
+ description: "\u7EC4\u4EF6\u76F8\u5173\u547D\u4EE4",
4489
+ commands: [addCommand]
4490
+ };
4491
+
3455
4492
  // src/commands/migration/version-manager.ts
3456
- import fs10 from "fs";
3457
- import path8 from "path";
4493
+ import fs14 from "fs";
4494
+ import path12 from "path";
3458
4495
  var PACKAGE_JSON = "package.json";
3459
4496
  var VERSION_FIELD = "migrationVersion";
3460
4497
  function getPackageJsonPath2() {
3461
- return path8.join(process.cwd(), PACKAGE_JSON);
4498
+ return path12.join(process.cwd(), PACKAGE_JSON);
3462
4499
  }
3463
4500
  function getCurrentVersion() {
3464
4501
  const pkgPath = getPackageJsonPath2();
3465
- if (!fs10.existsSync(pkgPath)) {
4502
+ if (!fs14.existsSync(pkgPath)) {
3466
4503
  throw new Error("package.json not found");
3467
4504
  }
3468
- const pkg2 = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
4505
+ const pkg2 = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
3469
4506
  return pkg2[VERSION_FIELD] ?? 0;
3470
4507
  }
3471
4508
  function setCurrentVersion(version) {
3472
4509
  const pkgPath = getPackageJsonPath2();
3473
- const pkg2 = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
4510
+ const pkg2 = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
3474
4511
  pkg2[VERSION_FIELD] = version;
3475
- fs10.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
4512
+ fs14.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
3476
4513
  }
3477
4514
 
3478
4515
  // src/commands/migration/versions/v001_capability/json-migrator/detector.ts
3479
- import fs12 from "fs";
3480
- import path10 from "path";
4516
+ import fs16 from "fs";
4517
+ import path14 from "path";
3481
4518
 
3482
4519
  // src/commands/migration/versions/v001_capability/utils.ts
3483
- import fs11 from "fs";
3484
- import path9 from "path";
4520
+ import fs15 from "fs";
4521
+ import path13 from "path";
3485
4522
  var CAPABILITIES_DIR2 = "server/capabilities";
3486
4523
  function getProjectRoot3() {
3487
4524
  return process.cwd();
3488
4525
  }
3489
4526
  function getCapabilitiesDir2() {
3490
- return path9.join(getProjectRoot3(), CAPABILITIES_DIR2);
4527
+ return path13.join(getProjectRoot3(), CAPABILITIES_DIR2);
3491
4528
  }
3492
4529
  function getPluginManifestPath2(pluginKey) {
3493
- return path9.join(getProjectRoot3(), "node_modules", pluginKey, "manifest.json");
4530
+ return path13.join(getProjectRoot3(), "node_modules", pluginKey, "manifest.json");
3494
4531
  }
3495
4532
 
3496
4533
  // src/commands/migration/versions/v001_capability/json-migrator/detector.ts
3497
4534
  function detectJsonMigration() {
3498
4535
  const capabilitiesDir = getCapabilitiesDir2();
3499
- const oldFilePath = path10.join(capabilitiesDir, "capabilities.json");
3500
- if (!fs12.existsSync(oldFilePath)) {
4536
+ const oldFilePath = path14.join(capabilitiesDir, "capabilities.json");
4537
+ if (!fs16.existsSync(oldFilePath)) {
3501
4538
  return {
3502
4539
  needsMigration: false,
3503
4540
  reason: "capabilities.json not found"
3504
4541
  };
3505
4542
  }
3506
4543
  try {
3507
- const content = fs12.readFileSync(oldFilePath, "utf-8");
4544
+ const content = fs16.readFileSync(oldFilePath, "utf-8");
3508
4545
  const parsed = JSON.parse(content);
3509
4546
  if (!Array.isArray(parsed)) {
3510
4547
  return {
@@ -3555,8 +4592,8 @@ async function check(options) {
3555
4592
  }
3556
4593
 
3557
4594
  // src/commands/migration/versions/v001_capability/json-migrator/index.ts
3558
- import fs13 from "fs";
3559
- import path11 from "path";
4595
+ import fs17 from "fs";
4596
+ import path15 from "path";
3560
4597
 
3561
4598
  // src/commands/migration/versions/v001_capability/mapping.ts
3562
4599
  var DEFAULT_PLUGIN_VERSION = "1.0.0";
@@ -3786,18 +4823,18 @@ function transformCapabilities(oldCapabilities) {
3786
4823
  // src/commands/migration/versions/v001_capability/json-migrator/index.ts
3787
4824
  function loadExistingCapabilities() {
3788
4825
  const capabilitiesDir = getCapabilitiesDir2();
3789
- if (!fs13.existsSync(capabilitiesDir)) {
4826
+ if (!fs17.existsSync(capabilitiesDir)) {
3790
4827
  return [];
3791
4828
  }
3792
- const files = fs13.readdirSync(capabilitiesDir);
4829
+ const files = fs17.readdirSync(capabilitiesDir);
3793
4830
  const capabilities = [];
3794
4831
  for (const file of files) {
3795
4832
  if (file === "capabilities.json" || !file.endsWith(".json")) {
3796
4833
  continue;
3797
4834
  }
3798
4835
  try {
3799
- const filePath = path11.join(capabilitiesDir, file);
3800
- const content = fs13.readFileSync(filePath, "utf-8");
4836
+ const filePath = path15.join(capabilitiesDir, file);
4837
+ const content = fs17.readFileSync(filePath, "utf-8");
3801
4838
  const capability = JSON.parse(content);
3802
4839
  if (capability.id && capability.pluginKey) {
3803
4840
  capabilities.push(capability);
@@ -3855,9 +4892,9 @@ async function migrateJsonFiles(options) {
3855
4892
  }
3856
4893
  const capabilitiesDir = getCapabilitiesDir2();
3857
4894
  for (const cap of newCapabilities) {
3858
- const filePath = path11.join(capabilitiesDir, `${cap.id}.json`);
4895
+ const filePath = path15.join(capabilitiesDir, `${cap.id}.json`);
3859
4896
  const content = JSON.stringify(cap, null, 2);
3860
- fs13.writeFileSync(filePath, content, "utf-8");
4897
+ fs17.writeFileSync(filePath, content, "utf-8");
3861
4898
  console.log(` \u2713 Created: ${cap.id}.json`);
3862
4899
  }
3863
4900
  return {
@@ -3869,11 +4906,11 @@ async function migrateJsonFiles(options) {
3869
4906
  }
3870
4907
 
3871
4908
  // src/commands/migration/versions/v001_capability/plugin-installer/detector.ts
3872
- import fs14 from "fs";
4909
+ import fs18 from "fs";
3873
4910
  function isPluginInstalled2(pluginKey) {
3874
4911
  const actionPlugins = readActionPlugins();
3875
4912
  const manifestPath = getPluginManifestPath2(pluginKey);
3876
- return fs14.existsSync(manifestPath) && !!actionPlugins[pluginKey];
4913
+ return fs18.existsSync(manifestPath) && !!actionPlugins[pluginKey];
3877
4914
  }
3878
4915
  function detectPluginsToInstall(capabilities) {
3879
4916
  const pluginKeys = /* @__PURE__ */ new Set();
@@ -3949,12 +4986,12 @@ async function installPlugins(capabilities, options) {
3949
4986
  }
3950
4987
 
3951
4988
  // src/commands/migration/versions/v001_capability/code-migrator/index.ts
3952
- import path13 from "path";
4989
+ import path17 from "path";
3953
4990
  import { Project as Project3 } from "ts-morph";
3954
4991
 
3955
4992
  // src/commands/migration/versions/v001_capability/code-migrator/scanner.ts
3956
- import fs15 from "fs";
3957
- import path12 from "path";
4993
+ import fs19 from "fs";
4994
+ import path16 from "path";
3958
4995
  var EXCLUDED_DIRS = [
3959
4996
  "node_modules",
3960
4997
  "dist",
@@ -3969,9 +5006,9 @@ var EXCLUDED_PATTERNS = [
3969
5006
  /\.d\.ts$/
3970
5007
  ];
3971
5008
  function scanDirectory(dir, files = []) {
3972
- const entries = fs15.readdirSync(dir, { withFileTypes: true });
5009
+ const entries = fs19.readdirSync(dir, { withFileTypes: true });
3973
5010
  for (const entry of entries) {
3974
- const fullPath = path12.join(dir, entry.name);
5011
+ const fullPath = path16.join(dir, entry.name);
3975
5012
  if (entry.isDirectory()) {
3976
5013
  if (EXCLUDED_DIRS.includes(entry.name)) {
3977
5014
  continue;
@@ -3987,14 +5024,14 @@ function scanDirectory(dir, files = []) {
3987
5024
  return files;
3988
5025
  }
3989
5026
  function scanServerFiles() {
3990
- const serverDir = path12.join(getProjectRoot3(), "server");
3991
- if (!fs15.existsSync(serverDir)) {
5027
+ const serverDir = path16.join(getProjectRoot3(), "server");
5028
+ if (!fs19.existsSync(serverDir)) {
3992
5029
  return [];
3993
5030
  }
3994
5031
  return scanDirectory(serverDir);
3995
5032
  }
3996
5033
  function hasCapabilityImport(filePath) {
3997
- const content = fs15.readFileSync(filePath, "utf-8");
5034
+ const content = fs19.readFileSync(filePath, "utf-8");
3998
5035
  return /import\s+.*from\s+['"][^'"]*capabilities[^'"]*['"]/.test(content);
3999
5036
  }
4000
5037
  function scanFilesToMigrate() {
@@ -4371,7 +5408,7 @@ function analyzeFile(project, filePath, actionNameMap) {
4371
5408
  const callSites = analyzeCallSites(sourceFile, imports);
4372
5409
  const classInfo = analyzeClass(sourceFile);
4373
5410
  const { canMigrate, reason } = canAutoMigrate(classInfo);
4374
- const relativePath = path13.relative(getProjectRoot3(), filePath);
5411
+ const relativePath = path17.relative(getProjectRoot3(), filePath);
4375
5412
  return {
4376
5413
  filePath: relativePath,
4377
5414
  imports,
@@ -4382,7 +5419,7 @@ function analyzeFile(project, filePath, actionNameMap) {
4382
5419
  };
4383
5420
  }
4384
5421
  function migrateFile(project, analysis, dryRun) {
4385
- const absolutePath = path13.join(getProjectRoot3(), analysis.filePath);
5422
+ const absolutePath = path17.join(getProjectRoot3(), analysis.filePath);
4386
5423
  if (!analysis.canAutoMigrate) {
4387
5424
  return {
4388
5425
  filePath: analysis.filePath,
@@ -4485,17 +5522,17 @@ function getSuggestion(analysis) {
4485
5522
  }
4486
5523
 
4487
5524
  // src/commands/migration/versions/v001_capability/cleanup.ts
4488
- import fs16 from "fs";
4489
- import path14 from "path";
5525
+ import fs20 from "fs";
5526
+ import path18 from "path";
4490
5527
  function cleanupOldFiles(capabilities, dryRun) {
4491
5528
  const deletedFiles = [];
4492
5529
  const errors = [];
4493
5530
  const capabilitiesDir = getCapabilitiesDir2();
4494
- const oldJsonPath = path14.join(capabilitiesDir, "capabilities.json");
4495
- if (fs16.existsSync(oldJsonPath)) {
5531
+ const oldJsonPath = path18.join(capabilitiesDir, "capabilities.json");
5532
+ if (fs20.existsSync(oldJsonPath)) {
4496
5533
  try {
4497
5534
  if (!dryRun) {
4498
- fs16.unlinkSync(oldJsonPath);
5535
+ fs20.unlinkSync(oldJsonPath);
4499
5536
  }
4500
5537
  deletedFiles.push("capabilities.json");
4501
5538
  } catch (error) {
@@ -4503,11 +5540,11 @@ function cleanupOldFiles(capabilities, dryRun) {
4503
5540
  }
4504
5541
  }
4505
5542
  for (const cap of capabilities) {
4506
- const tsFilePath = path14.join(capabilitiesDir, `${cap.id}.ts`);
4507
- if (fs16.existsSync(tsFilePath)) {
5543
+ const tsFilePath = path18.join(capabilitiesDir, `${cap.id}.ts`);
5544
+ if (fs20.existsSync(tsFilePath)) {
4508
5545
  try {
4509
5546
  if (!dryRun) {
4510
- fs16.unlinkSync(tsFilePath);
5547
+ fs20.unlinkSync(tsFilePath);
4511
5548
  }
4512
5549
  deletedFiles.push(`${cap.id}.ts`);
4513
5550
  } catch (error) {
@@ -4523,8 +5560,8 @@ function cleanupOldFiles(capabilities, dryRun) {
4523
5560
  }
4524
5561
 
4525
5562
  // src/commands/migration/versions/v001_capability/report-generator.ts
4526
- import fs17 from "fs";
4527
- import path15 from "path";
5563
+ import fs21 from "fs";
5564
+ import path19 from "path";
4528
5565
  var REPORT_FILE = "capability-migration-report.md";
4529
5566
  function printSummary(result) {
4530
5567
  const { jsonMigration, pluginInstallation, codeMigration, cleanup } = result;
@@ -4687,15 +5724,15 @@ async function generateReport(result) {
4687
5724
  }
4688
5725
  lines.push("");
4689
5726
  const logDir = process.env.LOG_DIR || "logs";
4690
- if (!fs17.existsSync(logDir)) {
5727
+ if (!fs21.existsSync(logDir)) {
4691
5728
  return;
4692
5729
  }
4693
- const reportDir = path15.join(logDir, "migration");
4694
- if (!fs17.existsSync(reportDir)) {
4695
- fs17.mkdirSync(reportDir, { recursive: true });
5730
+ const reportDir = path19.join(logDir, "migration");
5731
+ if (!fs21.existsSync(reportDir)) {
5732
+ fs21.mkdirSync(reportDir, { recursive: true });
4696
5733
  }
4697
- const reportPath = path15.join(reportDir, REPORT_FILE);
4698
- 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");
4699
5736
  console.log(`\u{1F4C4} Report generated: ${reportPath}`);
4700
5737
  }
4701
5738
 
@@ -4872,7 +5909,7 @@ function buildResult(jsonMigration, pluginInstallation, codeMigration, cleanup)
4872
5909
  }
4873
5910
 
4874
5911
  // src/commands/migration/versions/v001_capability/run.ts
4875
- async function run3(options) {
5912
+ async function run5(options) {
4876
5913
  try {
4877
5914
  const migrationOptions = {
4878
5915
  dryRun: options.dryRun ?? false
@@ -4937,7 +5974,7 @@ var v001CapabilityMigration = {
4937
5974
  name: "capability",
4938
5975
  description: "Migrate capability configurations from old format (capabilities.json array) to new format (individual JSON files)",
4939
5976
  check,
4940
- run: run3
5977
+ run: run5
4941
5978
  };
4942
5979
 
4943
5980
  // src/commands/migration/versions/index.ts
@@ -5227,10 +6264,10 @@ var migrationCommand = {
5227
6264
  };
5228
6265
 
5229
6266
  // src/commands/read-logs/index.ts
5230
- import path16 from "path";
6267
+ import path20 from "path";
5231
6268
 
5232
6269
  // src/commands/read-logs/std-utils.ts
5233
- import fs18 from "fs";
6270
+ import fs22 from "fs";
5234
6271
  function formatStdPrefixTime(localTime) {
5235
6272
  const match = localTime.match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
5236
6273
  if (!match) return localTime;
@@ -5260,11 +6297,11 @@ function stripPrefixFromStdLine(line) {
5260
6297
  return `[${time}] ${content}`;
5261
6298
  }
5262
6299
  function readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, isMarker) {
5263
- const stat = fs18.statSync(filePath);
6300
+ const stat = fs22.statSync(filePath);
5264
6301
  if (stat.size === 0) {
5265
6302
  return { lines: [], markerFound: false, totalLinesCount: 0 };
5266
6303
  }
5267
- const fd = fs18.openSync(filePath, "r");
6304
+ const fd = fs22.openSync(filePath, "r");
5268
6305
  const chunkSize = 64 * 1024;
5269
6306
  let position = stat.size;
5270
6307
  let remainder = "";
@@ -5278,7 +6315,7 @@ function readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, isMarke
5278
6315
  const length = Math.min(chunkSize, position);
5279
6316
  position -= length;
5280
6317
  const buffer = Buffer.alloc(length);
5281
- fs18.readSync(fd, buffer, 0, length, position);
6318
+ fs22.readSync(fd, buffer, 0, length, position);
5282
6319
  let chunk = buffer.toString("utf8");
5283
6320
  if (remainder) {
5284
6321
  chunk += remainder;
@@ -5320,7 +6357,7 @@ function readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, isMarke
5320
6357
  }
5321
6358
  }
5322
6359
  } finally {
5323
- fs18.closeSync(fd);
6360
+ fs22.closeSync(fd);
5324
6361
  }
5325
6362
  return { lines: collected.reverse(), markerFound, totalLinesCount };
5326
6363
  }
@@ -5341,21 +6378,21 @@ function readServerStdSegment(filePath, maxLines, offset) {
5341
6378
  }
5342
6379
 
5343
6380
  // src/commands/read-logs/tail.ts
5344
- import fs19 from "fs";
6381
+ import fs23 from "fs";
5345
6382
  function fileExists(filePath) {
5346
6383
  try {
5347
- fs19.accessSync(filePath, fs19.constants.F_OK | fs19.constants.R_OK);
6384
+ fs23.accessSync(filePath, fs23.constants.F_OK | fs23.constants.R_OK);
5348
6385
  return true;
5349
6386
  } catch {
5350
6387
  return false;
5351
6388
  }
5352
6389
  }
5353
6390
  function readFileTailLines(filePath, maxLines) {
5354
- const stat = fs19.statSync(filePath);
6391
+ const stat = fs23.statSync(filePath);
5355
6392
  if (stat.size === 0) {
5356
6393
  return [];
5357
6394
  }
5358
- const fd = fs19.openSync(filePath, "r");
6395
+ const fd = fs23.openSync(filePath, "r");
5359
6396
  const chunkSize = 64 * 1024;
5360
6397
  const chunks = [];
5361
6398
  let position = stat.size;
@@ -5365,13 +6402,13 @@ function readFileTailLines(filePath, maxLines) {
5365
6402
  const length = Math.min(chunkSize, position);
5366
6403
  position -= length;
5367
6404
  const buffer = Buffer.alloc(length);
5368
- fs19.readSync(fd, buffer, 0, length, position);
6405
+ fs23.readSync(fd, buffer, 0, length, position);
5369
6406
  chunks.unshift(buffer.toString("utf8"));
5370
6407
  const chunkLines = buffer.toString("utf8").split("\n").length - 1;
5371
6408
  collectedLines += chunkLines;
5372
6409
  }
5373
6410
  } finally {
5374
- fs19.closeSync(fd);
6411
+ fs23.closeSync(fd);
5375
6412
  }
5376
6413
  const content = chunks.join("");
5377
6414
  const allLines = content.split("\n");
@@ -5387,11 +6424,11 @@ function readFileTailLines(filePath, maxLines) {
5387
6424
  return allLines.slice(allLines.length - maxLines);
5388
6425
  }
5389
6426
  function readFileTailNonEmptyLinesWithOffset(filePath, maxLines, offset) {
5390
- const stat = fs19.statSync(filePath);
6427
+ const stat = fs23.statSync(filePath);
5391
6428
  if (stat.size === 0) {
5392
6429
  return { lines: [], totalLinesCount: 0 };
5393
6430
  }
5394
- const fd = fs19.openSync(filePath, "r");
6431
+ const fd = fs23.openSync(filePath, "r");
5395
6432
  const chunkSize = 64 * 1024;
5396
6433
  let position = stat.size;
5397
6434
  let remainder = "";
@@ -5403,7 +6440,7 @@ function readFileTailNonEmptyLinesWithOffset(filePath, maxLines, offset) {
5403
6440
  const length = Math.min(chunkSize, position);
5404
6441
  position -= length;
5405
6442
  const buffer = Buffer.alloc(length);
5406
- fs19.readSync(fd, buffer, 0, length, position);
6443
+ fs23.readSync(fd, buffer, 0, length, position);
5407
6444
  let chunk = buffer.toString("utf8");
5408
6445
  if (remainder) {
5409
6446
  chunk += remainder;
@@ -5434,7 +6471,7 @@ function readFileTailNonEmptyLinesWithOffset(filePath, maxLines, offset) {
5434
6471
  }
5435
6472
  }
5436
6473
  } finally {
5437
- fs19.closeSync(fd);
6474
+ fs23.closeSync(fd);
5438
6475
  }
5439
6476
  return { lines: collected.reverse(), totalLinesCount };
5440
6477
  }
@@ -5447,13 +6484,19 @@ function readClientStdSegment(filePath, maxLines, offset) {
5447
6484
  function extractClientStdSegment(lines, maxLines, offset) {
5448
6485
  const bodyLines = lines.map(stripPrefixFromStdLine);
5449
6486
  const hotStartMarkers = [
6487
+ // Webpack markers
5450
6488
  /file change detected\..*incremental compilation/i,
5451
6489
  /starting incremental compilation/i,
5452
6490
  /starting compilation/i,
5453
6491
  /\bcompiling\b/i,
5454
- /\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
5455
6497
  ];
5456
6498
  const hotEndMarkers = [
6499
+ // Webpack markers
5457
6500
  /file change detected\..*incremental compilation/i,
5458
6501
  /\bwebpack compiled\b/i,
5459
6502
  /compiled successfully/i,
@@ -5464,14 +6507,25 @@ function extractClientStdSegment(lines, maxLines, offset) {
5464
6507
  /\bhmr\b/i,
5465
6508
  /hot update/i,
5466
6509
  /\bhot reload\b/i,
5467
- /\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
5468
6518
  ];
5469
6519
  const compiledMarkers = [
6520
+ // Webpack markers
5470
6521
  /\bwebpack compiled\b/i,
5471
6522
  /compiled successfully/i,
5472
6523
  /compiled with warnings/i,
5473
6524
  /compiled with errors/i,
5474
- /failed to compile/i
6525
+ /failed to compile/i,
6526
+ // Vite markers
6527
+ /VITE v[\d.]+.*ready/i,
6528
+ /\[vite\].*hmr update/i
5475
6529
  ];
5476
6530
  let startIndex = -1;
5477
6531
  for (let i = bodyLines.length - 1; i >= 0; i -= 1) {
@@ -5524,14 +6578,15 @@ function extractClientStdSegment(lines, maxLines, offset) {
5524
6578
  }
5525
6579
  const segment = startIndex === -1 ? bodyLines : bodyLines.slice(startIndex);
5526
6580
  if (segment.length === 0) {
5527
- return { lines: [], totalLinesCount: 0 };
6581
+ return { lines: [], totalLinesCount: 0, allLines: [] };
5528
6582
  }
5529
6583
  const totalLinesCount = segment.length;
5530
6584
  const endExclusive = Math.max(0, segment.length - offset);
5531
6585
  const start = Math.max(0, endExclusive - maxLines);
5532
6586
  return {
5533
6587
  lines: segment.slice(start, endExclusive),
5534
- totalLinesCount
6588
+ totalLinesCount,
6589
+ allLines: segment
5535
6590
  };
5536
6591
  }
5537
6592
 
@@ -5558,7 +6613,7 @@ function readDevStdSegment(filePath, maxLines, offset) {
5558
6613
  }
5559
6614
 
5560
6615
  // src/commands/read-logs/json-lines.ts
5561
- import fs20 from "fs";
6616
+ import fs24 from "fs";
5562
6617
  function normalizePid(value) {
5563
6618
  if (typeof value === "number") {
5564
6619
  return String(value);
@@ -5609,11 +6664,11 @@ function buildWantedLevelSet(levels) {
5609
6664
  return set.size > 0 ? set : null;
5610
6665
  }
5611
6666
  function readJsonLinesLastPid(filePath, maxLines, offset, levels) {
5612
- const stat = fs20.statSync(filePath);
6667
+ const stat = fs24.statSync(filePath);
5613
6668
  if (stat.size === 0) {
5614
6669
  return { lines: [], totalLinesCount: 0 };
5615
6670
  }
5616
- const fd = fs20.openSync(filePath, "r");
6671
+ const fd = fs24.openSync(filePath, "r");
5617
6672
  const chunkSize = 64 * 1024;
5618
6673
  let position = stat.size;
5619
6674
  let remainder = "";
@@ -5628,7 +6683,7 @@ function readJsonLinesLastPid(filePath, maxLines, offset, levels) {
5628
6683
  const length = Math.min(chunkSize, position);
5629
6684
  position -= length;
5630
6685
  const buffer = Buffer.alloc(length);
5631
- fs20.readSync(fd, buffer, 0, length, position);
6686
+ fs24.readSync(fd, buffer, 0, length, position);
5632
6687
  let chunk = buffer.toString("utf8");
5633
6688
  if (remainder) {
5634
6689
  chunk += remainder;
@@ -5690,7 +6745,7 @@ function readJsonLinesLastPid(filePath, maxLines, offset, levels) {
5690
6745
  }
5691
6746
  }
5692
6747
  } finally {
5693
- fs20.closeSync(fd);
6748
+ fs24.closeSync(fd);
5694
6749
  }
5695
6750
  return { lines: collected.reverse(), totalLinesCount };
5696
6751
  }
@@ -5733,11 +6788,11 @@ function extractTraceId(obj) {
5733
6788
  function readJsonLinesByTraceId(filePath, traceId, maxLines, offset, levels) {
5734
6789
  const wanted = traceId.trim();
5735
6790
  if (!wanted) return { lines: [], totalLinesCount: 0 };
5736
- const stat = fs20.statSync(filePath);
6791
+ const stat = fs24.statSync(filePath);
5737
6792
  if (stat.size === 0) {
5738
6793
  return { lines: [], totalLinesCount: 0 };
5739
6794
  }
5740
- const fd = fs20.openSync(filePath, "r");
6795
+ const fd = fs24.openSync(filePath, "r");
5741
6796
  const chunkSize = 64 * 1024;
5742
6797
  let position = stat.size;
5743
6798
  let remainder = "";
@@ -5750,7 +6805,7 @@ function readJsonLinesByTraceId(filePath, traceId, maxLines, offset, levels) {
5750
6805
  const length = Math.min(chunkSize, position);
5751
6806
  position -= length;
5752
6807
  const buffer = Buffer.alloc(length);
5753
- fs20.readSync(fd, buffer, 0, length, position);
6808
+ fs24.readSync(fd, buffer, 0, length, position);
5754
6809
  let chunk = buffer.toString("utf8");
5755
6810
  if (remainder) {
5756
6811
  chunk += remainder;
@@ -5803,7 +6858,7 @@ function readJsonLinesByTraceId(filePath, traceId, maxLines, offset, levels) {
5803
6858
  }
5804
6859
  }
5805
6860
  } finally {
5806
- fs20.closeSync(fd);
6861
+ fs24.closeSync(fd);
5807
6862
  }
5808
6863
  return { lines: collected.reverse(), totalLinesCount };
5809
6864
  }
@@ -5812,11 +6867,11 @@ function readJsonLinesTailByLevel(filePath, maxLines, offset, levels) {
5812
6867
  if (!wantedLevelSet) {
5813
6868
  return { lines: [], totalLinesCount: 0 };
5814
6869
  }
5815
- const stat = fs20.statSync(filePath);
6870
+ const stat = fs24.statSync(filePath);
5816
6871
  if (stat.size === 0) {
5817
6872
  return { lines: [], totalLinesCount: 0 };
5818
6873
  }
5819
- const fd = fs20.openSync(filePath, "r");
6874
+ const fd = fs24.openSync(filePath, "r");
5820
6875
  const chunkSize = 64 * 1024;
5821
6876
  let position = stat.size;
5822
6877
  let remainder = "";
@@ -5828,7 +6883,7 @@ function readJsonLinesTailByLevel(filePath, maxLines, offset, levels) {
5828
6883
  const length = Math.min(chunkSize, position);
5829
6884
  position -= length;
5830
6885
  const buffer = Buffer.alloc(length);
5831
- fs20.readSync(fd, buffer, 0, length, position);
6886
+ fs24.readSync(fd, buffer, 0, length, position);
5832
6887
  let chunk = buffer.toString("utf8");
5833
6888
  if (remainder) {
5834
6889
  chunk += remainder;
@@ -5875,7 +6930,7 @@ function readJsonLinesTailByLevel(filePath, maxLines, offset, levels) {
5875
6930
  }
5876
6931
  }
5877
6932
  } finally {
5878
- fs20.closeSync(fd);
6933
+ fs24.closeSync(fd);
5879
6934
  }
5880
6935
  return { lines: collected.reverse(), totalLinesCount };
5881
6936
  }
@@ -6068,12 +7123,21 @@ async function readLatestLogLinesMeta(options) {
6068
7123
  return { lines: [], totalLinesCount: 0 };
6069
7124
  }
6070
7125
  async function readLogsJsonResult(options) {
6071
- const { lines, totalLinesCount } = await readLatestLogLinesMeta(options);
7126
+ const { lines, totalLinesCount, allLines } = await readLatestLogLinesMeta(options);
6072
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);
6073
7137
  return {
6074
- hasError: hasErrorInStdLines(lines),
6075
- totalLinesCount,
6076
- logs: lines
7138
+ hasError: hasErrorInStdLines(linesForErrorCheck),
7139
+ totalLinesCount: filteredLines.length,
7140
+ logs: paginatedLogs
6077
7141
  };
6078
7142
  }
6079
7143
  const logs = [];
@@ -6100,34 +7164,34 @@ async function readLogsJsonResult(options) {
6100
7164
  };
6101
7165
  }
6102
7166
  function resolveLogFilePath(logDir, type) {
6103
- const base = path16.isAbsolute(logDir) ? logDir : path16.join(process.cwd(), logDir);
7167
+ const base = path20.isAbsolute(logDir) ? logDir : path20.join(process.cwd(), logDir);
6104
7168
  if (type === "server") {
6105
- return path16.join(base, "server.log");
7169
+ return path20.join(base, "server.log");
6106
7170
  }
6107
7171
  if (type === "trace") {
6108
- return path16.join(base, "trace.log");
7172
+ return path20.join(base, "trace.log");
6109
7173
  }
6110
7174
  if (type === "server-std") {
6111
- return path16.join(base, "server.std.log");
7175
+ return path20.join(base, "server.std.log");
6112
7176
  }
6113
7177
  if (type === "client-std") {
6114
- return path16.join(base, "client.std.log");
7178
+ return path20.join(base, "client.std.log");
6115
7179
  }
6116
7180
  if (type === "dev") {
6117
- return path16.join(base, "dev.log");
7181
+ return path20.join(base, "dev.log");
6118
7182
  }
6119
7183
  if (type === "dev-std") {
6120
- return path16.join(base, "dev.std.log");
7184
+ return path20.join(base, "dev.std.log");
6121
7185
  }
6122
7186
  if (type === "install-dep-std") {
6123
- return path16.join(base, "install-dep.std.log");
7187
+ return path20.join(base, "install-dep.std.log");
6124
7188
  }
6125
7189
  if (type === "browser") {
6126
- return path16.join(base, "browser.log");
7190
+ return path20.join(base, "browser.log");
6127
7191
  }
6128
7192
  throw new Error(`Unsupported log type: ${type}`);
6129
7193
  }
6130
- async function run4(options) {
7194
+ async function run6(options) {
6131
7195
  const result = await readLogsJsonResult(options);
6132
7196
  process.stdout.write(JSON.stringify(result) + "\n");
6133
7197
  }
@@ -6169,7 +7233,7 @@ var readLogsCommand = {
6169
7233
  const offset = parseNonNegativeInt(rawOptions.offset, "--offset");
6170
7234
  const traceId = typeof rawOptions.traceId === "string" ? rawOptions.traceId : void 0;
6171
7235
  const levels = parseCommaSeparatedList(rawOptions.level);
6172
- await run4({ logDir, type, maxLines, offset, traceId, levels });
7236
+ await run6({ logDir, type, maxLines, offset, traceId, levels });
6173
7237
  } catch (error) {
6174
7238
  const message = error instanceof Error ? error.message : String(error);
6175
7239
  process.stderr.write(message + "\n");
@@ -6179,23 +7243,352 @@ var readLogsCommand = {
6179
7243
  }
6180
7244
  };
6181
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 os2 from "os";
7355
+ import * as path21 from "path";
7356
+ import { execFileSync } from "child_process";
7357
+ function readCredentialsFromEnv() {
7358
+ const uploadPrefix = process.env.STATIC_UPLOAD_PREFIX;
7359
+ const uploadID = process.env.STATIC_UPLOAD_ID;
7360
+ const bucketId = process.env.STATIC_UPLOAD_BUCKET_ID;
7361
+ const accessKeyID = process.env.STATIC_UPLOAD_AK;
7362
+ const secretAccessKey = process.env.STATIC_UPLOAD_SK;
7363
+ const sessionToken = process.env.STATIC_UPLOAD_TOKEN;
7364
+ if (!uploadPrefix || !uploadID || !bucketId || !accessKeyID || !secretAccessKey || !sessionToken) {
7365
+ return null;
7366
+ }
7367
+ return { uploadPrefix, uploadID, bucketId, accessKeyID, secretAccessKey, sessionToken };
7368
+ }
7369
+ var LOG_PREFIX = "[upload-static]";
7370
+ async function uploadStatic(options) {
7371
+ try {
7372
+ const {
7373
+ appId,
7374
+ staticDir = UPLOAD_STATIC_DEFAULTS.staticDir,
7375
+ tosutilPath = UPLOAD_STATIC_DEFAULTS.tosutilPath,
7376
+ endpoint = UPLOAD_STATIC_DEFAULTS.endpoint,
7377
+ region = UPLOAD_STATIC_DEFAULTS.region
7378
+ } = options;
7379
+ const resolvedStaticDir = path21.resolve(staticDir);
7380
+ if (!fs25.existsSync(resolvedStaticDir)) {
7381
+ console.error(`${LOG_PREFIX} \u76EE\u5F55\u4E0D\u5B58\u5728: ${resolvedStaticDir}\uFF0C\u8DF3\u8FC7\u4E0A\u4F20`);
7382
+ return;
7383
+ }
7384
+ if (isDirEmpty(resolvedStaticDir)) {
7385
+ console.error(`${LOG_PREFIX} \u76EE\u5F55\u4E3A\u7A7A: ${resolvedStaticDir}\uFF0C\u8DF3\u8FC7\u4E0A\u4F20`);
7386
+ return;
7387
+ }
7388
+ const resolvedTosutil = resolveTosutilPath(tosutilPath);
7389
+ if (!resolvedTosutil) {
7390
+ throw new Error(
7391
+ `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`
7392
+ );
7393
+ }
7394
+ let uploadPrefix;
7395
+ let uploadID;
7396
+ let bucketId;
7397
+ let accessKeyID;
7398
+ let secretAccessKey;
7399
+ let sessionToken;
7400
+ const envCredentials = readCredentialsFromEnv();
7401
+ if (envCredentials) {
7402
+ console.error(`${LOG_PREFIX} \u4F7F\u7528\u73AF\u5883\u53D8\u91CF\u4E2D\u7684\u4E0A\u4F20\u51ED\u8BC1`);
7403
+ ({ uploadPrefix, uploadID, bucketId, accessKeyID, secretAccessKey, sessionToken } = envCredentials);
7404
+ } else {
7405
+ console.error(`${LOG_PREFIX} \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E\uFF0C\u8C03\u7528 preUploadStatic...`);
7406
+ bucketId = await resolveBucketId(appId);
7407
+ const preUploadResp = await fetchPreUpload(appId, bucketId);
7408
+ const { uploadCredential } = preUploadResp.data;
7409
+ ({ uploadPrefix, uploadID } = preUploadResp.data);
7410
+ ({ AccessKeyID: accessKeyID, SecretAccessKey: secretAccessKey, SessionToken: sessionToken } = uploadCredential);
7411
+ }
7412
+ console.error(`${LOG_PREFIX} \u4E0A\u4F20\u76EE\u6807: ${uploadPrefix}`);
7413
+ const confPath = path21.join(os2.tmpdir(), `.tosutilconfig-static-${process.pid}`);
7414
+ fs25.writeFileSync(confPath, "");
7415
+ try {
7416
+ console.error(`${LOG_PREFIX} \u914D\u7F6E tosutil...`);
7417
+ configureTosutil(resolvedTosutil, confPath, {
7418
+ endpoint,
7419
+ region,
7420
+ accessKeyID,
7421
+ secretAccessKey,
7422
+ sessionToken
7423
+ });
7424
+ console.error(`${LOG_PREFIX} \u4E0A\u4F20 ${resolvedStaticDir} -> ${uploadPrefix}`);
7425
+ uploadToTos(resolvedTosutil, confPath, resolvedStaticDir, uploadPrefix);
7426
+ } finally {
7427
+ try {
7428
+ fs25.unlinkSync(confPath);
7429
+ } catch {
7430
+ }
7431
+ }
7432
+ console.error(`${LOG_PREFIX} tosutil \u4E0A\u4F20\u5B8C\u6210`);
7433
+ console.error(`${LOG_PREFIX} \u8C03\u7528 callbackStatic (uploadID: ${uploadID})...`);
7434
+ const callbackResp = await uploadStaticAttachmentCallback(appId, bucketId, { uploadID });
7435
+ if (callbackResp.status_code !== "0") {
7436
+ throw new Error(`callbackStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${callbackResp.status_code}`);
7437
+ }
7438
+ const attachments = callbackResp.data?.attachments || [];
7439
+ console.error(`${LOG_PREFIX} \u4E0A\u4F20\u5B8C\u6210\uFF0C\u5171 ${attachments.length} \u4E2A\u6587\u4EF6`);
7440
+ console.log(JSON.stringify(callbackResp));
7441
+ } catch (error) {
7442
+ const message = error instanceof Error ? error.message : String(error);
7443
+ console.error(`${LOG_PREFIX} Error: ${message}`);
7444
+ process.exit(1);
7445
+ }
7446
+ }
7447
+ function resolveTosutilPath(tosutilPath) {
7448
+ if (path21.isAbsolute(tosutilPath)) {
7449
+ return fs25.existsSync(tosutilPath) ? tosutilPath : null;
7450
+ }
7451
+ try {
7452
+ const resolved = execFileSync("which", [tosutilPath], { encoding: "utf-8" }).trim();
7453
+ return resolved || null;
7454
+ } catch {
7455
+ return null;
7456
+ }
7457
+ }
7458
+ async function fetchPreUpload(appId, bucketId) {
7459
+ const response = await preUploadStaticAttachment(appId, bucketId);
7460
+ if (response.status_code !== "0") {
7461
+ throw new Error(`preUploadStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${response.status_code}`);
7462
+ }
7463
+ const { uploadPrefix, uploadID, uploadCredential } = response.data || {};
7464
+ if (!uploadPrefix || !uploadID) {
7465
+ throw new Error("preUploadStatic \u8FD4\u56DE\u6570\u636E\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11 uploadPrefix \u6216 uploadID");
7466
+ }
7467
+ if (!uploadCredential?.AccessKeyID || !uploadCredential?.SecretAccessKey || !uploadCredential?.SessionToken) {
7468
+ throw new Error("preUploadStatic \u8FD4\u56DE\u7684\u51ED\u8BC1\u5B57\u6BB5\u4E0D\u5B8C\u6574");
7469
+ }
7470
+ return response;
7471
+ }
7472
+ function configureTosutil(tosutilPath, confPath, config) {
7473
+ const { endpoint, region, accessKeyID, secretAccessKey, sessionToken } = config;
7474
+ execFileSync(
7475
+ tosutilPath,
7476
+ ["config", "-conf", confPath, "-e", endpoint, "-i", accessKeyID, "-k", secretAccessKey, "-t", sessionToken, "-re", region],
7477
+ { stdio: "pipe" }
7478
+ );
7479
+ }
7480
+ function uploadToTos(tosutilPath, confPath, sourceDir, destUrl) {
7481
+ execFileSync(
7482
+ tosutilPath,
7483
+ ["cp", "-conf", confPath, sourceDir, destUrl, "-r", "-flat", "-j", "5", "-p", "3", "-ps", "10485760", "-f"],
7484
+ { stdio: "inherit" }
7485
+ );
7486
+ }
7487
+ async function resolveBucketId(appId) {
7488
+ console.error(`${LOG_PREFIX} \u83B7\u53D6\u9ED8\u8BA4\u5B58\u50A8\u6876...`);
7489
+ const bucketId = await getDefaultBucketId(appId);
7490
+ console.error(`${LOG_PREFIX} \u9ED8\u8BA4\u5B58\u50A8\u6876: ${bucketId}`);
7491
+ return bucketId;
7492
+ }
7493
+ function isDirEmpty(dirPath) {
7494
+ const entries = fs25.readdirSync(dirPath);
7495
+ return entries.length === 0;
7496
+ }
7497
+
7498
+ // src/commands/build/pre-upload-static.handler.ts
7499
+ var LOG_PREFIX2 = "[pre-upload-static]";
7500
+ function shellEscape(value) {
7501
+ return value.replace(/["\\`$]/g, "\\$&");
7502
+ }
7503
+ async function preUploadStatic(options) {
7504
+ try {
7505
+ const { appId } = options;
7506
+ console.error(`${LOG_PREFIX2} \u83B7\u53D6\u9ED8\u8BA4\u5B58\u50A8\u6876...`);
7507
+ const bucketId = await getDefaultBucketId(appId);
7508
+ console.error(`${LOG_PREFIX2} \u9ED8\u8BA4\u5B58\u50A8\u6876: ${bucketId}`);
7509
+ console.error(`${LOG_PREFIX2} \u8C03\u7528 preUploadStatic...`);
7510
+ const response = await preUploadStaticAttachment(appId, bucketId);
7511
+ if (response.status_code !== "0") {
7512
+ console.error(`${LOG_PREFIX2} preUploadStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${response.status_code}`);
7513
+ return;
7514
+ }
7515
+ const { downloadURLPrefix, uploadPrefix, uploadID, uploadCredential } = response.data || {};
7516
+ if (!downloadURLPrefix || !uploadPrefix || !uploadID) {
7517
+ console.error(`${LOG_PREFIX2} preUploadStatic \u8FD4\u56DE\u6570\u636E\u4E0D\u5B8C\u6574`);
7518
+ return;
7519
+ }
7520
+ if (!uploadCredential?.AccessKeyID || !uploadCredential?.SecretAccessKey || !uploadCredential?.SessionToken) {
7521
+ console.error(`${LOG_PREFIX2} preUploadStatic \u8FD4\u56DE\u7684\u51ED\u8BC1\u5B57\u6BB5\u4E0D\u5B8C\u6574`);
7522
+ return;
7523
+ }
7524
+ console.log(`export STATIC_ASSETS_BASE_URL="${shellEscape(downloadURLPrefix)}"`);
7525
+ console.log(`export STATIC_UPLOAD_PREFIX="${shellEscape(uploadPrefix)}"`);
7526
+ console.log(`export STATIC_UPLOAD_ID="${shellEscape(uploadID)}"`);
7527
+ console.log(`export STATIC_UPLOAD_AK="${shellEscape(uploadCredential.AccessKeyID)}"`);
7528
+ console.log(`export STATIC_UPLOAD_SK="${shellEscape(uploadCredential.SecretAccessKey)}"`);
7529
+ console.log(`export STATIC_UPLOAD_TOKEN="${shellEscape(uploadCredential.SessionToken)}"`);
7530
+ console.log(`export STATIC_UPLOAD_BUCKET_ID="${shellEscape(bucketId)}"`);
7531
+ console.error(`${LOG_PREFIX2} \u73AF\u5883\u53D8\u91CF\u5DF2\u8F93\u51FA`);
7532
+ } catch (error) {
7533
+ const message = error instanceof Error ? error.message : String(error);
7534
+ console.error(`${LOG_PREFIX2} Warning: ${message}`);
7535
+ }
7536
+ }
7537
+
7538
+ // src/commands/build/index.ts
7539
+ var getTokenCommand = {
7540
+ name: "get-token",
7541
+ description: "Get artifact upload credential (STI token)",
7542
+ register(program) {
7543
+ 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) => {
7544
+ await getToken(options);
7545
+ });
7546
+ }
7547
+ };
7548
+ var uploadStaticCommand = {
7549
+ name: "upload-static",
7550
+ description: "Upload shared/static files to TOS",
7551
+ register(program) {
7552
+ 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) => {
7553
+ await uploadStatic(options);
7554
+ });
7555
+ }
7556
+ };
7557
+ var preUploadStaticCommand = {
7558
+ name: "pre-upload-static",
7559
+ description: "Get TOS upload info and output as env vars for build.sh eval",
7560
+ register(program) {
7561
+ program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").action(async (options) => {
7562
+ await preUploadStatic(options);
7563
+ });
7564
+ }
7565
+ };
7566
+ var buildCommandGroup = {
7567
+ name: "build",
7568
+ description: "Build related commands",
7569
+ commands: [getTokenCommand, uploadStaticCommand, preUploadStaticCommand]
7570
+ };
7571
+
6182
7572
  // src/commands/index.ts
6183
7573
  var commands = [
6184
7574
  genDbSchemaCommand,
6185
7575
  syncCommand,
7576
+ upgradeCommand,
6186
7577
  actionPluginCommandGroup,
6187
7578
  capabilityCommandGroup,
7579
+ componentCommandGroup,
6188
7580
  migrationCommand,
6189
- readLogsCommand
7581
+ readLogsCommand,
7582
+ buildCommandGroup
6190
7583
  ];
6191
7584
 
6192
7585
  // src/index.ts
6193
- var envPath = path17.join(process.cwd(), ".env");
6194
- if (fs21.existsSync(envPath)) {
7586
+ var envPath = path22.join(process.cwd(), ".env");
7587
+ if (fs26.existsSync(envPath)) {
6195
7588
  dotenvConfig({ path: envPath });
6196
7589
  }
6197
- var __dirname = path17.dirname(fileURLToPath4(import.meta.url));
6198
- var pkg = JSON.parse(fs21.readFileSync(path17.join(__dirname, "../package.json"), "utf-8"));
7590
+ var __dirname = path22.dirname(fileURLToPath5(import.meta.url));
7591
+ var pkg = JSON.parse(fs26.readFileSync(path22.join(__dirname, "../package.json"), "utf-8"));
6199
7592
  var cli = new FullstackCLI(pkg.version);
6200
7593
  cli.useAll(commands);
6201
7594
  cli.run();