@lark-apaas/fullstack-cli 1.1.59-alpha.3 → 1.1.59-beta.0
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 +162 -37
- package/package.json +1 -1
- package/templates/.spark_project +2 -2
- package/templates/nest-cli.json +1 -5
- package/templates/scripts/dev-local.js +113 -0
- package/templates/scripts/dev.js +18 -238
- package/templates/scripts/dev.sh +23 -1
- package/templates/scripts/lint.js +51 -16
- package/templates/scripts/prune-smart.js +41 -1
- package/templates/scripts/preview-startup-timing.cjs +0 -377
package/dist/index.js
CHANGED
|
@@ -847,6 +847,117 @@ var tweakImportsTransform = {
|
|
|
847
847
|
}
|
|
848
848
|
};
|
|
849
849
|
|
|
850
|
+
// src/commands/db/gen-dbschema/transforms/ast/fix-opclass.ts
|
|
851
|
+
import { Node as Node9 } from "ts-morph";
|
|
852
|
+
var TYPE_TO_OPCLASS = {
|
|
853
|
+
uuid: "uuid_ops",
|
|
854
|
+
date: "date_ops",
|
|
855
|
+
varchar: "text_ops",
|
|
856
|
+
text: "text_ops",
|
|
857
|
+
boolean: "bool_ops",
|
|
858
|
+
integer: "int4_ops",
|
|
859
|
+
serial: "int4_ops",
|
|
860
|
+
numeric: "numeric_ops",
|
|
861
|
+
jsonb: "jsonb_ops",
|
|
862
|
+
customTimestamptz: "timestamptz_ops",
|
|
863
|
+
bigint: "int8_ops",
|
|
864
|
+
smallint: "int2_ops",
|
|
865
|
+
real: "float4_ops",
|
|
866
|
+
doublePrecision: "float8_ops"
|
|
867
|
+
};
|
|
868
|
+
var fixOpclassTransform = {
|
|
869
|
+
name: "fix-opclass",
|
|
870
|
+
transform(ctx) {
|
|
871
|
+
const { sourceFile } = ctx;
|
|
872
|
+
sourceFile.forEachDescendant((node) => {
|
|
873
|
+
if (!Node9.isCallExpression(node)) return;
|
|
874
|
+
const expr = node.getExpression();
|
|
875
|
+
if (!Node9.isIdentifier(expr)) return;
|
|
876
|
+
const fnName = expr.getText();
|
|
877
|
+
if (fnName !== "pgTable") return;
|
|
878
|
+
const args = node.getArguments();
|
|
879
|
+
if (args.length < 3) return;
|
|
880
|
+
const tableColumns = collectTableColumns(args[1]);
|
|
881
|
+
if (!tableColumns.size) return;
|
|
882
|
+
const indexFn = args[2];
|
|
883
|
+
if (!Node9.isArrowFunction(indexFn)) return;
|
|
884
|
+
fixOpclassInIndexes(indexFn, tableColumns);
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
function collectTableColumns(columnsArg) {
|
|
889
|
+
const columns = /* @__PURE__ */ new Map();
|
|
890
|
+
if (!Node9.isObjectLiteralExpression(columnsArg)) return columns;
|
|
891
|
+
for (const prop of columnsArg.getProperties()) {
|
|
892
|
+
if (!Node9.isPropertyAssignment(prop)) continue;
|
|
893
|
+
const colName = prop.getName();
|
|
894
|
+
const initializer = prop.getInitializer();
|
|
895
|
+
if (!initializer) continue;
|
|
896
|
+
const colType = extractColumnType(initializer);
|
|
897
|
+
if (colType) {
|
|
898
|
+
columns.set(colName, colType);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return columns;
|
|
902
|
+
}
|
|
903
|
+
function extractColumnType(node) {
|
|
904
|
+
const text = node.getText();
|
|
905
|
+
for (const typeName of Object.keys(TYPE_TO_OPCLASS)) {
|
|
906
|
+
const pattern = new RegExp(`\\b${typeName}\\s*\\(`);
|
|
907
|
+
if (pattern.test(text)) {
|
|
908
|
+
return typeName;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return void 0;
|
|
912
|
+
}
|
|
913
|
+
function fixOpclassInIndexes(indexFn, tableColumns) {
|
|
914
|
+
const paramName = getArrowParam(indexFn);
|
|
915
|
+
if (!paramName) return;
|
|
916
|
+
indexFn.forEachDescendant((node) => {
|
|
917
|
+
if (!Node9.isCallExpression(node)) return;
|
|
918
|
+
const expr = node.getExpression();
|
|
919
|
+
if (!Node9.isPropertyAccessExpression(expr)) return;
|
|
920
|
+
if (expr.getName() !== "op") return;
|
|
921
|
+
const opArgs = node.getArguments();
|
|
922
|
+
if (opArgs.length !== 1) return;
|
|
923
|
+
const currentOp = opArgs[0];
|
|
924
|
+
if (!Node9.isStringLiteral(currentOp)) return;
|
|
925
|
+
const colName = resolveColumnName(expr.getExpression(), paramName);
|
|
926
|
+
if (!colName) return;
|
|
927
|
+
const colType = tableColumns.get(colName);
|
|
928
|
+
if (!colType) return;
|
|
929
|
+
const correctOp = TYPE_TO_OPCLASS[colType];
|
|
930
|
+
if (!correctOp) return;
|
|
931
|
+
const currentValue = currentOp.getLiteralValue();
|
|
932
|
+
if (currentValue !== correctOp) {
|
|
933
|
+
currentOp.setLiteralValue(correctOp);
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
function getArrowParam(node) {
|
|
938
|
+
if (!Node9.isArrowFunction(node)) return void 0;
|
|
939
|
+
const params = node.getParameters();
|
|
940
|
+
if (params.length !== 1) return void 0;
|
|
941
|
+
return params[0].getName();
|
|
942
|
+
}
|
|
943
|
+
function resolveColumnName(node, tableParam) {
|
|
944
|
+
if (Node9.isPropertyAccessExpression(node)) {
|
|
945
|
+
const obj = node.getExpression();
|
|
946
|
+
const propName = node.getName();
|
|
947
|
+
if (Node9.isIdentifier(obj) && obj.getText() === tableParam) {
|
|
948
|
+
return propName;
|
|
949
|
+
}
|
|
950
|
+
return resolveColumnName(obj, tableParam);
|
|
951
|
+
}
|
|
952
|
+
if (Node9.isCallExpression(node)) {
|
|
953
|
+
const callExpr = node.getExpression();
|
|
954
|
+
if (Node9.isPropertyAccessExpression(callExpr)) {
|
|
955
|
+
return resolveColumnName(callExpr.getExpression(), tableParam);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return void 0;
|
|
959
|
+
}
|
|
960
|
+
|
|
850
961
|
// src/commands/db/gen-dbschema/transforms/ast/index.ts
|
|
851
962
|
var defaultTransforms = [
|
|
852
963
|
patchDefectsTransform,
|
|
@@ -865,6 +976,8 @@ var defaultTransforms = [
|
|
|
865
976
|
// #9 Replace .defaultNow()
|
|
866
977
|
removeSystemFieldsTransform,
|
|
867
978
|
// #10 Remove conflicting system fields
|
|
979
|
+
fixOpclassTransform,
|
|
980
|
+
// #11 Fix multi-column index opclass mapping
|
|
868
981
|
tweakImportsTransform
|
|
869
982
|
// #12 Adjust imports
|
|
870
983
|
];
|
|
@@ -1901,7 +2014,7 @@ export class ${className}Module {}
|
|
|
1901
2014
|
}
|
|
1902
2015
|
|
|
1903
2016
|
// src/commands/db/gen-nest-resource/schema-parser.ts
|
|
1904
|
-
import { Project as Project2, Node as
|
|
2017
|
+
import { Project as Project2, Node as Node10 } from "ts-morph";
|
|
1905
2018
|
var DrizzleSchemaParser = class {
|
|
1906
2019
|
constructor(projectOptions) {
|
|
1907
2020
|
this.project = new Project2(projectOptions);
|
|
@@ -1914,7 +2027,7 @@ var DrizzleSchemaParser = class {
|
|
|
1914
2027
|
const declarations = statement.getDeclarations();
|
|
1915
2028
|
for (const declaration of declarations) {
|
|
1916
2029
|
const initializer = declaration.getInitializer();
|
|
1917
|
-
if (initializer &&
|
|
2030
|
+
if (initializer && Node10.isCallExpression(initializer)) {
|
|
1918
2031
|
const expression = initializer.getExpression();
|
|
1919
2032
|
if (expression.getText() === "pgTable") {
|
|
1920
2033
|
const tableInfo = this.parsePgTable(
|
|
@@ -1937,13 +2050,13 @@ var DrizzleSchemaParser = class {
|
|
|
1937
2050
|
}
|
|
1938
2051
|
const tableName = args[0].getText().replace(/['"]/g, "");
|
|
1939
2052
|
const fieldsArg = args[1];
|
|
1940
|
-
if (!
|
|
2053
|
+
if (!Node10.isObjectLiteralExpression(fieldsArg)) {
|
|
1941
2054
|
return null;
|
|
1942
2055
|
}
|
|
1943
2056
|
const fields = [];
|
|
1944
2057
|
const properties = fieldsArg.getProperties();
|
|
1945
2058
|
for (const prop of properties) {
|
|
1946
|
-
if (
|
|
2059
|
+
if (Node10.isPropertyAssignment(prop)) {
|
|
1947
2060
|
const fieldName = prop.getName();
|
|
1948
2061
|
const initializer = prop.getInitializer();
|
|
1949
2062
|
const leadingComments = prop.getLeadingCommentRanges();
|
|
@@ -1951,7 +2064,7 @@ var DrizzleSchemaParser = class {
|
|
|
1951
2064
|
if (leadingComments.length > 0) {
|
|
1952
2065
|
comment = leadingComments.map((c) => c.getText()).join("\n").replace(/\/\//g, "").trim();
|
|
1953
2066
|
}
|
|
1954
|
-
if (initializer &&
|
|
2067
|
+
if (initializer && Node10.isCallExpression(initializer)) {
|
|
1955
2068
|
const fieldInfo = this.parseField(fieldName, initializer, comment);
|
|
1956
2069
|
fields.push(fieldInfo);
|
|
1957
2070
|
}
|
|
@@ -1983,10 +2096,10 @@ var DrizzleSchemaParser = class {
|
|
|
1983
2096
|
parseBaseType(callExpr, fieldInfo) {
|
|
1984
2097
|
let current = callExpr;
|
|
1985
2098
|
let baseCall = null;
|
|
1986
|
-
while (
|
|
2099
|
+
while (Node10.isCallExpression(current)) {
|
|
1987
2100
|
baseCall = current;
|
|
1988
2101
|
const expression2 = current.getExpression();
|
|
1989
|
-
if (
|
|
2102
|
+
if (Node10.isPropertyAccessExpression(expression2)) {
|
|
1990
2103
|
current = expression2.getExpression();
|
|
1991
2104
|
} else {
|
|
1992
2105
|
break;
|
|
@@ -1997,7 +2110,7 @@ var DrizzleSchemaParser = class {
|
|
|
1997
2110
|
}
|
|
1998
2111
|
const expression = baseCall.getExpression();
|
|
1999
2112
|
let typeName = "";
|
|
2000
|
-
if (
|
|
2113
|
+
if (Node10.isPropertyAccessExpression(expression)) {
|
|
2001
2114
|
typeName = expression.getName();
|
|
2002
2115
|
} else {
|
|
2003
2116
|
typeName = expression.getText();
|
|
@@ -2006,25 +2119,25 @@ var DrizzleSchemaParser = class {
|
|
|
2006
2119
|
const args = baseCall.getArguments();
|
|
2007
2120
|
if (args.length > 0) {
|
|
2008
2121
|
const firstArg = args[0];
|
|
2009
|
-
if (
|
|
2122
|
+
if (Node10.isStringLiteral(firstArg)) {
|
|
2010
2123
|
fieldInfo.columnName = firstArg.getLiteralText();
|
|
2011
|
-
} else if (
|
|
2124
|
+
} else if (Node10.isObjectLiteralExpression(firstArg)) {
|
|
2012
2125
|
this.parseTypeConfig(firstArg, fieldInfo);
|
|
2013
|
-
} else if (
|
|
2126
|
+
} else if (Node10.isArrayLiteralExpression(firstArg)) {
|
|
2014
2127
|
fieldInfo.enumValues = firstArg.getElements().map((el) => el.getText().replace(/['"]/g, ""));
|
|
2015
2128
|
}
|
|
2016
2129
|
}
|
|
2017
|
-
if (args.length > 1 &&
|
|
2130
|
+
if (args.length > 1 && Node10.isObjectLiteralExpression(args[1])) {
|
|
2018
2131
|
this.parseTypeConfig(args[1], fieldInfo);
|
|
2019
2132
|
}
|
|
2020
2133
|
}
|
|
2021
2134
|
parseTypeConfig(objLiteral, fieldInfo) {
|
|
2022
|
-
if (!
|
|
2135
|
+
if (!Node10.isObjectLiteralExpression(objLiteral)) {
|
|
2023
2136
|
return;
|
|
2024
2137
|
}
|
|
2025
2138
|
const properties = objLiteral.getProperties();
|
|
2026
2139
|
for (const prop of properties) {
|
|
2027
|
-
if (
|
|
2140
|
+
if (Node10.isPropertyAssignment(prop)) {
|
|
2028
2141
|
const propName = prop.getName();
|
|
2029
2142
|
const value = prop.getInitializer()?.getText();
|
|
2030
2143
|
switch (propName) {
|
|
@@ -2056,9 +2169,9 @@ var DrizzleSchemaParser = class {
|
|
|
2056
2169
|
}
|
|
2057
2170
|
parseCallChain(callExpr, fieldInfo) {
|
|
2058
2171
|
let current = callExpr;
|
|
2059
|
-
while (
|
|
2172
|
+
while (Node10.isCallExpression(current)) {
|
|
2060
2173
|
const expression = current.getExpression();
|
|
2061
|
-
if (
|
|
2174
|
+
if (Node10.isPropertyAccessExpression(expression)) {
|
|
2062
2175
|
const methodName = expression.getName();
|
|
2063
2176
|
const args = current.getArguments();
|
|
2064
2177
|
switch (methodName) {
|
|
@@ -2154,9 +2267,9 @@ async function parseAndGenerateNestResourceTemplate(options) {
|
|
|
2154
2267
|
var require2 = createRequire(import.meta.url);
|
|
2155
2268
|
async function run(options = {}) {
|
|
2156
2269
|
let exitCode = 0;
|
|
2157
|
-
const
|
|
2158
|
-
if (fs4.existsSync(
|
|
2159
|
-
loadEnv({ path:
|
|
2270
|
+
const envPath = path2.resolve(process.cwd(), ".env");
|
|
2271
|
+
if (fs4.existsSync(envPath)) {
|
|
2272
|
+
loadEnv({ path: envPath });
|
|
2160
2273
|
console.log("[gen-db-schema] \u2713 Loaded .env file");
|
|
2161
2274
|
}
|
|
2162
2275
|
const databaseUrl = process.env.SUDA_DATABASE_URL;
|
|
@@ -2455,6 +2568,12 @@ function buildDefaultRules(opts) {
|
|
|
2455
2568
|
to: ".gitignore",
|
|
2456
2569
|
line: ".agent/"
|
|
2457
2570
|
},
|
|
2571
|
+
// 7a. 确保 .gitignore 包含 .npm_cache 目录
|
|
2572
|
+
{
|
|
2573
|
+
type: "add-line",
|
|
2574
|
+
to: ".gitignore",
|
|
2575
|
+
line: ".npm_cache"
|
|
2576
|
+
},
|
|
2458
2577
|
// 8. 同步 .spark_project 配置文件(总是覆盖)
|
|
2459
2578
|
{
|
|
2460
2579
|
from: "templates/.spark_project",
|
|
@@ -2507,7 +2626,9 @@ var SYNC_PROFILES = {
|
|
|
2507
2626
|
"design-stack": defaultProfile,
|
|
2508
2627
|
"nestjs-react-fullstack": defaultProfile,
|
|
2509
2628
|
"vite-react": viteReactProfile,
|
|
2510
|
-
html: emptyProfile
|
|
2629
|
+
html: emptyProfile,
|
|
2630
|
+
// design-html 与 html 同语义:空 sync + 不激活 git hooks(彻底 no-op)
|
|
2631
|
+
"design-html": emptyProfile
|
|
2511
2632
|
};
|
|
2512
2633
|
function genSyncConfig(stack, opts = {}) {
|
|
2513
2634
|
const factory = stack && SYNC_PROFILES[stack] || defaultProfile;
|
|
@@ -5641,17 +5762,17 @@ function analyzeImports(sourceFile) {
|
|
|
5641
5762
|
}
|
|
5642
5763
|
|
|
5643
5764
|
// src/commands/migration/versions/v001_capability/code-migrator/analyzers/call-site-analyzer.ts
|
|
5644
|
-
import { SyntaxKind as
|
|
5765
|
+
import { SyntaxKind as SyntaxKind5 } from "ts-morph";
|
|
5645
5766
|
function analyzeCallSites(sourceFile, imports) {
|
|
5646
5767
|
const callSites = [];
|
|
5647
5768
|
const importMap = /* @__PURE__ */ new Map();
|
|
5648
5769
|
for (const imp of imports) {
|
|
5649
5770
|
importMap.set(imp.importName, imp.capabilityId);
|
|
5650
5771
|
}
|
|
5651
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
5772
|
+
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind5.CallExpression);
|
|
5652
5773
|
for (const callExpr of callExpressions) {
|
|
5653
5774
|
const expression = callExpr.getExpression();
|
|
5654
|
-
if (expression.getKind() ===
|
|
5775
|
+
if (expression.getKind() === SyntaxKind5.Identifier) {
|
|
5655
5776
|
const functionName = expression.getText();
|
|
5656
5777
|
const capabilityId = importMap.get(functionName);
|
|
5657
5778
|
if (capabilityId) {
|
|
@@ -5664,11 +5785,11 @@ function analyzeCallSites(sourceFile, imports) {
|
|
|
5664
5785
|
text: callExpr.getText()
|
|
5665
5786
|
});
|
|
5666
5787
|
}
|
|
5667
|
-
} else if (expression.getKind() ===
|
|
5668
|
-
const propAccess = expression.asKind(
|
|
5788
|
+
} else if (expression.getKind() === SyntaxKind5.PropertyAccessExpression) {
|
|
5789
|
+
const propAccess = expression.asKind(SyntaxKind5.PropertyAccessExpression);
|
|
5669
5790
|
if (propAccess) {
|
|
5670
5791
|
const objectExpr = propAccess.getExpression();
|
|
5671
|
-
if (objectExpr.getKind() ===
|
|
5792
|
+
if (objectExpr.getKind() === SyntaxKind5.Identifier) {
|
|
5672
5793
|
const objectName = objectExpr.getText();
|
|
5673
5794
|
const capabilityId = importMap.get(objectName);
|
|
5674
5795
|
if (capabilityId) {
|
|
@@ -5897,7 +6018,7 @@ function addInjection(sourceFile) {
|
|
|
5897
6018
|
}
|
|
5898
6019
|
|
|
5899
6020
|
// src/commands/migration/versions/v001_capability/code-migrator/transformers/call-site-transformer.ts
|
|
5900
|
-
import { SyntaxKind as
|
|
6021
|
+
import { SyntaxKind as SyntaxKind6 } from "ts-morph";
|
|
5901
6022
|
var DEFAULT_ACTION_NAME = "run";
|
|
5902
6023
|
function generateNewCallText(capabilityId, actionName, args) {
|
|
5903
6024
|
const argsText = args.trim() || "{}";
|
|
@@ -5912,19 +6033,19 @@ function transformCallSites(sourceFile, imports) {
|
|
|
5912
6033
|
});
|
|
5913
6034
|
}
|
|
5914
6035
|
let replacedCount = 0;
|
|
5915
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
6036
|
+
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind6.CallExpression);
|
|
5916
6037
|
const sortedCalls = [...callExpressions].sort((a, b) => b.getStart() - a.getStart());
|
|
5917
6038
|
for (const callExpr of sortedCalls) {
|
|
5918
6039
|
const expression = callExpr.getExpression();
|
|
5919
6040
|
let importInfo;
|
|
5920
|
-
if (expression.getKind() ===
|
|
6041
|
+
if (expression.getKind() === SyntaxKind6.Identifier) {
|
|
5921
6042
|
const functionName = expression.getText();
|
|
5922
6043
|
importInfo = importMap.get(functionName);
|
|
5923
|
-
} else if (expression.getKind() ===
|
|
5924
|
-
const propAccess = expression.asKind(
|
|
6044
|
+
} else if (expression.getKind() === SyntaxKind6.PropertyAccessExpression) {
|
|
6045
|
+
const propAccess = expression.asKind(SyntaxKind6.PropertyAccessExpression);
|
|
5925
6046
|
if (propAccess) {
|
|
5926
6047
|
const objectExpr = propAccess.getExpression();
|
|
5927
|
-
if (objectExpr.getKind() ===
|
|
6048
|
+
if (objectExpr.getKind() === SyntaxKind6.Identifier) {
|
|
5928
6049
|
const objectName = objectExpr.getText();
|
|
5929
6050
|
importInfo = importMap.get(objectName);
|
|
5930
6051
|
}
|
|
@@ -7577,8 +7698,10 @@ function sanitizeStructuredLog(value) {
|
|
|
7577
7698
|
delete sanitized.pid;
|
|
7578
7699
|
return sanitized;
|
|
7579
7700
|
}
|
|
7701
|
+
var TRANSIENT_CONNECTION_ERROR_PATTERN = /ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENETUNREACH|socket hang up|proxy error|\[Proxy\] (?:Error:\s*$|Error during|Connection error|Non-connection error|Headers already sent|Service (?:recovered|did not recover))/i;
|
|
7580
7702
|
function hasErrorInStdLines(lines) {
|
|
7581
|
-
const
|
|
7703
|
+
const filtered = lines.filter((line) => !TRANSIENT_CONNECTION_ERROR_PATTERN.test(line));
|
|
7704
|
+
const combined = filtered.join("\n");
|
|
7582
7705
|
if (!combined) return false;
|
|
7583
7706
|
const strong = [
|
|
7584
7707
|
/compiled with errors/i,
|
|
@@ -7597,7 +7720,7 @@ function hasErrorInStdLines(lines) {
|
|
|
7597
7720
|
/\b0\s+errors?\b/i,
|
|
7598
7721
|
/Server Error \d{3}/i
|
|
7599
7722
|
];
|
|
7600
|
-
return
|
|
7723
|
+
return filtered.some((line) => {
|
|
7601
7724
|
const text = line.trim();
|
|
7602
7725
|
if (!text) return false;
|
|
7603
7726
|
if (ignorePatterns.some((re) => re.test(text))) return false;
|
|
@@ -8146,9 +8269,11 @@ var commands = [
|
|
|
8146
8269
|
];
|
|
8147
8270
|
|
|
8148
8271
|
// src/index.ts
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8272
|
+
for (const filename of [".env.local", ".env"]) {
|
|
8273
|
+
const envPath = path25.join(process.cwd(), filename);
|
|
8274
|
+
if (fs29.existsSync(envPath)) {
|
|
8275
|
+
dotenvConfig({ path: envPath });
|
|
8276
|
+
}
|
|
8152
8277
|
}
|
|
8153
8278
|
var __dirname = path25.dirname(fileURLToPath5(import.meta.url));
|
|
8154
8279
|
var pkg = JSON.parse(fs29.readFileSync(path25.join(__dirname, "../package.json"), "utf-8"));
|
package/package.json
CHANGED
package/templates/.spark_project
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
run = ["npm", "run", "dev"] # 默认 spark-cli dev
|
|
2
|
-
hidden = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
|
2
|
+
hidden = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", ".claude", "tmp", ".spark_project", ".playwright-cli"]
|
|
3
3
|
lint = ["npm", "run", "lint"]
|
|
4
4
|
test = ["npm", "run", "test"]
|
|
5
5
|
genDbSchema = ["npm", "run", "gen:db-schema"]
|
|
@@ -13,4 +13,4 @@ run = ["npm", "run", "start"]
|
|
|
13
13
|
[files.restrict]
|
|
14
14
|
pathPatterns = ["client/src/api/gen", "package.json", ".spark_project", ".gitignore"]
|
|
15
15
|
[files.hidden]
|
|
16
|
-
pathPatterns = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
|
16
|
+
pathPatterns = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", ".claude", "tmp", ".spark_project", ".playwright-cli"]
|
package/templates/nest-cli.json
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// 本地开发启动脚本(由 miaoda app sync 维护,请勿手改)
|
|
4
|
+
// Stack: nestjs-react-fullstack
|
|
5
|
+
//
|
|
6
|
+
// 流程:
|
|
7
|
+
// 1. env pull —— 拉沙箱身份/凭证到 .env.local
|
|
8
|
+
// 2. skills sync —— 同步当前 stack 的 agent skills
|
|
9
|
+
// 3. dotenv 加载 .env / .env.local 到 process.env(含 SUDA_WEBUSER 适配)
|
|
10
|
+
// 4. 并发起 dev:server + dev:client(子进程继承 process.env)
|
|
11
|
+
//
|
|
12
|
+
// 关键设计:本脚本在 spawn 子进程之前先把 .env / .env.local 加载到 process.env,
|
|
13
|
+
// 然后 spawn 的 server / client 进程通过 env 继承直接拿到——SDK(fullstack-nestjs-core
|
|
14
|
+
// / fullstack-vite-preset / fullstack-rspack-preset)无需自己 require('dotenv')。
|
|
15
|
+
//
|
|
16
|
+
// SUDA_WEBUSER 适配:沙箱 env pull 下发到 .env.local 的形态是
|
|
17
|
+
// `SUDA_WEBUSER="{\"user_id\":\"...\"}"`(shell-quoted JSON),dotenv@17 剥外层引号后
|
|
18
|
+
// 保留内部 `\"` 转义不还原,导致 process.env.SUDA_WEBUSER 是 `{\"user_id\":\"...\"}`
|
|
19
|
+
// 这种带反斜杠串,下游 JSON.parse 直接挂。这里做一次「直接 parse 失败则 unescape
|
|
20
|
+
// 后重 parse」兜底,把容错收敛在启动期单点,下游拿到干净 JSON 字符串。
|
|
21
|
+
// ============================================================================
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
const { execSync, spawn, spawnSync } = require('node:child_process');
|
|
25
|
+
|
|
26
|
+
process.chdir(path.resolve(__dirname, '..'));
|
|
27
|
+
|
|
28
|
+
function warn(msg) {
|
|
29
|
+
if (process.stderr.isTTY) process.stderr.write(`\x1b[33mWARNING: ${msg}\x1b[0m\n`);
|
|
30
|
+
else process.stderr.write(`WARNING: ${msg}\n`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!process.env.MIAODA_APP_TYPE) process.env.MIAODA_APP_TYPE = '3';
|
|
34
|
+
process.env.MIAODA_LOCAL_DEV = '1';
|
|
35
|
+
|
|
36
|
+
// 1. env pull
|
|
37
|
+
console.log('[dev-local] (1/4) env pull...');
|
|
38
|
+
const hasLarkCli = spawnSync('command', ['-v', 'lark-cli'], { shell: true, stdio: 'ignore' }).status === 0;
|
|
39
|
+
if (hasLarkCli) {
|
|
40
|
+
let appId = '';
|
|
41
|
+
try {
|
|
42
|
+
appId = JSON.parse(fs.readFileSync('.spark/meta.json', 'utf8')).app_id || '';
|
|
43
|
+
} catch {
|
|
44
|
+
/* meta.json 不存在或非法 JSON */
|
|
45
|
+
}
|
|
46
|
+
if (appId) {
|
|
47
|
+
const r = spawnSync('lark-cli', ['apps', '+env-pull', '--app-id', appId, '--as', 'user'], {
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
});
|
|
50
|
+
if (r.status !== 0) warn('env pull 失败,继续按 .env.local 现状启动');
|
|
51
|
+
} else {
|
|
52
|
+
warn('.spark/meta.json 缺 app_id,请先跑 `miaoda app init --app-id <id>`');
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
warn('lark-cli 未安装,跳过 env pull;请确保 .env.local 已就绪');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. skills sync —— --local 切到 flat layout (.agents/skills + .claude/skills 软链),
|
|
59
|
+
// 跟沙箱 nested layout 区分。不传 --version,handler 默认拉 coding-steering@latest,
|
|
60
|
+
// 保证每次本地 npm run dev 都把 skills 升到最新。
|
|
61
|
+
console.log('[dev-local] (2/4) miaoda skills sync...');
|
|
62
|
+
try {
|
|
63
|
+
execSync('npx -y @lark-apaas/miaoda-cli@latest skills sync --local', { stdio: 'inherit' });
|
|
64
|
+
} catch {
|
|
65
|
+
console.log(' (skills sync 失败,继续启动)');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. 加载 .env / .env.local 到 process.env
|
|
69
|
+
// dotenv 默认 override:false,先到先得 → 先 .env.local 让它优先于 .env;
|
|
70
|
+
// shell env 已在 process.env,两次 config 都不会覆盖。
|
|
71
|
+
console.log('[dev-local] (3/4) loading .env / .env.local...');
|
|
72
|
+
const dotenv = require('dotenv');
|
|
73
|
+
dotenv.config({ path: '.env.local' });
|
|
74
|
+
dotenv.config({ path: '.env' });
|
|
75
|
+
|
|
76
|
+
// SUDA_WEBUSER 适配(详见文件头注释)
|
|
77
|
+
if (process.env.SUDA_WEBUSER) {
|
|
78
|
+
const raw = process.env.SUDA_WEBUSER;
|
|
79
|
+
try {
|
|
80
|
+
JSON.parse(raw);
|
|
81
|
+
} catch {
|
|
82
|
+
try {
|
|
83
|
+
const unescaped = raw.replace(/\\"/g, '"');
|
|
84
|
+
JSON.parse(unescaped);
|
|
85
|
+
process.env.SUDA_WEBUSER = unescaped;
|
|
86
|
+
} catch {
|
|
87
|
+
warn(`SUDA_WEBUSER 解析失败,值头部: ${raw.slice(0, 80)}...`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 4. 并发起前后端 dev server
|
|
93
|
+
console.log('[dev-local] (4/4) 并发起 dev:server + dev:client');
|
|
94
|
+
const child = spawn(
|
|
95
|
+
'npx',
|
|
96
|
+
[
|
|
97
|
+
'--no-install',
|
|
98
|
+
'concurrently',
|
|
99
|
+
'--names',
|
|
100
|
+
'server,client',
|
|
101
|
+
'--prefix-colors',
|
|
102
|
+
'blue,green',
|
|
103
|
+
'--kill-others-on-fail',
|
|
104
|
+
'npm run dev:server',
|
|
105
|
+
'npm run dev:client',
|
|
106
|
+
],
|
|
107
|
+
{ stdio: 'inherit', env: process.env },
|
|
108
|
+
);
|
|
109
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
110
|
+
child.on('error', (err) => {
|
|
111
|
+
console.error(err);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|