@kithinji/pod 1.0.24 → 1.0.26
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/main.js +660 -461
- package/dist/main.js.map +4 -4
- package/dist/types/config/config.d.ts +2 -0
- package/dist/types/config/config.d.ts.map +1 -1
- package/dist/types/dev/server.d.ts.map +1 -1
- package/dist/types/html/index.d.ts +22 -0
- package/dist/types/html/index.d.ts.map +1 -0
- package/dist/types/plugins/generators/generate_controller.d.ts.map +1 -1
- package/dist/types/plugins/generators/generate_rpc.d.ts.map +1 -1
- package/dist/types/plugins/generators/utils.d.ts +68 -0
- package/dist/types/plugins/generators/utils.d.ts.map +1 -0
- package/dist/types/plugins/transformers/j2d.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -11,7 +11,8 @@ import { Command } from "commander";
|
|
|
11
11
|
// src/dev/server.ts
|
|
12
12
|
import * as esbuild2 from "esbuild";
|
|
13
13
|
import { spawn } from "child_process";
|
|
14
|
-
import * as
|
|
14
|
+
import * as fs6 from "fs/promises";
|
|
15
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
15
16
|
|
|
16
17
|
// src/config/config.ts
|
|
17
18
|
import * as path from "path";
|
|
@@ -926,72 +927,32 @@ async function expandMacros(source, filePath, projectRoot = process.cwd()) {
|
|
|
926
927
|
|
|
927
928
|
// src/plugins/generators/generate_controller.ts
|
|
928
929
|
import * as path4 from "path";
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
throw {
|
|
948
|
-
type: "parse",
|
|
949
|
-
message: `Failed to parse TypeScript file: ${error.message}`,
|
|
950
|
-
filePath,
|
|
951
|
-
details: error
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
function extractServiceInfo(ast, filePath) {
|
|
956
|
-
let serviceClass = null;
|
|
957
|
-
let hasInjectable = false;
|
|
958
|
-
const importMap = {};
|
|
959
|
-
try {
|
|
960
|
-
for (const item of ast.body) {
|
|
961
|
-
if (item.type === "ImportDeclaration") {
|
|
962
|
-
const decl = item;
|
|
963
|
-
const source = decl.source.value;
|
|
964
|
-
decl.specifiers.forEach((spec) => {
|
|
965
|
-
if (spec.type === "ImportSpecifier" || spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") {
|
|
966
|
-
importMap[spec.local.value] = source;
|
|
967
|
-
}
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
if (item.type === "ExportDeclaration" && item.declaration?.type === "ClassDeclaration") {
|
|
971
|
-
const classDecl = item.declaration;
|
|
972
|
-
if (hasInjectableDecorator(classDecl.decorators)) {
|
|
973
|
-
serviceClass = classDecl;
|
|
974
|
-
hasInjectable = true;
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
if (!serviceClass?.identifier) {
|
|
979
|
-
return null;
|
|
980
|
-
}
|
|
981
|
-
return {
|
|
982
|
-
className: serviceClass.identifier.value,
|
|
983
|
-
methods: extractMethods(serviceClass, filePath),
|
|
984
|
-
hasInjectable,
|
|
985
|
-
importMap
|
|
986
|
-
};
|
|
987
|
-
} catch (error) {
|
|
988
|
-
throw {
|
|
989
|
-
type: "parse",
|
|
990
|
-
message: `Failed to extract service info: ${error.message}`,
|
|
991
|
-
filePath,
|
|
992
|
-
details: error
|
|
993
|
-
};
|
|
930
|
+
|
|
931
|
+
// src/plugins/generators/utils.ts
|
|
932
|
+
import {
|
|
933
|
+
parseSync
|
|
934
|
+
} from "@swc/core";
|
|
935
|
+
var RETURN_TYPE_CONFIGS = [
|
|
936
|
+
{
|
|
937
|
+
typeName: "Observable",
|
|
938
|
+
isStreamable: true,
|
|
939
|
+
streamType: "Observable" /* Observable */,
|
|
940
|
+
decoratorName: "Sse",
|
|
941
|
+
isSubjectLike: false
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
typeName: "Promise",
|
|
945
|
+
isStreamable: false,
|
|
946
|
+
decoratorName: "Post",
|
|
947
|
+
isSubjectLike: false
|
|
994
948
|
}
|
|
949
|
+
];
|
|
950
|
+
function parseTypeScript(filePath, code) {
|
|
951
|
+
return parseSync(code, {
|
|
952
|
+
syntax: "typescript",
|
|
953
|
+
tsx: filePath.endsWith("x") || filePath.endsWith(".tsx"),
|
|
954
|
+
decorators: true
|
|
955
|
+
});
|
|
995
956
|
}
|
|
996
957
|
function hasInjectableDecorator(decorators) {
|
|
997
958
|
if (!decorators) return false;
|
|
@@ -1000,39 +961,6 @@ function hasInjectableDecorator(decorators) {
|
|
|
1000
961
|
return expr.type === "Identifier" && expr.value === "Injectable" || expr.type === "CallExpression" && expr.callee.type === "Identifier" && expr.callee.value === "Injectable";
|
|
1001
962
|
});
|
|
1002
963
|
}
|
|
1003
|
-
function extractMethods(classDecl, filePath) {
|
|
1004
|
-
const methods = [];
|
|
1005
|
-
const className = classDecl.identifier?.value || "UnknownClass";
|
|
1006
|
-
for (const member of classDecl.body) {
|
|
1007
|
-
if (!isPublicMethod(member)) continue;
|
|
1008
|
-
const method = member;
|
|
1009
|
-
const methodName = getMethodName(method);
|
|
1010
|
-
if (!methodName) continue;
|
|
1011
|
-
const returnTypeInfo = analyzeReturnType(method);
|
|
1012
|
-
if (!returnTypeInfo.isObservable && !method.function.async) {
|
|
1013
|
-
throw {
|
|
1014
|
-
type: "validation",
|
|
1015
|
-
message: `Method ${className}.${methodName} must be async or return an Observable`,
|
|
1016
|
-
filePath,
|
|
1017
|
-
details: { className, methodName }
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
const { paramSchemas, returnSchema } = extractSignature(
|
|
1021
|
-
method.function.decorators,
|
|
1022
|
-
method.function.params.length
|
|
1023
|
-
);
|
|
1024
|
-
methods.push({
|
|
1025
|
-
name: methodName,
|
|
1026
|
-
params: extractMethodParams(method.function.params),
|
|
1027
|
-
returnType: returnTypeInfo.type,
|
|
1028
|
-
isAsync: method.function.async,
|
|
1029
|
-
isObservable: returnTypeInfo.isObservable,
|
|
1030
|
-
paramSchemas,
|
|
1031
|
-
returnSchema
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
return methods;
|
|
1035
|
-
}
|
|
1036
964
|
function isPublicMethod(member) {
|
|
1037
965
|
return member.type === "ClassMethod" && (member.accessibility === "public" || !member.accessibility);
|
|
1038
966
|
}
|
|
@@ -1042,30 +970,107 @@ function getMethodName(method) {
|
|
|
1042
970
|
}
|
|
1043
971
|
return null;
|
|
1044
972
|
}
|
|
973
|
+
function extractImportMap(ast) {
|
|
974
|
+
const importMap = {};
|
|
975
|
+
for (const item of ast.body) {
|
|
976
|
+
if (item.type === "ImportDeclaration") {
|
|
977
|
+
const decl = item;
|
|
978
|
+
const source = decl.source.value;
|
|
979
|
+
decl.specifiers.forEach((spec) => {
|
|
980
|
+
if (spec.type === "ImportSpecifier" || spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") {
|
|
981
|
+
importMap[spec.local.value] = source;
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return importMap;
|
|
987
|
+
}
|
|
988
|
+
function findInjectableClass(ast) {
|
|
989
|
+
for (const item of ast.body) {
|
|
990
|
+
if (item.type === "ExportDeclaration" && item.declaration?.type === "ClassDeclaration") {
|
|
991
|
+
const classDecl = item.declaration;
|
|
992
|
+
if (hasInjectableDecorator(classDecl.decorators)) {
|
|
993
|
+
return classDecl;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
1045
999
|
function analyzeReturnType(method) {
|
|
1046
1000
|
const returnType = method.function.returnType?.typeAnnotation;
|
|
1047
1001
|
if (!returnType) {
|
|
1048
|
-
return { type: "any", isObservable: false };
|
|
1049
|
-
}
|
|
1050
|
-
if (returnType.type === "TsTypeReference" && returnType.typeName.type === "Identifier" && returnType.typeName.value === "Observable") {
|
|
1051
|
-
const innerType = returnType.typeParams?.params[0];
|
|
1052
1002
|
return {
|
|
1053
|
-
type:
|
|
1054
|
-
|
|
1003
|
+
type: "any",
|
|
1004
|
+
isStreamable: false,
|
|
1005
|
+
isSubjectLike: false
|
|
1055
1006
|
};
|
|
1056
1007
|
}
|
|
1057
|
-
if (returnType.type === "TsTypeReference" && returnType.typeName.type === "Identifier"
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1008
|
+
if (returnType.type === "TsTypeReference" && returnType.typeName.type === "Identifier") {
|
|
1009
|
+
const typeName = returnType.typeName.value;
|
|
1010
|
+
const config = RETURN_TYPE_CONFIGS.find((c) => c.typeName === typeName);
|
|
1011
|
+
if (config) {
|
|
1012
|
+
const innerType = returnType.typeParams?.params[0];
|
|
1013
|
+
return {
|
|
1014
|
+
type: innerType ? stringifyType(innerType) : "any",
|
|
1015
|
+
isStreamable: config.isStreamable,
|
|
1016
|
+
streamType: config.streamType,
|
|
1017
|
+
isSubjectLike: config.isSubjectLike
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1063
1020
|
}
|
|
1064
1021
|
return {
|
|
1065
1022
|
type: stringifyType(returnType),
|
|
1066
|
-
|
|
1023
|
+
isStreamable: false,
|
|
1024
|
+
isSubjectLike: false
|
|
1067
1025
|
};
|
|
1068
1026
|
}
|
|
1027
|
+
function stringifyType(node) {
|
|
1028
|
+
if (!node) return "any";
|
|
1029
|
+
switch (node.type) {
|
|
1030
|
+
case "TsKeywordType":
|
|
1031
|
+
return node.kind;
|
|
1032
|
+
case "TsTypeReference":
|
|
1033
|
+
if (node.typeName.type !== "Identifier") return "any";
|
|
1034
|
+
const base = node.typeName.value;
|
|
1035
|
+
const args = node.typeParams?.params ? `<${node.typeParams.params.map(stringifyType).join(", ")}>` : "";
|
|
1036
|
+
return base + args;
|
|
1037
|
+
case "TsArrayType":
|
|
1038
|
+
return `${stringifyType(node.elemType)}[]`;
|
|
1039
|
+
case "TsUnionType":
|
|
1040
|
+
return node.types.map(stringifyType).join(" | ");
|
|
1041
|
+
case "TsIntersectionType":
|
|
1042
|
+
return node.types.map(stringifyType).join(" & ");
|
|
1043
|
+
case "TsTypeLiteral":
|
|
1044
|
+
const props = node.members.map((member) => {
|
|
1045
|
+
if (member.type === "TsPropertySignature") {
|
|
1046
|
+
const key = member.key.type === "Identifier" ? member.key.value : "";
|
|
1047
|
+
const type = member.typeAnnotation ? stringifyType(member.typeAnnotation.typeAnnotation) : "any";
|
|
1048
|
+
return `${key}: ${type}`;
|
|
1049
|
+
}
|
|
1050
|
+
return "";
|
|
1051
|
+
}).filter(Boolean);
|
|
1052
|
+
return `{ ${props.join("; ")} }`;
|
|
1053
|
+
default:
|
|
1054
|
+
return "any";
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function extractMethodParams(params) {
|
|
1058
|
+
return params.map((p) => {
|
|
1059
|
+
const pat = p.pat;
|
|
1060
|
+
if (pat.type !== "Identifier") {
|
|
1061
|
+
return {
|
|
1062
|
+
name: "param",
|
|
1063
|
+
type: "any",
|
|
1064
|
+
decorators: []
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
name: pat.value,
|
|
1069
|
+
type: pat.typeAnnotation ? stringifyType(pat.typeAnnotation.typeAnnotation) : "any",
|
|
1070
|
+
decorators: []
|
|
1071
|
+
};
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1069
1074
|
function extractSignature(decorators, paramCount) {
|
|
1070
1075
|
if (!decorators) return { paramSchemas: [] };
|
|
1071
1076
|
for (const decorator of decorators) {
|
|
@@ -1103,42 +1108,50 @@ function stringifyExpression(expr) {
|
|
|
1103
1108
|
}
|
|
1104
1109
|
return "any";
|
|
1105
1110
|
}
|
|
1106
|
-
function
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1111
|
+
function extractMethods(classDecl, filePath, includeSignatures = true) {
|
|
1112
|
+
const methods = [];
|
|
1113
|
+
const className = classDecl.identifier?.value || "UnknownClass";
|
|
1114
|
+
for (const member of classDecl.body) {
|
|
1115
|
+
if (!isPublicMethod(member)) continue;
|
|
1116
|
+
const method = member;
|
|
1117
|
+
const methodName = getMethodName(method);
|
|
1118
|
+
if (!methodName) continue;
|
|
1119
|
+
const returnTypeInfo = analyzeReturnType(method);
|
|
1120
|
+
if (!returnTypeInfo.isStreamable && !method.function.async) {
|
|
1121
|
+
throw {
|
|
1122
|
+
type: "validation",
|
|
1123
|
+
message: `Method ${className}.${methodName} must be async or return a streamable type (${RETURN_TYPE_CONFIGS.filter(
|
|
1124
|
+
(c) => c.isStreamable
|
|
1125
|
+
).map((c) => c.typeName).join(", ")})`,
|
|
1126
|
+
filePath,
|
|
1127
|
+
details: { className, methodName }
|
|
1114
1128
|
};
|
|
1115
1129
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
const base = node.typeName.value;
|
|
1131
|
-
const args = node.typeParams?.params ? `<${node.typeParams.params.map(stringifyType).join(", ")}>` : "";
|
|
1132
|
-
return base + args;
|
|
1133
|
-
case "TsArrayType":
|
|
1134
|
-
return `${stringifyType(node.elemType)}[]`;
|
|
1135
|
-
case "TsUnionType":
|
|
1136
|
-
return node.types.map(stringifyType).join(" | ");
|
|
1137
|
-
case "TsIntersectionType":
|
|
1138
|
-
return node.types.map(stringifyType).join(" & ");
|
|
1139
|
-
default:
|
|
1140
|
-
return "any";
|
|
1130
|
+
const signatures = includeSignatures ? extractSignature(
|
|
1131
|
+
method.function.decorators,
|
|
1132
|
+
method.function.params.length
|
|
1133
|
+
) : { paramSchemas: [] };
|
|
1134
|
+
methods.push({
|
|
1135
|
+
name: methodName,
|
|
1136
|
+
params: extractMethodParams(method.function.params),
|
|
1137
|
+
returnType: returnTypeInfo.type,
|
|
1138
|
+
isAsync: method.function.async,
|
|
1139
|
+
isStreamable: returnTypeInfo.isStreamable,
|
|
1140
|
+
streamType: returnTypeInfo.streamType,
|
|
1141
|
+
paramSchemas: signatures.paramSchemas,
|
|
1142
|
+
returnSchema: signatures.returnSchema
|
|
1143
|
+
});
|
|
1141
1144
|
}
|
|
1145
|
+
return methods;
|
|
1146
|
+
}
|
|
1147
|
+
function serviceNameToPath(serviceName) {
|
|
1148
|
+
return serviceName.replace(/Service$/, "").replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1149
|
+
}
|
|
1150
|
+
function toInstanceName(className) {
|
|
1151
|
+
return className.charAt(0).toLowerCase() + className.slice(1);
|
|
1152
|
+
}
|
|
1153
|
+
function capitalize(str) {
|
|
1154
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1142
1155
|
}
|
|
1143
1156
|
function validateServiceInfo(serviceInfo, filePath) {
|
|
1144
1157
|
if (!serviceInfo.className) {
|
|
@@ -1154,13 +1167,58 @@ function validateServiceInfo(serviceInfo, filePath) {
|
|
|
1154
1167
|
);
|
|
1155
1168
|
}
|
|
1156
1169
|
serviceInfo.methods.forEach((method) => {
|
|
1157
|
-
if (method.params.length > 0 && method.paramSchemas
|
|
1170
|
+
if (method.params.length > 0 && method.paramSchemas?.length === 0) {
|
|
1158
1171
|
console.warn(
|
|
1159
1172
|
`Warning: Method ${serviceInfo.className}.${method.name} has parameters but no @Signature validation`
|
|
1160
1173
|
);
|
|
1161
1174
|
}
|
|
1162
1175
|
});
|
|
1163
1176
|
}
|
|
1177
|
+
function extractServiceInfo(ast, filePath, includeSignatures = true) {
|
|
1178
|
+
try {
|
|
1179
|
+
const serviceClass = findInjectableClass(ast);
|
|
1180
|
+
const importMap = extractImportMap(ast);
|
|
1181
|
+
if (!serviceClass?.identifier) {
|
|
1182
|
+
return null;
|
|
1183
|
+
}
|
|
1184
|
+
return {
|
|
1185
|
+
className: serviceClass.identifier.value,
|
|
1186
|
+
methods: extractMethods(serviceClass, filePath, includeSignatures),
|
|
1187
|
+
hasInjectable: true,
|
|
1188
|
+
importMap
|
|
1189
|
+
};
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
throw {
|
|
1192
|
+
type: "parse",
|
|
1193
|
+
message: `Failed to extract service info: ${error.message}`,
|
|
1194
|
+
filePath,
|
|
1195
|
+
details: error
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/plugins/generators/generate_controller.ts
|
|
1201
|
+
function generateController(filePath, code) {
|
|
1202
|
+
try {
|
|
1203
|
+
const ast = parseTypeScript(filePath, code);
|
|
1204
|
+
const serviceInfo = extractServiceInfo(ast, filePath, true);
|
|
1205
|
+
if (!serviceInfo || !serviceInfo.hasInjectable) {
|
|
1206
|
+
return null;
|
|
1207
|
+
}
|
|
1208
|
+
validateServiceInfo(serviceInfo, filePath);
|
|
1209
|
+
return generateControllerCode(serviceInfo, filePath);
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
if (error.type) {
|
|
1212
|
+
throw error;
|
|
1213
|
+
}
|
|
1214
|
+
throw {
|
|
1215
|
+
type: "parse",
|
|
1216
|
+
message: `Failed to parse TypeScript file: ${error.message}`,
|
|
1217
|
+
filePath,
|
|
1218
|
+
details: error
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1164
1222
|
function generateControllerCode(serviceInfo, filePath) {
|
|
1165
1223
|
const serviceName = serviceInfo.className;
|
|
1166
1224
|
const controllerName = serviceName.replace(/Service$/, "GenController");
|
|
@@ -1175,7 +1233,9 @@ function generateControllerCode(serviceInfo, filePath) {
|
|
|
1175
1233
|
providedIn: "root",
|
|
1176
1234
|
})
|
|
1177
1235
|
export class ${controllerName} {
|
|
1178
|
-
constructor(
|
|
1236
|
+
constructor(
|
|
1237
|
+
private readonly ${serviceInstance}: ${serviceName}
|
|
1238
|
+
) {}
|
|
1179
1239
|
|
|
1180
1240
|
${methods}
|
|
1181
1241
|
}`;
|
|
@@ -1184,12 +1244,6 @@ function getImportPath(filePath) {
|
|
|
1184
1244
|
const basename3 = path4.basename(filePath);
|
|
1185
1245
|
return `./${basename3.replace(/\.tsx?$/, "")}`;
|
|
1186
1246
|
}
|
|
1187
|
-
function serviceNameToPath(serviceName) {
|
|
1188
|
-
return serviceName.replace(/Service$/, "").replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1189
|
-
}
|
|
1190
|
-
function toInstanceName(className) {
|
|
1191
|
-
return className.charAt(0).toLowerCase() + className.slice(1);
|
|
1192
|
-
}
|
|
1193
1247
|
function generateImports(serviceInfo, serviceName, serviceImportPath) {
|
|
1194
1248
|
const importGroups = /* @__PURE__ */ new Map();
|
|
1195
1249
|
const registerIdentifier = (id) => {
|
|
@@ -1209,21 +1263,23 @@ function generateImports(serviceInfo, serviceName, serviceImportPath) {
|
|
|
1209
1263
|
});
|
|
1210
1264
|
});
|
|
1211
1265
|
const hasPost = serviceInfo.methods.some(
|
|
1212
|
-
(m) => !m.
|
|
1266
|
+
(m) => !m.isStreamable && m.params.length > 0
|
|
1213
1267
|
);
|
|
1214
1268
|
const hasGet = serviceInfo.methods.some(
|
|
1215
|
-
(m) => !m.
|
|
1269
|
+
(m) => !m.isStreamable && m.params.length === 0
|
|
1270
|
+
);
|
|
1271
|
+
const hasSse = serviceInfo.methods.some(
|
|
1272
|
+
(m) => m.isStreamable && m.streamType == "Observable" /* Observable */
|
|
1216
1273
|
);
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
(m) => m.isObservable && m.params.length > 0
|
|
1274
|
+
const hasStreamableWithParams = serviceInfo.methods.some(
|
|
1275
|
+
(m) => m.isStreamable && m.params.length > 0
|
|
1220
1276
|
);
|
|
1221
1277
|
const decorators = ["Controller"];
|
|
1222
1278
|
if (hasPost) decorators.push("Post");
|
|
1223
1279
|
if (hasGet) decorators.push("Get");
|
|
1224
1280
|
if (hasPost) decorators.push("Body");
|
|
1225
1281
|
if (hasSse) decorators.push("Sse");
|
|
1226
|
-
if (
|
|
1282
|
+
if (hasStreamableWithParams) decorators.push("Query");
|
|
1227
1283
|
let importStrings = `import { ${decorators.join(
|
|
1228
1284
|
", "
|
|
1229
1285
|
)} } from "@kithinji/orca";
|
|
@@ -1245,11 +1301,12 @@ function generateMethods(serviceInfo) {
|
|
|
1245
1301
|
function generateMethod(method, serviceName) {
|
|
1246
1302
|
const hasParams = method.params.length > 0;
|
|
1247
1303
|
const serviceInstance = toInstanceName(serviceName);
|
|
1248
|
-
if (method.
|
|
1304
|
+
if (method.isStreamable) {
|
|
1249
1305
|
const queryParams = hasParams ? method.params.map((p) => `@Query('${p.name}') ${p.name}: ${p.type}`).join(", ") : "";
|
|
1250
1306
|
const body2 = generateMethodBody(method, serviceInstance, false);
|
|
1307
|
+
const returnTypeName = method.streamType || "Observable";
|
|
1251
1308
|
return ` @Sse("${method.name}")
|
|
1252
|
-
${method.name}(${queryParams}):
|
|
1309
|
+
${method.name}(${queryParams}): ${returnTypeName}<${method.returnType}> {
|
|
1253
1310
|
${body2}
|
|
1254
1311
|
}`;
|
|
1255
1312
|
}
|
|
@@ -1264,14 +1321,14 @@ ${body}
|
|
|
1264
1321
|
function generateMethodBody(method, serviceInstance, isAsync) {
|
|
1265
1322
|
const lines = [];
|
|
1266
1323
|
const hasParams = method.params.length > 0;
|
|
1267
|
-
if (hasParams && method.
|
|
1324
|
+
if (hasParams && method.isStreamable && method.paramSchemas.length > 0) {
|
|
1268
1325
|
method.params.forEach((p, i) => {
|
|
1269
1326
|
lines.push(
|
|
1270
1327
|
` const validated${capitalize(p.name)} = ${method.paramSchemas[i]}.parse(${p.name});`
|
|
1271
1328
|
);
|
|
1272
1329
|
});
|
|
1273
1330
|
}
|
|
1274
|
-
if (hasParams && !method.
|
|
1331
|
+
if (hasParams && !method.isStreamable) {
|
|
1275
1332
|
if (method.paramSchemas.length > 0) {
|
|
1276
1333
|
lines.push(
|
|
1277
1334
|
` const b = typeof body === 'object' && body !== null ? body : {};`
|
|
@@ -1287,7 +1344,7 @@ function generateMethodBody(method, serviceInstance, isAsync) {
|
|
|
1287
1344
|
}
|
|
1288
1345
|
}
|
|
1289
1346
|
let callArgs;
|
|
1290
|
-
if (hasParams && method.
|
|
1347
|
+
if (hasParams && method.isStreamable && method.paramSchemas.length > 0) {
|
|
1291
1348
|
callArgs = method.params.map((p) => `validated${capitalize(p.name)}`).join(", ");
|
|
1292
1349
|
} else {
|
|
1293
1350
|
callArgs = method.params.map((p) => p.name).join(", ");
|
|
@@ -1303,9 +1360,6 @@ function generateMethodBody(method, serviceInstance, isAsync) {
|
|
|
1303
1360
|
}
|
|
1304
1361
|
return lines.join("\n");
|
|
1305
1362
|
}
|
|
1306
|
-
function capitalize(str) {
|
|
1307
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1308
|
-
}
|
|
1309
1363
|
|
|
1310
1364
|
// src/plugins/generators/tsx_server_stub.ts
|
|
1311
1365
|
import * as path5 from "path";
|
|
@@ -1551,7 +1605,7 @@ var NodeTypeGuards = class {
|
|
|
1551
1605
|
isSignalMember(expr) {
|
|
1552
1606
|
return this.t.isMemberExpression(expr) && this.t.isIdentifier(expr.property, { name: "value" });
|
|
1553
1607
|
}
|
|
1554
|
-
|
|
1608
|
+
isBehaviorSubjectMember(expr) {
|
|
1555
1609
|
return this.t.isMemberExpression(expr) && this.t.isIdentifier(expr.property, { name: "$value" });
|
|
1556
1610
|
}
|
|
1557
1611
|
};
|
|
@@ -1561,7 +1615,7 @@ var ASTUtilities = class {
|
|
|
1561
1615
|
this.guards = guards;
|
|
1562
1616
|
}
|
|
1563
1617
|
getObject(expr) {
|
|
1564
|
-
if (this.guards.isSignalMember(expr) || this.guards.
|
|
1618
|
+
if (this.guards.isSignalMember(expr) || this.guards.isBehaviorSubjectMember(expr)) {
|
|
1565
1619
|
return expr.object;
|
|
1566
1620
|
}
|
|
1567
1621
|
return expr;
|
|
@@ -1696,7 +1750,7 @@ var ObservableManager = class {
|
|
|
1696
1750
|
}
|
|
1697
1751
|
collectObservables(node, observables, astUtils) {
|
|
1698
1752
|
this.walkNode(node, (n) => {
|
|
1699
|
-
if (this.guards.
|
|
1753
|
+
if (this.guards.isBehaviorSubjectMember(n)) {
|
|
1700
1754
|
const observable = astUtils.replaceThisWithSelf(
|
|
1701
1755
|
n.object
|
|
1702
1756
|
);
|
|
@@ -1710,7 +1764,7 @@ var ObservableManager = class {
|
|
|
1710
1764
|
replaceObservablesWithSignals(node, observableSignals, astUtils) {
|
|
1711
1765
|
const cloned = this.t.cloneNode(node, true);
|
|
1712
1766
|
this.walkNode(cloned, (n) => {
|
|
1713
|
-
if (this.guards.
|
|
1767
|
+
if (this.guards.isBehaviorSubjectMember(n)) {
|
|
1714
1768
|
const observable = astUtils.replaceThisWithSelf(n.object);
|
|
1715
1769
|
const key = this.getObservableKey(observable);
|
|
1716
1770
|
const signalId = observableSignals.get(key);
|
|
@@ -1833,7 +1887,8 @@ var ElementTransformer = class {
|
|
|
1833
1887
|
elId,
|
|
1834
1888
|
statements,
|
|
1835
1889
|
scope,
|
|
1836
|
-
context2
|
|
1890
|
+
context2,
|
|
1891
|
+
tag
|
|
1837
1892
|
);
|
|
1838
1893
|
if (hasRef && refValue) {
|
|
1839
1894
|
statements.push(
|
|
@@ -1927,7 +1982,7 @@ var ElementTransformer = class {
|
|
|
1927
1982
|
context2.observables,
|
|
1928
1983
|
this.astUtils
|
|
1929
1984
|
);
|
|
1930
|
-
if (this.guards.isSignalMember(expr) || this.guards.
|
|
1985
|
+
if (this.guards.isSignalMember(expr) || this.guards.isBehaviorSubjectMember(expr)) {
|
|
1931
1986
|
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1932
1987
|
expr,
|
|
1933
1988
|
context2.observableSignals,
|
|
@@ -1968,11 +2023,13 @@ var ElementTransformer = class {
|
|
|
1968
2023
|
}
|
|
1969
2024
|
}
|
|
1970
2025
|
}
|
|
1971
|
-
processDOMAttributes(attributes, elId, statements, scope, context2) {
|
|
2026
|
+
processDOMAttributes(attributes, elId, statements, scope, context2, tag) {
|
|
1972
2027
|
let hasRef = false;
|
|
1973
2028
|
let refValue = null;
|
|
1974
2029
|
let hasDangerousHTML = false;
|
|
1975
2030
|
let dangerousHTMLValue = null;
|
|
2031
|
+
let hasClickHandler = false;
|
|
2032
|
+
let hrefValue = null;
|
|
1976
2033
|
for (const attr of attributes) {
|
|
1977
2034
|
if (this.t.isJSXSpreadAttribute(attr)) {
|
|
1978
2035
|
this.observableManager.collectObservables(
|
|
@@ -2037,17 +2094,59 @@ var ElementTransformer = class {
|
|
|
2037
2094
|
continue;
|
|
2038
2095
|
}
|
|
2039
2096
|
if (/^on[A-Z]/.test(key)) {
|
|
2097
|
+
if (key === "onClick") {
|
|
2098
|
+
hasClickHandler = true;
|
|
2099
|
+
}
|
|
2040
2100
|
this.processEventListener(key, attr, elId, statements, context2);
|
|
2041
2101
|
continue;
|
|
2042
2102
|
}
|
|
2103
|
+
if (key === "href" && this.t.isStringLiteral(attr.value)) {
|
|
2104
|
+
hrefValue = attr.value.value;
|
|
2105
|
+
}
|
|
2043
2106
|
if (key === "style" && this.t.isJSXExpressionContainer(attr.value)) {
|
|
2044
2107
|
this.processStyleAttribute(attr, elId, statements, scope, context2);
|
|
2045
2108
|
continue;
|
|
2046
2109
|
}
|
|
2047
2110
|
this.processRegularAttribute(key, attr, elId, statements, context2);
|
|
2048
2111
|
}
|
|
2112
|
+
if (tag === "a" && !hasClickHandler && hrefValue && this.isRelativeUrl(hrefValue)) {
|
|
2113
|
+
statements.push(
|
|
2114
|
+
this.t.expressionStatement(
|
|
2115
|
+
this.t.callExpression(
|
|
2116
|
+
this.t.memberExpression(
|
|
2117
|
+
elId,
|
|
2118
|
+
this.t.identifier("addEventListener")
|
|
2119
|
+
),
|
|
2120
|
+
[
|
|
2121
|
+
this.t.stringLiteral("click"),
|
|
2122
|
+
this.t.arrowFunctionExpression(
|
|
2123
|
+
[this.t.identifier("event")],
|
|
2124
|
+
this.t.callExpression(
|
|
2125
|
+
this.t.memberExpression(
|
|
2126
|
+
this.t.identifier("Orca"),
|
|
2127
|
+
this.t.identifier("navigate")
|
|
2128
|
+
),
|
|
2129
|
+
[this.t.identifier("event"), this.t.stringLiteral(hrefValue)]
|
|
2130
|
+
)
|
|
2131
|
+
)
|
|
2132
|
+
]
|
|
2133
|
+
)
|
|
2134
|
+
)
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2049
2137
|
return { hasRef, refValue, hasDangerousHTML, dangerousHTMLValue };
|
|
2050
2138
|
}
|
|
2139
|
+
isRelativeUrl(url) {
|
|
2140
|
+
try {
|
|
2141
|
+
new URL(url);
|
|
2142
|
+
return false;
|
|
2143
|
+
} catch {
|
|
2144
|
+
if (url.startsWith("#") || url.startsWith("mailto:") || url.startsWith("tel:")) {
|
|
2145
|
+
return false;
|
|
2146
|
+
}
|
|
2147
|
+
return true;
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2051
2150
|
processEventListener(key, attr, elId, statements, context2) {
|
|
2052
2151
|
const eventName = key.slice(2).toLowerCase();
|
|
2053
2152
|
let handler = this.t.nullLiteral();
|
|
@@ -2179,7 +2278,7 @@ var ElementTransformer = class {
|
|
|
2179
2278
|
insertedValue = this.astUtils.getObject(
|
|
2180
2279
|
expr
|
|
2181
2280
|
);
|
|
2182
|
-
} else if (this.guards.
|
|
2281
|
+
} else if (this.guards.isBehaviorSubjectMember(expr)) {
|
|
2183
2282
|
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
2184
2283
|
expr,
|
|
2185
2284
|
context2.observableSignals,
|
|
@@ -2698,224 +2797,55 @@ ${decoratorsStr}export class ${className} extends _OrcaComponent {
|
|
|
2698
2797
|
}
|
|
2699
2798
|
|
|
2700
2799
|
// src/plugins/generators/generate_rpc.ts
|
|
2701
|
-
import { parseSync as parseSync4 } from "@swc/core";
|
|
2702
2800
|
function generateRpcStub(filePath, code) {
|
|
2703
2801
|
try {
|
|
2704
|
-
const ast =
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
decorators: true
|
|
2708
|
-
});
|
|
2709
|
-
const serviceInfo = extractServiceInfo2(ast, filePath);
|
|
2710
|
-
validateServiceInfo2(serviceInfo, filePath);
|
|
2711
|
-
return generateStubCode2(serviceInfo);
|
|
2712
|
-
} catch (error) {
|
|
2713
|
-
if (error.type) {
|
|
2714
|
-
throw error;
|
|
2715
|
-
}
|
|
2716
|
-
throw {
|
|
2717
|
-
type: "parse",
|
|
2718
|
-
message: `Failed to parse TypeScript file: ${error.message}`,
|
|
2719
|
-
filePath,
|
|
2720
|
-
details: error
|
|
2721
|
-
};
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
function extractServiceInfo2(ast, filePath) {
|
|
2725
|
-
let serviceClass = null;
|
|
2726
|
-
try {
|
|
2727
|
-
for (const item of ast.body) {
|
|
2728
|
-
if (item.type === "ExportDeclaration" && item.declaration?.type === "ClassDeclaration") {
|
|
2729
|
-
const classDecl = item.declaration;
|
|
2730
|
-
if (hasInjectableDecorator2(classDecl.decorators)) {
|
|
2731
|
-
serviceClass = classDecl;
|
|
2732
|
-
break;
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
if (!serviceClass?.identifier) {
|
|
2802
|
+
const ast = parseTypeScript(filePath, code);
|
|
2803
|
+
const serviceInfo = extractServiceInfo(ast, filePath, false);
|
|
2804
|
+
if (!serviceInfo) {
|
|
2737
2805
|
throw {
|
|
2738
2806
|
type: "validation",
|
|
2739
2807
|
message: "No exported class with @Injectable decorator found",
|
|
2740
2808
|
filePath
|
|
2741
2809
|
};
|
|
2742
2810
|
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
return {
|
|
2746
|
-
className,
|
|
2747
|
-
methods
|
|
2748
|
-
};
|
|
2811
|
+
validateServiceInfo(serviceInfo, filePath);
|
|
2812
|
+
return generateStubCode2(serviceInfo);
|
|
2749
2813
|
} catch (error) {
|
|
2750
2814
|
if (error.type) {
|
|
2751
2815
|
throw error;
|
|
2752
2816
|
}
|
|
2753
2817
|
throw {
|
|
2754
2818
|
type: "parse",
|
|
2755
|
-
message: `Failed to
|
|
2819
|
+
message: `Failed to parse TypeScript file: ${error.message}`,
|
|
2756
2820
|
filePath,
|
|
2757
2821
|
details: error
|
|
2758
2822
|
};
|
|
2759
2823
|
}
|
|
2760
2824
|
}
|
|
2761
|
-
function hasInjectableDecorator2(decorators) {
|
|
2762
|
-
if (!decorators) return false;
|
|
2763
|
-
return decorators.some((decorator) => {
|
|
2764
|
-
const expr = decorator.expression;
|
|
2765
|
-
if (expr.type === "CallExpression") {
|
|
2766
|
-
if (expr.callee.type === "Identifier" && expr.callee.value === "Injectable") {
|
|
2767
|
-
return true;
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
if (expr.type === "Identifier" && expr.value === "Injectable") {
|
|
2771
|
-
return true;
|
|
2772
|
-
}
|
|
2773
|
-
return false;
|
|
2774
|
-
});
|
|
2775
|
-
}
|
|
2776
|
-
function extractMethods3(classDecl, className, filePath) {
|
|
2777
|
-
const methods = [];
|
|
2778
|
-
for (const member of classDecl.body) {
|
|
2779
|
-
if (!isPublicMethod2(member)) continue;
|
|
2780
|
-
const method = member;
|
|
2781
|
-
const methodName = getMethodName2(method);
|
|
2782
|
-
if (!methodName) continue;
|
|
2783
|
-
const returnTypeInfo = analyzeReturnType2(method);
|
|
2784
|
-
if (!returnTypeInfo.isObservable && !method.function.async) {
|
|
2785
|
-
throw {
|
|
2786
|
-
type: "validation",
|
|
2787
|
-
message: `Method ${className}.${methodName} must be async or return an Observable`,
|
|
2788
|
-
filePath,
|
|
2789
|
-
details: { className, methodName }
|
|
2790
|
-
};
|
|
2791
|
-
}
|
|
2792
|
-
const params = extractMethodParams3(method.function.params || []);
|
|
2793
|
-
methods.push({
|
|
2794
|
-
name: methodName,
|
|
2795
|
-
params,
|
|
2796
|
-
returnType: returnTypeInfo.type,
|
|
2797
|
-
isAsync: method.function.async,
|
|
2798
|
-
isObservable: returnTypeInfo.isObservable
|
|
2799
|
-
});
|
|
2800
|
-
}
|
|
2801
|
-
return methods;
|
|
2802
|
-
}
|
|
2803
|
-
function isPublicMethod2(member) {
|
|
2804
|
-
return member.type === "ClassMethod" && (member.accessibility === "public" || !member.accessibility);
|
|
2805
|
-
}
|
|
2806
|
-
function getMethodName2(method) {
|
|
2807
|
-
if (method.key.type === "Identifier") {
|
|
2808
|
-
return method.key.value;
|
|
2809
|
-
}
|
|
2810
|
-
return null;
|
|
2811
|
-
}
|
|
2812
|
-
function analyzeReturnType2(method) {
|
|
2813
|
-
const returnType = method.function.returnType?.typeAnnotation;
|
|
2814
|
-
if (!returnType) {
|
|
2815
|
-
return { type: "any", isObservable: false };
|
|
2816
|
-
}
|
|
2817
|
-
if (returnType.type === "TsTypeReference" && returnType.typeName.type === "Identifier" && returnType.typeName.value === "Observable") {
|
|
2818
|
-
const innerType = returnType.typeParams?.params[0];
|
|
2819
|
-
return {
|
|
2820
|
-
type: innerType ? stringifyType4(innerType) : "any",
|
|
2821
|
-
isObservable: true
|
|
2822
|
-
};
|
|
2823
|
-
}
|
|
2824
|
-
if (returnType.type === "TsTypeReference" && returnType.typeName.type === "Identifier" && returnType.typeName.value === "Promise") {
|
|
2825
|
-
const innerType = returnType.typeParams?.params[0];
|
|
2826
|
-
return {
|
|
2827
|
-
type: innerType ? stringifyType4(innerType) : "any",
|
|
2828
|
-
isObservable: false
|
|
2829
|
-
};
|
|
2830
|
-
}
|
|
2831
|
-
return {
|
|
2832
|
-
type: stringifyType4(returnType),
|
|
2833
|
-
isObservable: false
|
|
2834
|
-
};
|
|
2835
|
-
}
|
|
2836
|
-
function extractMethodParams3(params) {
|
|
2837
|
-
const result = [];
|
|
2838
|
-
for (const param of params) {
|
|
2839
|
-
const pat = param.pat;
|
|
2840
|
-
if (pat.type === "Identifier") {
|
|
2841
|
-
const name = pat.value;
|
|
2842
|
-
const type = pat.typeAnnotation?.typeAnnotation ? stringifyType4(pat.typeAnnotation.typeAnnotation) : "any";
|
|
2843
|
-
result.push({
|
|
2844
|
-
name,
|
|
2845
|
-
type
|
|
2846
|
-
});
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
return result;
|
|
2850
|
-
}
|
|
2851
|
-
function stringifyType4(typeNode) {
|
|
2852
|
-
if (!typeNode) return "any";
|
|
2853
|
-
switch (typeNode.type) {
|
|
2854
|
-
case "TsKeywordType":
|
|
2855
|
-
return typeNode.kind;
|
|
2856
|
-
case "TsTypeReference":
|
|
2857
|
-
if (typeNode.typeName.type === "Identifier") {
|
|
2858
|
-
const baseName = typeNode.typeName.value;
|
|
2859
|
-
if (typeNode.typeParams?.params.length) {
|
|
2860
|
-
const params = typeNode.typeParams.params.map(stringifyType4).join(", ");
|
|
2861
|
-
return `${baseName}<${params}>`;
|
|
2862
|
-
}
|
|
2863
|
-
return baseName;
|
|
2864
|
-
}
|
|
2865
|
-
return "any";
|
|
2866
|
-
case "TsArrayType":
|
|
2867
|
-
return `${stringifyType4(typeNode.elemType)}[]`;
|
|
2868
|
-
case "TsUnionType":
|
|
2869
|
-
return typeNode.types.map(stringifyType4).join(" | ");
|
|
2870
|
-
case "TsIntersectionType":
|
|
2871
|
-
return typeNode.types.map(stringifyType4).join(" & ");
|
|
2872
|
-
case "TsTypeLiteral":
|
|
2873
|
-
const props = typeNode.members.map((member) => {
|
|
2874
|
-
if (member.type === "TsPropertySignature") {
|
|
2875
|
-
const key = member.key.type === "Identifier" ? member.key.value : "";
|
|
2876
|
-
const type = member.typeAnnotation ? stringifyType4(member.typeAnnotation.typeAnnotation) : "any";
|
|
2877
|
-
return `${key}: ${type}`;
|
|
2878
|
-
}
|
|
2879
|
-
return "";
|
|
2880
|
-
}).filter(Boolean);
|
|
2881
|
-
return `{ ${props.join("; ")} }`;
|
|
2882
|
-
default:
|
|
2883
|
-
return "any";
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
function validateServiceInfo2(serviceInfo, filePath) {
|
|
2887
|
-
if (!serviceInfo.className) {
|
|
2888
|
-
throw {
|
|
2889
|
-
type: "validation",
|
|
2890
|
-
message: "Service class must have a valid name",
|
|
2891
|
-
filePath
|
|
2892
|
-
};
|
|
2893
|
-
}
|
|
2894
|
-
if (serviceInfo.methods.length === 0) {
|
|
2895
|
-
console.warn(
|
|
2896
|
-
`Warning: Service ${serviceInfo.className} has no public methods`
|
|
2897
|
-
);
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
2825
|
function generateStubCode2(serviceInfo) {
|
|
2901
2826
|
const className = serviceInfo.className;
|
|
2902
|
-
const basePath =
|
|
2903
|
-
const methods = serviceInfo.methods.map((method) => generateMethod2(method, basePath)).join("\n\n");
|
|
2904
|
-
const
|
|
2905
|
-
const
|
|
2906
|
-
|
|
2907
|
-
return `${observableImport}import { Injectable } from "@kithinji/orca";
|
|
2827
|
+
const basePath = serviceNameToPath(className);
|
|
2828
|
+
const methods = serviceInfo.methods.map((method) => generateMethod2(method, basePath, className)).join("\n\n");
|
|
2829
|
+
const hasStreamable = serviceInfo.methods.some((m) => m.isStreamable);
|
|
2830
|
+
const imports = generateImports2(hasStreamable);
|
|
2831
|
+
return `${imports}
|
|
2908
2832
|
|
|
2909
2833
|
@Injectable()
|
|
2910
2834
|
export class ${className} {
|
|
2911
2835
|
${methods}
|
|
2912
2836
|
}`;
|
|
2913
2837
|
}
|
|
2914
|
-
function
|
|
2915
|
-
|
|
2838
|
+
function generateImports2(hasStreamable) {
|
|
2839
|
+
let imports = `import { Injectable } from "@kithinji/orca";
|
|
2840
|
+
`;
|
|
2841
|
+
if (hasStreamable) {
|
|
2842
|
+
imports += `import { Observable } from "@kithinji/orca";
|
|
2843
|
+
`;
|
|
2844
|
+
}
|
|
2845
|
+
return imports;
|
|
2916
2846
|
}
|
|
2917
|
-
function generateMethod2(method, basePath) {
|
|
2918
|
-
if (method.
|
|
2847
|
+
function generateMethod2(method, basePath, serviceName) {
|
|
2848
|
+
if (method.isStreamable) {
|
|
2919
2849
|
return generateSseMethod(method, basePath);
|
|
2920
2850
|
}
|
|
2921
2851
|
const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
|
|
@@ -3177,7 +3107,7 @@ function useMyPlugin(options) {
|
|
|
3177
3107
|
}
|
|
3178
3108
|
|
|
3179
3109
|
// src/plugins/analyzers/graph.ts
|
|
3180
|
-
import { parseSync as
|
|
3110
|
+
import { parseSync as parseSync4 } from "@swc/core";
|
|
3181
3111
|
import * as fs3 from "fs";
|
|
3182
3112
|
import * as path7 from "path";
|
|
3183
3113
|
function resolveFilePath(fromFile, importPath) {
|
|
@@ -3325,7 +3255,7 @@ function buildGraph(entryPoints) {
|
|
|
3325
3255
|
const store = Store.getInstance();
|
|
3326
3256
|
const newCode = store.get(filePath);
|
|
3327
3257
|
const content = newCode ? newCode[0] : fs3.readFileSync(filePath, "utf-8");
|
|
3328
|
-
const ast =
|
|
3258
|
+
const ast = parseSync4(content, {
|
|
3329
3259
|
syntax: "typescript",
|
|
3330
3260
|
tsx: isTsx,
|
|
3331
3261
|
decorators: true
|
|
@@ -3393,30 +3323,247 @@ function stylePlugin(store) {
|
|
|
3393
3323
|
};
|
|
3394
3324
|
}
|
|
3395
3325
|
|
|
3326
|
+
// src/html/index.ts
|
|
3327
|
+
import * as fs5 from "fs/promises";
|
|
3328
|
+
var HtmlPreprocessor = class {
|
|
3329
|
+
constructor(options = {}) {
|
|
3330
|
+
this.options = options;
|
|
3331
|
+
}
|
|
3332
|
+
async processFile(inputPath, outputPath) {
|
|
3333
|
+
const html = await fs5.readFile(inputPath, "utf-8");
|
|
3334
|
+
const processed = await this.process(html);
|
|
3335
|
+
await fs5.writeFile(outputPath, processed, "utf-8");
|
|
3336
|
+
}
|
|
3337
|
+
async process(html) {
|
|
3338
|
+
let result = html;
|
|
3339
|
+
if (this.options.transformers) {
|
|
3340
|
+
for (const transformer of this.options.transformers) {
|
|
3341
|
+
result = await transformer(result);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
if (this.options.injectScripts && this.options.injectScripts.length > 0) {
|
|
3345
|
+
result = this.injectScripts(result, this.options.injectScripts);
|
|
3346
|
+
}
|
|
3347
|
+
if (this.options.injectStyles && this.options.injectStyles.length > 0) {
|
|
3348
|
+
result = this.injectStyles(result, this.options.injectStyles);
|
|
3349
|
+
}
|
|
3350
|
+
if (this.options.replaceVariables) {
|
|
3351
|
+
result = this.replaceVariables(result, this.options.replaceVariables);
|
|
3352
|
+
}
|
|
3353
|
+
if (this.options.minify) {
|
|
3354
|
+
result = this.minify(result);
|
|
3355
|
+
}
|
|
3356
|
+
return result;
|
|
3357
|
+
}
|
|
3358
|
+
injectScripts(html, scripts) {
|
|
3359
|
+
const scriptTags = scripts.map((src) => ` <script src="${src}"></script>`).join("\n");
|
|
3360
|
+
if (html.includes("</body>")) {
|
|
3361
|
+
return html.replace("</body>", `${scriptTags}
|
|
3362
|
+
</body>`);
|
|
3363
|
+
} else if (html.includes("</head>")) {
|
|
3364
|
+
return html.replace("</head>", `${scriptTags}
|
|
3365
|
+
</head>`);
|
|
3366
|
+
} else {
|
|
3367
|
+
return html + `
|
|
3368
|
+
${scriptTags}`;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
injectStyles(html, styles) {
|
|
3372
|
+
const styleTags = styles.map((href) => ` <link rel="stylesheet" href="${href}">`).join("\n");
|
|
3373
|
+
if (html.includes("</head>")) {
|
|
3374
|
+
return html.replace("</head>", `${styleTags}
|
|
3375
|
+
</head>`);
|
|
3376
|
+
} else if (html.includes("<head>")) {
|
|
3377
|
+
return html.replace("<head>", `<head>
|
|
3378
|
+
${styleTags}`);
|
|
3379
|
+
} else {
|
|
3380
|
+
return styleTags + "\n" + html;
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
replaceVariables(html, variables) {
|
|
3384
|
+
let result = html;
|
|
3385
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
3386
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
3387
|
+
result = result.replace(new RegExp(`\\$\\{${key}\\}`, "g"), value);
|
|
3388
|
+
}
|
|
3389
|
+
return result;
|
|
3390
|
+
}
|
|
3391
|
+
minify(html) {
|
|
3392
|
+
return html.replace(/<!--[\s\S]*?-->/g, "").replace(/\s+/g, " ").replace(/>\s+</g, "><").trim();
|
|
3393
|
+
}
|
|
3394
|
+
};
|
|
3395
|
+
function createHotReloadTransformer(port) {
|
|
3396
|
+
return (html) => {
|
|
3397
|
+
const hotReloadScript = `
|
|
3398
|
+
<script>
|
|
3399
|
+
(function() {
|
|
3400
|
+
let ws;
|
|
3401
|
+
let reconnectAttempts = 0;
|
|
3402
|
+
const maxReconnectAttempts = 10;
|
|
3403
|
+
|
|
3404
|
+
function connect() {
|
|
3405
|
+
ws = new WebSocket('ws://localhost:${port}');
|
|
3406
|
+
|
|
3407
|
+
ws.onopen = () => {
|
|
3408
|
+
reconnectAttempts = 0;
|
|
3409
|
+
};
|
|
3410
|
+
|
|
3411
|
+
ws.onmessage = (event) => {
|
|
3412
|
+
if (event.data === 'reload') {
|
|
3413
|
+
window.location.reload();
|
|
3414
|
+
}
|
|
3415
|
+
};
|
|
3416
|
+
|
|
3417
|
+
ws.onclose = () => {
|
|
3418
|
+
if (reconnectAttempts < maxReconnectAttempts) {
|
|
3419
|
+
reconnectAttempts++;
|
|
3420
|
+
console.log(\`\u{1F504} Reconnecting... (attempt \${reconnectAttempts}/\${maxReconnectAttempts})\`);
|
|
3421
|
+
setTimeout(connect, 1000 * reconnectAttempts);
|
|
3422
|
+
} else {
|
|
3423
|
+
console.log('\u274C Max reconnection attempts reached');
|
|
3424
|
+
}
|
|
3425
|
+
};
|
|
3426
|
+
|
|
3427
|
+
ws.onerror = (error) => {
|
|
3428
|
+
console.error('\u{1F525} Hot reload error:', error);
|
|
3429
|
+
};
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
connect();
|
|
3433
|
+
})();
|
|
3434
|
+
</script>`;
|
|
3435
|
+
if (html.includes("</body>")) {
|
|
3436
|
+
return html.replace("</body>", `${hotReloadScript}
|
|
3437
|
+
</body>`);
|
|
3438
|
+
}
|
|
3439
|
+
return html + hotReloadScript;
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3396
3443
|
// src/dev/server.ts
|
|
3397
|
-
|
|
3444
|
+
var virtualClientFiles = {
|
|
3445
|
+
"virtual:navigate": {
|
|
3446
|
+
output: "navigate",
|
|
3447
|
+
code: `
|
|
3448
|
+
export async function navigate(event, url) {
|
|
3449
|
+
event.preventDefault();
|
|
3450
|
+
|
|
3451
|
+
try {
|
|
3452
|
+
const { Navigate, getCurrentInjector } = await import("./src/client/client.js");
|
|
3453
|
+
const injector = getCurrentInjector();
|
|
3454
|
+
|
|
3455
|
+
if (injector) {
|
|
3456
|
+
const navigate = injector.resolve(Navigate);
|
|
3457
|
+
navigate.go(url);
|
|
3458
|
+
} else {
|
|
3459
|
+
window.location.href = url;
|
|
3460
|
+
}
|
|
3461
|
+
} catch (error) {
|
|
3462
|
+
console.error("Navigation error:", error);
|
|
3463
|
+
window.location.href = url;
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
`.trim()
|
|
3467
|
+
}
|
|
3468
|
+
};
|
|
3469
|
+
function createVirtualModulePlugin(virtualFiles) {
|
|
3470
|
+
return {
|
|
3471
|
+
name: "virtual-module",
|
|
3472
|
+
setup(build) {
|
|
3473
|
+
build.onResolve({ filter: /^virtual:/ }, (args) => {
|
|
3474
|
+
if (virtualFiles[args.path]) {
|
|
3475
|
+
return {
|
|
3476
|
+
path: args.path,
|
|
3477
|
+
namespace: "virtual"
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3481
|
+
build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => {
|
|
3482
|
+
const virtualFile = virtualFiles[args.path];
|
|
3483
|
+
if (virtualFile) {
|
|
3484
|
+
return {
|
|
3485
|
+
contents: virtualFile.code,
|
|
3486
|
+
loader: "js"
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
});
|
|
3490
|
+
}
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
var HotReloadManager = class {
|
|
3494
|
+
constructor(port = 3001) {
|
|
3495
|
+
this.wss = null;
|
|
3496
|
+
this.clients = /* @__PURE__ */ new Set();
|
|
3497
|
+
this.port = port;
|
|
3498
|
+
}
|
|
3499
|
+
start() {
|
|
3500
|
+
this.wss = new WebSocketServer({ port: this.port });
|
|
3501
|
+
this.wss.on("connection", (ws) => {
|
|
3502
|
+
this.clients.add(ws);
|
|
3503
|
+
ws.on("close", () => {
|
|
3504
|
+
this.clients.delete(ws);
|
|
3505
|
+
});
|
|
3506
|
+
ws.on("error", (error) => {
|
|
3507
|
+
console.error("WebSocket error:", error);
|
|
3508
|
+
this.clients.delete(ws);
|
|
3509
|
+
});
|
|
3510
|
+
});
|
|
3511
|
+
console.log(`Hot reload server listening on ws://localhost:${this.port}`);
|
|
3512
|
+
}
|
|
3513
|
+
reload() {
|
|
3514
|
+
const activeClients = Array.from(this.clients).filter(
|
|
3515
|
+
(client) => client.readyState === WebSocket.OPEN
|
|
3516
|
+
);
|
|
3517
|
+
if (activeClients.length === 0) {
|
|
3518
|
+
return;
|
|
3519
|
+
}
|
|
3520
|
+
activeClients.forEach((client) => {
|
|
3521
|
+
try {
|
|
3522
|
+
client.send("reload");
|
|
3523
|
+
} catch (error) {
|
|
3524
|
+
console.error("Failed to send reload signal:", error);
|
|
3525
|
+
this.clients.delete(client);
|
|
3526
|
+
}
|
|
3527
|
+
});
|
|
3528
|
+
}
|
|
3529
|
+
close() {
|
|
3530
|
+
if (this.wss) {
|
|
3531
|
+
this.clients.forEach((client) => client.close());
|
|
3532
|
+
this.wss.close();
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
};
|
|
3536
|
+
async function copyAndProcessHtml(hotReloadPort, preprocessorOptions) {
|
|
3398
3537
|
try {
|
|
3399
|
-
await
|
|
3400
|
-
|
|
3538
|
+
await fs6.mkdir("public", { recursive: true });
|
|
3539
|
+
const preprocessor = new HtmlPreprocessor({
|
|
3540
|
+
transformers: [createHotReloadTransformer(hotReloadPort)],
|
|
3541
|
+
injectScripts: ["./navigate.js"],
|
|
3542
|
+
...preprocessorOptions
|
|
3543
|
+
});
|
|
3544
|
+
await preprocessor.processFile(
|
|
3545
|
+
"./src/client/index.html",
|
|
3546
|
+
"./public/index.html"
|
|
3547
|
+
);
|
|
3401
3548
|
} catch (error) {
|
|
3402
|
-
console.error("
|
|
3549
|
+
console.error("Failed to copy and process index.html:", error);
|
|
3403
3550
|
throw error;
|
|
3404
3551
|
}
|
|
3405
3552
|
}
|
|
3406
3553
|
async function cleanDirectories() {
|
|
3407
3554
|
await Promise.all([
|
|
3408
|
-
|
|
3409
|
-
|
|
3555
|
+
fs6.rm("dist", { recursive: true, force: true }),
|
|
3556
|
+
fs6.rm("public", { recursive: true, force: true })
|
|
3410
3557
|
]);
|
|
3411
3558
|
}
|
|
3412
|
-
function createRestartServerPlugin(serverProcess, onServerBuildComplete) {
|
|
3559
|
+
function createRestartServerPlugin(serverProcess, onServerBuildComplete, hotReloadManager) {
|
|
3413
3560
|
return {
|
|
3414
3561
|
name: "restart-server",
|
|
3415
3562
|
setup(build) {
|
|
3416
3563
|
build.onEnd((result) => {
|
|
3417
3564
|
if (result.errors.length > 0) {
|
|
3418
3565
|
console.error(
|
|
3419
|
-
|
|
3566
|
+
`Server build failed with ${result.errors.length} error(s)`
|
|
3420
3567
|
);
|
|
3421
3568
|
return;
|
|
3422
3569
|
}
|
|
@@ -3427,8 +3574,11 @@ function createRestartServerPlugin(serverProcess, onServerBuildComplete) {
|
|
|
3427
3574
|
stdio: "inherit"
|
|
3428
3575
|
});
|
|
3429
3576
|
serverProcess.current.on("error", (err) => {
|
|
3430
|
-
console.error("
|
|
3577
|
+
console.error("Server process error:", err);
|
|
3431
3578
|
});
|
|
3579
|
+
setTimeout(() => {
|
|
3580
|
+
hotReloadManager.reload();
|
|
3581
|
+
}, 500);
|
|
3432
3582
|
onServerBuildComplete();
|
|
3433
3583
|
});
|
|
3434
3584
|
}
|
|
@@ -3438,15 +3588,48 @@ async function startDevServer() {
|
|
|
3438
3588
|
const store = Store.getInstance();
|
|
3439
3589
|
const userConfig = await loadConfig();
|
|
3440
3590
|
const config = mergeConfig(getDefaultConfig(), userConfig);
|
|
3591
|
+
const HOT_RELOAD_PORT = 3001;
|
|
3592
|
+
const hotReloadManager = new HotReloadManager(HOT_RELOAD_PORT);
|
|
3441
3593
|
await cleanDirectories();
|
|
3442
|
-
await
|
|
3594
|
+
await copyAndProcessHtml(HOT_RELOAD_PORT, config.htmlPreprocessor);
|
|
3595
|
+
hotReloadManager.start();
|
|
3443
3596
|
const entryPoints = ["src/main.ts"];
|
|
3444
3597
|
const clientFiles = /* @__PURE__ */ new Set(["src/client/client.tsx"]);
|
|
3445
3598
|
const serverProcessRef = { current: null };
|
|
3446
3599
|
let clientCtx = null;
|
|
3600
|
+
let virtualCtx = null;
|
|
3447
3601
|
let isShuttingDown = false;
|
|
3448
3602
|
let pendingClientFiles = /* @__PURE__ */ new Set();
|
|
3449
3603
|
let needsClientRebuild = false;
|
|
3604
|
+
async function buildVirtualFiles() {
|
|
3605
|
+
if (isShuttingDown) return;
|
|
3606
|
+
try {
|
|
3607
|
+
if (virtualCtx) {
|
|
3608
|
+
await virtualCtx.dispose();
|
|
3609
|
+
virtualCtx = null;
|
|
3610
|
+
}
|
|
3611
|
+
const virtualEntryPoints = {};
|
|
3612
|
+
Object.entries(virtualClientFiles).forEach(([key, value]) => {
|
|
3613
|
+
virtualEntryPoints[value.output] = key;
|
|
3614
|
+
});
|
|
3615
|
+
virtualCtx = await esbuild2.context({
|
|
3616
|
+
entryPoints: virtualEntryPoints,
|
|
3617
|
+
bundle: true,
|
|
3618
|
+
outdir: "public",
|
|
3619
|
+
platform: "browser",
|
|
3620
|
+
format: "iife",
|
|
3621
|
+
globalName: "Orca",
|
|
3622
|
+
sourcemap: config.build?.sourcemap ?? true,
|
|
3623
|
+
minify: config.build?.minify ?? false,
|
|
3624
|
+
plugins: [createVirtualModulePlugin(virtualClientFiles)],
|
|
3625
|
+
write: true
|
|
3626
|
+
});
|
|
3627
|
+
await virtualCtx.rebuild();
|
|
3628
|
+
} catch (error) {
|
|
3629
|
+
console.error("Failed to build virtual files:", error);
|
|
3630
|
+
throw error;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3450
3633
|
async function rebuildClient() {
|
|
3451
3634
|
if (isShuttingDown) return;
|
|
3452
3635
|
try {
|
|
@@ -3466,7 +3649,7 @@ async function startDevServer() {
|
|
|
3466
3649
|
format: "esm",
|
|
3467
3650
|
sourcemap: config.build?.sourcemap ?? true,
|
|
3468
3651
|
splitting: true,
|
|
3469
|
-
minify: config.build?.minify ??
|
|
3652
|
+
minify: config.build?.minify ?? false,
|
|
3470
3653
|
plugins: [
|
|
3471
3654
|
...config.plugins?.map((cb) => cb(store)) || [],
|
|
3472
3655
|
...config.client_plugins?.map((cb) => cb(store)) || [],
|
|
@@ -3482,8 +3665,11 @@ async function startDevServer() {
|
|
|
3482
3665
|
build.onEnd((result) => {
|
|
3483
3666
|
if (result.errors.length > 0) {
|
|
3484
3667
|
console.error(
|
|
3485
|
-
|
|
3668
|
+
`Client build failed with ${result.errors.length} error(s)`
|
|
3486
3669
|
);
|
|
3670
|
+
} else {
|
|
3671
|
+
console.log("Client build completed");
|
|
3672
|
+
hotReloadManager.reload();
|
|
3487
3673
|
}
|
|
3488
3674
|
});
|
|
3489
3675
|
}
|
|
@@ -3495,7 +3681,7 @@ async function startDevServer() {
|
|
|
3495
3681
|
pendingClientFiles.clear();
|
|
3496
3682
|
needsClientRebuild = false;
|
|
3497
3683
|
} catch (error) {
|
|
3498
|
-
console.error("
|
|
3684
|
+
console.error("Failed to rebuild client:", error);
|
|
3499
3685
|
throw error;
|
|
3500
3686
|
}
|
|
3501
3687
|
}
|
|
@@ -3526,13 +3712,18 @@ async function startDevServer() {
|
|
|
3526
3712
|
}
|
|
3527
3713
|
}
|
|
3528
3714
|
}),
|
|
3529
|
-
createRestartServerPlugin(
|
|
3715
|
+
createRestartServerPlugin(
|
|
3716
|
+
serverProcessRef,
|
|
3717
|
+
onServerBuildComplete,
|
|
3718
|
+
hotReloadManager
|
|
3719
|
+
)
|
|
3530
3720
|
],
|
|
3531
3721
|
write: true
|
|
3532
3722
|
});
|
|
3533
3723
|
async function shutdown() {
|
|
3534
3724
|
if (isShuttingDown) return;
|
|
3535
3725
|
isShuttingDown = true;
|
|
3726
|
+
console.log("\nShutting down dev server...");
|
|
3536
3727
|
try {
|
|
3537
3728
|
if (serverProcessRef.current) {
|
|
3538
3729
|
serverProcessRef.current.kill("SIGTERM");
|
|
@@ -3540,14 +3731,19 @@ async function startDevServer() {
|
|
|
3540
3731
|
}
|
|
3541
3732
|
await serverCtx.dispose();
|
|
3542
3733
|
if (clientCtx) await clientCtx.dispose();
|
|
3734
|
+
if (virtualCtx) await virtualCtx.dispose();
|
|
3735
|
+
hotReloadManager.close();
|
|
3736
|
+
console.log("Dev server shut down successfully");
|
|
3543
3737
|
process.exit(0);
|
|
3544
3738
|
} catch (error) {
|
|
3545
|
-
console.error("
|
|
3739
|
+
console.error("Error during shutdown:", error);
|
|
3546
3740
|
process.exit(1);
|
|
3547
3741
|
}
|
|
3548
3742
|
}
|
|
3549
3743
|
process.on("SIGINT", shutdown);
|
|
3550
3744
|
process.on("SIGTERM", shutdown);
|
|
3745
|
+
console.log("Starting dev server...");
|
|
3746
|
+
await buildVirtualFiles();
|
|
3551
3747
|
await serverCtx.watch();
|
|
3552
3748
|
}
|
|
3553
3749
|
|
|
@@ -3560,7 +3756,7 @@ __export(config_exports, {
|
|
|
3560
3756
|
});
|
|
3561
3757
|
|
|
3562
3758
|
// src/add/component/component.ts
|
|
3563
|
-
import * as
|
|
3759
|
+
import * as fs7 from "fs";
|
|
3564
3760
|
import * as path8 from "path";
|
|
3565
3761
|
import * as ts2 from "typescript";
|
|
3566
3762
|
var ComponentDefinition = class {
|
|
@@ -3737,30 +3933,30 @@ Processing dependencies for "${name}": [${component.dependencies.join(
|
|
|
3737
3933
|
);
|
|
3738
3934
|
const componentDir = path8.dirname(componentPath);
|
|
3739
3935
|
const appModulePath = path8.join(process.cwd(), "src/app/app.module.ts");
|
|
3740
|
-
if (!
|
|
3936
|
+
if (!fs7.existsSync(componentModulePath)) {
|
|
3741
3937
|
const moduleDir = path8.dirname(componentModulePath);
|
|
3742
|
-
if (!
|
|
3743
|
-
|
|
3938
|
+
if (!fs7.existsSync(moduleDir)) {
|
|
3939
|
+
fs7.mkdirSync(moduleDir, { recursive: true });
|
|
3744
3940
|
}
|
|
3745
|
-
|
|
3941
|
+
fs7.writeFileSync(componentModulePath, createModule(), "utf-8");
|
|
3746
3942
|
}
|
|
3747
|
-
if (!
|
|
3748
|
-
|
|
3943
|
+
if (!fs7.existsSync(componentDir)) {
|
|
3944
|
+
fs7.mkdirSync(componentDir, { recursive: true });
|
|
3749
3945
|
}
|
|
3750
|
-
if (!
|
|
3751
|
-
|
|
3946
|
+
if (!fs7.existsSync(componentPath)) {
|
|
3947
|
+
fs7.writeFileSync(componentPath, component.generate(), "utf-8");
|
|
3752
3948
|
console.log(`Created ${name}.component.tsx`);
|
|
3753
3949
|
} else {
|
|
3754
3950
|
console.log(`${name}.component.tsx already exists, skipping file creation`);
|
|
3755
3951
|
}
|
|
3756
|
-
const moduleContent =
|
|
3952
|
+
const moduleContent = fs7.readFileSync(componentModulePath, "utf-8");
|
|
3757
3953
|
const updatedModule = updateModuleWithComponent(moduleContent, name);
|
|
3758
|
-
|
|
3759
|
-
if (
|
|
3760
|
-
const appModuleContent =
|
|
3954
|
+
fs7.writeFileSync(componentModulePath, updatedModule, "utf-8");
|
|
3955
|
+
if (fs7.existsSync(appModulePath)) {
|
|
3956
|
+
const appModuleContent = fs7.readFileSync(appModulePath, "utf-8");
|
|
3761
3957
|
const updatedAppModule = ensureComponentModuleImported(appModuleContent);
|
|
3762
3958
|
if (updatedAppModule !== appModuleContent) {
|
|
3763
|
-
|
|
3959
|
+
fs7.writeFileSync(appModulePath, updatedAppModule, "utf-8");
|
|
3764
3960
|
}
|
|
3765
3961
|
}
|
|
3766
3962
|
}
|
|
@@ -3946,12 +4142,12 @@ function toPascalCase(str) {
|
|
|
3946
4142
|
}
|
|
3947
4143
|
|
|
3948
4144
|
// src/utils/create.ts
|
|
3949
|
-
import * as
|
|
4145
|
+
import * as fs8 from "fs";
|
|
3950
4146
|
import * as path9 from "path";
|
|
3951
4147
|
function createStructure(basePath, entry) {
|
|
3952
|
-
|
|
4148
|
+
fs8.mkdirSync(basePath, { recursive: true });
|
|
3953
4149
|
entry.files?.forEach((file) => {
|
|
3954
|
-
|
|
4150
|
+
fs8.writeFileSync(path9.join(basePath, file.name), file.content);
|
|
3955
4151
|
});
|
|
3956
4152
|
entry.dirs?.forEach((dir) => {
|
|
3957
4153
|
const dirPath = path9.join(basePath, dir.name || "");
|
|
@@ -3964,7 +4160,7 @@ import path11 from "path";
|
|
|
3964
4160
|
|
|
3965
4161
|
// src/add/module/module.ts
|
|
3966
4162
|
import * as path10 from "path";
|
|
3967
|
-
import * as
|
|
4163
|
+
import * as fs9 from "fs";
|
|
3968
4164
|
import * as ts3 from "typescript";
|
|
3969
4165
|
function addFeature(name) {
|
|
3970
4166
|
const featureDir = path10.join(process.cwd(), "src", "features", name);
|
|
@@ -4027,8 +4223,8 @@ function updateFeaturesIndex(featureName) {
|
|
|
4027
4223
|
);
|
|
4028
4224
|
const moduleName = toPascalCase(featureName + "_Module");
|
|
4029
4225
|
const importPath = `./${featureName}/${featureName}.module`;
|
|
4030
|
-
if (
|
|
4031
|
-
let content =
|
|
4226
|
+
if (fs9.existsSync(featuresIndexPath)) {
|
|
4227
|
+
let content = fs9.readFileSync(featuresIndexPath, "utf-8");
|
|
4032
4228
|
const sourceFile = ts3.createSourceFile(
|
|
4033
4229
|
"index.ts",
|
|
4034
4230
|
content,
|
|
@@ -4054,24 +4250,24 @@ function updateFeaturesIndex(featureName) {
|
|
|
4054
4250
|
}
|
|
4055
4251
|
const exportStatement = `export { ${moduleName} } from "${importPath}";
|
|
4056
4252
|
`;
|
|
4057
|
-
|
|
4253
|
+
fs9.appendFileSync(featuresIndexPath, exportStatement);
|
|
4058
4254
|
} else {
|
|
4059
4255
|
const featuresDir = path10.dirname(featuresIndexPath);
|
|
4060
|
-
if (!
|
|
4061
|
-
|
|
4256
|
+
if (!fs9.existsSync(featuresDir)) {
|
|
4257
|
+
fs9.mkdirSync(featuresDir, { recursive: true });
|
|
4062
4258
|
}
|
|
4063
4259
|
const exportStatement = `export { ${moduleName} } from "${importPath}";
|
|
4064
4260
|
`;
|
|
4065
|
-
|
|
4261
|
+
fs9.writeFileSync(featuresIndexPath, exportStatement, "utf-8");
|
|
4066
4262
|
}
|
|
4067
4263
|
}
|
|
4068
4264
|
function updateAppModule(featureName) {
|
|
4069
4265
|
const appModulePath = path10.join(process.cwd(), "src", "app", "app.module.ts");
|
|
4070
|
-
if (!
|
|
4266
|
+
if (!fs9.existsSync(appModulePath)) {
|
|
4071
4267
|
return;
|
|
4072
4268
|
}
|
|
4073
4269
|
const moduleName = toPascalCase(featureName + "_Module");
|
|
4074
|
-
let content =
|
|
4270
|
+
let content = fs9.readFileSync(appModulePath, "utf-8");
|
|
4075
4271
|
const sourceFile = ts3.createSourceFile(
|
|
4076
4272
|
"app.module.ts",
|
|
4077
4273
|
content,
|
|
@@ -4095,7 +4291,7 @@ function updateAppModule(featureName) {
|
|
|
4095
4291
|
});
|
|
4096
4292
|
if (hasImport) {
|
|
4097
4293
|
content = addToModuleImportsArray(content, sourceFile, moduleName);
|
|
4098
|
-
|
|
4294
|
+
fs9.writeFileSync(appModulePath, content, "utf-8");
|
|
4099
4295
|
return;
|
|
4100
4296
|
}
|
|
4101
4297
|
let lastImportEnd = 0;
|
|
@@ -4114,7 +4310,7 @@ function updateAppModule(featureName) {
|
|
|
4114
4310
|
true
|
|
4115
4311
|
);
|
|
4116
4312
|
content = addToModuleImportsArray(content, newSourceFile, moduleName);
|
|
4117
|
-
|
|
4313
|
+
fs9.writeFileSync(appModulePath, content, "utf-8");
|
|
4118
4314
|
}
|
|
4119
4315
|
function addToModuleImportsArray(content, sourceFile, moduleName) {
|
|
4120
4316
|
let decoratorNode;
|
|
@@ -4388,24 +4584,19 @@ export class ${serviceName} {
|
|
|
4388
4584
|
}
|
|
4389
4585
|
function createPage(name) {
|
|
4390
4586
|
const pageName = toPascalCase(name + "_Page");
|
|
4391
|
-
const serviceName = toPascalCase(name + "_Service");
|
|
4392
|
-
const serviceVar = toCamelCase(name + "_Service");
|
|
4393
4587
|
const listComponent = toPascalCase(name + "_List");
|
|
4394
4588
|
return `import { Component } from "@kithinji/orca";
|
|
4395
|
-
import { ${serviceName} } from "./${name}.service";
|
|
4396
4589
|
import { ${listComponent} } from "./components/${name}-list.component";
|
|
4397
4590
|
|
|
4398
|
-
@Component(
|
|
4591
|
+
@Component({
|
|
4592
|
+
deps: [${listComponent}]
|
|
4593
|
+
})
|
|
4399
4594
|
export class ${pageName} {
|
|
4400
|
-
constructor(
|
|
4401
|
-
public ${serviceVar}: ${serviceName}
|
|
4402
|
-
) {}
|
|
4403
|
-
|
|
4404
4595
|
build() {
|
|
4405
4596
|
return (
|
|
4406
4597
|
<div>
|
|
4407
4598
|
<h1>${toPascalCase(name)} Management</h1>
|
|
4408
|
-
<${listComponent}
|
|
4599
|
+
<${listComponent} name="hello" />
|
|
4409
4600
|
</div>
|
|
4410
4601
|
);
|
|
4411
4602
|
}
|
|
@@ -4415,6 +4606,7 @@ export class ${pageName} {
|
|
|
4415
4606
|
function createListComponent(name) {
|
|
4416
4607
|
const componentName = toPascalCase(name + "_List");
|
|
4417
4608
|
const serviceName = toPascalCase(name + "_Service");
|
|
4609
|
+
const serviceVar = toCamelCase(name + "_Service");
|
|
4418
4610
|
return `"use interactive";
|
|
4419
4611
|
|
|
4420
4612
|
import { Component } from "@kithinji/orca";
|
|
@@ -4423,9 +4615,13 @@ import { ${serviceName} } from "../${name}.service";
|
|
|
4423
4615
|
@Component()
|
|
4424
4616
|
export class ${componentName} {
|
|
4425
4617
|
props!: {
|
|
4426
|
-
|
|
4618
|
+
name: string;
|
|
4427
4619
|
};
|
|
4428
4620
|
|
|
4621
|
+
constructor(
|
|
4622
|
+
public readonly ${serviceVar}: ${serviceName}
|
|
4623
|
+
) {}
|
|
4624
|
+
|
|
4429
4625
|
build() {
|
|
4430
4626
|
return (
|
|
4431
4627
|
<div>
|
|
@@ -4599,6 +4795,9 @@ export function bootstrap() {
|
|
|
4599
4795
|
}
|
|
4600
4796
|
|
|
4601
4797
|
bootstrap();
|
|
4798
|
+
|
|
4799
|
+
/* Don't modify */
|
|
4800
|
+
export { Navigate, getCurrentInjector } from "@kithinji/orca";
|
|
4602
4801
|
`;
|
|
4603
4802
|
}
|
|
4604
4803
|
function genMainTs() {
|
|
@@ -4621,17 +4820,17 @@ import path14 from "path";
|
|
|
4621
4820
|
import { execSync } from "child_process";
|
|
4622
4821
|
|
|
4623
4822
|
// src/docker/docker.ts
|
|
4624
|
-
import
|
|
4823
|
+
import fs10 from "fs-extra";
|
|
4625
4824
|
import path12 from "path";
|
|
4626
4825
|
import prompts from "prompts";
|
|
4627
4826
|
import yaml from "js-yaml";
|
|
4628
4827
|
async function dockerize(env = "prod") {
|
|
4629
4828
|
const cwd = process.cwd();
|
|
4630
4829
|
const packageJsonPath = path12.join(cwd, "package.json");
|
|
4631
|
-
if (!
|
|
4830
|
+
if (!fs10.existsSync(packageJsonPath)) {
|
|
4632
4831
|
throw new Error("package.json not found. Are you in a Pod project?");
|
|
4633
4832
|
}
|
|
4634
|
-
const packageJson = await
|
|
4833
|
+
const packageJson = await fs10.readJSON(packageJsonPath);
|
|
4635
4834
|
const projectName = packageJson.name;
|
|
4636
4835
|
const detectedServices = detectServices(packageJson);
|
|
4637
4836
|
const selectedServices = await selectServices(detectedServices);
|
|
@@ -4672,30 +4871,30 @@ async function selectServices(detected) {
|
|
|
4672
4871
|
}
|
|
4673
4872
|
async function restructureProject(cwd, projectName) {
|
|
4674
4873
|
const nestedDir = path12.join(cwd, projectName);
|
|
4675
|
-
if (
|
|
4874
|
+
if (fs10.existsSync(nestedDir)) {
|
|
4676
4875
|
console.log("\u26A0\uFE0F Project already restructured, skipping...");
|
|
4677
4876
|
return;
|
|
4678
4877
|
}
|
|
4679
|
-
await
|
|
4680
|
-
const items = await
|
|
4878
|
+
await fs10.ensureDir(nestedDir);
|
|
4879
|
+
const items = await fs10.readdir(cwd);
|
|
4681
4880
|
const toMove = items.filter((item) => item !== projectName);
|
|
4682
4881
|
for (const item of toMove) {
|
|
4683
4882
|
const src = path12.join(cwd, item);
|
|
4684
4883
|
const dest = path12.join(nestedDir, item);
|
|
4685
|
-
await
|
|
4884
|
+
await fs10.move(src, dest, { overwrite: true });
|
|
4686
4885
|
}
|
|
4687
4886
|
const envSrc = path12.join(nestedDir, ".env");
|
|
4688
4887
|
const envDest = path12.join(cwd, ".env");
|
|
4689
|
-
if (
|
|
4690
|
-
await
|
|
4888
|
+
if (fs10.existsSync(envSrc)) {
|
|
4889
|
+
await fs10.move(envSrc, envDest, { overwrite: true });
|
|
4691
4890
|
}
|
|
4692
4891
|
}
|
|
4693
4892
|
async function writeEnvVars(cwd, services, env) {
|
|
4694
4893
|
const envPath = path12.join(cwd, ".env");
|
|
4695
4894
|
let existingEnv = {};
|
|
4696
4895
|
let existingContent = "";
|
|
4697
|
-
if (
|
|
4698
|
-
existingContent = await
|
|
4896
|
+
if (fs10.existsSync(envPath)) {
|
|
4897
|
+
existingContent = await fs10.readFile(envPath, "utf8");
|
|
4699
4898
|
existingEnv = parseEnvFile(existingContent);
|
|
4700
4899
|
}
|
|
4701
4900
|
const newVars = [];
|
|
@@ -4724,7 +4923,7 @@ async function writeEnvVars(cwd, services, env) {
|
|
|
4724
4923
|
if (newVars.length > 0) {
|
|
4725
4924
|
const separator = existingContent && !existingContent.endsWith("\n") ? "\n" : "";
|
|
4726
4925
|
const newContent = existingContent + separator + (existingContent ? "\n" : "") + newVars.join("\n") + "\n";
|
|
4727
|
-
await
|
|
4926
|
+
await fs10.writeFile(envPath, newContent);
|
|
4728
4927
|
console.log(
|
|
4729
4928
|
`\u2705 Added ${newVars.length} new environment variable(s) to .env`
|
|
4730
4929
|
);
|
|
@@ -4820,8 +5019,8 @@ docker-compose*.yml
|
|
|
4820
5019
|
tmp
|
|
4821
5020
|
temp
|
|
4822
5021
|
`;
|
|
4823
|
-
await
|
|
4824
|
-
await
|
|
5022
|
+
await fs10.writeFile(dockerfilePath, dockerfile);
|
|
5023
|
+
await fs10.writeFile(dockerignorePath, dockerignore);
|
|
4825
5024
|
}
|
|
4826
5025
|
async function createDeployfile(cwd, projectName) {
|
|
4827
5026
|
const deployFile = `name: ${projectName}
|
|
@@ -4946,7 +5145,7 @@ targets:
|
|
|
4946
5145
|
docker image prune -af --filter "until=24h"
|
|
4947
5146
|
`;
|
|
4948
5147
|
const deployFilePath = path12.join(cwd, "pod.deploy.yml");
|
|
4949
|
-
await
|
|
5148
|
+
await fs10.writeFile(deployFilePath, deployFile);
|
|
4950
5149
|
}
|
|
4951
5150
|
async function setupProduction(cwd, projectName, services) {
|
|
4952
5151
|
const compose = {
|
|
@@ -5017,7 +5216,7 @@ async function setupProduction(cwd, projectName, services) {
|
|
|
5017
5216
|
compose.services[projectName].depends_on.push(service.name);
|
|
5018
5217
|
}
|
|
5019
5218
|
const composePath = path12.join(cwd, "docker-compose.yml");
|
|
5020
|
-
await
|
|
5219
|
+
await fs10.writeFile(
|
|
5021
5220
|
composePath,
|
|
5022
5221
|
yaml.dump(compose, { indent: 2, lineWidth: -1 })
|
|
5023
5222
|
);
|
|
@@ -5025,8 +5224,8 @@ async function setupProduction(cwd, projectName, services) {
|
|
|
5025
5224
|
async function setupDevelopment(cwd, projectName, services) {
|
|
5026
5225
|
const existingCompose = path12.join(cwd, "docker-compose.yml");
|
|
5027
5226
|
let existingServices = [];
|
|
5028
|
-
if (
|
|
5029
|
-
const content = await
|
|
5227
|
+
if (fs10.existsSync(existingCompose)) {
|
|
5228
|
+
const content = await fs10.readFile(existingCompose, "utf8");
|
|
5030
5229
|
const existing = yaml.load(content);
|
|
5031
5230
|
if (existing.services) {
|
|
5032
5231
|
existingServices = Object.keys(existing.services).filter((s) => ["postgres", "mysql", "redis", "mongodb"].includes(s)).map((name) => ({ name }));
|
|
@@ -5096,14 +5295,14 @@ async function setupDevelopment(cwd, projectName, services) {
|
|
|
5096
5295
|
compose.services[projectName].depends_on.push(tunnelName);
|
|
5097
5296
|
}
|
|
5098
5297
|
const devComposePath = path12.join(cwd, "docker-compose.dev.yml");
|
|
5099
|
-
await
|
|
5298
|
+
await fs10.writeFile(
|
|
5100
5299
|
devComposePath,
|
|
5101
5300
|
yaml.dump(compose, { indent: 2, lineWidth: -1 })
|
|
5102
5301
|
);
|
|
5103
5302
|
}
|
|
5104
5303
|
async function createTunnelService(projectDir, serviceName) {
|
|
5105
5304
|
const tunnelDir = path12.join(projectDir, `${serviceName}-tunnel`);
|
|
5106
|
-
await
|
|
5305
|
+
await fs10.ensureDir(tunnelDir);
|
|
5107
5306
|
const dockerfile = `FROM alpine:latest
|
|
5108
5307
|
|
|
5109
5308
|
RUN apk add --no-cache openssh-client
|
|
@@ -5131,8 +5330,8 @@ ssh -i $SSH_KEY \\
|
|
|
5131
5330
|
-o ServerAliveInterval=60 \\
|
|
5132
5331
|
$REMOTE_HOST
|
|
5133
5332
|
`;
|
|
5134
|
-
await
|
|
5135
|
-
await
|
|
5333
|
+
await fs10.writeFile(path12.join(tunnelDir, "Dockerfile"), dockerfile);
|
|
5334
|
+
await fs10.writeFile(path12.join(tunnelDir, "tunnel.sh"), tunnelScript);
|
|
5136
5335
|
}
|
|
5137
5336
|
function getServiceConfig(serviceName) {
|
|
5138
5337
|
const configs = {
|
|
@@ -5242,7 +5441,7 @@ function printNextSteps(projectName, env, services) {
|
|
|
5242
5441
|
}
|
|
5243
5442
|
|
|
5244
5443
|
// src/deploy/deploy.ts
|
|
5245
|
-
import
|
|
5444
|
+
import fs11 from "fs-extra";
|
|
5246
5445
|
import yaml2 from "js-yaml";
|
|
5247
5446
|
import path13 from "path";
|
|
5248
5447
|
import os from "os";
|
|
@@ -5499,12 +5698,12 @@ STDERR: ${result.stderr}`);
|
|
|
5499
5698
|
}
|
|
5500
5699
|
async uploadContent(remotePath, content) {
|
|
5501
5700
|
const localTmp = path13.join(os.tmpdir(), `pod_tmp_${Date.now()}`);
|
|
5502
|
-
|
|
5701
|
+
fs11.writeFileSync(localTmp, content);
|
|
5503
5702
|
try {
|
|
5504
5703
|
await this.ssh.execCommand(`mkdir -p $(dirname ${remotePath})`);
|
|
5505
5704
|
await this.ssh.putFile(localTmp, remotePath);
|
|
5506
5705
|
} finally {
|
|
5507
|
-
if (
|
|
5706
|
+
if (fs11.existsSync(localTmp)) fs11.unlinkSync(localTmp);
|
|
5508
5707
|
}
|
|
5509
5708
|
}
|
|
5510
5709
|
async readJson(remotePath) {
|
|
@@ -5578,25 +5777,25 @@ STDERR: ${err.stderr || err.message}`
|
|
|
5578
5777
|
os.tmpdir(),
|
|
5579
5778
|
`pod_script_${name}_${Date.now()}.sh`
|
|
5580
5779
|
);
|
|
5581
|
-
|
|
5582
|
-
|
|
5780
|
+
fs11.writeFileSync(scriptPath, interpolated);
|
|
5781
|
+
fs11.chmodSync(scriptPath, "755");
|
|
5583
5782
|
try {
|
|
5584
5783
|
await this.runCommand(scriptPath);
|
|
5585
5784
|
} finally {
|
|
5586
|
-
if (
|
|
5785
|
+
if (fs11.existsSync(scriptPath)) fs11.unlinkSync(scriptPath);
|
|
5587
5786
|
}
|
|
5588
5787
|
}
|
|
5589
5788
|
async uploadContent(localPath, content) {
|
|
5590
5789
|
const dir = path13.dirname(localPath);
|
|
5591
|
-
if (!
|
|
5592
|
-
|
|
5790
|
+
if (!fs11.existsSync(dir)) {
|
|
5791
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
5593
5792
|
}
|
|
5594
|
-
|
|
5793
|
+
fs11.writeFileSync(localPath, content);
|
|
5595
5794
|
}
|
|
5596
5795
|
async readJson(localPath) {
|
|
5597
5796
|
try {
|
|
5598
|
-
if (!
|
|
5599
|
-
const content =
|
|
5797
|
+
if (!fs11.existsSync(localPath)) return null;
|
|
5798
|
+
const content = fs11.readFileSync(localPath, "utf8");
|
|
5600
5799
|
return JSON.parse(content);
|
|
5601
5800
|
} catch {
|
|
5602
5801
|
return null;
|
|
@@ -5604,8 +5803,8 @@ STDERR: ${err.stderr || err.message}`
|
|
|
5604
5803
|
}
|
|
5605
5804
|
async syncDirectory(source, destination, exclude) {
|
|
5606
5805
|
console.log(chalk.gray(` Copying ${source} \u2192 ${destination}`));
|
|
5607
|
-
if (!
|
|
5608
|
-
|
|
5806
|
+
if (!fs11.existsSync(destination)) {
|
|
5807
|
+
fs11.mkdirSync(destination, { recursive: true });
|
|
5609
5808
|
}
|
|
5610
5809
|
const shouldExclude = (relativePath) => {
|
|
5611
5810
|
if (!exclude?.length) return false;
|
|
@@ -5622,19 +5821,19 @@ STDERR: ${err.stderr || err.message}`
|
|
|
5622
5821
|
});
|
|
5623
5822
|
};
|
|
5624
5823
|
const copyRecursive = (src, dest) => {
|
|
5625
|
-
const entries =
|
|
5824
|
+
const entries = fs11.readdirSync(src, { withFileTypes: true });
|
|
5626
5825
|
for (const entry of entries) {
|
|
5627
5826
|
const srcPath = path13.join(src, entry.name);
|
|
5628
5827
|
const destPath = path13.join(dest, entry.name);
|
|
5629
5828
|
const relativePath = path13.relative(source, srcPath);
|
|
5630
5829
|
if (shouldExclude(relativePath)) continue;
|
|
5631
5830
|
if (entry.isDirectory()) {
|
|
5632
|
-
if (!
|
|
5633
|
-
|
|
5831
|
+
if (!fs11.existsSync(destPath)) {
|
|
5832
|
+
fs11.mkdirSync(destPath, { recursive: true });
|
|
5634
5833
|
}
|
|
5635
5834
|
copyRecursive(srcPath, destPath);
|
|
5636
5835
|
} else {
|
|
5637
|
-
|
|
5836
|
+
fs11.copyFileSync(srcPath, destPath);
|
|
5638
5837
|
}
|
|
5639
5838
|
}
|
|
5640
5839
|
};
|
|
@@ -5797,7 +5996,7 @@ var OperationHandler = class {
|
|
|
5797
5996
|
async function deploy(targetName, options) {
|
|
5798
5997
|
const cwd = process.cwd();
|
|
5799
5998
|
const rawConfig = yaml2.load(
|
|
5800
|
-
|
|
5999
|
+
fs11.readFileSync(path13.join(cwd, "pod.deploy.yml"), "utf8"),
|
|
5801
6000
|
{ schema: yaml2.DEFAULT_SCHEMA }
|
|
5802
6001
|
);
|
|
5803
6002
|
const rawTarget = rawConfig.targets?.[targetName];
|
|
@@ -5859,7 +6058,7 @@ Deployment Failed: ${err.message}`));
|
|
|
5859
6058
|
// src/main.ts
|
|
5860
6059
|
import chalk2 from "chalk";
|
|
5861
6060
|
var program = new Command();
|
|
5862
|
-
program.name("pod").description("Pod cli tool").version("1.0.
|
|
6061
|
+
program.name("pod").description("Pod cli tool").version("1.0.26");
|
|
5863
6062
|
program.command("new <name>").description("Start a new Orca Project").action(async (name) => {
|
|
5864
6063
|
await addNew(name);
|
|
5865
6064
|
const appDir = path14.resolve(process.cwd());
|