@apso/cli 0.7.4 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/dist/autogen/index.d.ts +2 -0
  2. package/dist/autogen/index.js +3 -0
  3. package/dist/commands/server/new.d.ts +4 -1
  4. package/dist/commands/server/new.js +115 -22
  5. package/dist/commands/server/scaffold.d.ts +10 -0
  6. package/dist/commands/server/scaffold.js +200 -10
  7. package/dist/lib/api/client.d.ts +65 -0
  8. package/dist/lib/api/client.js +305 -0
  9. package/dist/lib/api/index.d.ts +8 -0
  10. package/dist/lib/api/index.js +22 -0
  11. package/dist/lib/api/services.d.ts +166 -0
  12. package/dist/lib/api/services.js +202 -0
  13. package/dist/lib/api/types.d.ts +212 -0
  14. package/dist/lib/api/types.js +7 -0
  15. package/dist/lib/apsorc-parser.d.ts +3 -0
  16. package/dist/lib/apsorc-parser.js +4 -0
  17. package/dist/lib/config/index.d.ts +8 -0
  18. package/dist/lib/config/index.js +17 -0
  19. package/dist/lib/config/manager.d.ts +127 -0
  20. package/dist/lib/config/manager.js +304 -0
  21. package/dist/lib/config/paths.d.ts +65 -0
  22. package/dist/lib/config/paths.js +127 -0
  23. package/dist/lib/config/types.d.ts +230 -0
  24. package/dist/lib/config/types.js +30 -0
  25. package/dist/lib/generators/base.d.ts +93 -0
  26. package/dist/lib/generators/base.js +90 -0
  27. package/dist/lib/generators/go.d.ts +37 -0
  28. package/dist/lib/generators/go.js +340 -0
  29. package/dist/lib/generators/index.d.ts +21 -0
  30. package/dist/lib/generators/index.js +51 -0
  31. package/dist/lib/generators/python.d.ts +21 -0
  32. package/dist/lib/generators/python.js +253 -0
  33. package/dist/lib/generators/typescript.d.ts +25 -0
  34. package/dist/lib/generators/typescript.js +373 -0
  35. package/dist/lib/index.d.ts +2 -0
  36. package/dist/lib/index.js +9 -1
  37. package/dist/lib/templates/entities/entity-col-timestamp.eta +9 -2
  38. package/dist/lib/templates/entities/entity-col-timestamptz.eta +9 -2
  39. package/dist/lib/templates/entities/entity.eta +18 -5
  40. package/dist/lib/templates/go/dto/dto-col-array.eta +7 -0
  41. package/dist/lib/templates/go/dto/dto-col-boolean.eta +7 -0
  42. package/dist/lib/templates/go/dto/dto-col-date.eta +7 -0
  43. package/dist/lib/templates/go/dto/dto-col-decimal.eta +7 -0
  44. package/dist/lib/templates/go/dto/dto-col-enum.eta +8 -0
  45. package/dist/lib/templates/go/dto/dto-col-float.eta +7 -0
  46. package/dist/lib/templates/go/dto/dto-col-integer.eta +7 -0
  47. package/dist/lib/templates/go/dto/dto-col-json.eta +7 -0
  48. package/dist/lib/templates/go/dto/dto-col-jsonb.eta +7 -0
  49. package/dist/lib/templates/go/dto/dto-col-numeric.eta +7 -0
  50. package/dist/lib/templates/go/dto/dto-col-string.eta +7 -0
  51. package/dist/lib/templates/go/dto/dto-col-text.eta +7 -0
  52. package/dist/lib/templates/go/dto/dto-col-timestamp.eta +7 -0
  53. package/dist/lib/templates/go/dto/dto-col-timestamptz.eta +7 -0
  54. package/dist/lib/templates/go/dto/dto-col-uuid.eta +7 -0
  55. package/dist/lib/templates/go/dto/dto-col-varchar.eta +7 -0
  56. package/dist/lib/templates/go/dto/dto.eta +78 -0
  57. package/dist/lib/templates/go/enums.eta +44 -0
  58. package/dist/lib/templates/go/handlers/handler.eta +212 -0
  59. package/dist/lib/templates/go/header.eta +3 -0
  60. package/dist/lib/templates/go/index-module.eta +53 -0
  61. package/dist/lib/templates/go/middleware/auth.eta +101 -0
  62. package/dist/lib/templates/go/models/model-col-array.eta +1 -0
  63. package/dist/lib/templates/go/models/model-col-boolean.eta +1 -0
  64. package/dist/lib/templates/go/models/model-col-date.eta +1 -0
  65. package/dist/lib/templates/go/models/model-col-decimal.eta +1 -0
  66. package/dist/lib/templates/go/models/model-col-enum.eta +2 -0
  67. package/dist/lib/templates/go/models/model-col-float.eta +1 -0
  68. package/dist/lib/templates/go/models/model-col-integer.eta +1 -0
  69. package/dist/lib/templates/go/models/model-col-json.eta +1 -0
  70. package/dist/lib/templates/go/models/model-col-jsonb.eta +1 -0
  71. package/dist/lib/templates/go/models/model-col-numeric.eta +1 -0
  72. package/dist/lib/templates/go/models/model-col-string.eta +1 -0
  73. package/dist/lib/templates/go/models/model-col-text.eta +1 -0
  74. package/dist/lib/templates/go/models/model-col-timestamp.eta +1 -0
  75. package/dist/lib/templates/go/models/model-col-timestamptz.eta +1 -0
  76. package/dist/lib/templates/go/models/model-col-uuid.eta +1 -0
  77. package/dist/lib/templates/go/models/model-col-varchar.eta +1 -0
  78. package/dist/lib/templates/go/models/model.eta +53 -0
  79. package/dist/lib/templates/go/services/service.eta +190 -0
  80. package/dist/lib/templates/python/enums.eta +10 -0
  81. package/dist/lib/templates/python/header.eta +6 -0
  82. package/dist/lib/templates/python/index-module.eta +18 -0
  83. package/dist/lib/templates/python/models/model-col-array.eta +4 -0
  84. package/dist/lib/templates/python/models/model-col-boolean.eta +5 -0
  85. package/dist/lib/templates/python/models/model-col-date.eta +4 -0
  86. package/dist/lib/templates/python/models/model-col-decimal.eta +4 -0
  87. package/dist/lib/templates/python/models/model-col-enum.eta +16 -0
  88. package/dist/lib/templates/python/models/model-col-float.eta +5 -0
  89. package/dist/lib/templates/python/models/model-col-integer.eta +6 -0
  90. package/dist/lib/templates/python/models/model-col-json.eta +4 -0
  91. package/dist/lib/templates/python/models/model-col-jsonb.eta +4 -0
  92. package/dist/lib/templates/python/models/model-col-numeric.eta +4 -0
  93. package/dist/lib/templates/python/models/model-col-string.eta +6 -0
  94. package/dist/lib/templates/python/models/model-col-text.eta +6 -0
  95. package/dist/lib/templates/python/models/model-col-timestamp.eta +11 -0
  96. package/dist/lib/templates/python/models/model-col-timestamptz.eta +11 -0
  97. package/dist/lib/templates/python/models/model-col-uuid.eta +5 -0
  98. package/dist/lib/templates/python/models/model-col-varchar.eta +6 -0
  99. package/dist/lib/templates/python/models/model.eta +124 -0
  100. package/dist/lib/templates/python/routers/router.eta +206 -0
  101. package/dist/lib/templates/python/schemas/schema-col-array.eta +1 -0
  102. package/dist/lib/templates/python/schemas/schema-col-boolean.eta +1 -0
  103. package/dist/lib/templates/python/schemas/schema-col-date.eta +1 -0
  104. package/dist/lib/templates/python/schemas/schema-col-decimal.eta +1 -0
  105. package/dist/lib/templates/python/schemas/schema-col-enum.eta +6 -0
  106. package/dist/lib/templates/python/schemas/schema-col-float.eta +1 -0
  107. package/dist/lib/templates/python/schemas/schema-col-integer.eta +1 -0
  108. package/dist/lib/templates/python/schemas/schema-col-json.eta +1 -0
  109. package/dist/lib/templates/python/schemas/schema-col-jsonb.eta +1 -0
  110. package/dist/lib/templates/python/schemas/schema-col-numeric.eta +1 -0
  111. package/dist/lib/templates/python/schemas/schema-col-string.eta +1 -0
  112. package/dist/lib/templates/python/schemas/schema-col-text.eta +1 -0
  113. package/dist/lib/templates/python/schemas/schema-col-timestamp.eta +1 -0
  114. package/dist/lib/templates/python/schemas/schema-col-timestamptz.eta +1 -0
  115. package/dist/lib/templates/python/schemas/schema-col-uuid.eta +1 -0
  116. package/dist/lib/templates/python/schemas/schema-col-varchar.eta +1 -0
  117. package/dist/lib/templates/python/schemas/schema.eta +87 -0
  118. package/dist/lib/templates/python/services/service.eta +163 -0
  119. package/dist/lib/templates/rest/controller-rest.eta +13 -12
  120. package/dist/lib/templates/rest/dto-rest.eta +173 -59
  121. package/dist/lib/types/generator.d.ts +241 -0
  122. package/dist/lib/types/generator.js +2 -0
  123. package/dist/lib/types/index.d.ts +1 -0
  124. package/dist/lib/utils/casing.d.ts +1 -0
  125. package/dist/lib/utils/casing.js +11 -1
  126. package/oclif.manifest.json +32 -3
  127. package/package.json +4 -2
  128. package/dist/lib/utils/relationships/parse.spec.d.ts +0 -1
  129. package/dist/lib/utils/relationships/parse.spec.js +0 -402
  130. package/dist/tests/templates/entities/types/array-template.spec.d.ts +0 -1
  131. package/dist/tests/templates/entities/types/array-template.spec.js +0 -82
  132. package/dist/tests/templates/entities/types/bigint-template.spec.d.ts +0 -1
  133. package/dist/tests/templates/entities/types/bigint-template.spec.js +0 -82
  134. package/dist/tests/templates/entities/types/boolean-template.spec.d.ts +0 -1
  135. package/dist/tests/templates/entities/types/boolean-template.spec.js +0 -77
  136. package/dist/tests/templates/entities/types/bytea-template.spec.d.ts +0 -1
  137. package/dist/tests/templates/entities/types/bytea-template.spec.js +0 -68
  138. package/dist/tests/templates/entities/types/char-template.spec.d.ts +0 -1
  139. package/dist/tests/templates/entities/types/char-template.spec.js +0 -68
  140. package/dist/tests/templates/entities/types/date-template.spec.d.ts +0 -1
  141. package/dist/tests/templates/entities/types/date-template.spec.js +0 -56
  142. package/dist/tests/templates/entities/types/decimal-field-validation.spec.d.ts +0 -1
  143. package/dist/tests/templates/entities/types/decimal-field-validation.spec.js +0 -100
  144. package/dist/tests/templates/entities/types/decimal-template.spec.d.ts +0 -1
  145. package/dist/tests/templates/entities/types/decimal-template.spec.js +0 -86
  146. package/dist/tests/templates/entities/types/double-template.spec.d.ts +0 -1
  147. package/dist/tests/templates/entities/types/double-template.spec.js +0 -70
  148. package/dist/tests/templates/entities/types/enum-template.spec.d.ts +0 -1
  149. package/dist/tests/templates/entities/types/enum-template.spec.js +0 -85
  150. package/dist/tests/templates/entities/types/float-template.spec.d.ts +0 -1
  151. package/dist/tests/templates/entities/types/float-template.spec.js +0 -70
  152. package/dist/tests/templates/entities/types/geometry-template.spec.d.ts +0 -1
  153. package/dist/tests/templates/entities/types/geometry-template.spec.js +0 -58
  154. package/dist/tests/templates/entities/types/inet-template.spec.d.ts +0 -1
  155. package/dist/tests/templates/entities/types/inet-template.spec.js +0 -69
  156. package/dist/tests/templates/entities/types/int4range-template.spec.d.ts +0 -1
  157. package/dist/tests/templates/entities/types/int4range-template.spec.js +0 -72
  158. package/dist/tests/templates/entities/types/integer-template.spec.d.ts +0 -1
  159. package/dist/tests/templates/entities/types/integer-template.spec.js +0 -79
  160. package/dist/tests/templates/entities/types/interval-template.spec.d.ts +0 -1
  161. package/dist/tests/templates/entities/types/interval-template.spec.js +0 -68
  162. package/dist/tests/templates/entities/types/json-plain-template.spec.d.ts +0 -1
  163. package/dist/tests/templates/entities/types/json-plain-template.spec.js +0 -54
  164. package/dist/tests/templates/entities/types/json-template.spec.d.ts +0 -1
  165. package/dist/tests/templates/entities/types/json-template.spec.js +0 -40
  166. package/dist/tests/templates/entities/types/jsonb-template.spec.d.ts +0 -1
  167. package/dist/tests/templates/entities/types/jsonb-template.spec.js +0 -40
  168. package/dist/tests/templates/entities/types/money-template.spec.d.ts +0 -1
  169. package/dist/tests/templates/entities/types/money-template.spec.js +0 -68
  170. package/dist/tests/templates/entities/types/numeric-template.spec.d.ts +0 -1
  171. package/dist/tests/templates/entities/types/numeric-template.spec.js +0 -84
  172. package/dist/tests/templates/entities/types/point-template.spec.d.ts +0 -1
  173. package/dist/tests/templates/entities/types/point-template.spec.js +0 -64
  174. package/dist/tests/templates/entities/types/polygon-template.spec.d.ts +0 -1
  175. package/dist/tests/templates/entities/types/polygon-template.spec.js +0 -70
  176. package/dist/tests/templates/entities/types/real-template.spec.d.ts +0 -1
  177. package/dist/tests/templates/entities/types/real-template.spec.js +0 -67
  178. package/dist/tests/templates/entities/types/serial-template.spec.d.ts +0 -1
  179. package/dist/tests/templates/entities/types/serial-template.spec.js +0 -113
  180. package/dist/tests/templates/entities/types/smallint-template.spec.d.ts +0 -1
  181. package/dist/tests/templates/entities/types/smallint-template.spec.js +0 -82
  182. package/dist/tests/templates/entities/types/string-template.spec.d.ts +0 -1
  183. package/dist/tests/templates/entities/types/string-template.spec.js +0 -111
  184. package/dist/tests/templates/entities/types/text-template.spec.d.ts +0 -1
  185. package/dist/tests/templates/entities/types/text-template.spec.js +0 -70
  186. package/dist/tests/templates/entities/types/time-template.spec.d.ts +0 -1
  187. package/dist/tests/templates/entities/types/time-template.spec.js +0 -68
  188. package/dist/tests/templates/entities/types/timestamp-template.spec.d.ts +0 -1
  189. package/dist/tests/templates/entities/types/timestamp-template.spec.js +0 -74
  190. package/dist/tests/templates/entities/types/timetz-template.spec.d.ts +0 -1
  191. package/dist/tests/templates/entities/types/timetz-template.spec.js +0 -68
  192. package/dist/tests/templates/entities/types/tsvector-template.spec.d.ts +0 -1
  193. package/dist/tests/templates/entities/types/tsvector-template.spec.js +0 -55
  194. package/dist/tests/templates/entities/types/uuid-template.spec.d.ts +0 -1
  195. package/dist/tests/templates/entities/types/uuid-template.spec.js +0 -82
  196. package/dist/tests/templates/entities/types/varchar-template.spec.d.ts +0 -1
  197. package/dist/tests/templates/entities/types/varchar-template.spec.js +0 -105
  198. package/dist/tests/templates/entities/types/xml-template.spec.d.ts +0 -1
  199. package/dist/tests/templates/entities/types/xml-template.spec.js +0 -55
  200. package/dist/tests/templates/entities/uuid-entity-template.spec.d.ts +0 -1
  201. package/dist/tests/templates/entities/uuid-entity-template.spec.js +0 -277
  202. package/dist/tests/utils/relationship-deduplication.spec.d.ts +0 -1
  203. package/dist/tests/utils/relationship-deduplication.spec.js +0 -98
@@ -0,0 +1,2 @@
1
+ declare const _default: never[];
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = [];
@@ -1,4 +1,5 @@
1
1
  import BaseCommand from "../../lib/base-command";
2
+ import { TargetLanguage } from "../../lib/types";
2
3
  export default class New extends BaseCommand {
3
4
  static description: string;
4
5
  static examples: string[];
@@ -6,6 +7,7 @@ export default class New extends BaseCommand {
6
7
  static flags: {
7
8
  name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
9
  type: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
10
+ language: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
9
11
  };
10
12
  static args: {
11
13
  name: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
@@ -13,10 +15,11 @@ export default class New extends BaseCommand {
13
15
  installModules(projectPath: string): Promise<void>;
14
16
  formatApp(projectPath: string): Promise<void>;
15
17
  startApp(projectPath: string): Promise<void>;
16
- cloneTemplate(projectPath: string): Promise<void>;
18
+ cloneTemplate(projectPath: string, language: TargetLanguage): Promise<void>;
17
19
  validateFlags(): Promise<{
18
20
  projectName: string;
19
21
  apiType: string;
22
+ language: TargetLanguage;
20
23
  }>;
21
24
  run(): Promise<void>;
22
25
  }
@@ -5,8 +5,15 @@ const core_1 = require("@oclif/core");
5
5
  const child_process_1 = require("child_process");
6
6
  const fs = tslib_1.__importStar(require("fs"));
7
7
  const shelljs_1 = tslib_1.__importDefault(require("shelljs"));
8
+ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
8
9
  const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
9
10
  const path = tslib_1.__importStar(require("path"));
11
+ const TEMPLATE_REPOS = {
12
+ typescript: "https://github.com/apsoai/service-template.git",
13
+ python: "https://github.com/apsoai/service-template-python.git",
14
+ go: "https://github.com/apsoai/service-template-go.git",
15
+ };
16
+ const PROJECT_NAME_PATTERN = /^[A-Za-z][\w-]*$/;
10
17
  class New extends base_command_1.default {
11
18
  constructor() {
12
19
  super(...arguments);
@@ -46,33 +53,63 @@ class New extends base_command_1.default {
46
53
  });
47
54
  });
48
55
  }
49
- async cloneTemplate(projectPath) {
50
- this.log("Cloning service template...");
51
- if (!fs.existsSync(projectPath) && this.CURR_DIR !== projectPath) {
52
- fs.mkdirSync(projectPath);
56
+ async cloneTemplate(projectPath, language) {
57
+ const repoUrl = TEMPLATE_REPOS[language];
58
+ this.log(`Cloning ${language} service template...`);
59
+ // Create directory if it doesn't exist
60
+ if (fs.existsSync(projectPath)) {
61
+ this.error(`Directory already exists: ${projectPath}`);
53
62
  }
54
- shelljs_1.default.cd(projectPath);
55
- const cloneResult = shelljs_1.default.exec(`git clone --depth=1 --branch=main https://github.com/apsoai/service-template.git ${projectPath}`, { silent: true }); // Use silent: true to suppress default output
63
+ else {
64
+ try {
65
+ fs.mkdirSync(projectPath, { recursive: true });
66
+ }
67
+ catch (error) {
68
+ this.error(`Failed to create directory: ${error.message}`);
69
+ }
70
+ }
71
+ // Use quoted paths to prevent command injection
72
+ const cloneResult = shelljs_1.default.exec(`git clone --depth=1 --branch=main "${repoUrl}" "${projectPath}"`, { silent: true });
56
73
  // Check if the command failed
57
74
  if (cloneResult.code !== 0) {
58
- // Provide a more generic error message for clone failures
75
+ // Clean up the failed directory
76
+ shelljs_1.default.rm("-rf", projectPath);
59
77
  this.error(`Failed to clone the template repository from GitHub.\n` +
60
78
  `Error Output:\n${cloneResult.stderr}\n\n` +
61
- `Please check your network connection and ensure the repository exists at https://github.com/apsoai/service-template.git\n\n` +
62
- `If the problem persists, please remove the partially created directory "${projectPath}" and try again.`);
63
- // Throwing error via this.error will stop execution
79
+ `Please check your network connection and ensure the repository exists at ${repoUrl}`);
64
80
  }
65
- // If clone was successful, proceed to remove the .git directory
66
- shelljs_1.default.exec(`rm -rf ${projectPath}/.git`);
67
- shelljs_1.default.cd(this.CURR_DIR);
81
+ // If clone was successful, proceed to remove the .git directory using safe shelljs method
82
+ shelljs_1.default.rm("-rf", path.join(projectPath, ".git"));
68
83
  }
69
84
  async validateFlags() {
70
85
  var _a;
71
86
  const { flags } = await this.parse(New);
72
87
  let projectName = flags.name;
73
88
  let apiType = (_a = flags.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();
89
+ let language = flags.language;
90
+ // Prompt for project name if not provided
74
91
  if (!projectName) {
75
- projectName = "";
92
+ const response = await inquirer_1.default.prompt([
93
+ {
94
+ type: "input",
95
+ name: "projectName",
96
+ message: "Enter your project name:",
97
+ validate: (input) => {
98
+ if (!input.trim()) {
99
+ return "Project name is required";
100
+ }
101
+ if (!PROJECT_NAME_PATTERN.test(input)) {
102
+ return "Project name must start with a letter and contain only letters, numbers, hyphens, and underscores";
103
+ }
104
+ return true;
105
+ },
106
+ },
107
+ ]);
108
+ projectName = response.projectName;
109
+ }
110
+ // Validate project name format (security: prevent command injection and path traversal)
111
+ if (!projectName || !PROJECT_NAME_PATTERN.test(projectName)) {
112
+ this.error("Project name must start with a letter and contain only letters, numbers, hyphens, and underscores");
76
113
  }
77
114
  if (!apiType) {
78
115
  apiType = "rest";
@@ -80,20 +117,71 @@ class New extends base_command_1.default {
80
117
  if (!["rest", "graphql"].includes(apiType)) {
81
118
  this.error("`apiType` must be 'rest' or 'graphql'");
82
119
  }
83
- return { projectName, apiType };
120
+ // Prompt for language if not provided
121
+ if (!language) {
122
+ const response = await inquirer_1.default.prompt([
123
+ {
124
+ type: "list",
125
+ name: "language",
126
+ message: "Select your target language:",
127
+ choices: [
128
+ { name: "TypeScript", value: "typescript" },
129
+ { name: "Python", value: "python" },
130
+ { name: "Go", value: "go" },
131
+ ],
132
+ default: "typescript",
133
+ },
134
+ ]);
135
+ language = response.language;
136
+ }
137
+ return { projectName: projectName, apiType, language };
84
138
  }
85
139
  async run() {
140
+ // Check for required dependencies
141
+ if (!shelljs_1.default.which("git")) {
142
+ this.error("git is required but not found. Please install git and try again.");
143
+ }
86
144
  this.log("Initializing New Apso Server...");
87
- const { projectName } = await this.validateFlags();
88
- const CURR_DIR = process.cwd();
89
- const projectPath = path.join(CURR_DIR, projectName);
90
- await this.cloneTemplate(projectPath);
91
- await this.installModules(projectPath);
92
- await this.formatApp(projectPath);
145
+ const { projectName, language } = await this.validateFlags();
146
+ const projectPath = path.join(this.CURR_DIR, projectName);
147
+ // Validate the resolved path is within current directory (prevent path traversal)
148
+ const resolvedPath = path.resolve(projectPath);
149
+ if (!resolvedPath.startsWith(path.resolve(this.CURR_DIR))) {
150
+ this.error("Invalid project name: path traversal detected");
151
+ }
152
+ await this.cloneTemplate(projectPath, language);
153
+ // Language-specific setup
154
+ switch (language) {
155
+ case "typescript": {
156
+ if (shelljs_1.default.which("npm")) {
157
+ await this.installModules(projectPath);
158
+ await this.formatApp(projectPath);
159
+ }
160
+ else {
161
+ this.log("Warning: npm not found. Skipping module installation.");
162
+ }
163
+ break;
164
+ }
165
+ case "python": {
166
+ this.log("Python project created. Run 'pip install -e .[dev]' to install dependencies.");
167
+ break;
168
+ }
169
+ case "go": {
170
+ this.log("Go project created. Run 'go mod tidy' to install dependencies.");
171
+ break;
172
+ }
173
+ // No default
174
+ }
175
+ this.log(`\nProject created at ${projectPath}`);
176
+ this.log(`Language: ${language}`);
93
177
  }
94
178
  }
95
179
  New.description = "Initialize your server project";
96
- New.examples = [`$ apso server new --name TestProject`];
180
+ New.examples = [
181
+ `$ apso server new --name TestProject`,
182
+ `$ apso server new --name TestProject --language python`,
183
+ `$ apso server new --name TestProject --language go`,
184
+ ];
97
185
  New.flags = {
98
186
  name: core_1.Flags.string({
99
187
  char: "n",
@@ -103,6 +191,11 @@ New.flags = {
103
191
  char: "t",
104
192
  description: "api type (rest or graphql)",
105
193
  }),
194
+ language: core_1.Flags.string({
195
+ char: "l",
196
+ description: "target language (typescript, python, go)",
197
+ options: ["typescript", "python", "go"],
198
+ }),
106
199
  };
107
200
  New.args = { name: core_1.Args.string() };
108
201
  exports.default = New;
@@ -6,6 +6,7 @@ export default class Scaffold extends BaseCommand {
6
6
  static flags: {
7
7
  help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
8
8
  "skip-format": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ language: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
9
10
  };
10
11
  static args: {};
11
12
  scaffoldServer(options: {
@@ -18,4 +19,13 @@ export default class Scaffold extends BaseCommand {
18
19
  setupRestFiles(dir: string, entity: Entity, relationshipMap: RelationshipMap, apiType: string, allEntities: Entity[]): Promise<void>;
19
20
  setupGraphqlFiles(dir: string, entity: Entity, relationshipMap: RelationshipMap): Promise<void>;
20
21
  run(): Promise<void>;
22
+ /**
23
+ * Scaffolds TypeScript code using the existing code path (for backward compatibility)
24
+ */
25
+ private scaffoldTypeScript;
26
+ /**
27
+ * Scaffolds code using the new generator system (for Python, Go, and future languages)
28
+ * Note: Sequential file operations are intentional to provide progress feedback
29
+ */
30
+ private scaffoldWithGenerator;
21
31
  }
@@ -3,11 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const core_1 = require("@oclif/core");
5
5
  const path = tslib_1.__importStar(require("path"));
6
+ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
6
7
  const lib_1 = require("../../lib");
7
8
  const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
8
9
  const apsorc_parser_1 = require("../../lib/apsorc-parser");
9
10
  const perf_hooks_1 = require("perf_hooks");
10
11
  const Eta = tslib_1.__importStar(require("eta"));
12
+ const file_system_1 = require("../../lib/utils/file-system");
11
13
  Eta.configure({
12
14
  views: path.join(__dirname, "../../lib/templates"),
13
15
  cache: false,
@@ -80,18 +82,83 @@ class Scaffold extends base_command_1.default {
80
82
  const { flags } = await this.parse(Scaffold);
81
83
  const skipFormat = flags["skip-format"];
82
84
  const totalBuildStart = perf_hooks_1.performance.now();
83
- const { rootFolder, entities, relationshipMap, apiType, auth } = (0, lib_1.parseApsorc)();
85
+ const { rootFolder, entities, relationshipMap, apiType, auth, language: configLanguage } = (0, lib_1.parseApsorc)();
86
+ // Resolve language: flag > .apsorc > prompt
87
+ let language;
88
+ if (flags.language) {
89
+ language = flags.language;
90
+ }
91
+ else if (configLanguage && (0, lib_1.isLanguageSupported)(configLanguage)) {
92
+ language = configLanguage;
93
+ console.log(`[apso] Using language from .apsorc: ${language}`);
94
+ }
95
+ else {
96
+ // Prompt the user
97
+ const implementedLanguages = (0, lib_1.getImplementedLanguages)();
98
+ const { selectedLanguage } = await inquirer_1.default.prompt([
99
+ {
100
+ type: "list",
101
+ name: "selectedLanguage",
102
+ message: "Select target language for code generation:",
103
+ choices: implementedLanguages.map((lang) => ({
104
+ name: lang === "typescript" ? "TypeScript (NestJS + TypeORM)" :
105
+ lang === "python" ? "Python (FastAPI + SQLAlchemy)" :
106
+ "Go (Gin + GORM)",
107
+ value: lang,
108
+ })),
109
+ default: "typescript",
110
+ },
111
+ ]);
112
+ language = selectedLanguage;
113
+ }
114
+ // Validate language support
115
+ if (!(0, lib_1.isLanguageSupported)(language)) {
116
+ this.error(`Unsupported language: ${language}. Supported: typescript, python, go`);
117
+ }
118
+ const implementedLanguages = (0, lib_1.getImplementedLanguages)();
119
+ if (!implementedLanguages.includes(language)) {
120
+ this.error(`Language '${language}' is not yet implemented. Currently available: ${implementedLanguages.join(", ")}`);
121
+ }
84
122
  const rootPath = path.join(process.cwd(), rootFolder);
85
123
  const autogenPath = path.join(rootPath, "autogen");
86
124
  const lowerCaseApiType = apiType.toLowerCase();
87
- await (0, lib_1.createEnums)(autogenPath, entities, lowerCaseApiType);
125
+ console.log(`[apso] Generating ${language} code for ${entities.length} entities...`);
126
+ // For TypeScript, use the existing code path (for backward compatibility)
127
+ // For other languages, use the new generator system
128
+ await (language === "typescript" ? this.scaffoldTypeScript({
129
+ autogenPath,
130
+ rootPath,
131
+ entities,
132
+ relationshipMap,
133
+ apiType: lowerCaseApiType,
134
+ auth,
135
+ skipFormat,
136
+ }) : this.scaffoldWithGenerator({
137
+ language,
138
+ rootFolder,
139
+ autogenPath,
140
+ entities,
141
+ relationshipMap,
142
+ apiType: lowerCaseApiType,
143
+ auth,
144
+ skipFormat,
145
+ }));
146
+ const totalBuildTime = perf_hooks_1.performance.now() - totalBuildStart;
147
+ console.log(`[apso] Finished building all entities in ${totalBuildTime.toFixed(2)} ms`);
148
+ }
149
+ /**
150
+ * Scaffolds TypeScript code using the existing code path (for backward compatibility)
151
+ */
152
+ async scaffoldTypeScript(options) {
153
+ const { autogenPath, rootPath, entities, relationshipMap, apiType, auth, skipFormat } = options;
154
+ await (0, lib_1.createEnums)(autogenPath, entities, apiType);
88
155
  await Promise.all(entities.map(async (entity) => {
89
156
  const scaffoldModel = this.scaffoldServer.bind(this);
90
157
  await scaffoldModel({
91
158
  dir: autogenPath,
92
159
  entity,
93
160
  relationshipMap,
94
- apiType: lowerCaseApiType,
161
+ apiType,
95
162
  allEntities: entities,
96
163
  });
97
164
  if (process.env.DEBUG) {
@@ -103,29 +170,152 @@ class Scaffold extends base_command_1.default {
103
170
  }));
104
171
  // Generate guards (auth and/or scope) based on .apsorc configuration
105
172
  await (0, lib_1.createGuards)(rootPath, entities, auth);
106
- await (0, lib_1.createIndexAppModule)(autogenPath, entities, lowerCaseApiType);
107
- const totalBuildTime = perf_hooks_1.performance.now() - totalBuildStart;
108
- console.log(`[apso] Finished building all entities in ${totalBuildTime.toFixed(2)} ms`);
109
- if (!skipFormat) {
173
+ await (0, lib_1.createIndexAppModule)(autogenPath, entities, apiType);
174
+ if (skipFormat) {
175
+ console.log("[apso] Skipping formatting (--skip-format flag set)");
176
+ }
177
+ else {
110
178
  const formatStart = perf_hooks_1.performance.now();
111
179
  console.log("[apso] Formatting files...");
112
180
  await this.runNpmCommand(["run", "format", "src/autogen/**/*.ts", "src/guards/**/*.ts"], true);
113
181
  const formatTime = perf_hooks_1.performance.now() - formatStart;
114
182
  console.log(`[apso] Finished formatting in ${formatTime.toFixed(2)} ms`);
115
183
  }
116
- else {
117
- console.log("[apso] Skipping formatting (--skip-format flag set)");
184
+ }
185
+ /**
186
+ * Scaffolds code using the new generator system (for Python, Go, and future languages)
187
+ * Note: Sequential file operations are intentional to provide progress feedback
188
+ */
189
+ /* eslint-disable no-await-in-loop */
190
+ async scaffoldWithGenerator(options) {
191
+ const { language, rootFolder, autogenPath, entities, relationshipMap, apiType, auth } = options;
192
+ // Create generator configuration
193
+ const generatorConfig = {
194
+ language,
195
+ rootFolder,
196
+ apiType,
197
+ entities,
198
+ relationshipMap,
199
+ auth,
200
+ };
201
+ // Create the appropriate generator
202
+ const generator = (0, lib_1.createGenerator)(generatorConfig);
203
+ // Validate configuration
204
+ const validationResult = generator.validateConfig(generatorConfig);
205
+ if (!validationResult.valid) {
206
+ this.error(`Configuration validation failed:\n${validationResult.errors.join("\n")}`);
207
+ }
208
+ if (validationResult.warnings.length > 0) {
209
+ console.log(`[apso] Warnings:\n${validationResult.warnings.join("\n")}`);
210
+ }
211
+ // Generate enums
212
+ const enumFiles = await generator.generateEnums(entities, apiType);
213
+ for (const file of enumFiles) {
214
+ const fullPath = path.join(autogenPath, file.path);
215
+ await (0, file_system_1.createFile)(fullPath, file.content);
216
+ console.log(`[apso] Generated ${fullPath}`);
217
+ }
218
+ // Generate files for each entity
219
+ for (const entity of entities) {
220
+ console.log(`[apso] Building... ${entity.name}`);
221
+ const entityBuildStart = perf_hooks_1.performance.now();
222
+ const entityRelationships = relationshipMap[entity.name] || [];
223
+ // Generate entity files
224
+ const entityFiles = await generator.generateEntity({
225
+ entity,
226
+ relationships: entityRelationships,
227
+ allEntities: entities,
228
+ apiType,
229
+ });
230
+ for (const file of entityFiles) {
231
+ const fullPath = path.join(autogenPath, file.path);
232
+ await (0, file_system_1.createFile)(fullPath, file.content);
233
+ }
234
+ // Generate DTO files
235
+ const dtoFiles = await generator.generateDto({
236
+ entity,
237
+ relationships: entityRelationships,
238
+ allEntities: entities,
239
+ apiType,
240
+ });
241
+ for (const file of dtoFiles) {
242
+ const fullPath = path.join(autogenPath, file.path);
243
+ await (0, file_system_1.createFile)(fullPath, file.content);
244
+ }
245
+ // Generate service files
246
+ const serviceFiles = await generator.generateService({
247
+ entity,
248
+ relationships: entityRelationships,
249
+ allEntities: entities,
250
+ apiType,
251
+ relationshipMap,
252
+ });
253
+ for (const file of serviceFiles) {
254
+ const fullPath = path.join(autogenPath, file.path);
255
+ await (0, file_system_1.createFile)(fullPath, file.content);
256
+ }
257
+ // Generate controller files
258
+ const controllerFiles = await generator.generateController({
259
+ entity,
260
+ relationships: entityRelationships,
261
+ allEntities: entities,
262
+ apiType,
263
+ relationshipMap,
264
+ });
265
+ for (const file of controllerFiles) {
266
+ const fullPath = path.join(autogenPath, file.path);
267
+ await (0, file_system_1.createFile)(fullPath, file.content);
268
+ }
269
+ // Generate module files
270
+ const moduleFiles = await generator.generateModule({
271
+ entity,
272
+ relationships: entityRelationships,
273
+ allEntities: entities,
274
+ apiType,
275
+ });
276
+ for (const file of moduleFiles) {
277
+ const fullPath = path.join(autogenPath, file.path);
278
+ await (0, file_system_1.createFile)(fullPath, file.content);
279
+ }
280
+ const entityBuildTime = perf_hooks_1.performance.now() - entityBuildStart;
281
+ console.log(`[apso] Finished building entity '${entity.name}' in ${entityBuildTime.toFixed(2)} ms`);
282
+ }
283
+ // Generate guards
284
+ const guardFiles = await generator.generateGuards(entities, auth);
285
+ const rootPath = path.dirname(autogenPath);
286
+ for (const file of guardFiles) {
287
+ const fullPath = path.join(rootPath, "autogen", file.path);
288
+ await (0, file_system_1.createFile)(fullPath, file.content);
289
+ console.log(`[apso] Generated ${fullPath}`);
290
+ }
291
+ // Generate index module
292
+ const indexFiles = await generator.generateIndexModule(entities, apiType);
293
+ for (const file of indexFiles) {
294
+ const fullPath = path.join(autogenPath, file.path);
295
+ await (0, file_system_1.createFile)(fullPath, file.content);
296
+ console.log(`[apso] Generated ${fullPath}`);
118
297
  }
119
298
  }
120
299
  }
121
300
  Scaffold.description = "Setup new entities and interfaces for an Apso Server";
122
- Scaffold.examples = [`$ apso server scaffold`, `$ apso server scaffold --skip-format`];
301
+ Scaffold.examples = [
302
+ `$ apso server scaffold`,
303
+ `$ apso server scaffold --skip-format`,
304
+ `$ apso server scaffold --language typescript`,
305
+ `$ apso server scaffold --language python`,
306
+ `$ apso server scaffold --language go`,
307
+ ];
123
308
  Scaffold.flags = {
124
309
  help: core_1.Flags.help({ char: "h" }),
125
310
  "skip-format": core_1.Flags.boolean({
126
311
  description: "Skip the formatting step (useful when prettier is not available)",
127
312
  default: false,
128
313
  }),
314
+ language: core_1.Flags.string({
315
+ char: "l",
316
+ description: "Target language for code generation (typescript, python, go). If not specified, uses .apsorc config or prompts.",
317
+ options: ["typescript", "python", "go"],
318
+ }),
129
319
  };
130
320
  Scaffold.args = {};
131
321
  exports.default = Scaffold;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Platform API Client
3
+ *
4
+ * HTTP client for making authenticated requests to the Apso platform.
5
+ * Features:
6
+ * - Automatic token refresh
7
+ * - Request/response interceptors
8
+ * - Retry with exponential backoff
9
+ * - Timeout handling
10
+ */
11
+ import { ApiRequestOptions } from "./types";
12
+ /**
13
+ * Custom error class for API errors
14
+ */
15
+ export declare class ApiClientError extends Error {
16
+ statusCode: number;
17
+ details?: Record<string, unknown> | undefined;
18
+ constructor(message: string, statusCode: number, details?: Record<string, unknown> | undefined);
19
+ /**
20
+ * Check if error is due to authentication failure
21
+ */
22
+ isAuthError(): boolean;
23
+ /**
24
+ * Check if error is a rate limit error
25
+ */
26
+ isRateLimitError(): boolean;
27
+ /**
28
+ * Check if error is a server error (5xx)
29
+ */
30
+ isServerError(): boolean;
31
+ }
32
+ /**
33
+ * Check if we're running in an environment with network access
34
+ */
35
+ export declare function checkNetworkConnectivity(): Promise<boolean>;
36
+ /**
37
+ * Make an authenticated request to the platform API
38
+ * Note: Sequential retries are intentional for proper backoff behavior
39
+ */
40
+ export declare function apiRequest<T>(path: string, options?: ApiRequestOptions): Promise<T>;
41
+ /**
42
+ * Platform API client with typed methods
43
+ */
44
+ export declare const api: {
45
+ /**
46
+ * Make a GET request
47
+ */
48
+ get<T>(path: string, options?: Omit<ApiRequestOptions, "method" | "body">): Promise<T>;
49
+ /**
50
+ * Make a POST request
51
+ */
52
+ post<T_1>(path: string, body?: unknown, options?: Omit<ApiRequestOptions, "method" | "body">): Promise<T_1>;
53
+ /**
54
+ * Make a PUT request
55
+ */
56
+ put<T_2>(path: string, body?: unknown, options?: Omit<ApiRequestOptions, "method" | "body">): Promise<T_2>;
57
+ /**
58
+ * Make a PATCH request
59
+ */
60
+ patch<T_3>(path: string, body?: unknown, options?: Omit<ApiRequestOptions, "method" | "body">): Promise<T_3>;
61
+ /**
62
+ * Make a DELETE request
63
+ */
64
+ delete<T_4>(path: string, options?: Omit<ApiRequestOptions, "method">): Promise<T_4>;
65
+ };