@lark-apaas/fullstack-cli 1.1.16-beta.0 → 1.1.16-beta.2
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/gen-dbschema-template/types.ts +54 -0
- package/dist/index.js +495 -41
- package/package.json +1 -1
- package/templates/drizzle.config.ts +55 -0
- package/templates/scripts/dev.sh +5 -1
|
@@ -38,6 +38,60 @@ export const fileAttachment = customType<{
|
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
/** Escape single quotes in SQL string literals */
|
|
42
|
+
function escapeLiteral(str: string): string {
|
|
43
|
+
return `'${str.replace(/'/g, "''")}'`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const userProfileArray = customType<{
|
|
47
|
+
data: string[];
|
|
48
|
+
driverData: string;
|
|
49
|
+
}>({
|
|
50
|
+
dataType() {
|
|
51
|
+
return 'user_profile[]';
|
|
52
|
+
},
|
|
53
|
+
toDriver(value: string[]) {
|
|
54
|
+
if (!value || value.length === 0) {
|
|
55
|
+
return sql`'{}'::user_profile[]`;
|
|
56
|
+
}
|
|
57
|
+
const elements = value.map(id => `ROW(${escapeLiteral(id)})::user_profile`).join(',');
|
|
58
|
+
return sql.raw(`ARRAY[${elements}]::user_profile[]`);
|
|
59
|
+
},
|
|
60
|
+
fromDriver(value: string): string[] {
|
|
61
|
+
if (!value || value === '{}') return [];
|
|
62
|
+
const inner = value.slice(1, -1);
|
|
63
|
+
const matches = inner.match(/\([^)]*\)/g) || [];
|
|
64
|
+
return matches.map(m => m.slice(1, -1).split(',')[0].trim());
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const fileAttachmentArray = customType<{
|
|
69
|
+
data: FileAttachment[];
|
|
70
|
+
driverData: string;
|
|
71
|
+
}>({
|
|
72
|
+
dataType() {
|
|
73
|
+
return 'file_attachment[]';
|
|
74
|
+
},
|
|
75
|
+
toDriver(value: FileAttachment[]) {
|
|
76
|
+
if (!value || value.length === 0) {
|
|
77
|
+
return sql`'{}'::file_attachment[]`;
|
|
78
|
+
}
|
|
79
|
+
const elements = value.map(f =>
|
|
80
|
+
`ROW(${escapeLiteral(f.bucket_id)},${escapeLiteral(f.file_path)})::file_attachment`
|
|
81
|
+
).join(',');
|
|
82
|
+
return sql.raw(`ARRAY[${elements}]::file_attachment[]`);
|
|
83
|
+
},
|
|
84
|
+
fromDriver(value: string): FileAttachment[] {
|
|
85
|
+
if (!value || value === '{}') return [];
|
|
86
|
+
const inner = value.slice(1, -1);
|
|
87
|
+
const matches = inner.match(/\([^)]*\)/g) || [];
|
|
88
|
+
return matches.map(m => {
|
|
89
|
+
const [bucketId, filePath] = m.slice(1, -1).split(',');
|
|
90
|
+
return { bucket_id: bucketId.trim(), file_path: filePath.trim() };
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
41
95
|
export const customTimestamptz = customType<{
|
|
42
96
|
data: Date;
|
|
43
97
|
driverData: string;
|
package/dist/index.js
CHANGED
|
@@ -472,6 +472,10 @@ var KNOWN_TYPE_FACTORIES = {
|
|
|
472
472
|
user_profile: "userProfile",
|
|
473
473
|
file_attachment: "fileAttachment"
|
|
474
474
|
};
|
|
475
|
+
var KNOWN_ARRAY_TYPE_FACTORIES = {
|
|
476
|
+
user_profile: "userProfileArray",
|
|
477
|
+
file_attachment: "fileAttachmentArray"
|
|
478
|
+
};
|
|
475
479
|
var replaceUnknownTransform = {
|
|
476
480
|
name: "replace-unknown",
|
|
477
481
|
transform(ctx) {
|
|
@@ -490,12 +494,17 @@ var replaceUnknownTransform = {
|
|
|
490
494
|
const lines = textBefore.split("\n");
|
|
491
495
|
let factoryName = "text";
|
|
492
496
|
let foundKnownType = false;
|
|
497
|
+
let isArrayType = false;
|
|
493
498
|
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 5); i--) {
|
|
494
499
|
const line = lines[i];
|
|
495
500
|
const todoMatch = line.match(/\/\/ TODO: failed to parse database type '(?:\w+\.)?([\w_]+)(\[\])?'/);
|
|
496
501
|
if (todoMatch) {
|
|
497
502
|
const typeName = todoMatch[1];
|
|
498
|
-
|
|
503
|
+
isArrayType = todoMatch[2] === "[]";
|
|
504
|
+
if (isArrayType && KNOWN_ARRAY_TYPE_FACTORIES[typeName]) {
|
|
505
|
+
factoryName = KNOWN_ARRAY_TYPE_FACTORIES[typeName];
|
|
506
|
+
foundKnownType = true;
|
|
507
|
+
} else if (KNOWN_TYPE_FACTORIES[typeName]) {
|
|
499
508
|
factoryName = KNOWN_TYPE_FACTORIES[typeName];
|
|
500
509
|
foundKnownType = true;
|
|
501
510
|
}
|
|
@@ -503,6 +512,15 @@ var replaceUnknownTransform = {
|
|
|
503
512
|
}
|
|
504
513
|
}
|
|
505
514
|
expression.replaceWithText(factoryName);
|
|
515
|
+
if (isArrayType && foundKnownType) {
|
|
516
|
+
const parent = node.getParent();
|
|
517
|
+
if (Node5.isPropertyAccessExpression(parent) && parent.getName() === "array") {
|
|
518
|
+
const grandParent = parent.getParent();
|
|
519
|
+
if (Node5.isCallExpression(grandParent)) {
|
|
520
|
+
grandParent.replaceWithText(node.getText());
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
506
524
|
if (foundKnownType) {
|
|
507
525
|
stats.replacedUnknown++;
|
|
508
526
|
} else {
|
|
@@ -1005,6 +1023,139 @@ function resolveTemplateTypesPath() {
|
|
|
1005
1023
|
return void 0;
|
|
1006
1024
|
}
|
|
1007
1025
|
|
|
1026
|
+
// src/commands/db/gen-dbschema/utils/fetch-column-comments.ts
|
|
1027
|
+
import postgres from "postgres";
|
|
1028
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
1029
|
+
async function fetchColumnComments(connectionString, options = {}) {
|
|
1030
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1031
|
+
const url = new URL(connectionString);
|
|
1032
|
+
const schemaName = url.searchParams.get("schema") ?? "public";
|
|
1033
|
+
const sql = postgres(connectionString, {
|
|
1034
|
+
connect_timeout: Math.ceil(timeoutMs / 1e3),
|
|
1035
|
+
idle_timeout: Math.ceil(timeoutMs / 1e3)
|
|
1036
|
+
});
|
|
1037
|
+
try {
|
|
1038
|
+
const queryPromise = sql`
|
|
1039
|
+
SELECT
|
|
1040
|
+
c.table_name AS "tableName",
|
|
1041
|
+
c.column_name AS "columnName",
|
|
1042
|
+
pgd.description AS "comment"
|
|
1043
|
+
FROM information_schema.columns c
|
|
1044
|
+
JOIN pg_catalog.pg_statio_all_tables st
|
|
1045
|
+
ON c.table_name = st.relname AND c.table_schema = st.schemaname
|
|
1046
|
+
JOIN pg_catalog.pg_description pgd
|
|
1047
|
+
ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
|
|
1048
|
+
WHERE c.table_schema = ${schemaName}
|
|
1049
|
+
AND pgd.description IS NOT NULL
|
|
1050
|
+
AND pgd.description != ''
|
|
1051
|
+
`;
|
|
1052
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1053
|
+
setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
1054
|
+
});
|
|
1055
|
+
const result = await Promise.race([queryPromise, timeoutPromise]);
|
|
1056
|
+
const commentMap = /* @__PURE__ */ new Map();
|
|
1057
|
+
for (const row of result) {
|
|
1058
|
+
const key = `${row.tableName}.${row.columnName}`;
|
|
1059
|
+
commentMap.set(key, row.comment);
|
|
1060
|
+
}
|
|
1061
|
+
return commentMap;
|
|
1062
|
+
} finally {
|
|
1063
|
+
await sql.end().catch(() => {
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
function extractTypeAnnotation(comment) {
|
|
1068
|
+
const typeStart = comment.indexOf("@type");
|
|
1069
|
+
if (typeStart === -1) return null;
|
|
1070
|
+
const afterType = comment.slice(typeStart + 5).trimStart();
|
|
1071
|
+
if (!afterType.startsWith("{")) return null;
|
|
1072
|
+
let depth = 0;
|
|
1073
|
+
let endIndex = 0;
|
|
1074
|
+
for (let i = 0; i < afterType.length; i++) {
|
|
1075
|
+
if (afterType[i] === "{") depth++;
|
|
1076
|
+
if (afterType[i] === "}") depth--;
|
|
1077
|
+
if (depth === 0) {
|
|
1078
|
+
endIndex = i + 1;
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (endIndex === 0) return null;
|
|
1083
|
+
return afterType.slice(0, endIndex);
|
|
1084
|
+
}
|
|
1085
|
+
function parseColumnComment(comment) {
|
|
1086
|
+
const typeValue = extractTypeAnnotation(comment);
|
|
1087
|
+
if (typeValue) {
|
|
1088
|
+
const descMatch = comment.match(/@description\s+([^@]+)/);
|
|
1089
|
+
return {
|
|
1090
|
+
type: typeValue,
|
|
1091
|
+
description: descMatch?.[1]?.trim()
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
return { description: comment.trim() };
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/commands/db/gen-dbschema/transforms/text/jsonb-comments.ts
|
|
1098
|
+
function addJsonbTypeComments(source, columnComments) {
|
|
1099
|
+
if (!columnComments || columnComments.size === 0) {
|
|
1100
|
+
return { text: source, added: 0 };
|
|
1101
|
+
}
|
|
1102
|
+
const lines = source.split("\n");
|
|
1103
|
+
const result = [];
|
|
1104
|
+
let added = 0;
|
|
1105
|
+
let currentTableName = null;
|
|
1106
|
+
const tableDefRegex = /export const\s+\w+\s*=\s*pgTable\(\s*["'`]([^"'`]+)["'`]/;
|
|
1107
|
+
const jsonFieldWithNameRegex = /^\s*(\w+):\s*(?:json|jsonb)\(\s*["'`]([^"'`]+)["'`]\)/;
|
|
1108
|
+
const jsonFieldNoNameRegex = /^\s*(\w+):\s*(?:json|jsonb)\(\s*\)/;
|
|
1109
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1110
|
+
const line = lines[i];
|
|
1111
|
+
const tableMatch = line.match(tableDefRegex);
|
|
1112
|
+
if (tableMatch) {
|
|
1113
|
+
currentTableName = tableMatch[1];
|
|
1114
|
+
}
|
|
1115
|
+
let columnName = null;
|
|
1116
|
+
const jsonMatchWithName = line.match(jsonFieldWithNameRegex);
|
|
1117
|
+
const jsonMatchNoName = line.match(jsonFieldNoNameRegex);
|
|
1118
|
+
if (jsonMatchWithName) {
|
|
1119
|
+
columnName = jsonMatchWithName[2];
|
|
1120
|
+
} else if (jsonMatchNoName) {
|
|
1121
|
+
columnName = jsonMatchNoName[1];
|
|
1122
|
+
}
|
|
1123
|
+
if (columnName && currentTableName) {
|
|
1124
|
+
const commentKey = `${currentTableName}.${columnName}`;
|
|
1125
|
+
const comment = columnComments.get(commentKey);
|
|
1126
|
+
if (comment) {
|
|
1127
|
+
const parsed = parseColumnComment(comment);
|
|
1128
|
+
const indentMatch = line.match(/^\s*/);
|
|
1129
|
+
const indent = indentMatch ? indentMatch[0] : "";
|
|
1130
|
+
const prevLine = result[result.length - 1]?.trim() ?? "";
|
|
1131
|
+
if (!prevLine.startsWith("/**") && !prevLine.startsWith("*") && !prevLine.startsWith("//")) {
|
|
1132
|
+
const commentLines = [];
|
|
1133
|
+
commentLines.push(`${indent}/**`);
|
|
1134
|
+
if (parsed.description) {
|
|
1135
|
+
const safeDesc = parsed.description.replace(/[\r\n]+/g, " ").trim();
|
|
1136
|
+
commentLines.push(`${indent} * ${safeDesc}`);
|
|
1137
|
+
}
|
|
1138
|
+
if (parsed.type) {
|
|
1139
|
+
if (parsed.description) {
|
|
1140
|
+
commentLines.push(`${indent} *`);
|
|
1141
|
+
}
|
|
1142
|
+
const safeType = parsed.type.replace(/[\r\n]+/g, " ").trim();
|
|
1143
|
+
commentLines.push(`${indent} * @type ${safeType}`);
|
|
1144
|
+
}
|
|
1145
|
+
commentLines.push(`${indent} */`);
|
|
1146
|
+
result.push(...commentLines);
|
|
1147
|
+
added++;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (line.match(/^\}\);?\s*$/) || line.match(/^}\s*,\s*\{/)) {
|
|
1152
|
+
currentTableName = null;
|
|
1153
|
+
}
|
|
1154
|
+
result.push(line);
|
|
1155
|
+
}
|
|
1156
|
+
return { text: result.join("\n"), added };
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1008
1159
|
// src/commands/db/gen-dbschema/transforms/text/table-aliases.ts
|
|
1009
1160
|
var TABLE_ALIAS_MARKER = "// table aliases";
|
|
1010
1161
|
function generateTableAliases(source) {
|
|
@@ -1041,7 +1192,7 @@ function formatSource(source) {
|
|
|
1041
1192
|
}
|
|
1042
1193
|
|
|
1043
1194
|
// src/commands/db/gen-dbschema/postprocess.ts
|
|
1044
|
-
function postprocessSchema(rawSource) {
|
|
1195
|
+
function postprocessSchema(rawSource, options = {}) {
|
|
1045
1196
|
const patchResult = patchDefects(rawSource);
|
|
1046
1197
|
let source = patchResult.text;
|
|
1047
1198
|
const { sourceFile } = parseSource(source);
|
|
@@ -1051,12 +1202,15 @@ function postprocessSchema(rawSource) {
|
|
|
1051
1202
|
source = ensureHeader(source);
|
|
1052
1203
|
source = addSystemFieldComments(source);
|
|
1053
1204
|
source = inlineCustomTypes(source);
|
|
1205
|
+
const jsonbCommentsResult = addJsonbTypeComments(source, options.columnComments);
|
|
1206
|
+
source = jsonbCommentsResult.text;
|
|
1054
1207
|
source = generateTableAliases(source);
|
|
1055
1208
|
source = formatSource(source);
|
|
1056
1209
|
return {
|
|
1057
1210
|
source,
|
|
1058
1211
|
astStats,
|
|
1059
|
-
patchedDefects: patchResult.fixed
|
|
1212
|
+
patchedDefects: patchResult.fixed,
|
|
1213
|
+
addedJsonbComments: jsonbCommentsResult.added
|
|
1060
1214
|
};
|
|
1061
1215
|
}
|
|
1062
1216
|
function logStats(result, prefix = "[postprocess]") {
|
|
@@ -1103,17 +1257,20 @@ function logStats(result, prefix = "[postprocess]") {
|
|
|
1103
1257
|
if (astStats.removedImports.length > 0) {
|
|
1104
1258
|
console.info(`${prefix} Removed imports: ${astStats.removedImports.join(", ")}`);
|
|
1105
1259
|
}
|
|
1260
|
+
if (result.addedJsonbComments > 0) {
|
|
1261
|
+
console.info(`${prefix} Added ${result.addedJsonbComments} JSDoc comments for jsonb fields`);
|
|
1262
|
+
}
|
|
1106
1263
|
}
|
|
1107
1264
|
|
|
1108
1265
|
// src/commands/db/gen-dbschema/index.ts
|
|
1109
|
-
function postprocessDrizzleSchema(targetPath) {
|
|
1266
|
+
async function postprocessDrizzleSchema(targetPath, options = {}) {
|
|
1110
1267
|
const resolvedPath = path.resolve(targetPath);
|
|
1111
1268
|
if (!fs3.existsSync(resolvedPath)) {
|
|
1112
1269
|
console.warn(`[postprocess-drizzle-schema] File not found: ${resolvedPath}`);
|
|
1113
1270
|
return void 0;
|
|
1114
1271
|
}
|
|
1115
1272
|
const rawSource = fs3.readFileSync(resolvedPath, "utf8");
|
|
1116
|
-
const result = postprocessSchema(rawSource);
|
|
1273
|
+
const result = postprocessSchema(rawSource, { columnComments: options.columnComments });
|
|
1117
1274
|
fs3.writeFileSync(resolvedPath, result.source, "utf8");
|
|
1118
1275
|
logStats(result, "[postprocess-drizzle-schema]");
|
|
1119
1276
|
return {
|
|
@@ -1122,7 +1279,8 @@ function postprocessDrizzleSchema(targetPath) {
|
|
|
1122
1279
|
unmatchedUnknown: result.astStats.unmatchedUnknown,
|
|
1123
1280
|
patchedDefects: result.patchedDefects,
|
|
1124
1281
|
replacedTimestamps: result.astStats.replacedTimestamp,
|
|
1125
|
-
replacedDefaultNow: result.astStats.replacedDefaultNow
|
|
1282
|
+
replacedDefaultNow: result.astStats.replacedDefaultNow,
|
|
1283
|
+
addedJsonbComments: result.addedJsonbComments
|
|
1126
1284
|
};
|
|
1127
1285
|
}
|
|
1128
1286
|
|
|
@@ -1847,6 +2005,16 @@ async function run(options = {}) {
|
|
|
1847
2005
|
}
|
|
1848
2006
|
throw new Error("Unable to locate drizzle-kit package root");
|
|
1849
2007
|
};
|
|
2008
|
+
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) {
|
|
2013
|
+
console.warn(
|
|
2014
|
+
"[gen-db-schema] \u26A0 Failed to fetch column comments (skipping):",
|
|
2015
|
+
err instanceof Error ? err.message : String(err)
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
1850
2018
|
try {
|
|
1851
2019
|
const env = {
|
|
1852
2020
|
...process.env,
|
|
@@ -1870,7 +2038,9 @@ async function run(options = {}) {
|
|
|
1870
2038
|
console.error("[gen-db-schema] schema.ts not generated");
|
|
1871
2039
|
throw new Error("drizzle-kit introspect failed to generate schema.ts");
|
|
1872
2040
|
}
|
|
1873
|
-
const stats = postprocessDrizzleSchema(generatedSchema
|
|
2041
|
+
const stats = await postprocessDrizzleSchema(generatedSchema, {
|
|
2042
|
+
columnComments
|
|
2043
|
+
});
|
|
1874
2044
|
if (stats?.unmatchedUnknown?.length) {
|
|
1875
2045
|
console.warn("[gen-db-schema] Unmatched custom types detected:", stats.unmatchedUnknown);
|
|
1876
2046
|
}
|
|
@@ -1960,6 +2130,27 @@ var syncConfig = {
|
|
|
1960
2130
|
type: "remove-line",
|
|
1961
2131
|
to: ".gitignore",
|
|
1962
2132
|
pattern: "package-lock.json"
|
|
2133
|
+
},
|
|
2134
|
+
// 5. 注册 postinstall 脚本,自动恢复 action plugins
|
|
2135
|
+
{
|
|
2136
|
+
type: "add-script",
|
|
2137
|
+
name: "postinstall",
|
|
2138
|
+
command: "fullstack-cli action-plugin init",
|
|
2139
|
+
overwrite: false
|
|
2140
|
+
},
|
|
2141
|
+
// 6. 替换 drizzle.config.ts(仅当文件存在时)
|
|
2142
|
+
{
|
|
2143
|
+
from: "templates/drizzle.config.ts",
|
|
2144
|
+
to: "drizzle.config.ts",
|
|
2145
|
+
type: "file",
|
|
2146
|
+
overwrite: true,
|
|
2147
|
+
onlyIfExists: true
|
|
2148
|
+
},
|
|
2149
|
+
// 7. 确保 .gitignore 包含 .agent/ 目录
|
|
2150
|
+
{
|
|
2151
|
+
type: "add-line",
|
|
2152
|
+
to: ".gitignore",
|
|
2153
|
+
line: ".agent/"
|
|
1963
2154
|
}
|
|
1964
2155
|
],
|
|
1965
2156
|
// 文件权限设置
|
|
@@ -2053,6 +2244,16 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
|
|
|
2053
2244
|
removeLineFromFile(destPath2, rule.pattern);
|
|
2054
2245
|
return;
|
|
2055
2246
|
}
|
|
2247
|
+
if (rule.type === "add-script") {
|
|
2248
|
+
const packageJsonPath = path4.join(userProjectRoot, "package.json");
|
|
2249
|
+
addScript(packageJsonPath, rule.name, rule.command, rule.overwrite ?? false);
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
if (rule.type === "add-line") {
|
|
2253
|
+
const destPath2 = path4.join(userProjectRoot, rule.to);
|
|
2254
|
+
addLineToFile(destPath2, rule.line);
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2056
2257
|
if (!("from" in rule)) {
|
|
2057
2258
|
return;
|
|
2058
2259
|
}
|
|
@@ -2067,14 +2268,18 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
|
|
|
2067
2268
|
syncDirectory(srcPath, destPath, rule.overwrite ?? true);
|
|
2068
2269
|
break;
|
|
2069
2270
|
case "file":
|
|
2070
|
-
syncFile(srcPath, destPath, rule.overwrite ?? true);
|
|
2271
|
+
syncFile(srcPath, destPath, rule.overwrite ?? true, rule.onlyIfExists ?? false);
|
|
2071
2272
|
break;
|
|
2072
2273
|
case "append":
|
|
2073
2274
|
appendToFile(srcPath, destPath);
|
|
2074
2275
|
break;
|
|
2075
2276
|
}
|
|
2076
2277
|
}
|
|
2077
|
-
function syncFile(src, dest, overwrite = true) {
|
|
2278
|
+
function syncFile(src, dest, overwrite = true, onlyIfExists = false) {
|
|
2279
|
+
if (onlyIfExists && !fs6.existsSync(dest)) {
|
|
2280
|
+
console.log(`[fullstack-cli] \u25CB ${path4.basename(dest)} (skipped, target not exists)`);
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2078
2283
|
const destDir = path4.dirname(dest);
|
|
2079
2284
|
if (!fs6.existsSync(destDir)) {
|
|
2080
2285
|
fs6.mkdirSync(destDir, { recursive: true });
|
|
@@ -2155,6 +2360,42 @@ function deleteDirectory(dirPath) {
|
|
|
2155
2360
|
console.log(`[fullstack-cli] \u25CB ${path4.basename(dirPath)} (not found)`);
|
|
2156
2361
|
}
|
|
2157
2362
|
}
|
|
2363
|
+
function addScript(packageJsonPath, name, command, overwrite) {
|
|
2364
|
+
if (!fs6.existsSync(packageJsonPath)) {
|
|
2365
|
+
console.log(`[fullstack-cli] \u25CB package.json (not found)`);
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
const content = fs6.readFileSync(packageJsonPath, "utf-8");
|
|
2369
|
+
const pkg2 = JSON.parse(content);
|
|
2370
|
+
if (!pkg2.scripts) {
|
|
2371
|
+
pkg2.scripts = {};
|
|
2372
|
+
}
|
|
2373
|
+
if (pkg2.scripts[name]) {
|
|
2374
|
+
if (!overwrite) {
|
|
2375
|
+
console.log(`[fullstack-cli] \u25CB scripts.${name} (already exists)`);
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
pkg2.scripts[name] = command;
|
|
2380
|
+
fs6.writeFileSync(packageJsonPath, JSON.stringify(pkg2, null, 2) + "\n");
|
|
2381
|
+
console.log(`[fullstack-cli] \u2713 scripts.${name}`);
|
|
2382
|
+
}
|
|
2383
|
+
function addLineToFile(filePath, line) {
|
|
2384
|
+
const fileName = path4.basename(filePath);
|
|
2385
|
+
if (!fs6.existsSync(filePath)) {
|
|
2386
|
+
console.log(`[fullstack-cli] \u25CB ${fileName} (not found, skipped)`);
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
2390
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
2391
|
+
if (lines.includes(line)) {
|
|
2392
|
+
console.log(`[fullstack-cli] \u25CB ${fileName} (line already exists: ${line})`);
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
const appendContent = (content.endsWith("\n") ? "" : "\n") + line + "\n";
|
|
2396
|
+
fs6.appendFileSync(filePath, appendContent);
|
|
2397
|
+
console.log(`[fullstack-cli] \u2713 ${fileName} (added: ${line})`);
|
|
2398
|
+
}
|
|
2158
2399
|
|
|
2159
2400
|
// src/commands/sync/index.ts
|
|
2160
2401
|
var syncCommand = {
|
|
@@ -2438,9 +2679,24 @@ function ensureCacheDir() {
|
|
|
2438
2679
|
function getTempFilePath(pluginKey, version) {
|
|
2439
2680
|
ensureCacheDir();
|
|
2440
2681
|
const safeKey = pluginKey.replace(/[/@]/g, "_");
|
|
2441
|
-
const filename = `${safeKey}
|
|
2682
|
+
const filename = `${safeKey}@${version}.tgz`;
|
|
2442
2683
|
return path6.join(getPluginCacheDir(), filename);
|
|
2443
2684
|
}
|
|
2685
|
+
var MAX_RETRIES = 2;
|
|
2686
|
+
async function withRetry(operation, description, maxRetries = MAX_RETRIES) {
|
|
2687
|
+
let lastError;
|
|
2688
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2689
|
+
try {
|
|
2690
|
+
return await operation();
|
|
2691
|
+
} catch (error) {
|
|
2692
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2693
|
+
if (attempt < maxRetries) {
|
|
2694
|
+
console.log(`[action-plugin] ${description} failed, retrying (${attempt + 1}/${maxRetries})...`);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
throw lastError;
|
|
2699
|
+
}
|
|
2444
2700
|
async function downloadPlugin(pluginKey, requestedVersion) {
|
|
2445
2701
|
console.log(`[action-plugin] Fetching plugin info for ${pluginKey}@${requestedVersion}...`);
|
|
2446
2702
|
const pluginInfo = await getPluginVersion(pluginKey, requestedVersion);
|
|
@@ -2449,10 +2705,16 @@ async function downloadPlugin(pluginKey, requestedVersion) {
|
|
|
2449
2705
|
let tgzBuffer;
|
|
2450
2706
|
if (pluginInfo.downloadApproach === "inner") {
|
|
2451
2707
|
console.log(`[action-plugin] Downloading from inner API...`);
|
|
2452
|
-
tgzBuffer = await
|
|
2708
|
+
tgzBuffer = await withRetry(
|
|
2709
|
+
() => downloadFromInner(pluginKey, pluginInfo.version),
|
|
2710
|
+
"Download"
|
|
2711
|
+
);
|
|
2453
2712
|
} else {
|
|
2454
2713
|
console.log(`[action-plugin] Downloading from public URL...`);
|
|
2455
|
-
tgzBuffer = await
|
|
2714
|
+
tgzBuffer = await withRetry(
|
|
2715
|
+
() => downloadFromPublic(pluginInfo.downloadURL),
|
|
2716
|
+
"Download"
|
|
2717
|
+
);
|
|
2456
2718
|
}
|
|
2457
2719
|
const tgzPath = getTempFilePath(pluginKey, pluginInfo.version);
|
|
2458
2720
|
fs8.writeFileSync(tgzPath, tgzBuffer);
|
|
@@ -2464,25 +2726,99 @@ async function downloadPlugin(pluginKey, requestedVersion) {
|
|
|
2464
2726
|
};
|
|
2465
2727
|
}
|
|
2466
2728
|
function cleanupTempFile(tgzPath) {
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2729
|
+
}
|
|
2730
|
+
function getCachePath(pluginKey, version) {
|
|
2731
|
+
ensureCacheDir();
|
|
2732
|
+
const safeKey = pluginKey.replace(/[/@]/g, "_");
|
|
2733
|
+
const filename = `${safeKey}@${version}.tgz`;
|
|
2734
|
+
return path6.join(getPluginCacheDir(), filename);
|
|
2735
|
+
}
|
|
2736
|
+
function hasCachedPlugin(pluginKey, version) {
|
|
2737
|
+
const cachePath = getCachePath(pluginKey, version);
|
|
2738
|
+
return fs8.existsSync(cachePath);
|
|
2739
|
+
}
|
|
2740
|
+
function listCachedPlugins() {
|
|
2741
|
+
const cacheDir = getPluginCacheDir();
|
|
2742
|
+
if (!fs8.existsSync(cacheDir)) {
|
|
2743
|
+
return [];
|
|
2744
|
+
}
|
|
2745
|
+
const files = fs8.readdirSync(cacheDir);
|
|
2746
|
+
const result = [];
|
|
2747
|
+
for (const file of files) {
|
|
2748
|
+
if (!file.endsWith(".tgz")) continue;
|
|
2749
|
+
const match = file.match(/^(.+)@(.+)\.tgz$/);
|
|
2750
|
+
if (!match) continue;
|
|
2751
|
+
const [, rawName, version] = match;
|
|
2752
|
+
const name = rawName.replace(/^_/, "@").replace(/_/, "/");
|
|
2753
|
+
const filePath = path6.join(cacheDir, file);
|
|
2754
|
+
const stat = fs8.statSync(filePath);
|
|
2755
|
+
result.push({
|
|
2756
|
+
name,
|
|
2757
|
+
version,
|
|
2758
|
+
filePath,
|
|
2759
|
+
size: stat.size,
|
|
2760
|
+
mtime: stat.mtime
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
return result;
|
|
2764
|
+
}
|
|
2765
|
+
function cleanAllCache() {
|
|
2766
|
+
const cacheDir = getPluginCacheDir();
|
|
2767
|
+
if (!fs8.existsSync(cacheDir)) {
|
|
2768
|
+
return 0;
|
|
2769
|
+
}
|
|
2770
|
+
const files = fs8.readdirSync(cacheDir);
|
|
2771
|
+
let count = 0;
|
|
2772
|
+
for (const file of files) {
|
|
2773
|
+
if (file.endsWith(".tgz")) {
|
|
2774
|
+
fs8.unlinkSync(path6.join(cacheDir, file));
|
|
2775
|
+
count++;
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
return count;
|
|
2779
|
+
}
|
|
2780
|
+
function cleanPluginCache(pluginKey, version) {
|
|
2781
|
+
const cacheDir = getPluginCacheDir();
|
|
2782
|
+
if (!fs8.existsSync(cacheDir)) {
|
|
2783
|
+
return 0;
|
|
2784
|
+
}
|
|
2785
|
+
const safeKey = pluginKey.replace(/[/@]/g, "_");
|
|
2786
|
+
const files = fs8.readdirSync(cacheDir);
|
|
2787
|
+
let count = 0;
|
|
2788
|
+
for (const file of files) {
|
|
2789
|
+
if (version) {
|
|
2790
|
+
if (file === `${safeKey}@${version}.tgz`) {
|
|
2791
|
+
fs8.unlinkSync(path6.join(cacheDir, file));
|
|
2792
|
+
count++;
|
|
2793
|
+
}
|
|
2794
|
+
} else {
|
|
2795
|
+
if (file.startsWith(`${safeKey}@`) && file.endsWith(".tgz")) {
|
|
2796
|
+
fs8.unlinkSync(path6.join(cacheDir, file));
|
|
2797
|
+
count++;
|
|
2798
|
+
}
|
|
2470
2799
|
}
|
|
2471
|
-
} catch {
|
|
2472
2800
|
}
|
|
2801
|
+
return count;
|
|
2473
2802
|
}
|
|
2474
2803
|
|
|
2475
2804
|
// src/commands/action-plugin/init.handler.ts
|
|
2476
2805
|
async function installOneForInit(name, version) {
|
|
2477
|
-
let tgzPath;
|
|
2478
2806
|
try {
|
|
2479
2807
|
const installedVersion = getPackageVersion(name);
|
|
2480
2808
|
if (installedVersion === version) {
|
|
2481
2809
|
return { name, version, success: true, skipped: true };
|
|
2482
2810
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2811
|
+
let tgzPath;
|
|
2812
|
+
let fromCache = false;
|
|
2813
|
+
if (hasCachedPlugin(name, version)) {
|
|
2814
|
+
console.log(`[action-plugin] \u21BB Restoring ${name}@${version} from cache...`);
|
|
2815
|
+
tgzPath = getCachePath(name, version);
|
|
2816
|
+
fromCache = true;
|
|
2817
|
+
} else {
|
|
2818
|
+
console.log(`[action-plugin] \u2193 Downloading ${name}@${version}...`);
|
|
2819
|
+
const downloadResult = await downloadPlugin(name, version);
|
|
2820
|
+
tgzPath = downloadResult.tgzPath;
|
|
2821
|
+
}
|
|
2486
2822
|
const pluginDir = extractTgzToNodeModules(tgzPath, name);
|
|
2487
2823
|
const pluginPkg = readPluginPackageJson(pluginDir);
|
|
2488
2824
|
if (pluginPkg?.peerDependencies) {
|
|
@@ -2491,16 +2827,13 @@ async function installOneForInit(name, version) {
|
|
|
2491
2827
|
installMissingDeps(missingDeps);
|
|
2492
2828
|
}
|
|
2493
2829
|
}
|
|
2494
|
-
|
|
2830
|
+
const source = fromCache ? "from cache" : "downloaded";
|
|
2831
|
+
console.log(`[action-plugin] \u2713 Installed ${name}@${version} (${source})`);
|
|
2495
2832
|
return { name, version, success: true };
|
|
2496
2833
|
} catch (error) {
|
|
2497
2834
|
const message = error instanceof Error ? error.message : String(error);
|
|
2498
2835
|
console.error(`[action-plugin] \u2717 Failed to install ${name}: ${message}`);
|
|
2499
2836
|
return { name, version, success: false, error: message };
|
|
2500
|
-
} finally {
|
|
2501
|
-
if (tgzPath) {
|
|
2502
|
-
cleanupTempFile(tgzPath);
|
|
2503
|
-
}
|
|
2504
2837
|
}
|
|
2505
2838
|
}
|
|
2506
2839
|
async function init() {
|
|
@@ -2546,7 +2879,6 @@ function syncActionPluginsRecord(name, version) {
|
|
|
2546
2879
|
}
|
|
2547
2880
|
}
|
|
2548
2881
|
async function installOne(nameWithVersion) {
|
|
2549
|
-
let tgzPath;
|
|
2550
2882
|
const { name, version: requestedVersion } = parsePluginName(nameWithVersion);
|
|
2551
2883
|
try {
|
|
2552
2884
|
console.log(`[action-plugin] Installing ${name}@${requestedVersion}...`);
|
|
@@ -2558,17 +2890,28 @@ async function installOne(nameWithVersion) {
|
|
|
2558
2890
|
return { name, version: actualVersion, success: true, skipped: true };
|
|
2559
2891
|
}
|
|
2560
2892
|
}
|
|
2561
|
-
|
|
2893
|
+
let targetVersion = requestedVersion;
|
|
2894
|
+
if (requestedVersion === "latest") {
|
|
2562
2895
|
const latestInfo = await getPluginVersion(name, "latest");
|
|
2563
|
-
|
|
2896
|
+
targetVersion = latestInfo.version;
|
|
2897
|
+
if (actualVersion === targetVersion) {
|
|
2564
2898
|
console.log(`[action-plugin] Plugin ${name} is already up to date (version: ${actualVersion})`);
|
|
2565
2899
|
syncActionPluginsRecord(name, actualVersion);
|
|
2566
2900
|
return { name, version: actualVersion, success: true, skipped: true };
|
|
2567
2901
|
}
|
|
2568
|
-
console.log(`[action-plugin] Found newer version: ${
|
|
2902
|
+
console.log(`[action-plugin] Found newer version: ${targetVersion} (installed: ${actualVersion || "none"})`);
|
|
2903
|
+
}
|
|
2904
|
+
let tgzPath;
|
|
2905
|
+
let fromCache = false;
|
|
2906
|
+
if (hasCachedPlugin(name, targetVersion)) {
|
|
2907
|
+
console.log(`[action-plugin] \u21BB Using cached ${name}@${targetVersion}...`);
|
|
2908
|
+
tgzPath = getCachePath(name, targetVersion);
|
|
2909
|
+
fromCache = true;
|
|
2910
|
+
} else {
|
|
2911
|
+
console.log(`[action-plugin] \u2193 Downloading ${name}@${targetVersion}...`);
|
|
2912
|
+
const downloadResult = await downloadPlugin(name, requestedVersion);
|
|
2913
|
+
tgzPath = downloadResult.tgzPath;
|
|
2569
2914
|
}
|
|
2570
|
-
const downloadResult = await downloadPlugin(name, requestedVersion);
|
|
2571
|
-
tgzPath = downloadResult.tgzPath;
|
|
2572
2915
|
console.log(`[action-plugin] Extracting to node_modules...`);
|
|
2573
2916
|
const pluginDir = extractTgzToNodeModules(tgzPath, name);
|
|
2574
2917
|
const pluginPkg = readPluginPackageJson(pluginDir);
|
|
@@ -2578,20 +2921,17 @@ async function installOne(nameWithVersion) {
|
|
|
2578
2921
|
installMissingDeps(missingDeps);
|
|
2579
2922
|
}
|
|
2580
2923
|
}
|
|
2581
|
-
const installedVersion = getPackageVersion(name) ||
|
|
2924
|
+
const installedVersion = getPackageVersion(name) || targetVersion;
|
|
2582
2925
|
const plugins = readActionPlugins();
|
|
2583
2926
|
plugins[name] = installedVersion;
|
|
2584
2927
|
writeActionPlugins(plugins);
|
|
2585
|
-
|
|
2928
|
+
const source = fromCache ? "from cache" : "downloaded";
|
|
2929
|
+
console.log(`[action-plugin] Successfully installed ${name}@${installedVersion} (${source})`);
|
|
2586
2930
|
return { name, version: installedVersion, success: true };
|
|
2587
2931
|
} catch (error) {
|
|
2588
2932
|
const message = error instanceof Error ? error.message : String(error);
|
|
2589
2933
|
console.error(`[action-plugin] Failed to install ${name}: ${message}`);
|
|
2590
2934
|
return { name, version: requestedVersion, success: false, error: message };
|
|
2591
|
-
} finally {
|
|
2592
|
-
if (tgzPath) {
|
|
2593
|
-
cleanupTempFile(tgzPath);
|
|
2594
|
-
}
|
|
2595
2935
|
}
|
|
2596
2936
|
}
|
|
2597
2937
|
async function install(namesWithVersion) {
|
|
@@ -2745,6 +3085,58 @@ async function list() {
|
|
|
2745
3085
|
}
|
|
2746
3086
|
}
|
|
2747
3087
|
|
|
3088
|
+
// src/commands/action-plugin/cache.handler.ts
|
|
3089
|
+
async function cacheList() {
|
|
3090
|
+
const cached = listCachedPlugins();
|
|
3091
|
+
if (cached.length === 0) {
|
|
3092
|
+
console.log("[action-plugin] No cached plugins found");
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
console.log("[action-plugin] Cached plugins:\n");
|
|
3096
|
+
console.log(" Name Version Size Modified");
|
|
3097
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
3098
|
+
let totalSize = 0;
|
|
3099
|
+
for (const plugin of cached) {
|
|
3100
|
+
const name = plugin.name.padEnd(38);
|
|
3101
|
+
const version = plugin.version.padEnd(10);
|
|
3102
|
+
const size = formatSize(plugin.size).padEnd(9);
|
|
3103
|
+
const mtime = plugin.mtime.toISOString().replace("T", " ").slice(0, 19);
|
|
3104
|
+
console.log(` ${name} ${version} ${size} ${mtime}`);
|
|
3105
|
+
totalSize += plugin.size;
|
|
3106
|
+
}
|
|
3107
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
3108
|
+
console.log(` Total: ${cached.length} cached file(s), ${formatSize(totalSize)}`);
|
|
3109
|
+
}
|
|
3110
|
+
async function cacheClean(pluginName, version) {
|
|
3111
|
+
let count;
|
|
3112
|
+
if (pluginName) {
|
|
3113
|
+
console.log(`[action-plugin] Cleaning cache for ${pluginName}${version ? `@${version}` : ""}...`);
|
|
3114
|
+
count = cleanPluginCache(pluginName, version);
|
|
3115
|
+
if (count === 0) {
|
|
3116
|
+
console.log(`[action-plugin] No cached files found for ${pluginName}${version ? `@${version}` : ""}`);
|
|
3117
|
+
} else {
|
|
3118
|
+
console.log(`[action-plugin] Cleaned ${count} cached file(s)`);
|
|
3119
|
+
}
|
|
3120
|
+
} else {
|
|
3121
|
+
console.log("[action-plugin] Cleaning all cached plugins...");
|
|
3122
|
+
count = cleanAllCache();
|
|
3123
|
+
if (count === 0) {
|
|
3124
|
+
console.log("[action-plugin] No cached files found");
|
|
3125
|
+
} else {
|
|
3126
|
+
console.log(`[action-plugin] Cleaned ${count} cached file(s)`);
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
function formatSize(bytes) {
|
|
3131
|
+
if (bytes < 1024) {
|
|
3132
|
+
return `${bytes} B`;
|
|
3133
|
+
} else if (bytes < 1024 * 1024) {
|
|
3134
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3135
|
+
} else {
|
|
3136
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
|
|
2748
3140
|
// src/commands/action-plugin/index.ts
|
|
2749
3141
|
var initCommand = {
|
|
2750
3142
|
name: "init",
|
|
@@ -2795,14 +3187,33 @@ var listCommand = {
|
|
|
2795
3187
|
});
|
|
2796
3188
|
}
|
|
2797
3189
|
};
|
|
3190
|
+
var cacheListCommand = {
|
|
3191
|
+
name: "cache-list",
|
|
3192
|
+
description: "List all cached plugin packages",
|
|
3193
|
+
register(program) {
|
|
3194
|
+
program.command(this.name).description(this.description).action(async () => {
|
|
3195
|
+
await cacheList();
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
};
|
|
3199
|
+
var cacheCleanCommand = {
|
|
3200
|
+
name: "cache-clean",
|
|
3201
|
+
description: "Clean cached plugin packages",
|
|
3202
|
+
register(program) {
|
|
3203
|
+
program.command(this.name).description(this.description).argument("[name]", "Plugin name to clean (e.g., @office/feishu-create-group). If not provided, clean all cache.").option("-v, --version <version>", "Specific version to clean").action(async (name, options) => {
|
|
3204
|
+
await cacheClean(name, options.version);
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
3207
|
+
};
|
|
2798
3208
|
var actionPluginCommandGroup = {
|
|
2799
3209
|
name: "action-plugin",
|
|
2800
3210
|
description: "Manage action plugins",
|
|
2801
|
-
commands: [initCommand, installCommand, updateCommand, removeCommand, listCommand]
|
|
3211
|
+
commands: [initCommand, installCommand, updateCommand, removeCommand, listCommand, cacheListCommand, cacheCleanCommand]
|
|
2802
3212
|
};
|
|
2803
3213
|
|
|
2804
3214
|
// src/commands/capability/utils.ts
|
|
2805
3215
|
import fs9 from "fs";
|
|
3216
|
+
import { createRequire as createRequire2 } from "module";
|
|
2806
3217
|
import path7 from "path";
|
|
2807
3218
|
var CAPABILITIES_DIR = "server/capabilities";
|
|
2808
3219
|
function getProjectRoot2() {
|
|
@@ -2882,7 +3293,10 @@ function hasValidParamsSchema(paramsSchema) {
|
|
|
2882
3293
|
}
|
|
2883
3294
|
async function loadPlugin(pluginKey) {
|
|
2884
3295
|
try {
|
|
2885
|
-
const
|
|
3296
|
+
const userRequire = createRequire2(path7.join(getProjectRoot2(), "package.json"));
|
|
3297
|
+
const resolvedPath = userRequire.resolve(pluginKey);
|
|
3298
|
+
const pluginModule = await import(resolvedPath);
|
|
3299
|
+
const pluginPackage = pluginModule.default ?? pluginModule;
|
|
2886
3300
|
if (!pluginPackage || typeof pluginPackage.create !== "function") {
|
|
2887
3301
|
throw new Error(`Plugin ${pluginKey} does not export a valid create function`);
|
|
2888
3302
|
}
|
|
@@ -5121,6 +5535,28 @@ function extractClientStdSegment(lines, maxLines, offset) {
|
|
|
5121
5535
|
};
|
|
5122
5536
|
}
|
|
5123
5537
|
|
|
5538
|
+
// src/commands/read-logs/dev.ts
|
|
5539
|
+
function readDevSegment(filePath, maxLines, offset) {
|
|
5540
|
+
const marker = (line) => {
|
|
5541
|
+
if (!line) return false;
|
|
5542
|
+
if (line.includes("Dev session started")) return true;
|
|
5543
|
+
return false;
|
|
5544
|
+
};
|
|
5545
|
+
const segment = readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, marker);
|
|
5546
|
+
return { lines: segment.lines, totalLinesCount: segment.totalLinesCount };
|
|
5547
|
+
}
|
|
5548
|
+
|
|
5549
|
+
// src/commands/read-logs/dev-std.ts
|
|
5550
|
+
function readDevStdSegment(filePath, maxLines, offset) {
|
|
5551
|
+
const marker = (line) => {
|
|
5552
|
+
if (!line) return false;
|
|
5553
|
+
if (line.includes("Dev session started")) return true;
|
|
5554
|
+
return false;
|
|
5555
|
+
};
|
|
5556
|
+
const segment = readStdLinesTailFromLastMarkerPaged(filePath, maxLines, offset, marker);
|
|
5557
|
+
return { lines: segment.lines, totalLinesCount: segment.totalLinesCount };
|
|
5558
|
+
}
|
|
5559
|
+
|
|
5124
5560
|
// src/commands/read-logs/json-lines.ts
|
|
5125
5561
|
import fs20 from "fs";
|
|
5126
5562
|
function normalizePid(value) {
|
|
@@ -5445,7 +5881,7 @@ function readJsonLinesTailByLevel(filePath, maxLines, offset, levels) {
|
|
|
5445
5881
|
}
|
|
5446
5882
|
|
|
5447
5883
|
// src/commands/read-logs/index.ts
|
|
5448
|
-
var LOG_TYPES = ["server", "trace", "server-std", "client-std", "browser"];
|
|
5884
|
+
var LOG_TYPES = ["server", "trace", "server-std", "client-std", "dev", "dev-std", "install-dep-std", "browser"];
|
|
5449
5885
|
function normalizeObjectKey(key) {
|
|
5450
5886
|
return key.toLowerCase().replace(/_/g, "");
|
|
5451
5887
|
}
|
|
@@ -5607,6 +6043,15 @@ async function readLatestLogLinesMeta(options) {
|
|
|
5607
6043
|
if (options.type === "client-std") {
|
|
5608
6044
|
return readClientStdSegment(filePath, maxLines, offset);
|
|
5609
6045
|
}
|
|
6046
|
+
if (options.type === "dev") {
|
|
6047
|
+
return readDevSegment(filePath, maxLines, offset);
|
|
6048
|
+
}
|
|
6049
|
+
if (options.type === "dev-std") {
|
|
6050
|
+
return readDevStdSegment(filePath, maxLines, offset);
|
|
6051
|
+
}
|
|
6052
|
+
if (options.type === "install-dep-std") {
|
|
6053
|
+
return readFileTailNonEmptyLinesWithOffset(filePath, maxLines, offset);
|
|
6054
|
+
}
|
|
5610
6055
|
const traceId = typeof options.traceId === "string" ? options.traceId.trim() : "";
|
|
5611
6056
|
if (traceId) {
|
|
5612
6057
|
return readJsonLinesByTraceId(filePath, traceId, maxLines, offset, levels);
|
|
@@ -5624,7 +6069,7 @@ async function readLatestLogLinesMeta(options) {
|
|
|
5624
6069
|
}
|
|
5625
6070
|
async function readLogsJsonResult(options) {
|
|
5626
6071
|
const { lines, totalLinesCount } = await readLatestLogLinesMeta(options);
|
|
5627
|
-
if (options.type === "server-std" || options.type === "client-std") {
|
|
6072
|
+
if (options.type === "server-std" || options.type === "client-std" || options.type === "dev" || options.type === "dev-std" || options.type === "install-dep-std") {
|
|
5628
6073
|
return {
|
|
5629
6074
|
hasError: hasErrorInStdLines(lines),
|
|
5630
6075
|
totalLinesCount,
|
|
@@ -5668,6 +6113,15 @@ function resolveLogFilePath(logDir, type) {
|
|
|
5668
6113
|
if (type === "client-std") {
|
|
5669
6114
|
return path16.join(base, "client.std.log");
|
|
5670
6115
|
}
|
|
6116
|
+
if (type === "dev") {
|
|
6117
|
+
return path16.join(base, "dev.log");
|
|
6118
|
+
}
|
|
6119
|
+
if (type === "dev-std") {
|
|
6120
|
+
return path16.join(base, "dev.std.log");
|
|
6121
|
+
}
|
|
6122
|
+
if (type === "install-dep-std") {
|
|
6123
|
+
return path16.join(base, "install-dep.std.log");
|
|
6124
|
+
}
|
|
5671
6125
|
if (type === "browser") {
|
|
5672
6126
|
return path16.join(base, "browser.log");
|
|
5673
6127
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineConfig, Config } from 'drizzle-kit';
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
|
|
4
|
+
const outputDir = process.env.__DRIZZLE_OUT_DIR__ || './server/database/.introspect';
|
|
5
|
+
const schemaPath = process.env.__DRIZZLE_SCHEMA_PATH__ || './server/database/schema.ts';
|
|
6
|
+
|
|
7
|
+
const parsedUrl = new URL(process.env.SUDA_DATABASE_URL || '');
|
|
8
|
+
|
|
9
|
+
const envSchemaFilter = process.env.DRIZZLE_SCHEMA_FILTER;
|
|
10
|
+
const urlSchemaFilter = parsedUrl.searchParams.get('schema');
|
|
11
|
+
|
|
12
|
+
const schemaFilter = (envSchemaFilter ?? urlSchemaFilter ?? '')
|
|
13
|
+
.split(',')
|
|
14
|
+
.map((s) => s.trim())
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
|
|
17
|
+
parsedUrl.searchParams.delete('schema'); // 移除schema参数,避免 drizzle-kit 解析错误
|
|
18
|
+
|
|
19
|
+
// 默认排除的系统对象(PostgreSQL 扩展和系统视图)
|
|
20
|
+
// 这些对象在 drizzle-kit introspect 时可能导致无效的 JS 代码生成
|
|
21
|
+
const SYSTEM_OBJECTS_EXCLUSIONS = [
|
|
22
|
+
'!spatial_ref_sys', // PostGIS 空间参考系统表
|
|
23
|
+
'!geography_columns', // PostGIS 地理列视图
|
|
24
|
+
'!geometry_columns', // PostGIS 几何列视图
|
|
25
|
+
'!raster_columns', // PostGIS 栅格列视图
|
|
26
|
+
'!raster_overviews', // PostGIS 栅格概览视图
|
|
27
|
+
'!pg_stat_statements', // pg_stat_statements 扩展
|
|
28
|
+
'!pg_stat_statements_info', // pg_stat_statements 扩展
|
|
29
|
+
'!part_config', // pg_partman 分区配置表
|
|
30
|
+
'!part_config_sub', // pg_partman 子分区配置表
|
|
31
|
+
'!table_privs', // 系统权限视图
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const envTablesFilter = process.env.DRIZZLE_TABLES_FILTER;
|
|
35
|
+
const userTablesFilter = (envTablesFilter ?? '*')
|
|
36
|
+
.split(',')
|
|
37
|
+
.map((s) => s.trim())
|
|
38
|
+
.filter(Boolean);
|
|
39
|
+
|
|
40
|
+
// 合并用户过滤器和系统对象排除
|
|
41
|
+
// 用户可以通过设置 DRIZZLE_TABLES_FILTER 来覆盖(如果需要包含某些系统对象)
|
|
42
|
+
const tablesFilter = [...userTablesFilter, ...SYSTEM_OBJECTS_EXCLUSIONS];
|
|
43
|
+
|
|
44
|
+
const config:Config = {
|
|
45
|
+
schema: schemaPath,
|
|
46
|
+
out: outputDir,
|
|
47
|
+
tablesFilter,
|
|
48
|
+
schemaFilter,
|
|
49
|
+
dialect: 'postgresql',
|
|
50
|
+
dbCredentials: {
|
|
51
|
+
url: parsedUrl.toString(),
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default defineConfig(config);
|
package/templates/scripts/dev.sh
CHANGED
|
@@ -21,6 +21,10 @@ CLEANUP_DONE=false
|
|
|
21
21
|
|
|
22
22
|
mkdir -p "${LOG_DIR}"
|
|
23
23
|
|
|
24
|
+
# Redirect all stdout/stderr to both terminal and log file
|
|
25
|
+
DEV_STD_LOG="${LOG_DIR}/dev.std.log"
|
|
26
|
+
exec > >(tee -a "$DEV_STD_LOG") 2>&1
|
|
27
|
+
|
|
24
28
|
# Log event to dev.log with timestamp
|
|
25
29
|
log_event() {
|
|
26
30
|
local level=$1
|
|
@@ -234,7 +238,7 @@ log_event "INFO" "client" "Supervisor started with PID ${CLIENT_PID}"
|
|
|
234
238
|
log_event "INFO" "main" "All processes started, monitoring..."
|
|
235
239
|
echo ""
|
|
236
240
|
echo "📋 Dev processes running. Press Ctrl+C to stop."
|
|
237
|
-
echo "📄 Logs: ${
|
|
241
|
+
echo "📄 Logs: ${DEV_STD_LOG}"
|
|
238
242
|
echo ""
|
|
239
243
|
|
|
240
244
|
# Wait for all background processes
|