@lark-apaas/fullstack-cli 1.1.56 → 1.1.58-alpha.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 +37 -162
- package/package.json +1 -1
- package/templates/.spark_project +2 -2
- package/templates/scripts/dev.js +56 -1
- package/templates/scripts/dev.sh +1 -23
- package/templates/scripts/lint.js +15 -27
- package/templates/scripts/preview-startup-timing.cjs +126 -0
- package/templates/scripts/dev-local.js +0 -113
package/dist/index.js
CHANGED
|
@@ -847,117 +847,6 @@ 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
|
-
|
|
961
850
|
// src/commands/db/gen-dbschema/transforms/ast/index.ts
|
|
962
851
|
var defaultTransforms = [
|
|
963
852
|
patchDefectsTransform,
|
|
@@ -976,8 +865,6 @@ var defaultTransforms = [
|
|
|
976
865
|
// #9 Replace .defaultNow()
|
|
977
866
|
removeSystemFieldsTransform,
|
|
978
867
|
// #10 Remove conflicting system fields
|
|
979
|
-
fixOpclassTransform,
|
|
980
|
-
// #11 Fix multi-column index opclass mapping
|
|
981
868
|
tweakImportsTransform
|
|
982
869
|
// #12 Adjust imports
|
|
983
870
|
];
|
|
@@ -2014,7 +1901,7 @@ export class ${className}Module {}
|
|
|
2014
1901
|
}
|
|
2015
1902
|
|
|
2016
1903
|
// src/commands/db/gen-nest-resource/schema-parser.ts
|
|
2017
|
-
import { Project as Project2, Node as
|
|
1904
|
+
import { Project as Project2, Node as Node9 } from "ts-morph";
|
|
2018
1905
|
var DrizzleSchemaParser = class {
|
|
2019
1906
|
constructor(projectOptions) {
|
|
2020
1907
|
this.project = new Project2(projectOptions);
|
|
@@ -2027,7 +1914,7 @@ var DrizzleSchemaParser = class {
|
|
|
2027
1914
|
const declarations = statement.getDeclarations();
|
|
2028
1915
|
for (const declaration of declarations) {
|
|
2029
1916
|
const initializer = declaration.getInitializer();
|
|
2030
|
-
if (initializer &&
|
|
1917
|
+
if (initializer && Node9.isCallExpression(initializer)) {
|
|
2031
1918
|
const expression = initializer.getExpression();
|
|
2032
1919
|
if (expression.getText() === "pgTable") {
|
|
2033
1920
|
const tableInfo = this.parsePgTable(
|
|
@@ -2050,13 +1937,13 @@ var DrizzleSchemaParser = class {
|
|
|
2050
1937
|
}
|
|
2051
1938
|
const tableName = args[0].getText().replace(/['"]/g, "");
|
|
2052
1939
|
const fieldsArg = args[1];
|
|
2053
|
-
if (!
|
|
1940
|
+
if (!Node9.isObjectLiteralExpression(fieldsArg)) {
|
|
2054
1941
|
return null;
|
|
2055
1942
|
}
|
|
2056
1943
|
const fields = [];
|
|
2057
1944
|
const properties = fieldsArg.getProperties();
|
|
2058
1945
|
for (const prop of properties) {
|
|
2059
|
-
if (
|
|
1946
|
+
if (Node9.isPropertyAssignment(prop)) {
|
|
2060
1947
|
const fieldName = prop.getName();
|
|
2061
1948
|
const initializer = prop.getInitializer();
|
|
2062
1949
|
const leadingComments = prop.getLeadingCommentRanges();
|
|
@@ -2064,7 +1951,7 @@ var DrizzleSchemaParser = class {
|
|
|
2064
1951
|
if (leadingComments.length > 0) {
|
|
2065
1952
|
comment = leadingComments.map((c) => c.getText()).join("\n").replace(/\/\//g, "").trim();
|
|
2066
1953
|
}
|
|
2067
|
-
if (initializer &&
|
|
1954
|
+
if (initializer && Node9.isCallExpression(initializer)) {
|
|
2068
1955
|
const fieldInfo = this.parseField(fieldName, initializer, comment);
|
|
2069
1956
|
fields.push(fieldInfo);
|
|
2070
1957
|
}
|
|
@@ -2096,10 +1983,10 @@ var DrizzleSchemaParser = class {
|
|
|
2096
1983
|
parseBaseType(callExpr, fieldInfo) {
|
|
2097
1984
|
let current = callExpr;
|
|
2098
1985
|
let baseCall = null;
|
|
2099
|
-
while (
|
|
1986
|
+
while (Node9.isCallExpression(current)) {
|
|
2100
1987
|
baseCall = current;
|
|
2101
1988
|
const expression2 = current.getExpression();
|
|
2102
|
-
if (
|
|
1989
|
+
if (Node9.isPropertyAccessExpression(expression2)) {
|
|
2103
1990
|
current = expression2.getExpression();
|
|
2104
1991
|
} else {
|
|
2105
1992
|
break;
|
|
@@ -2110,7 +1997,7 @@ var DrizzleSchemaParser = class {
|
|
|
2110
1997
|
}
|
|
2111
1998
|
const expression = baseCall.getExpression();
|
|
2112
1999
|
let typeName = "";
|
|
2113
|
-
if (
|
|
2000
|
+
if (Node9.isPropertyAccessExpression(expression)) {
|
|
2114
2001
|
typeName = expression.getName();
|
|
2115
2002
|
} else {
|
|
2116
2003
|
typeName = expression.getText();
|
|
@@ -2119,25 +2006,25 @@ var DrizzleSchemaParser = class {
|
|
|
2119
2006
|
const args = baseCall.getArguments();
|
|
2120
2007
|
if (args.length > 0) {
|
|
2121
2008
|
const firstArg = args[0];
|
|
2122
|
-
if (
|
|
2009
|
+
if (Node9.isStringLiteral(firstArg)) {
|
|
2123
2010
|
fieldInfo.columnName = firstArg.getLiteralText();
|
|
2124
|
-
} else if (
|
|
2011
|
+
} else if (Node9.isObjectLiteralExpression(firstArg)) {
|
|
2125
2012
|
this.parseTypeConfig(firstArg, fieldInfo);
|
|
2126
|
-
} else if (
|
|
2013
|
+
} else if (Node9.isArrayLiteralExpression(firstArg)) {
|
|
2127
2014
|
fieldInfo.enumValues = firstArg.getElements().map((el) => el.getText().replace(/['"]/g, ""));
|
|
2128
2015
|
}
|
|
2129
2016
|
}
|
|
2130
|
-
if (args.length > 1 &&
|
|
2017
|
+
if (args.length > 1 && Node9.isObjectLiteralExpression(args[1])) {
|
|
2131
2018
|
this.parseTypeConfig(args[1], fieldInfo);
|
|
2132
2019
|
}
|
|
2133
2020
|
}
|
|
2134
2021
|
parseTypeConfig(objLiteral, fieldInfo) {
|
|
2135
|
-
if (!
|
|
2022
|
+
if (!Node9.isObjectLiteralExpression(objLiteral)) {
|
|
2136
2023
|
return;
|
|
2137
2024
|
}
|
|
2138
2025
|
const properties = objLiteral.getProperties();
|
|
2139
2026
|
for (const prop of properties) {
|
|
2140
|
-
if (
|
|
2027
|
+
if (Node9.isPropertyAssignment(prop)) {
|
|
2141
2028
|
const propName = prop.getName();
|
|
2142
2029
|
const value = prop.getInitializer()?.getText();
|
|
2143
2030
|
switch (propName) {
|
|
@@ -2169,9 +2056,9 @@ var DrizzleSchemaParser = class {
|
|
|
2169
2056
|
}
|
|
2170
2057
|
parseCallChain(callExpr, fieldInfo) {
|
|
2171
2058
|
let current = callExpr;
|
|
2172
|
-
while (
|
|
2059
|
+
while (Node9.isCallExpression(current)) {
|
|
2173
2060
|
const expression = current.getExpression();
|
|
2174
|
-
if (
|
|
2061
|
+
if (Node9.isPropertyAccessExpression(expression)) {
|
|
2175
2062
|
const methodName = expression.getName();
|
|
2176
2063
|
const args = current.getArguments();
|
|
2177
2064
|
switch (methodName) {
|
|
@@ -2267,9 +2154,9 @@ async function parseAndGenerateNestResourceTemplate(options) {
|
|
|
2267
2154
|
var require2 = createRequire(import.meta.url);
|
|
2268
2155
|
async function run(options = {}) {
|
|
2269
2156
|
let exitCode = 0;
|
|
2270
|
-
const
|
|
2271
|
-
if (fs4.existsSync(
|
|
2272
|
-
loadEnv({ path:
|
|
2157
|
+
const envPath2 = path2.resolve(process.cwd(), ".env");
|
|
2158
|
+
if (fs4.existsSync(envPath2)) {
|
|
2159
|
+
loadEnv({ path: envPath2 });
|
|
2273
2160
|
console.log("[gen-db-schema] \u2713 Loaded .env file");
|
|
2274
2161
|
}
|
|
2275
2162
|
const databaseUrl = process.env.SUDA_DATABASE_URL;
|
|
@@ -2568,12 +2455,6 @@ function buildDefaultRules(opts) {
|
|
|
2568
2455
|
to: ".gitignore",
|
|
2569
2456
|
line: ".agent/"
|
|
2570
2457
|
},
|
|
2571
|
-
// 7a. 确保 .gitignore 包含 .npm_cache 目录
|
|
2572
|
-
{
|
|
2573
|
-
type: "add-line",
|
|
2574
|
-
to: ".gitignore",
|
|
2575
|
-
line: ".npm_cache"
|
|
2576
|
-
},
|
|
2577
2458
|
// 8. 同步 .spark_project 配置文件(总是覆盖)
|
|
2578
2459
|
{
|
|
2579
2460
|
from: "templates/.spark_project",
|
|
@@ -2626,9 +2507,7 @@ var SYNC_PROFILES = {
|
|
|
2626
2507
|
"design-stack": defaultProfile,
|
|
2627
2508
|
"nestjs-react-fullstack": defaultProfile,
|
|
2628
2509
|
"vite-react": viteReactProfile,
|
|
2629
|
-
html: emptyProfile
|
|
2630
|
-
// design-html 与 html 同语义:空 sync + 不激活 git hooks(彻底 no-op)
|
|
2631
|
-
"design-html": emptyProfile
|
|
2510
|
+
html: emptyProfile
|
|
2632
2511
|
};
|
|
2633
2512
|
function genSyncConfig(stack, opts = {}) {
|
|
2634
2513
|
const factory = stack && SYNC_PROFILES[stack] || defaultProfile;
|
|
@@ -5762,17 +5641,17 @@ function analyzeImports(sourceFile) {
|
|
|
5762
5641
|
}
|
|
5763
5642
|
|
|
5764
5643
|
// src/commands/migration/versions/v001_capability/code-migrator/analyzers/call-site-analyzer.ts
|
|
5765
|
-
import { SyntaxKind as
|
|
5644
|
+
import { SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
5766
5645
|
function analyzeCallSites(sourceFile, imports) {
|
|
5767
5646
|
const callSites = [];
|
|
5768
5647
|
const importMap = /* @__PURE__ */ new Map();
|
|
5769
5648
|
for (const imp of imports) {
|
|
5770
5649
|
importMap.set(imp.importName, imp.capabilityId);
|
|
5771
5650
|
}
|
|
5772
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
5651
|
+
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind4.CallExpression);
|
|
5773
5652
|
for (const callExpr of callExpressions) {
|
|
5774
5653
|
const expression = callExpr.getExpression();
|
|
5775
|
-
if (expression.getKind() ===
|
|
5654
|
+
if (expression.getKind() === SyntaxKind4.Identifier) {
|
|
5776
5655
|
const functionName = expression.getText();
|
|
5777
5656
|
const capabilityId = importMap.get(functionName);
|
|
5778
5657
|
if (capabilityId) {
|
|
@@ -5785,11 +5664,11 @@ function analyzeCallSites(sourceFile, imports) {
|
|
|
5785
5664
|
text: callExpr.getText()
|
|
5786
5665
|
});
|
|
5787
5666
|
}
|
|
5788
|
-
} else if (expression.getKind() ===
|
|
5789
|
-
const propAccess = expression.asKind(
|
|
5667
|
+
} else if (expression.getKind() === SyntaxKind4.PropertyAccessExpression) {
|
|
5668
|
+
const propAccess = expression.asKind(SyntaxKind4.PropertyAccessExpression);
|
|
5790
5669
|
if (propAccess) {
|
|
5791
5670
|
const objectExpr = propAccess.getExpression();
|
|
5792
|
-
if (objectExpr.getKind() ===
|
|
5671
|
+
if (objectExpr.getKind() === SyntaxKind4.Identifier) {
|
|
5793
5672
|
const objectName = objectExpr.getText();
|
|
5794
5673
|
const capabilityId = importMap.get(objectName);
|
|
5795
5674
|
if (capabilityId) {
|
|
@@ -6018,7 +5897,7 @@ function addInjection(sourceFile) {
|
|
|
6018
5897
|
}
|
|
6019
5898
|
|
|
6020
5899
|
// src/commands/migration/versions/v001_capability/code-migrator/transformers/call-site-transformer.ts
|
|
6021
|
-
import { SyntaxKind as
|
|
5900
|
+
import { SyntaxKind as SyntaxKind5 } from "ts-morph";
|
|
6022
5901
|
var DEFAULT_ACTION_NAME = "run";
|
|
6023
5902
|
function generateNewCallText(capabilityId, actionName, args) {
|
|
6024
5903
|
const argsText = args.trim() || "{}";
|
|
@@ -6033,19 +5912,19 @@ function transformCallSites(sourceFile, imports) {
|
|
|
6033
5912
|
});
|
|
6034
5913
|
}
|
|
6035
5914
|
let replacedCount = 0;
|
|
6036
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
5915
|
+
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind5.CallExpression);
|
|
6037
5916
|
const sortedCalls = [...callExpressions].sort((a, b) => b.getStart() - a.getStart());
|
|
6038
5917
|
for (const callExpr of sortedCalls) {
|
|
6039
5918
|
const expression = callExpr.getExpression();
|
|
6040
5919
|
let importInfo;
|
|
6041
|
-
if (expression.getKind() ===
|
|
5920
|
+
if (expression.getKind() === SyntaxKind5.Identifier) {
|
|
6042
5921
|
const functionName = expression.getText();
|
|
6043
5922
|
importInfo = importMap.get(functionName);
|
|
6044
|
-
} else if (expression.getKind() ===
|
|
6045
|
-
const propAccess = expression.asKind(
|
|
5923
|
+
} else if (expression.getKind() === SyntaxKind5.PropertyAccessExpression) {
|
|
5924
|
+
const propAccess = expression.asKind(SyntaxKind5.PropertyAccessExpression);
|
|
6046
5925
|
if (propAccess) {
|
|
6047
5926
|
const objectExpr = propAccess.getExpression();
|
|
6048
|
-
if (objectExpr.getKind() ===
|
|
5927
|
+
if (objectExpr.getKind() === SyntaxKind5.Identifier) {
|
|
6049
5928
|
const objectName = objectExpr.getText();
|
|
6050
5929
|
importInfo = importMap.get(objectName);
|
|
6051
5930
|
}
|
|
@@ -7698,10 +7577,8 @@ function sanitizeStructuredLog(value) {
|
|
|
7698
7577
|
delete sanitized.pid;
|
|
7699
7578
|
return sanitized;
|
|
7700
7579
|
}
|
|
7701
|
-
var TRANSIENT_CONNECTION_ERROR_PATTERN = /ECONNREFUSED|ECONNRESET|proxy error/i;
|
|
7702
7580
|
function hasErrorInStdLines(lines) {
|
|
7703
|
-
const
|
|
7704
|
-
const combined = filtered.join("\n");
|
|
7581
|
+
const combined = lines.join("\n");
|
|
7705
7582
|
if (!combined) return false;
|
|
7706
7583
|
const strong = [
|
|
7707
7584
|
/compiled with errors/i,
|
|
@@ -7720,7 +7597,7 @@ function hasErrorInStdLines(lines) {
|
|
|
7720
7597
|
/\b0\s+errors?\b/i,
|
|
7721
7598
|
/Server Error \d{3}/i
|
|
7722
7599
|
];
|
|
7723
|
-
return
|
|
7600
|
+
return lines.some((line) => {
|
|
7724
7601
|
const text = line.trim();
|
|
7725
7602
|
if (!text) return false;
|
|
7726
7603
|
if (ignorePatterns.some((re) => re.test(text))) return false;
|
|
@@ -8269,11 +8146,9 @@ var commands = [
|
|
|
8269
8146
|
];
|
|
8270
8147
|
|
|
8271
8148
|
// src/index.ts
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
dotenvConfig({ path: envPath });
|
|
8276
|
-
}
|
|
8149
|
+
var envPath = path25.join(process.cwd(), ".env");
|
|
8150
|
+
if (fs29.existsSync(envPath)) {
|
|
8151
|
+
dotenvConfig({ path: envPath });
|
|
8277
8152
|
}
|
|
8278
8153
|
var __dirname = path25.dirname(fileURLToPath5(import.meta.url));
|
|
8279
8154
|
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", "
|
|
2
|
+
hidden = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "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", "
|
|
16
|
+
pathPatterns = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
package/templates/scripts/dev.js
CHANGED
|
@@ -5,6 +5,10 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { spawn, execSync } = require('child_process');
|
|
7
7
|
const readline = require('readline');
|
|
8
|
+
const {
|
|
9
|
+
createPreviewPhaseReporter,
|
|
10
|
+
waitForTcpReady,
|
|
11
|
+
} = require('./preview-startup-timing.cjs');
|
|
8
12
|
|
|
9
13
|
// ── Project root ──────────────────────────────────────────────────────────────
|
|
10
14
|
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
@@ -78,6 +82,10 @@ function writeOutput(msg) {
|
|
|
78
82
|
try { fs.write(1, msg, () => { _stdoutInFlight--; }); } catch { _stdoutInFlight--; }
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
const previewPhaseReporter = createPreviewPhaseReporter({
|
|
86
|
+
write: (line) => writeOutput(`${line}\n`),
|
|
87
|
+
});
|
|
88
|
+
|
|
81
89
|
/** Structured event log → terminal + dev.std.log + dev.log */
|
|
82
90
|
function logEvent(level, name, message) {
|
|
83
91
|
const msg = `[${timestamp()}] [${level}] [${name}] ${message}\n`;
|
|
@@ -141,7 +149,39 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
141
149
|
entry.child = child;
|
|
142
150
|
|
|
143
151
|
const startTime = Date.now();
|
|
152
|
+
const attempt = restartCount + 1;
|
|
144
153
|
logEvent('INFO', name, `Process started (PGID: ${child.pid}): ${command} ${args.join(' ')}`);
|
|
154
|
+
const processPhase = name === 'server' ? 'backend_process_spawn' : 'client_process_spawn';
|
|
155
|
+
previewPhaseReporter.emit(processPhase, 'success', {
|
|
156
|
+
at_ms: startTime,
|
|
157
|
+
attempt,
|
|
158
|
+
exact: true,
|
|
159
|
+
});
|
|
160
|
+
if (name === 'server') {
|
|
161
|
+
void waitForTcpReady({
|
|
162
|
+
port: Number(SERVER_PORT),
|
|
163
|
+
started_at_ms: startTime,
|
|
164
|
+
interval_ms: 50,
|
|
165
|
+
timeout_ms: 120000,
|
|
166
|
+
should_continue: () => entry.child === child && !stopping,
|
|
167
|
+
}).then((result) => {
|
|
168
|
+
if (result.cancelled) return;
|
|
169
|
+
previewPhaseReporter.emit(
|
|
170
|
+
'backend_tcp_ready',
|
|
171
|
+
result.ready ? 'success' : 'error',
|
|
172
|
+
{ ...result, attempt, exact: false }
|
|
173
|
+
);
|
|
174
|
+
}).catch((error) => {
|
|
175
|
+
if (entry.child !== child || stopping) return;
|
|
176
|
+
previewPhaseReporter.emit('backend_tcp_ready', 'error', {
|
|
177
|
+
duration_ms: Date.now() - startTime,
|
|
178
|
+
attempt,
|
|
179
|
+
exact: false,
|
|
180
|
+
precision_ms: 50,
|
|
181
|
+
error: error instanceof Error ? error.message : String(error),
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
145
185
|
|
|
146
186
|
// Pipe stdout and stderr through readline for timestamped logging
|
|
147
187
|
const pipeLines = (stream) => {
|
|
@@ -264,15 +304,30 @@ function cleanStaleDist() {
|
|
|
264
304
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
265
305
|
async function main() {
|
|
266
306
|
logEvent('INFO', 'main', '========== Dev session started ==========');
|
|
307
|
+
previewPhaseReporter.emit('dev_orchestrator_start', 'success', { exact: true });
|
|
267
308
|
|
|
268
309
|
cleanStaleDist();
|
|
269
310
|
|
|
270
311
|
// Initialize action plugins
|
|
271
312
|
writeOutput('\n🔌 Initializing action plugins...\n');
|
|
313
|
+
const actionPluginStartedAt = Date.now();
|
|
314
|
+
previewPhaseReporter.emit('action_plugin_init', 'start', {
|
|
315
|
+
at_ms: actionPluginStartedAt,
|
|
316
|
+
exact: true,
|
|
317
|
+
});
|
|
272
318
|
try {
|
|
273
319
|
execSync('fullstack-cli action-plugin init', { cwd: PROJECT_ROOT, stdio: 'inherit' });
|
|
320
|
+
previewPhaseReporter.emit('action_plugin_init', 'success', {
|
|
321
|
+
duration_ms: Date.now() - actionPluginStartedAt,
|
|
322
|
+
exact: true,
|
|
323
|
+
});
|
|
274
324
|
writeOutput('✅ Action plugins initialized\n\n');
|
|
275
|
-
} catch {
|
|
325
|
+
} catch (error) {
|
|
326
|
+
previewPhaseReporter.emit('action_plugin_init', 'error', {
|
|
327
|
+
duration_ms: Date.now() - actionPluginStartedAt,
|
|
328
|
+
exact: true,
|
|
329
|
+
error: error instanceof Error ? error.message : String(error),
|
|
330
|
+
});
|
|
276
331
|
writeOutput('⚠️ Action plugin initialization failed, continuing anyway...\n\n');
|
|
277
332
|
}
|
|
278
333
|
|
package/templates/scripts/dev.sh
CHANGED
|
@@ -1,24 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
|
|
3
|
-
# - SANDBOX_ID 非空(沙箱平台注入应用所属沙箱 ID)→ 直接跑 dev.js
|
|
4
|
-
# (保活 / restart loop / 文件日志 —— 沙箱生产形态)。脚本同步由平台 pod 启动阶段做过,
|
|
5
|
-
# dev 入口不再额外 `npm run upgrade`。
|
|
6
|
-
# - 否则(本地)→ 走 miaoda app sync 兜底 + 跑 dev-local.js:纯 stdout、崩了就崩、Agent 友好。
|
|
7
|
-
# 显式想跑本地路径可用 `npm run dev:local`(绕过 SANDBOX_ID 判断)。
|
|
8
|
-
set -euo pipefail
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
-
|
|
11
|
-
if [ -n "${SANDBOX_ID:-}" ]; then
|
|
12
|
-
exec node "$SCRIPT_DIR/dev.js" "$@"
|
|
13
|
-
fi
|
|
14
|
-
|
|
15
|
-
if [ ! -f "$SCRIPT_DIR/dev-local.js" ]; then
|
|
16
|
-
echo "[dev] scripts/dev-local.js 缺失;先跑 \`npx -y @lark-apaas/miaoda-cli@latest app sync\` 同步平台脚本" >&2
|
|
17
|
-
exit 1
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
# 本地启动前先跑一次 miaoda app sync:同步 platform-controlled 内容 + 升 @lark-apaas/* 到
|
|
21
|
-
# latest + 迁移老 npm scripts。沙箱不走这里(SANDBOX_ID 分支已经 exec return)。
|
|
22
|
-
npx -y @lark-apaas/miaoda-cli@latest app sync || echo "[dev] miaoda app sync 失败,按现状继续" >&2
|
|
23
|
-
|
|
24
|
-
exec node "$SCRIPT_DIR/dev-local.js" "$@"
|
|
2
|
+
exec node "$(dirname "$0")/dev.js" "$@"
|
|
@@ -23,19 +23,6 @@ function runCommand(command, args) {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// 串行依次执行每个任务,全部跑完后再聚合退出码:
|
|
27
|
-
// 降低并发资源占用、让各任务输出按顺序清晰可读,同时保留“一次暴露所有 lint 问题”的行为。
|
|
28
|
-
async function runTasksSerially(taskSpecs) {
|
|
29
|
-
let exitCode = 0;
|
|
30
|
-
for (const [command, args] of taskSpecs) {
|
|
31
|
-
const code = await runCommand(command, args);
|
|
32
|
-
if (code !== 0) {
|
|
33
|
-
exitCode = 1;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return exitCode;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
26
|
function normalizeProjectFile(filePath) {
|
|
40
27
|
const absolutePath = path.isAbsolute(filePath)
|
|
41
28
|
? filePath
|
|
@@ -77,13 +64,13 @@ function isStylelintTarget(filePath) {
|
|
|
77
64
|
}
|
|
78
65
|
|
|
79
66
|
async function runDefaultLint() {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
process.exit(
|
|
67
|
+
const code = await runCommand(getBinName('npx'), [
|
|
68
|
+
'concurrently',
|
|
69
|
+
'npm run eslint',
|
|
70
|
+
'npm run type:check',
|
|
71
|
+
'npm run stylelint',
|
|
72
|
+
]);
|
|
73
|
+
process.exit(code);
|
|
87
74
|
}
|
|
88
75
|
|
|
89
76
|
async function runSelectiveLint(inputFiles) {
|
|
@@ -114,30 +101,31 @@ async function runSelectiveLint(inputFiles) {
|
|
|
114
101
|
}
|
|
115
102
|
}
|
|
116
103
|
|
|
117
|
-
const
|
|
104
|
+
const tasks = [];
|
|
118
105
|
|
|
119
106
|
if (eslintFiles.length > 0) {
|
|
120
|
-
|
|
107
|
+
tasks.push(runCommand(getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]));
|
|
121
108
|
}
|
|
122
109
|
|
|
123
110
|
if (stylelintFiles.length > 0) {
|
|
124
|
-
|
|
111
|
+
tasks.push(runCommand(getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]));
|
|
125
112
|
}
|
|
126
113
|
|
|
127
114
|
if (clientTypeFiles.length > 0) {
|
|
128
|
-
|
|
115
|
+
tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:client']));
|
|
129
116
|
}
|
|
130
117
|
|
|
131
118
|
if (serverTypeFiles.length > 0) {
|
|
132
|
-
|
|
119
|
+
tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:server']));
|
|
133
120
|
}
|
|
134
121
|
|
|
135
|
-
if (
|
|
122
|
+
if (tasks.length === 0) {
|
|
136
123
|
console.log('[lint] No supported files matched for lint');
|
|
137
124
|
process.exit(0);
|
|
138
125
|
}
|
|
139
126
|
|
|
140
|
-
|
|
127
|
+
const results = await Promise.all(tasks);
|
|
128
|
+
process.exit(results.some(code => code !== 0) ? 1 : 0);
|
|
141
129
|
}
|
|
142
130
|
|
|
143
131
|
async function main() {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const net = require('net');
|
|
4
|
+
|
|
5
|
+
const PREFIX = '[MiaodaPreviewPhase] ';
|
|
6
|
+
const RUN_ID_PATTERN = /^prv_[A-Za-z0-9_-]{1,128}$/;
|
|
7
|
+
|
|
8
|
+
function resolveAppId(env) {
|
|
9
|
+
const basePath = env.CLIENT_BASE_PATH || '';
|
|
10
|
+
const match = /^\/(?:app|af\/p)\/([^/]+)/.exec(basePath);
|
|
11
|
+
return match ? match[1] : env.MIAODA_APP_ID || '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function createPreviewPhaseReporter(options = {}) {
|
|
15
|
+
const env = options.env || process.env;
|
|
16
|
+
const write = options.write || (line => process.stdout.write(`${line}\n`));
|
|
17
|
+
const now = options.now || Date.now;
|
|
18
|
+
|
|
19
|
+
function emit(phase, status, detail = {}) {
|
|
20
|
+
const runId = env.MIAODA_PREVIEW_RUN_ID || '';
|
|
21
|
+
if (!RUN_ID_PATTERN.test(runId)) return false;
|
|
22
|
+
const atMs = detail.at_ms == null ? now() : detail.at_ms;
|
|
23
|
+
const event = {
|
|
24
|
+
...detail,
|
|
25
|
+
schema_version: 1,
|
|
26
|
+
run_id: runId,
|
|
27
|
+
app_id: resolveAppId(env),
|
|
28
|
+
sandbox_id: env.SANDBOX_ID || '',
|
|
29
|
+
phase,
|
|
30
|
+
status,
|
|
31
|
+
at_ms: atMs,
|
|
32
|
+
};
|
|
33
|
+
write(`${PREFIX}${JSON.stringify(event)}`);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { emit };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function connectTcpOnce({ host, port, timeout_ms }) {
|
|
41
|
+
return new Promise(resolve => {
|
|
42
|
+
const socket = net.createConnection({ host, port });
|
|
43
|
+
let settled = false;
|
|
44
|
+
const finish = ready => {
|
|
45
|
+
if (settled) return;
|
|
46
|
+
settled = true;
|
|
47
|
+
socket.destroy();
|
|
48
|
+
resolve(ready);
|
|
49
|
+
};
|
|
50
|
+
socket.setTimeout(timeout_ms, () => finish(false));
|
|
51
|
+
socket.once('connect', () => finish(true));
|
|
52
|
+
socket.once('error', () => finish(false));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function waitForTcpReady(options) {
|
|
57
|
+
const host = options.host || '127.0.0.1';
|
|
58
|
+
const port = Number(options.port);
|
|
59
|
+
const timeoutMs = options.timeout_ms == null ? 120000 : options.timeout_ms;
|
|
60
|
+
const intervalMs = options.interval_ms == null ? 50 : options.interval_ms;
|
|
61
|
+
const now = options.now || Date.now;
|
|
62
|
+
const sleep =
|
|
63
|
+
options.sleep || (ms => new Promise(resolve => setTimeout(resolve, ms)));
|
|
64
|
+
const connect = options.connect || connectTcpOnce;
|
|
65
|
+
const shouldContinue = options.should_continue || (() => true);
|
|
66
|
+
const startedAtMs =
|
|
67
|
+
options.started_at_ms == null ? now() : options.started_at_ms;
|
|
68
|
+
let attempts = 0;
|
|
69
|
+
|
|
70
|
+
while (now() - startedAtMs < timeoutMs) {
|
|
71
|
+
if (!shouldContinue()) {
|
|
72
|
+
const atMs = now();
|
|
73
|
+
return {
|
|
74
|
+
ready: false,
|
|
75
|
+
cancelled: true,
|
|
76
|
+
attempts,
|
|
77
|
+
at_ms: atMs,
|
|
78
|
+
duration_ms: atMs - startedAtMs,
|
|
79
|
+
precision_ms: intervalMs,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
attempts += 1;
|
|
83
|
+
const ready = await connect({
|
|
84
|
+
host,
|
|
85
|
+
port,
|
|
86
|
+
timeout_ms: Math.max(1, Math.min(intervalMs, timeoutMs)),
|
|
87
|
+
});
|
|
88
|
+
const atMs = now();
|
|
89
|
+
if (!shouldContinue()) {
|
|
90
|
+
return {
|
|
91
|
+
ready: false,
|
|
92
|
+
cancelled: true,
|
|
93
|
+
attempts,
|
|
94
|
+
at_ms: atMs,
|
|
95
|
+
duration_ms: atMs - startedAtMs,
|
|
96
|
+
precision_ms: intervalMs,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (ready) {
|
|
100
|
+
return {
|
|
101
|
+
ready: true,
|
|
102
|
+
attempts,
|
|
103
|
+
at_ms: atMs,
|
|
104
|
+
duration_ms: atMs - startedAtMs,
|
|
105
|
+
precision_ms: intervalMs,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const elapsed = now() - startedAtMs;
|
|
109
|
+
if (elapsed >= timeoutMs) break;
|
|
110
|
+
await sleep(Math.min(intervalMs, timeoutMs - elapsed));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const atMs = now();
|
|
114
|
+
return {
|
|
115
|
+
ready: false,
|
|
116
|
+
attempts,
|
|
117
|
+
at_ms: atMs,
|
|
118
|
+
duration_ms: atMs - startedAtMs,
|
|
119
|
+
precision_ms: intervalMs,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
createPreviewPhaseReporter,
|
|
125
|
+
waitForTcpReady,
|
|
126
|
+
};
|
|
@@ -1,113 +0,0 @@
|
|
|
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
|
-
});
|