@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.
- package/dist/autogen/index.d.ts +2 -0
- package/dist/autogen/index.js +3 -0
- package/dist/commands/server/new.d.ts +4 -1
- package/dist/commands/server/new.js +115 -22
- package/dist/commands/server/scaffold.d.ts +10 -0
- package/dist/commands/server/scaffold.js +200 -10
- package/dist/lib/api/client.d.ts +65 -0
- package/dist/lib/api/client.js +305 -0
- package/dist/lib/api/index.d.ts +8 -0
- package/dist/lib/api/index.js +22 -0
- package/dist/lib/api/services.d.ts +166 -0
- package/dist/lib/api/services.js +202 -0
- package/dist/lib/api/types.d.ts +212 -0
- package/dist/lib/api/types.js +7 -0
- package/dist/lib/apsorc-parser.d.ts +3 -0
- package/dist/lib/apsorc-parser.js +4 -0
- package/dist/lib/config/index.d.ts +8 -0
- package/dist/lib/config/index.js +17 -0
- package/dist/lib/config/manager.d.ts +127 -0
- package/dist/lib/config/manager.js +304 -0
- package/dist/lib/config/paths.d.ts +65 -0
- package/dist/lib/config/paths.js +127 -0
- package/dist/lib/config/types.d.ts +230 -0
- package/dist/lib/config/types.js +30 -0
- package/dist/lib/generators/base.d.ts +93 -0
- package/dist/lib/generators/base.js +90 -0
- package/dist/lib/generators/go.d.ts +37 -0
- package/dist/lib/generators/go.js +340 -0
- package/dist/lib/generators/index.d.ts +21 -0
- package/dist/lib/generators/index.js +51 -0
- package/dist/lib/generators/python.d.ts +21 -0
- package/dist/lib/generators/python.js +253 -0
- package/dist/lib/generators/typescript.d.ts +25 -0
- package/dist/lib/generators/typescript.js +373 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +9 -1
- package/dist/lib/templates/entities/entity-col-timestamp.eta +9 -2
- package/dist/lib/templates/entities/entity-col-timestamptz.eta +9 -2
- package/dist/lib/templates/entities/entity.eta +18 -5
- package/dist/lib/templates/go/dto/dto-col-array.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-boolean.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-date.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-decimal.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-enum.eta +8 -0
- package/dist/lib/templates/go/dto/dto-col-float.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-integer.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-json.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-jsonb.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-numeric.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-string.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-text.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-timestamp.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-timestamptz.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-uuid.eta +7 -0
- package/dist/lib/templates/go/dto/dto-col-varchar.eta +7 -0
- package/dist/lib/templates/go/dto/dto.eta +78 -0
- package/dist/lib/templates/go/enums.eta +44 -0
- package/dist/lib/templates/go/handlers/handler.eta +212 -0
- package/dist/lib/templates/go/header.eta +3 -0
- package/dist/lib/templates/go/index-module.eta +53 -0
- package/dist/lib/templates/go/middleware/auth.eta +101 -0
- package/dist/lib/templates/go/models/model-col-array.eta +1 -0
- package/dist/lib/templates/go/models/model-col-boolean.eta +1 -0
- package/dist/lib/templates/go/models/model-col-date.eta +1 -0
- package/dist/lib/templates/go/models/model-col-decimal.eta +1 -0
- package/dist/lib/templates/go/models/model-col-enum.eta +2 -0
- package/dist/lib/templates/go/models/model-col-float.eta +1 -0
- package/dist/lib/templates/go/models/model-col-integer.eta +1 -0
- package/dist/lib/templates/go/models/model-col-json.eta +1 -0
- package/dist/lib/templates/go/models/model-col-jsonb.eta +1 -0
- package/dist/lib/templates/go/models/model-col-numeric.eta +1 -0
- package/dist/lib/templates/go/models/model-col-string.eta +1 -0
- package/dist/lib/templates/go/models/model-col-text.eta +1 -0
- package/dist/lib/templates/go/models/model-col-timestamp.eta +1 -0
- package/dist/lib/templates/go/models/model-col-timestamptz.eta +1 -0
- package/dist/lib/templates/go/models/model-col-uuid.eta +1 -0
- package/dist/lib/templates/go/models/model-col-varchar.eta +1 -0
- package/dist/lib/templates/go/models/model.eta +53 -0
- package/dist/lib/templates/go/services/service.eta +190 -0
- package/dist/lib/templates/python/enums.eta +10 -0
- package/dist/lib/templates/python/header.eta +6 -0
- package/dist/lib/templates/python/index-module.eta +18 -0
- package/dist/lib/templates/python/models/model-col-array.eta +4 -0
- package/dist/lib/templates/python/models/model-col-boolean.eta +5 -0
- package/dist/lib/templates/python/models/model-col-date.eta +4 -0
- package/dist/lib/templates/python/models/model-col-decimal.eta +4 -0
- package/dist/lib/templates/python/models/model-col-enum.eta +16 -0
- package/dist/lib/templates/python/models/model-col-float.eta +5 -0
- package/dist/lib/templates/python/models/model-col-integer.eta +6 -0
- package/dist/lib/templates/python/models/model-col-json.eta +4 -0
- package/dist/lib/templates/python/models/model-col-jsonb.eta +4 -0
- package/dist/lib/templates/python/models/model-col-numeric.eta +4 -0
- package/dist/lib/templates/python/models/model-col-string.eta +6 -0
- package/dist/lib/templates/python/models/model-col-text.eta +6 -0
- package/dist/lib/templates/python/models/model-col-timestamp.eta +11 -0
- package/dist/lib/templates/python/models/model-col-timestamptz.eta +11 -0
- package/dist/lib/templates/python/models/model-col-uuid.eta +5 -0
- package/dist/lib/templates/python/models/model-col-varchar.eta +6 -0
- package/dist/lib/templates/python/models/model.eta +124 -0
- package/dist/lib/templates/python/routers/router.eta +206 -0
- package/dist/lib/templates/python/schemas/schema-col-array.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-boolean.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-date.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-decimal.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-enum.eta +6 -0
- package/dist/lib/templates/python/schemas/schema-col-float.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-integer.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-json.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-jsonb.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-numeric.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-string.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-text.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-timestamp.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-timestamptz.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-uuid.eta +1 -0
- package/dist/lib/templates/python/schemas/schema-col-varchar.eta +1 -0
- package/dist/lib/templates/python/schemas/schema.eta +87 -0
- package/dist/lib/templates/python/services/service.eta +163 -0
- package/dist/lib/templates/rest/controller-rest.eta +13 -12
- package/dist/lib/templates/rest/dto-rest.eta +173 -59
- package/dist/lib/types/generator.d.ts +241 -0
- package/dist/lib/types/generator.js +2 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/utils/casing.d.ts +1 -0
- package/dist/lib/utils/casing.js +11 -1
- package/oclif.manifest.json +32 -3
- package/package.json +4 -2
- package/dist/lib/utils/relationships/parse.spec.d.ts +0 -1
- package/dist/lib/utils/relationships/parse.spec.js +0 -402
- package/dist/tests/templates/entities/types/array-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/array-template.spec.js +0 -82
- package/dist/tests/templates/entities/types/bigint-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/bigint-template.spec.js +0 -82
- package/dist/tests/templates/entities/types/boolean-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/boolean-template.spec.js +0 -77
- package/dist/tests/templates/entities/types/bytea-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/bytea-template.spec.js +0 -68
- package/dist/tests/templates/entities/types/char-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/char-template.spec.js +0 -68
- package/dist/tests/templates/entities/types/date-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/date-template.spec.js +0 -56
- package/dist/tests/templates/entities/types/decimal-field-validation.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/decimal-field-validation.spec.js +0 -100
- package/dist/tests/templates/entities/types/decimal-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/decimal-template.spec.js +0 -86
- package/dist/tests/templates/entities/types/double-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/double-template.spec.js +0 -70
- package/dist/tests/templates/entities/types/enum-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/enum-template.spec.js +0 -85
- package/dist/tests/templates/entities/types/float-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/float-template.spec.js +0 -70
- package/dist/tests/templates/entities/types/geometry-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/geometry-template.spec.js +0 -58
- package/dist/tests/templates/entities/types/inet-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/inet-template.spec.js +0 -69
- package/dist/tests/templates/entities/types/int4range-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/int4range-template.spec.js +0 -72
- package/dist/tests/templates/entities/types/integer-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/integer-template.spec.js +0 -79
- package/dist/tests/templates/entities/types/interval-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/interval-template.spec.js +0 -68
- package/dist/tests/templates/entities/types/json-plain-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/json-plain-template.spec.js +0 -54
- package/dist/tests/templates/entities/types/json-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/json-template.spec.js +0 -40
- package/dist/tests/templates/entities/types/jsonb-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/jsonb-template.spec.js +0 -40
- package/dist/tests/templates/entities/types/money-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/money-template.spec.js +0 -68
- package/dist/tests/templates/entities/types/numeric-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/numeric-template.spec.js +0 -84
- package/dist/tests/templates/entities/types/point-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/point-template.spec.js +0 -64
- package/dist/tests/templates/entities/types/polygon-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/polygon-template.spec.js +0 -70
- package/dist/tests/templates/entities/types/real-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/real-template.spec.js +0 -67
- package/dist/tests/templates/entities/types/serial-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/serial-template.spec.js +0 -113
- package/dist/tests/templates/entities/types/smallint-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/smallint-template.spec.js +0 -82
- package/dist/tests/templates/entities/types/string-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/string-template.spec.js +0 -111
- package/dist/tests/templates/entities/types/text-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/text-template.spec.js +0 -70
- package/dist/tests/templates/entities/types/time-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/time-template.spec.js +0 -68
- package/dist/tests/templates/entities/types/timestamp-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/timestamp-template.spec.js +0 -74
- package/dist/tests/templates/entities/types/timetz-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/timetz-template.spec.js +0 -68
- package/dist/tests/templates/entities/types/tsvector-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/tsvector-template.spec.js +0 -55
- package/dist/tests/templates/entities/types/uuid-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/uuid-template.spec.js +0 -82
- package/dist/tests/templates/entities/types/varchar-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/varchar-template.spec.js +0 -105
- package/dist/tests/templates/entities/types/xml-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/types/xml-template.spec.js +0 -55
- package/dist/tests/templates/entities/uuid-entity-template.spec.d.ts +0 -1
- package/dist/tests/templates/entities/uuid-entity-template.spec.js +0 -277
- package/dist/tests/utils/relationship-deduplication.spec.d.ts +0 -1
- package/dist/tests/utils/relationship-deduplication.spec.js +0 -98
|
@@ -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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
//
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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 = [
|
|
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
|
-
|
|
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
|
|
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,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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 = [
|
|
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
|
+
};
|