@kithinji/pod 1.0.23 → 1.0.25

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 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 fs5 from "fs/promises";
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,20 +927,50 @@ 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
- import { parseSync } from "@swc/core";
930
- function generateController(filePath, code) {
931
- const ast = parseSync(code, {
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
948
+ }
949
+ ];
950
+ function parseTypeScript(filePath, code) {
951
+ return parseSync(code, {
932
952
  syntax: "typescript",
933
- tsx: filePath.endsWith("x"),
953
+ tsx: filePath.endsWith("x") || filePath.endsWith(".tsx"),
934
954
  decorators: true
935
955
  });
936
- const serviceInfo = extractServiceInfo(ast);
937
- if (!serviceInfo || !serviceInfo.hasInjectable) return null;
938
- return generateControllerCode(serviceInfo, filePath);
939
956
  }
940
- function extractServiceInfo(ast) {
941
- let serviceClass = null;
942
- let hasInjectable = false;
957
+ function hasInjectableDecorator(decorators) {
958
+ if (!decorators) return false;
959
+ return decorators.some((d) => {
960
+ const expr = d.expression;
961
+ return expr.type === "Identifier" && expr.value === "Injectable" || expr.type === "CallExpression" && expr.callee.type === "Identifier" && expr.callee.value === "Injectable";
962
+ });
963
+ }
964
+ function isPublicMethod(member) {
965
+ return member.type === "ClassMethod" && (member.accessibility === "public" || !member.accessibility);
966
+ }
967
+ function getMethodName(method) {
968
+ if (method.key.type === "Identifier") {
969
+ return method.key.value;
970
+ }
971
+ return null;
972
+ }
973
+ function extractImportMap(ast) {
943
974
  const importMap = {};
944
975
  for (const item of ast.body) {
945
976
  if (item.type === "ImportDeclaration") {
@@ -951,55 +982,94 @@ function extractServiceInfo(ast) {
951
982
  }
952
983
  });
953
984
  }
954
- if (item.type === "ExportDeclaration" && item.declaration.type === "ClassDeclaration") {
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") {
955
991
  const classDecl = item.declaration;
956
992
  if (hasInjectableDecorator(classDecl.decorators)) {
957
- serviceClass = classDecl;
958
- hasInjectable = true;
993
+ return classDecl;
959
994
  }
960
995
  }
961
996
  }
962
- if (!serviceClass || !serviceClass.identifier) return null;
997
+ return null;
998
+ }
999
+ function analyzeReturnType(method) {
1000
+ const returnType = method.function.returnType?.typeAnnotation;
1001
+ if (!returnType) {
1002
+ return {
1003
+ type: "any",
1004
+ isStreamable: false,
1005
+ isSubjectLike: false
1006
+ };
1007
+ }
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
+ }
1020
+ }
963
1021
  return {
964
- className: serviceClass.identifier.value,
965
- methods: extractMethods(serviceClass),
966
- hasInjectable,
967
- importMap
1022
+ type: stringifyType(returnType),
1023
+ isStreamable: false,
1024
+ isSubjectLike: false
968
1025
  };
969
1026
  }
970
- function hasInjectableDecorator(decorators) {
971
- return decorators?.some((d) => {
972
- const expr = d.expression;
973
- return expr.type === "Identifier" && expr.value === "Injectable" || expr.type === "CallExpression" && expr.callee.type === "Identifier" && expr.callee.value === "Injectable";
974
- }) ?? false;
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
+ }
975
1056
  }
976
- function extractMethods(classDecl) {
977
- const methods = [];
978
- for (const member of classDecl.body) {
979
- if (member.type === "ClassMethod" && member.accessibility === "public") {
980
- const method = member;
981
- const methodName = method.key.type === "Identifier" ? method.key.value : "";
982
- if (!methodName) continue;
983
- if (!method.function.async) {
984
- throw new Error(
985
- `Server action ${classDecl.identifier.value}.${methodName} must be async.`
986
- );
987
- }
988
- const { paramSchemas, returnSchema } = extractSignature(
989
- method.function.decorators,
990
- method.function.params.length
991
- );
992
- methods.push({
993
- name: methodName,
994
- params: extractMethodParams(method.function.params),
995
- returnType: extractReturnType(method.function.returnType),
996
- isAsync: true,
997
- paramSchemas,
998
- returnSchema
999
- });
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
+ };
1000
1066
  }
1001
- }
1002
- return methods;
1067
+ return {
1068
+ name: pat.value,
1069
+ type: pat.typeAnnotation ? stringifyType(pat.typeAnnotation.typeAnnotation) : "any",
1070
+ decorators: []
1071
+ };
1072
+ });
1003
1073
  }
1004
1074
  function extractSignature(decorators, paramCount) {
1005
1075
  if (!decorators) return { paramSchemas: [] };
@@ -1023,9 +1093,14 @@ function extractSignature(decorators, paramCount) {
1023
1093
  return { paramSchemas: [] };
1024
1094
  }
1025
1095
  function stringifyExpression(expr) {
1026
- if (expr.type === "Identifier") return expr.value;
1096
+ if (!expr) return "any";
1097
+ if (expr.type === "Identifier") {
1098
+ return expr.value;
1099
+ }
1027
1100
  if (expr.type === "MemberExpression") {
1028
- return `${stringifyExpression(expr.object)}.${expr.property.value || ""}`;
1101
+ const object = stringifyExpression(expr.object);
1102
+ const property = expr.property.value || stringifyExpression(expr.property);
1103
+ return `${object}.${property}`;
1029
1104
  }
1030
1105
  if (expr.type === "CallExpression") {
1031
1106
  const args = expr.arguments.map((a) => stringifyExpression(a.expression)).join(", ");
@@ -1033,107 +1108,257 @@ function stringifyExpression(expr) {
1033
1108
  }
1034
1109
  return "any";
1035
1110
  }
1036
- function extractMethodParams(params) {
1037
- return params.map((p) => {
1038
- const pat = p.pat;
1039
- return {
1040
- name: pat.value,
1041
- type: pat.typeAnnotation ? stringifyType(pat.typeAnnotation.typeAnnotation) : "any",
1042
- decorators: []
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 }
1128
+ };
1129
+ }
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
+ });
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);
1155
+ }
1156
+ function validateServiceInfo(serviceInfo, filePath) {
1157
+ if (!serviceInfo.className) {
1158
+ throw {
1159
+ type: "validation",
1160
+ message: "Service class must have a valid name",
1161
+ filePath
1043
1162
  };
1163
+ }
1164
+ if (serviceInfo.methods.length === 0) {
1165
+ console.warn(
1166
+ `Warning: Service ${serviceInfo.className} has no public methods`
1167
+ );
1168
+ }
1169
+ serviceInfo.methods.forEach((method) => {
1170
+ if (method.params.length > 0 && method.paramSchemas?.length === 0) {
1171
+ console.warn(
1172
+ `Warning: Method ${serviceInfo.className}.${method.name} has parameters but no @Signature validation`
1173
+ );
1174
+ }
1044
1175
  });
1045
1176
  }
1046
- function extractReturnType(node) {
1047
- if (!node?.typeAnnotation) return "any";
1048
- const type = node.typeAnnotation;
1049
- if (type.type === "TsTypeReference" && type.typeName.value === "Promise") {
1050
- return stringifyType(type.typeParams?.params[0]);
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
+ };
1051
1197
  }
1052
- return stringifyType(type);
1053
1198
  }
1054
- function stringifyType(node) {
1055
- if (!node) return "any";
1056
- switch (node.type) {
1057
- case "TsKeywordType":
1058
- return node.kind;
1059
- case "TsTypeReference":
1060
- const base = node.typeName.value;
1061
- const args = node.typeParams ? `<${node.typeParams.params.map(stringifyType).join(", ")}>` : "";
1062
- return base + args;
1063
- case "TsArrayType":
1064
- return `${stringifyType(node.elemType)}[]`;
1065
- default:
1066
- return "any";
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
+ };
1067
1220
  }
1068
1221
  }
1069
1222
  function generateControllerCode(serviceInfo, filePath) {
1070
1223
  const serviceName = serviceInfo.className;
1071
- const controllerName = serviceName.replace(/Service$/, "AutoController");
1072
- const serviceImportPath = `./${path4.basename(filePath).replace(/\.ts$/, "")}`;
1224
+ const controllerName = serviceName.replace(/Service$/, "GenController");
1225
+ const serviceImportPath = getImportPath(filePath);
1226
+ const controllerPath = serviceNameToPath(serviceName);
1227
+ const imports = generateImports(serviceInfo, serviceName, serviceImportPath);
1228
+ const methods = generateMethods(serviceInfo);
1229
+ const serviceInstance = toInstanceName(serviceName);
1230
+ return `${imports}
1231
+
1232
+ @Controller("/${controllerPath}", {
1233
+ providedIn: "root",
1234
+ })
1235
+ export class ${controllerName} {
1236
+ constructor(
1237
+ private readonly ${serviceInstance}: ${serviceName}
1238
+ ) {}
1239
+
1240
+ ${methods}
1241
+ }`;
1242
+ }
1243
+ function getImportPath(filePath) {
1244
+ const basename3 = path4.basename(filePath);
1245
+ return `./${basename3.replace(/\.tsx?$/, "")}`;
1246
+ }
1247
+ function generateImports(serviceInfo, serviceName, serviceImportPath) {
1073
1248
  const importGroups = /* @__PURE__ */ new Map();
1074
1249
  const registerIdentifier = (id) => {
1075
1250
  const source = serviceInfo.importMap[id] || serviceImportPath;
1076
- if (!importGroups.has(source)) importGroups.set(source, /* @__PURE__ */ new Set());
1251
+ if (!importGroups.has(source)) {
1252
+ importGroups.set(source, /* @__PURE__ */ new Set());
1253
+ }
1077
1254
  importGroups.get(source).add(id);
1078
1255
  };
1079
1256
  serviceInfo.methods.forEach((m) => {
1080
1257
  [...m.paramSchemas, m.returnSchema].filter(Boolean).forEach((s) => {
1081
1258
  const matches = s.match(/[A-Z][a-zA-Z0-9]*/g);
1082
1259
  matches?.forEach(registerIdentifier);
1083
- if (s.includes("z.")) registerIdentifier("z");
1260
+ if (s.includes("z.")) {
1261
+ registerIdentifier("z");
1262
+ }
1084
1263
  });
1085
1264
  });
1086
- let importStrings = `import { Controller, Post, Get, Body } from "@kithinji/orca";
1265
+ const hasPost = serviceInfo.methods.some(
1266
+ (m) => !m.isStreamable && m.params.length > 0
1267
+ );
1268
+ const hasGet = serviceInfo.methods.some(
1269
+ (m) => !m.isStreamable && m.params.length === 0
1270
+ );
1271
+ const hasSse = serviceInfo.methods.some(
1272
+ (m) => m.isStreamable && m.streamType == "Observable" /* Observable */
1273
+ );
1274
+ const hasStreamableWithParams = serviceInfo.methods.some(
1275
+ (m) => m.isStreamable && m.params.length > 0
1276
+ );
1277
+ const decorators = ["Controller"];
1278
+ if (hasPost) decorators.push("Post");
1279
+ if (hasGet) decorators.push("Get");
1280
+ if (hasPost) decorators.push("Body");
1281
+ if (hasSse) decorators.push("Sse");
1282
+ if (hasStreamableWithParams) decorators.push("Query");
1283
+ let importStrings = `import { ${decorators.join(
1284
+ ", "
1285
+ )} } from "@kithinji/orca";
1087
1286
  `;
1088
1287
  importGroups.forEach((ids, source) => {
1089
1288
  const filteredIds = Array.from(ids).filter((id) => id !== serviceName);
1090
1289
  if (filteredIds.length > 0) {
1091
- importStrings += `
1092
- import { ${filteredIds.join(
1290
+ importStrings += `import { ${filteredIds.join(
1093
1291
  ", "
1094
- )} } from "${source}";`;
1095
- }
1096
- });
1097
- const methods = serviceInfo.methods.map((m) => {
1098
- const hasParams = m.params.length > 0;
1099
- const bodyParam = hasParams ? `@Body() body: any` : "";
1100
- let body = "";
1101
- if (hasParams) {
1102
- if (m.paramSchemas.length > 0) {
1103
- body += ` const b = typeof body === 'object' && body !== null ? body : {};
1104
- `;
1105
- m.params.forEach((p, i) => {
1106
- body += ` const ${p.name} = ${m.paramSchemas[i]}.parse(b.${p.name});
1292
+ )} } from "${source}";
1107
1293
  `;
1108
- });
1109
- } else {
1110
- body += ` const { ${m.params.map((p) => p.name).join(", ")} } = body;
1111
- `;
1112
- }
1113
1294
  }
1114
- const callArgs = m.params.map((p) => p.name).join(", ");
1115
- const serviceCall = `this.${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}.${m.name}(${callArgs})`;
1116
- if (m.returnSchema) {
1117
- body += ` const res = await ${serviceCall};
1118
- return ${m.returnSchema}.parse(res);`;
1119
- } else {
1120
- body += ` return ${serviceCall};`;
1121
- }
1122
- return ` @${hasParams ? "Post" : "Get"}("${m.name}")
1123
- async ${m.name}(${bodyParam}): Promise<${m.returnType}> {
1295
+ });
1296
+ return importStrings;
1297
+ }
1298
+ function generateMethods(serviceInfo) {
1299
+ return serviceInfo.methods.map((m) => generateMethod(m, serviceInfo.className)).join("\n\n");
1300
+ }
1301
+ function generateMethod(method, serviceName) {
1302
+ const hasParams = method.params.length > 0;
1303
+ const serviceInstance = toInstanceName(serviceName);
1304
+ if (method.isStreamable) {
1305
+ const queryParams = hasParams ? method.params.map((p) => `@Query('${p.name}') ${p.name}: ${p.type}`).join(", ") : "";
1306
+ const body2 = generateMethodBody(method, serviceInstance, false);
1307
+ const returnTypeName = method.streamType || "Observable";
1308
+ return ` @Sse("${method.name}")
1309
+ ${method.name}(${queryParams}): ${returnTypeName}<${method.returnType}> {
1310
+ ${body2}
1311
+ }`;
1312
+ }
1313
+ const decorator = hasParams ? "Post" : "Get";
1314
+ const bodyParam = hasParams ? `@Body() body: any` : "";
1315
+ const body = generateMethodBody(method, serviceInstance, true);
1316
+ return ` @${decorator}("${method.name}")
1317
+ async ${method.name}(${bodyParam}): Promise<${method.returnType}> {
1124
1318
  ${body}
1125
1319
  }`;
1126
- }).join("\n\n");
1127
- return `${importStrings}
1128
-
1129
- @Controller("/${serviceName}", {
1130
- providedIn: "root",
1131
- })
1132
- export class ${controllerName} {
1133
- constructor(private readonly ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}: ${serviceName}) {}
1134
-
1135
- ${methods}
1136
- }`;
1320
+ }
1321
+ function generateMethodBody(method, serviceInstance, isAsync) {
1322
+ const lines = [];
1323
+ const hasParams = method.params.length > 0;
1324
+ if (hasParams && method.isStreamable && method.paramSchemas.length > 0) {
1325
+ method.params.forEach((p, i) => {
1326
+ lines.push(
1327
+ ` const validated${capitalize(p.name)} = ${method.paramSchemas[i]}.parse(${p.name});`
1328
+ );
1329
+ });
1330
+ }
1331
+ if (hasParams && !method.isStreamable) {
1332
+ if (method.paramSchemas.length > 0) {
1333
+ lines.push(
1334
+ ` const b = typeof body === 'object' && body !== null ? body : {};`
1335
+ );
1336
+ method.params.forEach((p, i) => {
1337
+ lines.push(
1338
+ ` const ${p.name} = ${method.paramSchemas[i]}.parse(b.${p.name});`
1339
+ );
1340
+ });
1341
+ } else {
1342
+ const paramNames = method.params.map((p) => p.name).join(", ");
1343
+ lines.push(` const { ${paramNames} } = body || {};`);
1344
+ }
1345
+ }
1346
+ let callArgs;
1347
+ if (hasParams && method.isStreamable && method.paramSchemas.length > 0) {
1348
+ callArgs = method.params.map((p) => `validated${capitalize(p.name)}`).join(", ");
1349
+ } else {
1350
+ callArgs = method.params.map((p) => p.name).join(", ");
1351
+ }
1352
+ const serviceCall = `${serviceInstance}.${method.name}(${callArgs})`;
1353
+ if (method.returnSchema && isAsync) {
1354
+ lines.push(` const res = await this.${serviceCall};`);
1355
+ lines.push(` return ${method.returnSchema}.parse(res);`);
1356
+ } else if (isAsync) {
1357
+ lines.push(` return this.${serviceCall};`);
1358
+ } else {
1359
+ lines.push(` return this.${serviceCall};`);
1360
+ }
1361
+ return lines.join("\n");
1137
1362
  }
1138
1363
 
1139
1364
  // src/plugins/generators/tsx_server_stub.ts
@@ -1380,7 +1605,7 @@ var NodeTypeGuards = class {
1380
1605
  isSignalMember(expr) {
1381
1606
  return this.t.isMemberExpression(expr) && this.t.isIdentifier(expr.property, { name: "value" });
1382
1607
  }
1383
- isObservableMember(expr) {
1608
+ isBehaviorSubjectMember(expr) {
1384
1609
  return this.t.isMemberExpression(expr) && this.t.isIdentifier(expr.property, { name: "$value" });
1385
1610
  }
1386
1611
  };
@@ -1390,7 +1615,7 @@ var ASTUtilities = class {
1390
1615
  this.guards = guards;
1391
1616
  }
1392
1617
  getObject(expr) {
1393
- if (this.guards.isSignalMember(expr) || this.guards.isObservableMember(expr)) {
1618
+ if (this.guards.isSignalMember(expr) || this.guards.isBehaviorSubjectMember(expr)) {
1394
1619
  return expr.object;
1395
1620
  }
1396
1621
  return expr;
@@ -1525,7 +1750,7 @@ var ObservableManager = class {
1525
1750
  }
1526
1751
  collectObservables(node, observables, astUtils) {
1527
1752
  this.walkNode(node, (n) => {
1528
- if (this.guards.isObservableMember(n)) {
1753
+ if (this.guards.isBehaviorSubjectMember(n)) {
1529
1754
  const observable = astUtils.replaceThisWithSelf(
1530
1755
  n.object
1531
1756
  );
@@ -1539,7 +1764,7 @@ var ObservableManager = class {
1539
1764
  replaceObservablesWithSignals(node, observableSignals, astUtils) {
1540
1765
  const cloned = this.t.cloneNode(node, true);
1541
1766
  this.walkNode(cloned, (n) => {
1542
- if (this.guards.isObservableMember(n)) {
1767
+ if (this.guards.isBehaviorSubjectMember(n)) {
1543
1768
  const observable = astUtils.replaceThisWithSelf(n.object);
1544
1769
  const key = this.getObservableKey(observable);
1545
1770
  const signalId = observableSignals.get(key);
@@ -1662,7 +1887,8 @@ var ElementTransformer = class {
1662
1887
  elId,
1663
1888
  statements,
1664
1889
  scope,
1665
- context2
1890
+ context2,
1891
+ tag
1666
1892
  );
1667
1893
  if (hasRef && refValue) {
1668
1894
  statements.push(
@@ -1756,7 +1982,7 @@ var ElementTransformer = class {
1756
1982
  context2.observables,
1757
1983
  this.astUtils
1758
1984
  );
1759
- if (this.guards.isSignalMember(expr) || this.guards.isObservableMember(expr)) {
1985
+ if (this.guards.isSignalMember(expr) || this.guards.isBehaviorSubjectMember(expr)) {
1760
1986
  const replaced = this.observableManager.replaceObservablesWithSignals(
1761
1987
  expr,
1762
1988
  context2.observableSignals,
@@ -1797,11 +2023,13 @@ var ElementTransformer = class {
1797
2023
  }
1798
2024
  }
1799
2025
  }
1800
- processDOMAttributes(attributes, elId, statements, scope, context2) {
2026
+ processDOMAttributes(attributes, elId, statements, scope, context2, tag) {
1801
2027
  let hasRef = false;
1802
2028
  let refValue = null;
1803
2029
  let hasDangerousHTML = false;
1804
2030
  let dangerousHTMLValue = null;
2031
+ let hasClickHandler = false;
2032
+ let hrefValue = null;
1805
2033
  for (const attr of attributes) {
1806
2034
  if (this.t.isJSXSpreadAttribute(attr)) {
1807
2035
  this.observableManager.collectObservables(
@@ -1866,17 +2094,59 @@ var ElementTransformer = class {
1866
2094
  continue;
1867
2095
  }
1868
2096
  if (/^on[A-Z]/.test(key)) {
2097
+ if (key === "onClick") {
2098
+ hasClickHandler = true;
2099
+ }
1869
2100
  this.processEventListener(key, attr, elId, statements, context2);
1870
2101
  continue;
1871
2102
  }
2103
+ if (key === "href" && this.t.isStringLiteral(attr.value)) {
2104
+ hrefValue = attr.value.value;
2105
+ }
1872
2106
  if (key === "style" && this.t.isJSXExpressionContainer(attr.value)) {
1873
2107
  this.processStyleAttribute(attr, elId, statements, scope, context2);
1874
2108
  continue;
1875
2109
  }
1876
2110
  this.processRegularAttribute(key, attr, elId, statements, context2);
1877
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
+ }
1878
2137
  return { hasRef, refValue, hasDangerousHTML, dangerousHTMLValue };
1879
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
+ }
1880
2150
  processEventListener(key, attr, elId, statements, context2) {
1881
2151
  const eventName = key.slice(2).toLowerCase();
1882
2152
  let handler = this.t.nullLiteral();
@@ -2008,7 +2278,7 @@ var ElementTransformer = class {
2008
2278
  insertedValue = this.astUtils.getObject(
2009
2279
  expr
2010
2280
  );
2011
- } else if (this.guards.isObservableMember(expr)) {
2281
+ } else if (this.guards.isBehaviorSubjectMember(expr)) {
2012
2282
  const replaced = this.observableManager.replaceObservablesWithSignals(
2013
2283
  expr,
2014
2284
  context2.observableSignals,
@@ -2378,7 +2648,7 @@ function extractMethods2(classDecl) {
2378
2648
  continue;
2379
2649
  }
2380
2650
  const params = extractMethodParams2(method.function.params || []);
2381
- const returnType = extractReturnType2(method.function.returnType);
2651
+ const returnType = extractReturnType(method.function.returnType);
2382
2652
  const isAsync = method.function.async || false;
2383
2653
  methods.push({
2384
2654
  name: methodName,
@@ -2407,7 +2677,7 @@ function extractMethodParams2(params) {
2407
2677
  }
2408
2678
  return result;
2409
2679
  }
2410
- function extractReturnType2(returnType) {
2680
+ function extractReturnType(returnType) {
2411
2681
  if (!returnType || !returnType.typeAnnotation) {
2412
2682
  return "any";
2413
2683
  }
@@ -2526,159 +2796,104 @@ ${decoratorsStr}export class ${className} extends _OrcaComponent {
2526
2796
  }`;
2527
2797
  }
2528
2798
 
2529
- // src/plugins/generators/generate_rsc.ts
2530
- import { parseSync as parseSync4 } from "@swc/core";
2531
- function generateRscStub(filePath, code) {
2532
- const ast = parseSync4(code, {
2533
- syntax: "typescript",
2534
- tsx: filePath.endsWith("x"),
2535
- decorators: true
2536
- });
2537
- const serviceInfo = extractServiceInfo2(ast);
2538
- return generateStubCode2(serviceInfo);
2539
- }
2540
- function extractServiceInfo2(ast) {
2541
- let serviceClass = null;
2542
- for (const item of ast.body) {
2543
- if (item.type === "ExportDeclaration" && item.declaration.type === "ClassDeclaration") {
2544
- const classDecl = item.declaration;
2545
- if (hasInjectableDecorator2(classDecl.decorators)) {
2546
- serviceClass = classDecl;
2547
- break;
2548
- }
2549
- }
2550
- }
2551
- if (!serviceClass || !serviceClass.identifier) {
2552
- throw new Error("Service class is undefined");
2553
- }
2554
- const className = serviceClass.identifier.value;
2555
- const methods = extractMethods3(serviceClass);
2556
- return {
2557
- className,
2558
- methods
2559
- };
2560
- }
2561
- function hasInjectableDecorator2(decorators) {
2562
- if (!decorators) return false;
2563
- return decorators.some((decorator) => {
2564
- const expr = decorator.expression;
2565
- if (expr.type === "CallExpression") {
2566
- if (expr.callee.type === "Identifier" && expr.callee.value === "Injectable") {
2567
- return true;
2568
- }
2569
- }
2570
- if (expr.type === "Identifier" && expr.value === "Injectable") {
2571
- return true;
2799
+ // src/plugins/generators/generate_rpc.ts
2800
+ function generateRpcStub(filePath, code) {
2801
+ try {
2802
+ const ast = parseTypeScript(filePath, code);
2803
+ const serviceInfo = extractServiceInfo(ast, filePath, false);
2804
+ if (!serviceInfo) {
2805
+ throw {
2806
+ type: "validation",
2807
+ message: "No exported class with @Injectable decorator found",
2808
+ filePath
2809
+ };
2572
2810
  }
2573
- return false;
2574
- });
2575
- }
2576
- function extractMethods3(classDecl) {
2577
- const methods = [];
2578
- for (const member of classDecl.body) {
2579
- if (member.type === "ClassMethod" && member.accessibility === "public") {
2580
- const method = member;
2581
- const methodName = method.key.type === "Identifier" ? method.key.value : "";
2582
- if (!methodName) {
2583
- continue;
2584
- }
2585
- if (!method.function.async) {
2586
- throw new Error(
2587
- `Server action ${classDecl.identifier.value}.${methodName} must be async.`
2588
- );
2589
- }
2590
- const params = extractMethodParams3(method.function.params || []);
2591
- const returnType = extractReturnType3(method.function.returnType);
2592
- const isAsync = method.function.async || false;
2593
- methods.push({
2594
- name: methodName,
2595
- params,
2596
- returnType,
2597
- isAsync
2598
- });
2811
+ validateServiceInfo(serviceInfo, filePath);
2812
+ return generateStubCode2(serviceInfo);
2813
+ } catch (error) {
2814
+ if (error.type) {
2815
+ throw error;
2599
2816
  }
2817
+ throw {
2818
+ type: "parse",
2819
+ message: `Failed to parse TypeScript file: ${error.message}`,
2820
+ filePath,
2821
+ details: error
2822
+ };
2600
2823
  }
2601
- return methods;
2602
2824
  }
2603
- function extractMethodParams3(params) {
2604
- const result = [];
2605
- for (const param of params) {
2606
- if (param.type === "Parameter") {
2607
- const pat = param.pat;
2608
- if (pat.type === "Identifier") {
2609
- const name = pat.value;
2610
- const type = pat.typeAnnotation?.typeAnnotation ? stringifyType4(pat.typeAnnotation.typeAnnotation) : "any";
2611
- result.push({
2612
- name,
2613
- type
2614
- });
2615
- }
2616
- }
2825
+ function generateStubCode2(serviceInfo) {
2826
+ const className = serviceInfo.className;
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}
2832
+
2833
+ @Injectable()
2834
+ export class ${className} {
2835
+ ${methods}
2836
+ }`;
2837
+ }
2838
+ function generateImports2(hasStreamable) {
2839
+ let imports = `import { Injectable } from "@kithinji/orca";
2840
+ `;
2841
+ if (hasStreamable) {
2842
+ imports += `import { Observable } from "@kithinji/orca";
2843
+ `;
2617
2844
  }
2618
- return result;
2845
+ return imports;
2619
2846
  }
2620
- function extractReturnType3(returnType) {
2621
- if (!returnType || !returnType.typeAnnotation) {
2622
- return "any";
2847
+ function generateMethod2(method, basePath, serviceName) {
2848
+ if (method.isStreamable) {
2849
+ return generateSseMethod(method, basePath);
2623
2850
  }
2624
- const type = returnType.typeAnnotation;
2625
- if (type.type === "TsTypeReference") {
2626
- const typeName = type.typeName;
2627
- if (typeName.type === "Identifier" && typeName.value === "Promise") {
2628
- if (type.typeParams && type.typeParams.params.length > 0) {
2629
- return stringifyType4(type.typeParams.params[0]);
2630
- }
2631
- }
2851
+ const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
2852
+ const hasParams = method.params.length > 0;
2853
+ if (!hasParams) {
2854
+ return generateGetMethod(method, basePath);
2632
2855
  }
2633
- return stringifyType4(type);
2856
+ return generatePostMethod(method, basePath, params);
2634
2857
  }
2635
- function stringifyType4(typeNode) {
2636
- if (!typeNode) return "any";
2637
- switch (typeNode.type) {
2638
- case "TsKeywordType":
2639
- return typeNode.kind;
2640
- case "TsTypeReference":
2641
- if (typeNode.typeName.type === "Identifier") {
2642
- const baseName = typeNode.typeName.value;
2643
- if (typeNode.typeParams && typeNode.typeParams.params.length > 0) {
2644
- const params = typeNode.typeParams.params.map(stringifyType4).join(", ");
2645
- return `${baseName}<${params}>`;
2646
- }
2647
- return baseName;
2648
- }
2649
- return "any";
2650
- case "TsArrayType":
2651
- return `${stringifyType4(typeNode.elemType)}[]`;
2652
- case "TsUnionType":
2653
- return typeNode.types.map(stringifyType4).join(" | ");
2654
- case "TsIntersectionType":
2655
- return typeNode.types.map(stringifyType4).join(" & ");
2656
- case "TsTypeLiteral":
2657
- const props = typeNode.members.map((member) => {
2658
- if (member.type === "TsPropertySignature") {
2659
- const key = member.key.type === "Identifier" ? member.key.value : "";
2660
- const type = member.typeAnnotation ? stringifyType4(member.typeAnnotation.typeAnnotation) : "any";
2661
- return `${key}: ${type}`;
2858
+ function generateSseMethod(method, basePath) {
2859
+ const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
2860
+ const hasParams = method.params.length > 0;
2861
+ let urlBuilder;
2862
+ if (hasParams) {
2863
+ const queryParams = method.params.map((p) => `${p.name}=\${encodeURIComponent(${p.name})}`).join("&");
2864
+ urlBuilder = `\`/${basePath}/${method.name}?${queryParams}\``;
2865
+ } else {
2866
+ urlBuilder = `\`/${basePath}/${method.name}\``;
2867
+ }
2868
+ return ` ${method.name}(${params}): Observable<${method.returnType}> {
2869
+ return new Observable((observer) => {
2870
+ const eventSource = new EventSource(${urlBuilder});
2871
+
2872
+ eventSource.onmessage = (event) => {
2873
+ try {
2874
+ const data = JSON.parse(event.data);
2875
+ observer.next(data);
2876
+ } catch (error) {
2877
+ observer.error?.(error);
2662
2878
  }
2663
- return "";
2664
- }).filter(Boolean);
2665
- return `{ ${props.join("; ")} }`;
2666
- default:
2667
- return "any";
2668
- }
2879
+ };
2880
+
2881
+ eventSource.onerror = (error) => {
2882
+ observer.error?.(error);
2883
+ eventSource.close();
2884
+ };
2885
+
2886
+ return () => {
2887
+ eventSource.close();
2888
+ };
2889
+ });
2890
+ }`;
2669
2891
  }
2670
- function generateStubCode2(serviceInfo) {
2671
- const className = serviceInfo.className;
2672
- const methods = serviceInfo.methods.map((method) => {
2673
- const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
2674
- const paramNames = method.params.map((p) => p.name).join(", ");
2675
- const asyncKeyword = method.isAsync ? "async " : "";
2676
- const returnType = method.isAsync ? `Promise<${method.returnType}>` : method.returnType;
2677
- const hasParams = method.params.length > 0;
2678
- const bodyParam = hasParams ? `{ ${paramNames} }` : "{}";
2679
- if (!hasParams) {
2680
- return ` ${asyncKeyword}${method.name}(${params}): ${returnType} {
2681
- const response = await fetch(\`/${className}/${method.name}\`, {
2892
+ function generateGetMethod(method, basePath) {
2893
+ const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
2894
+ const returnType = `Promise<${method.returnType}>`;
2895
+ return ` async ${method.name}(${params}): ${returnType} {
2896
+ const response = await fetch(\`/${basePath}/${method.name}\`, {
2682
2897
  method: 'GET',
2683
2898
  headers: {
2684
2899
  'Content-Type': 'application/json',
@@ -2691,14 +2906,17 @@ function generateStubCode2(serviceInfo) {
2691
2906
 
2692
2907
  return response.json();
2693
2908
  }`;
2694
- }
2695
- return ` ${asyncKeyword}${method.name}(${params}): ${returnType} {
2696
- const response = await fetch(\`/${className}/${method.name}\`, {
2909
+ }
2910
+ function generatePostMethod(method, basePath, params) {
2911
+ const paramNames = method.params.map((p) => p.name).join(", ");
2912
+ const returnType = `Promise<${method.returnType}>`;
2913
+ return ` async ${method.name}(${params}): ${returnType} {
2914
+ const response = await fetch(\`/${basePath}/${method.name}\`, {
2697
2915
  method: 'POST',
2698
2916
  headers: {
2699
2917
  'Content-Type': 'application/json',
2700
2918
  },
2701
- body: JSON.stringify(${bodyParam}),
2919
+ body: JSON.stringify({ ${paramNames} }),
2702
2920
  });
2703
2921
 
2704
2922
  if (!response.ok) {
@@ -2707,13 +2925,6 @@ function generateStubCode2(serviceInfo) {
2707
2925
 
2708
2926
  return response.json();
2709
2927
  }`;
2710
- }).join("\n\n");
2711
- return `import { Injectable } from "@kithinji/orca";
2712
-
2713
- @Injectable()
2714
- export class ${className} {
2715
- ${methods}
2716
- }`;
2717
2928
  }
2718
2929
 
2719
2930
  // src/plugins/my.ts
@@ -2830,8 +3041,8 @@ var ClientBuildTransformer = class {
2830
3041
  const scSource = generateServerComponent(path15, source);
2831
3042
  return swcTransform(scSource, path15);
2832
3043
  }
2833
- async transformPublicFileRsc(node, source, path15) {
2834
- const stubSource = generateRscStub(path15, source);
3044
+ async transformPublicFileRpc(node, source, path15) {
3045
+ const stubSource = generateRpcStub(path15, source);
2835
3046
  return swcTransform(stubSource, path15);
2836
3047
  }
2837
3048
  async transformSharedCode(source, path15) {
@@ -2853,7 +3064,7 @@ var ClientBuildTransformer = class {
2853
3064
  }
2854
3065
  }
2855
3066
  if (directive === "public") {
2856
- return this.transformPublicFileRsc(node, source, path15);
3067
+ return this.transformPublicFileRpc(node, source, path15);
2857
3068
  }
2858
3069
  if (directive === null) {
2859
3070
  return this.transformSharedCode(source, path15);
@@ -2896,7 +3107,7 @@ function useMyPlugin(options) {
2896
3107
  }
2897
3108
 
2898
3109
  // src/plugins/analyzers/graph.ts
2899
- import { parseSync as parseSync5 } from "@swc/core";
3110
+ import { parseSync as parseSync4 } from "@swc/core";
2900
3111
  import * as fs3 from "fs";
2901
3112
  import * as path7 from "path";
2902
3113
  function resolveFilePath(fromFile, importPath) {
@@ -3044,7 +3255,7 @@ function buildGraph(entryPoints) {
3044
3255
  const store = Store.getInstance();
3045
3256
  const newCode = store.get(filePath);
3046
3257
  const content = newCode ? newCode[0] : fs3.readFileSync(filePath, "utf-8");
3047
- const ast = parseSync5(content, {
3258
+ const ast = parseSync4(content, {
3048
3259
  syntax: "typescript",
3049
3260
  tsx: isTsx,
3050
3261
  decorators: true
@@ -3112,30 +3323,247 @@ function stylePlugin(store) {
3112
3323
  };
3113
3324
  }
3114
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
+
3115
3443
  // src/dev/server.ts
3116
- async function copyFile2() {
3444
+ var virtualClientFiles = {
3445
+ "virtual:navigate": {
3446
+ output: "navigate",
3447
+ code: `
3448
+ export async function navigate(event, url) {
3449
+ event.preventDefault();
3450
+
3117
3451
  try {
3118
- await fs5.mkdir("public", { recursive: true });
3119
- await fs5.copyFile("./src/client/index.html", "./public/index.html");
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
+ }
3120
3461
  } catch (error) {
3121
- console.error("\u274C Failed to copy index.html:", 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) {
3537
+ try {
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
+ );
3548
+ } catch (error) {
3549
+ console.error("Failed to copy and process index.html:", error);
3122
3550
  throw error;
3123
3551
  }
3124
3552
  }
3125
3553
  async function cleanDirectories() {
3126
3554
  await Promise.all([
3127
- fs5.rm("dist", { recursive: true, force: true }),
3128
- fs5.rm("public", { recursive: true, force: true })
3555
+ fs6.rm("dist", { recursive: true, force: true }),
3556
+ fs6.rm("public", { recursive: true, force: true })
3129
3557
  ]);
3130
3558
  }
3131
- function createRestartServerPlugin(serverProcess, onServerBuildComplete) {
3559
+ function createRestartServerPlugin(serverProcess, onServerBuildComplete, hotReloadManager) {
3132
3560
  return {
3133
3561
  name: "restart-server",
3134
3562
  setup(build) {
3135
3563
  build.onEnd((result) => {
3136
3564
  if (result.errors.length > 0) {
3137
3565
  console.error(
3138
- `\u274C Server build failed with ${result.errors.length} error(s)`
3566
+ `Server build failed with ${result.errors.length} error(s)`
3139
3567
  );
3140
3568
  return;
3141
3569
  }
@@ -3146,8 +3574,11 @@ function createRestartServerPlugin(serverProcess, onServerBuildComplete) {
3146
3574
  stdio: "inherit"
3147
3575
  });
3148
3576
  serverProcess.current.on("error", (err) => {
3149
- console.error("\u274C Server process error:", err);
3577
+ console.error("Server process error:", err);
3150
3578
  });
3579
+ setTimeout(() => {
3580
+ hotReloadManager.reload();
3581
+ }, 500);
3151
3582
  onServerBuildComplete();
3152
3583
  });
3153
3584
  }
@@ -3157,15 +3588,48 @@ async function startDevServer() {
3157
3588
  const store = Store.getInstance();
3158
3589
  const userConfig = await loadConfig();
3159
3590
  const config = mergeConfig(getDefaultConfig(), userConfig);
3591
+ const HOT_RELOAD_PORT = 3001;
3592
+ const hotReloadManager = new HotReloadManager(HOT_RELOAD_PORT);
3160
3593
  await cleanDirectories();
3161
- await copyFile2();
3594
+ await copyAndProcessHtml(HOT_RELOAD_PORT, config.htmlPreprocessor);
3595
+ hotReloadManager.start();
3162
3596
  const entryPoints = ["src/main.ts"];
3163
3597
  const clientFiles = /* @__PURE__ */ new Set(["src/client/client.tsx"]);
3164
3598
  const serverProcessRef = { current: null };
3165
3599
  let clientCtx = null;
3600
+ let virtualCtx = null;
3166
3601
  let isShuttingDown = false;
3167
3602
  let pendingClientFiles = /* @__PURE__ */ new Set();
3168
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
+ }
3169
3633
  async function rebuildClient() {
3170
3634
  if (isShuttingDown) return;
3171
3635
  try {
@@ -3185,7 +3649,7 @@ async function startDevServer() {
3185
3649
  format: "esm",
3186
3650
  sourcemap: config.build?.sourcemap ?? true,
3187
3651
  splitting: true,
3188
- minify: config.build?.minify ?? true,
3652
+ minify: config.build?.minify ?? false,
3189
3653
  plugins: [
3190
3654
  ...config.plugins?.map((cb) => cb(store)) || [],
3191
3655
  ...config.client_plugins?.map((cb) => cb(store)) || [],
@@ -3201,8 +3665,11 @@ async function startDevServer() {
3201
3665
  build.onEnd((result) => {
3202
3666
  if (result.errors.length > 0) {
3203
3667
  console.error(
3204
- `\u274C Client build failed with ${result.errors.length} error(s)`
3668
+ `Client build failed with ${result.errors.length} error(s)`
3205
3669
  );
3670
+ } else {
3671
+ console.log("Client build completed");
3672
+ hotReloadManager.reload();
3206
3673
  }
3207
3674
  });
3208
3675
  }
@@ -3214,7 +3681,7 @@ async function startDevServer() {
3214
3681
  pendingClientFiles.clear();
3215
3682
  needsClientRebuild = false;
3216
3683
  } catch (error) {
3217
- console.error("\u274C Failed to rebuild client:", error);
3684
+ console.error("Failed to rebuild client:", error);
3218
3685
  throw error;
3219
3686
  }
3220
3687
  }
@@ -3245,13 +3712,18 @@ async function startDevServer() {
3245
3712
  }
3246
3713
  }
3247
3714
  }),
3248
- createRestartServerPlugin(serverProcessRef, onServerBuildComplete)
3715
+ createRestartServerPlugin(
3716
+ serverProcessRef,
3717
+ onServerBuildComplete,
3718
+ hotReloadManager
3719
+ )
3249
3720
  ],
3250
3721
  write: true
3251
3722
  });
3252
3723
  async function shutdown() {
3253
3724
  if (isShuttingDown) return;
3254
3725
  isShuttingDown = true;
3726
+ console.log("\nShutting down dev server...");
3255
3727
  try {
3256
3728
  if (serverProcessRef.current) {
3257
3729
  serverProcessRef.current.kill("SIGTERM");
@@ -3259,14 +3731,19 @@ async function startDevServer() {
3259
3731
  }
3260
3732
  await serverCtx.dispose();
3261
3733
  if (clientCtx) await clientCtx.dispose();
3734
+ if (virtualCtx) await virtualCtx.dispose();
3735
+ hotReloadManager.close();
3736
+ console.log("Dev server shut down successfully");
3262
3737
  process.exit(0);
3263
3738
  } catch (error) {
3264
- console.error("\u274C Error during shutdown:", error);
3739
+ console.error("Error during shutdown:", error);
3265
3740
  process.exit(1);
3266
3741
  }
3267
3742
  }
3268
3743
  process.on("SIGINT", shutdown);
3269
3744
  process.on("SIGTERM", shutdown);
3745
+ console.log("Starting dev server...");
3746
+ await buildVirtualFiles();
3270
3747
  await serverCtx.watch();
3271
3748
  }
3272
3749
 
@@ -3279,7 +3756,7 @@ __export(config_exports, {
3279
3756
  });
3280
3757
 
3281
3758
  // src/add/component/component.ts
3282
- import * as fs6 from "fs";
3759
+ import * as fs7 from "fs";
3283
3760
  import * as path8 from "path";
3284
3761
  import * as ts2 from "typescript";
3285
3762
  var ComponentDefinition = class {
@@ -3456,35 +3933,35 @@ Processing dependencies for "${name}": [${component.dependencies.join(
3456
3933
  );
3457
3934
  const componentDir = path8.dirname(componentPath);
3458
3935
  const appModulePath = path8.join(process.cwd(), "src/app/app.module.ts");
3459
- if (!fs6.existsSync(componentModulePath)) {
3936
+ if (!fs7.existsSync(componentModulePath)) {
3460
3937
  const moduleDir = path8.dirname(componentModulePath);
3461
- if (!fs6.existsSync(moduleDir)) {
3462
- fs6.mkdirSync(moduleDir, { recursive: true });
3938
+ if (!fs7.existsSync(moduleDir)) {
3939
+ fs7.mkdirSync(moduleDir, { recursive: true });
3463
3940
  }
3464
- fs6.writeFileSync(componentModulePath, createModule(), "utf-8");
3941
+ fs7.writeFileSync(componentModulePath, createModule(), "utf-8");
3465
3942
  }
3466
- if (!fs6.existsSync(componentDir)) {
3467
- fs6.mkdirSync(componentDir, { recursive: true });
3943
+ if (!fs7.existsSync(componentDir)) {
3944
+ fs7.mkdirSync(componentDir, { recursive: true });
3468
3945
  }
3469
- if (!fs6.existsSync(componentPath)) {
3470
- fs6.writeFileSync(componentPath, component.generate(), "utf-8");
3946
+ if (!fs7.existsSync(componentPath)) {
3947
+ fs7.writeFileSync(componentPath, component.generate(), "utf-8");
3471
3948
  console.log(`Created ${name}.component.tsx`);
3472
3949
  } else {
3473
3950
  console.log(`${name}.component.tsx already exists, skipping file creation`);
3474
3951
  }
3475
- const moduleContent = fs6.readFileSync(componentModulePath, "utf-8");
3952
+ const moduleContent = fs7.readFileSync(componentModulePath, "utf-8");
3476
3953
  const updatedModule = updateModuleWithComponent(moduleContent, name);
3477
- fs6.writeFileSync(componentModulePath, updatedModule, "utf-8");
3478
- if (fs6.existsSync(appModulePath)) {
3479
- const appModuleContent = fs6.readFileSync(appModulePath, "utf-8");
3954
+ fs7.writeFileSync(componentModulePath, updatedModule, "utf-8");
3955
+ if (fs7.existsSync(appModulePath)) {
3956
+ const appModuleContent = fs7.readFileSync(appModulePath, "utf-8");
3480
3957
  const updatedAppModule = ensureComponentModuleImported(appModuleContent);
3481
3958
  if (updatedAppModule !== appModuleContent) {
3482
- fs6.writeFileSync(appModulePath, updatedAppModule, "utf-8");
3959
+ fs7.writeFileSync(appModulePath, updatedAppModule, "utf-8");
3483
3960
  }
3484
3961
  }
3485
3962
  }
3486
3963
  function updateModuleWithComponent(moduleContent, componentName) {
3487
- const className = capitalize(componentName);
3964
+ const className = capitalize2(componentName);
3488
3965
  const importPath = `./component/${componentName}.component`;
3489
3966
  const sourceFile = ts2.createSourceFile(
3490
3967
  "component.module.ts",
@@ -3640,7 +4117,7 @@ function ensureComponentModuleImported(appModuleContent) {
3640
4117
  function ensureInImportsArray(content, sourceFile) {
3641
4118
  return addToDecoratorArray(content, sourceFile, "imports", "ComponentModule");
3642
4119
  }
3643
- function capitalize(str) {
4120
+ function capitalize2(str) {
3644
4121
  return str.charAt(0).toUpperCase() + str.slice(1);
3645
4122
  }
3646
4123
  function createModule() {
@@ -3665,12 +4142,12 @@ function toPascalCase(str) {
3665
4142
  }
3666
4143
 
3667
4144
  // src/utils/create.ts
3668
- import * as fs7 from "fs";
4145
+ import * as fs8 from "fs";
3669
4146
  import * as path9 from "path";
3670
4147
  function createStructure(basePath, entry) {
3671
- fs7.mkdirSync(basePath, { recursive: true });
4148
+ fs8.mkdirSync(basePath, { recursive: true });
3672
4149
  entry.files?.forEach((file) => {
3673
- fs7.writeFileSync(path9.join(basePath, file.name), file.content);
4150
+ fs8.writeFileSync(path9.join(basePath, file.name), file.content);
3674
4151
  });
3675
4152
  entry.dirs?.forEach((dir) => {
3676
4153
  const dirPath = path9.join(basePath, dir.name || "");
@@ -3683,7 +4160,7 @@ import path11 from "path";
3683
4160
 
3684
4161
  // src/add/module/module.ts
3685
4162
  import * as path10 from "path";
3686
- import * as fs8 from "fs";
4163
+ import * as fs9 from "fs";
3687
4164
  import * as ts3 from "typescript";
3688
4165
  function addFeature(name) {
3689
4166
  const featureDir = path10.join(process.cwd(), "src", "features", name);
@@ -3746,8 +4223,8 @@ function updateFeaturesIndex(featureName) {
3746
4223
  );
3747
4224
  const moduleName = toPascalCase(featureName + "_Module");
3748
4225
  const importPath = `./${featureName}/${featureName}.module`;
3749
- if (fs8.existsSync(featuresIndexPath)) {
3750
- let content = fs8.readFileSync(featuresIndexPath, "utf-8");
4226
+ if (fs9.existsSync(featuresIndexPath)) {
4227
+ let content = fs9.readFileSync(featuresIndexPath, "utf-8");
3751
4228
  const sourceFile = ts3.createSourceFile(
3752
4229
  "index.ts",
3753
4230
  content,
@@ -3773,24 +4250,24 @@ function updateFeaturesIndex(featureName) {
3773
4250
  }
3774
4251
  const exportStatement = `export { ${moduleName} } from "${importPath}";
3775
4252
  `;
3776
- fs8.appendFileSync(featuresIndexPath, exportStatement);
4253
+ fs9.appendFileSync(featuresIndexPath, exportStatement);
3777
4254
  } else {
3778
4255
  const featuresDir = path10.dirname(featuresIndexPath);
3779
- if (!fs8.existsSync(featuresDir)) {
3780
- fs8.mkdirSync(featuresDir, { recursive: true });
4256
+ if (!fs9.existsSync(featuresDir)) {
4257
+ fs9.mkdirSync(featuresDir, { recursive: true });
3781
4258
  }
3782
4259
  const exportStatement = `export { ${moduleName} } from "${importPath}";
3783
4260
  `;
3784
- fs8.writeFileSync(featuresIndexPath, exportStatement, "utf-8");
4261
+ fs9.writeFileSync(featuresIndexPath, exportStatement, "utf-8");
3785
4262
  }
3786
4263
  }
3787
4264
  function updateAppModule(featureName) {
3788
4265
  const appModulePath = path10.join(process.cwd(), "src", "app", "app.module.ts");
3789
- if (!fs8.existsSync(appModulePath)) {
4266
+ if (!fs9.existsSync(appModulePath)) {
3790
4267
  return;
3791
4268
  }
3792
4269
  const moduleName = toPascalCase(featureName + "_Module");
3793
- let content = fs8.readFileSync(appModulePath, "utf-8");
4270
+ let content = fs9.readFileSync(appModulePath, "utf-8");
3794
4271
  const sourceFile = ts3.createSourceFile(
3795
4272
  "app.module.ts",
3796
4273
  content,
@@ -3814,7 +4291,7 @@ function updateAppModule(featureName) {
3814
4291
  });
3815
4292
  if (hasImport) {
3816
4293
  content = addToModuleImportsArray(content, sourceFile, moduleName);
3817
- fs8.writeFileSync(appModulePath, content, "utf-8");
4294
+ fs9.writeFileSync(appModulePath, content, "utf-8");
3818
4295
  return;
3819
4296
  }
3820
4297
  let lastImportEnd = 0;
@@ -3833,7 +4310,7 @@ function updateAppModule(featureName) {
3833
4310
  true
3834
4311
  );
3835
4312
  content = addToModuleImportsArray(content, newSourceFile, moduleName);
3836
- fs8.writeFileSync(appModulePath, content, "utf-8");
4313
+ fs9.writeFileSync(appModulePath, content, "utf-8");
3837
4314
  }
3838
4315
  function addToModuleImportsArray(content, sourceFile, moduleName) {
3839
4316
  let decoratorNode;
@@ -4107,24 +4584,19 @@ export class ${serviceName} {
4107
4584
  }
4108
4585
  function createPage(name) {
4109
4586
  const pageName = toPascalCase(name + "_Page");
4110
- const serviceName = toPascalCase(name + "_Service");
4111
- const serviceVar = toCamelCase(name + "_Service");
4112
4587
  const listComponent = toPascalCase(name + "_List");
4113
4588
  return `import { Component } from "@kithinji/orca";
4114
- import { ${serviceName} } from "./${name}.service";
4115
4589
  import { ${listComponent} } from "./components/${name}-list.component";
4116
4590
 
4117
- @Component()
4591
+ @Component({
4592
+ deps: [${listComponent}]
4593
+ })
4118
4594
  export class ${pageName} {
4119
- constructor(
4120
- public ${serviceVar}: ${serviceName}
4121
- ) {}
4122
-
4123
4595
  build() {
4124
4596
  return (
4125
4597
  <div>
4126
4598
  <h1>${toPascalCase(name)} Management</h1>
4127
- <${listComponent} service={this.${serviceVar}} />
4599
+ <${listComponent} name="hello" />
4128
4600
  </div>
4129
4601
  );
4130
4602
  }
@@ -4134,6 +4606,7 @@ export class ${pageName} {
4134
4606
  function createListComponent(name) {
4135
4607
  const componentName = toPascalCase(name + "_List");
4136
4608
  const serviceName = toPascalCase(name + "_Service");
4609
+ const serviceVar = toCamelCase(name + "_Service");
4137
4610
  return `"use interactive";
4138
4611
 
4139
4612
  import { Component } from "@kithinji/orca";
@@ -4142,9 +4615,13 @@ import { ${serviceName} } from "../${name}.service";
4142
4615
  @Component()
4143
4616
  export class ${componentName} {
4144
4617
  props!: {
4145
- service: ${serviceName};
4618
+ name: string;
4146
4619
  };
4147
4620
 
4621
+ constructor(
4622
+ public readonly ${serviceVar}: ${serviceName}
4623
+ ) {}
4624
+
4148
4625
  build() {
4149
4626
  return (
4150
4627
  <div>
@@ -4318,6 +4795,9 @@ export function bootstrap() {
4318
4795
  }
4319
4796
 
4320
4797
  bootstrap();
4798
+
4799
+ /* Don't modify */
4800
+ export { Navigate, getCurrentInjector } from "@kithinji/orca";
4321
4801
  `;
4322
4802
  }
4323
4803
  function genMainTs() {
@@ -4340,17 +4820,17 @@ import path14 from "path";
4340
4820
  import { execSync } from "child_process";
4341
4821
 
4342
4822
  // src/docker/docker.ts
4343
- import fs9 from "fs-extra";
4823
+ import fs10 from "fs-extra";
4344
4824
  import path12 from "path";
4345
4825
  import prompts from "prompts";
4346
4826
  import yaml from "js-yaml";
4347
4827
  async function dockerize(env = "prod") {
4348
4828
  const cwd = process.cwd();
4349
4829
  const packageJsonPath = path12.join(cwd, "package.json");
4350
- if (!fs9.existsSync(packageJsonPath)) {
4830
+ if (!fs10.existsSync(packageJsonPath)) {
4351
4831
  throw new Error("package.json not found. Are you in a Pod project?");
4352
4832
  }
4353
- const packageJson = await fs9.readJSON(packageJsonPath);
4833
+ const packageJson = await fs10.readJSON(packageJsonPath);
4354
4834
  const projectName = packageJson.name;
4355
4835
  const detectedServices = detectServices(packageJson);
4356
4836
  const selectedServices = await selectServices(detectedServices);
@@ -4391,30 +4871,30 @@ async function selectServices(detected) {
4391
4871
  }
4392
4872
  async function restructureProject(cwd, projectName) {
4393
4873
  const nestedDir = path12.join(cwd, projectName);
4394
- if (fs9.existsSync(nestedDir)) {
4874
+ if (fs10.existsSync(nestedDir)) {
4395
4875
  console.log("\u26A0\uFE0F Project already restructured, skipping...");
4396
4876
  return;
4397
4877
  }
4398
- await fs9.ensureDir(nestedDir);
4399
- const items = await fs9.readdir(cwd);
4878
+ await fs10.ensureDir(nestedDir);
4879
+ const items = await fs10.readdir(cwd);
4400
4880
  const toMove = items.filter((item) => item !== projectName);
4401
4881
  for (const item of toMove) {
4402
4882
  const src = path12.join(cwd, item);
4403
4883
  const dest = path12.join(nestedDir, item);
4404
- await fs9.move(src, dest, { overwrite: true });
4884
+ await fs10.move(src, dest, { overwrite: true });
4405
4885
  }
4406
4886
  const envSrc = path12.join(nestedDir, ".env");
4407
4887
  const envDest = path12.join(cwd, ".env");
4408
- if (fs9.existsSync(envSrc)) {
4409
- await fs9.move(envSrc, envDest, { overwrite: true });
4888
+ if (fs10.existsSync(envSrc)) {
4889
+ await fs10.move(envSrc, envDest, { overwrite: true });
4410
4890
  }
4411
4891
  }
4412
4892
  async function writeEnvVars(cwd, services, env) {
4413
4893
  const envPath = path12.join(cwd, ".env");
4414
4894
  let existingEnv = {};
4415
4895
  let existingContent = "";
4416
- if (fs9.existsSync(envPath)) {
4417
- existingContent = await fs9.readFile(envPath, "utf8");
4896
+ if (fs10.existsSync(envPath)) {
4897
+ existingContent = await fs10.readFile(envPath, "utf8");
4418
4898
  existingEnv = parseEnvFile(existingContent);
4419
4899
  }
4420
4900
  const newVars = [];
@@ -4443,7 +4923,7 @@ async function writeEnvVars(cwd, services, env) {
4443
4923
  if (newVars.length > 0) {
4444
4924
  const separator = existingContent && !existingContent.endsWith("\n") ? "\n" : "";
4445
4925
  const newContent = existingContent + separator + (existingContent ? "\n" : "") + newVars.join("\n") + "\n";
4446
- await fs9.writeFile(envPath, newContent);
4926
+ await fs10.writeFile(envPath, newContent);
4447
4927
  console.log(
4448
4928
  `\u2705 Added ${newVars.length} new environment variable(s) to .env`
4449
4929
  );
@@ -4539,8 +5019,8 @@ docker-compose*.yml
4539
5019
  tmp
4540
5020
  temp
4541
5021
  `;
4542
- await fs9.writeFile(dockerfilePath, dockerfile);
4543
- await fs9.writeFile(dockerignorePath, dockerignore);
5022
+ await fs10.writeFile(dockerfilePath, dockerfile);
5023
+ await fs10.writeFile(dockerignorePath, dockerignore);
4544
5024
  }
4545
5025
  async function createDeployfile(cwd, projectName) {
4546
5026
  const deployFile = `name: ${projectName}
@@ -4665,7 +5145,7 @@ targets:
4665
5145
  docker image prune -af --filter "until=24h"
4666
5146
  `;
4667
5147
  const deployFilePath = path12.join(cwd, "pod.deploy.yml");
4668
- await fs9.writeFile(deployFilePath, deployFile);
5148
+ await fs10.writeFile(deployFilePath, deployFile);
4669
5149
  }
4670
5150
  async function setupProduction(cwd, projectName, services) {
4671
5151
  const compose = {
@@ -4736,7 +5216,7 @@ async function setupProduction(cwd, projectName, services) {
4736
5216
  compose.services[projectName].depends_on.push(service.name);
4737
5217
  }
4738
5218
  const composePath = path12.join(cwd, "docker-compose.yml");
4739
- await fs9.writeFile(
5219
+ await fs10.writeFile(
4740
5220
  composePath,
4741
5221
  yaml.dump(compose, { indent: 2, lineWidth: -1 })
4742
5222
  );
@@ -4744,8 +5224,8 @@ async function setupProduction(cwd, projectName, services) {
4744
5224
  async function setupDevelopment(cwd, projectName, services) {
4745
5225
  const existingCompose = path12.join(cwd, "docker-compose.yml");
4746
5226
  let existingServices = [];
4747
- if (fs9.existsSync(existingCompose)) {
4748
- const content = await fs9.readFile(existingCompose, "utf8");
5227
+ if (fs10.existsSync(existingCompose)) {
5228
+ const content = await fs10.readFile(existingCompose, "utf8");
4749
5229
  const existing = yaml.load(content);
4750
5230
  if (existing.services) {
4751
5231
  existingServices = Object.keys(existing.services).filter((s) => ["postgres", "mysql", "redis", "mongodb"].includes(s)).map((name) => ({ name }));
@@ -4815,14 +5295,14 @@ async function setupDevelopment(cwd, projectName, services) {
4815
5295
  compose.services[projectName].depends_on.push(tunnelName);
4816
5296
  }
4817
5297
  const devComposePath = path12.join(cwd, "docker-compose.dev.yml");
4818
- await fs9.writeFile(
5298
+ await fs10.writeFile(
4819
5299
  devComposePath,
4820
5300
  yaml.dump(compose, { indent: 2, lineWidth: -1 })
4821
5301
  );
4822
5302
  }
4823
5303
  async function createTunnelService(projectDir, serviceName) {
4824
5304
  const tunnelDir = path12.join(projectDir, `${serviceName}-tunnel`);
4825
- await fs9.ensureDir(tunnelDir);
5305
+ await fs10.ensureDir(tunnelDir);
4826
5306
  const dockerfile = `FROM alpine:latest
4827
5307
 
4828
5308
  RUN apk add --no-cache openssh-client
@@ -4850,8 +5330,8 @@ ssh -i $SSH_KEY \\
4850
5330
  -o ServerAliveInterval=60 \\
4851
5331
  $REMOTE_HOST
4852
5332
  `;
4853
- await fs9.writeFile(path12.join(tunnelDir, "Dockerfile"), dockerfile);
4854
- await fs9.writeFile(path12.join(tunnelDir, "tunnel.sh"), tunnelScript);
5333
+ await fs10.writeFile(path12.join(tunnelDir, "Dockerfile"), dockerfile);
5334
+ await fs10.writeFile(path12.join(tunnelDir, "tunnel.sh"), tunnelScript);
4855
5335
  }
4856
5336
  function getServiceConfig(serviceName) {
4857
5337
  const configs = {
@@ -4961,7 +5441,7 @@ function printNextSteps(projectName, env, services) {
4961
5441
  }
4962
5442
 
4963
5443
  // src/deploy/deploy.ts
4964
- import fs10 from "fs-extra";
5444
+ import fs11 from "fs-extra";
4965
5445
  import yaml2 from "js-yaml";
4966
5446
  import path13 from "path";
4967
5447
  import os from "os";
@@ -5218,12 +5698,12 @@ STDERR: ${result.stderr}`);
5218
5698
  }
5219
5699
  async uploadContent(remotePath, content) {
5220
5700
  const localTmp = path13.join(os.tmpdir(), `pod_tmp_${Date.now()}`);
5221
- fs10.writeFileSync(localTmp, content);
5701
+ fs11.writeFileSync(localTmp, content);
5222
5702
  try {
5223
5703
  await this.ssh.execCommand(`mkdir -p $(dirname ${remotePath})`);
5224
5704
  await this.ssh.putFile(localTmp, remotePath);
5225
5705
  } finally {
5226
- if (fs10.existsSync(localTmp)) fs10.unlinkSync(localTmp);
5706
+ if (fs11.existsSync(localTmp)) fs11.unlinkSync(localTmp);
5227
5707
  }
5228
5708
  }
5229
5709
  async readJson(remotePath) {
@@ -5297,25 +5777,25 @@ STDERR: ${err.stderr || err.message}`
5297
5777
  os.tmpdir(),
5298
5778
  `pod_script_${name}_${Date.now()}.sh`
5299
5779
  );
5300
- fs10.writeFileSync(scriptPath, interpolated);
5301
- fs10.chmodSync(scriptPath, "755");
5780
+ fs11.writeFileSync(scriptPath, interpolated);
5781
+ fs11.chmodSync(scriptPath, "755");
5302
5782
  try {
5303
5783
  await this.runCommand(scriptPath);
5304
5784
  } finally {
5305
- if (fs10.existsSync(scriptPath)) fs10.unlinkSync(scriptPath);
5785
+ if (fs11.existsSync(scriptPath)) fs11.unlinkSync(scriptPath);
5306
5786
  }
5307
5787
  }
5308
5788
  async uploadContent(localPath, content) {
5309
5789
  const dir = path13.dirname(localPath);
5310
- if (!fs10.existsSync(dir)) {
5311
- fs10.mkdirSync(dir, { recursive: true });
5790
+ if (!fs11.existsSync(dir)) {
5791
+ fs11.mkdirSync(dir, { recursive: true });
5312
5792
  }
5313
- fs10.writeFileSync(localPath, content);
5793
+ fs11.writeFileSync(localPath, content);
5314
5794
  }
5315
5795
  async readJson(localPath) {
5316
5796
  try {
5317
- if (!fs10.existsSync(localPath)) return null;
5318
- const content = fs10.readFileSync(localPath, "utf8");
5797
+ if (!fs11.existsSync(localPath)) return null;
5798
+ const content = fs11.readFileSync(localPath, "utf8");
5319
5799
  return JSON.parse(content);
5320
5800
  } catch {
5321
5801
  return null;
@@ -5323,8 +5803,8 @@ STDERR: ${err.stderr || err.message}`
5323
5803
  }
5324
5804
  async syncDirectory(source, destination, exclude) {
5325
5805
  console.log(chalk.gray(` Copying ${source} \u2192 ${destination}`));
5326
- if (!fs10.existsSync(destination)) {
5327
- fs10.mkdirSync(destination, { recursive: true });
5806
+ if (!fs11.existsSync(destination)) {
5807
+ fs11.mkdirSync(destination, { recursive: true });
5328
5808
  }
5329
5809
  const shouldExclude = (relativePath) => {
5330
5810
  if (!exclude?.length) return false;
@@ -5341,19 +5821,19 @@ STDERR: ${err.stderr || err.message}`
5341
5821
  });
5342
5822
  };
5343
5823
  const copyRecursive = (src, dest) => {
5344
- const entries = fs10.readdirSync(src, { withFileTypes: true });
5824
+ const entries = fs11.readdirSync(src, { withFileTypes: true });
5345
5825
  for (const entry of entries) {
5346
5826
  const srcPath = path13.join(src, entry.name);
5347
5827
  const destPath = path13.join(dest, entry.name);
5348
5828
  const relativePath = path13.relative(source, srcPath);
5349
5829
  if (shouldExclude(relativePath)) continue;
5350
5830
  if (entry.isDirectory()) {
5351
- if (!fs10.existsSync(destPath)) {
5352
- fs10.mkdirSync(destPath, { recursive: true });
5831
+ if (!fs11.existsSync(destPath)) {
5832
+ fs11.mkdirSync(destPath, { recursive: true });
5353
5833
  }
5354
5834
  copyRecursive(srcPath, destPath);
5355
5835
  } else {
5356
- fs10.copyFileSync(srcPath, destPath);
5836
+ fs11.copyFileSync(srcPath, destPath);
5357
5837
  }
5358
5838
  }
5359
5839
  };
@@ -5516,7 +5996,7 @@ var OperationHandler = class {
5516
5996
  async function deploy(targetName, options) {
5517
5997
  const cwd = process.cwd();
5518
5998
  const rawConfig = yaml2.load(
5519
- fs10.readFileSync(path13.join(cwd, "pod.deploy.yml"), "utf8"),
5999
+ fs11.readFileSync(path13.join(cwd, "pod.deploy.yml"), "utf8"),
5520
6000
  { schema: yaml2.DEFAULT_SCHEMA }
5521
6001
  );
5522
6002
  const rawTarget = rawConfig.targets?.[targetName];
@@ -5578,7 +6058,7 @@ Deployment Failed: ${err.message}`));
5578
6058
  // src/main.ts
5579
6059
  import chalk2 from "chalk";
5580
6060
  var program = new Command();
5581
- program.name("pod").description("Pod cli tool").version("1.0.23");
6061
+ program.name("pod").description("Pod cli tool").version("1.0.24");
5582
6062
  program.command("new <name>").description("Start a new Orca Project").action(async (name) => {
5583
6063
  await addNew(name);
5584
6064
  const appDir = path14.resolve(process.cwd());