fexapi 0.1.0 → 0.1.1
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/README.md +115 -1
- package/dist/cli/args.d.ts +15 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +53 -0
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +40 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +73 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +66 -0
- package/dist/commands/serve.d.ts +5 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +56 -0
- package/dist/config/generated-spec.d.ts +3 -0
- package/dist/config/generated-spec.d.ts.map +1 -0
- package/dist/config/generated-spec.js +26 -0
- package/dist/config/runtime-config.d.ts +3 -0
- package/dist/config/runtime-config.d.ts.map +1 -0
- package/dist/config/runtime-config.js +97 -0
- package/dist/config/schema-definitions.d.ts +3 -0
- package/dist/config/schema-definitions.d.ts.map +1 -0
- package/dist/config/schema-definitions.js +90 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/index.js +23 -386
- package/dist/project/detect.d.ts +4 -0
- package/dist/project/detect.d.ts.map +1 -0
- package/dist/project/detect.js +113 -0
- package/dist/project/paths.d.ts +3 -0
- package/dist/project/paths.d.ts.map +1 -0
- package/dist/project/paths.js +28 -0
- package/dist/server.d.ts +4 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +119 -40
- package/dist/types/config.d.ts +20 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +2 -0
- package/dist/types/project.d.ts +7 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +2 -0
- package/package.json +53 -51
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadSchemaDefinitions = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const yaml_1 = require("yaml");
|
|
7
|
+
const VALID_TYPES = new Set([
|
|
8
|
+
"number",
|
|
9
|
+
"string",
|
|
10
|
+
"boolean",
|
|
11
|
+
"date",
|
|
12
|
+
"uuid",
|
|
13
|
+
"email",
|
|
14
|
+
"url",
|
|
15
|
+
"name",
|
|
16
|
+
"phone",
|
|
17
|
+
]);
|
|
18
|
+
const isRecord = (value) => {
|
|
19
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
|
+
};
|
|
21
|
+
const parseSchemaDefinition = (schemaName, rawValue) => {
|
|
22
|
+
if (!isRecord(rawValue)) {
|
|
23
|
+
console.error(`Invalid schemas/${schemaName}.yaml: expected root object of field definitions.`);
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const result = {};
|
|
27
|
+
for (const [fieldName, rawFieldConfig] of Object.entries(rawValue)) {
|
|
28
|
+
if (!isRecord(rawFieldConfig)) {
|
|
29
|
+
console.error(`Invalid schemas/${schemaName}.yaml field \`${fieldName}\`: expected object with type/faker/min/max.`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const rawType = rawFieldConfig.type;
|
|
33
|
+
if (typeof rawType !== "string") {
|
|
34
|
+
console.error(`Invalid schemas/${schemaName}.yaml field \`${fieldName}\`: missing string \`type\`.`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const normalizedType = rawType.trim().toLowerCase();
|
|
38
|
+
if (!VALID_TYPES.has(normalizedType)) {
|
|
39
|
+
console.error(`Invalid schemas/${schemaName}.yaml field \`${fieldName}\`: unknown type \`${rawType}\`.`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const minValue = rawFieldConfig.min;
|
|
43
|
+
const maxValue = rawFieldConfig.max;
|
|
44
|
+
result[fieldName] = {
|
|
45
|
+
type: normalizedType,
|
|
46
|
+
faker: typeof rawFieldConfig.faker === "string"
|
|
47
|
+
? rawFieldConfig.faker.trim()
|
|
48
|
+
: undefined,
|
|
49
|
+
min: typeof minValue === "number" && Number.isFinite(minValue)
|
|
50
|
+
? minValue
|
|
51
|
+
: undefined,
|
|
52
|
+
max: typeof maxValue === "number" && Number.isFinite(maxValue)
|
|
53
|
+
? maxValue
|
|
54
|
+
: undefined,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
58
|
+
};
|
|
59
|
+
const loadSchemaDefinitions = (projectRoot) => {
|
|
60
|
+
const schemasDirectoryPath = (0, node_path_1.join)(projectRoot, "schemas");
|
|
61
|
+
if (!(0, node_fs_1.existsSync)(schemasDirectoryPath)) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
const result = {};
|
|
65
|
+
for (const entry of (0, node_fs_1.readdirSync)(schemasDirectoryPath)) {
|
|
66
|
+
const schemaPath = (0, node_path_1.join)(schemasDirectoryPath, entry);
|
|
67
|
+
if (!(0, node_fs_1.statSync)(schemaPath).isFile()) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const extension = (0, node_path_1.extname)(entry).toLowerCase();
|
|
71
|
+
if (extension !== ".yaml" && extension !== ".yml") {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const schemaName = (0, node_path_1.basename)(entry, extension).toLowerCase();
|
|
75
|
+
try {
|
|
76
|
+
const source = (0, node_fs_1.readFileSync)(schemaPath, "utf-8");
|
|
77
|
+
const parsed = (0, yaml_1.parse)(source);
|
|
78
|
+
const definition = parseSchemaDefinition(schemaName, parsed);
|
|
79
|
+
if (definition) {
|
|
80
|
+
result[schemaName] = definition;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
console.error(`Failed to parse ${schemaPath}: ${message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
};
|
|
90
|
+
exports.loadSchemaDefinitions = loadSchemaDefinitions;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,8BAA8B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,353 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
4
|
+
const args_1 = require("./cli/args");
|
|
5
|
+
const help_1 = require("./cli/help");
|
|
6
|
+
const generate_1 = require("./commands/generate");
|
|
7
|
+
const init_1 = require("./commands/init");
|
|
8
|
+
const serve_1 = require("./commands/serve");
|
|
8
9
|
const args = process.argv.slice(2);
|
|
9
|
-
const GENERATED_SPEC_RELATIVE_PATH = "fexapi/generated.api.json";
|
|
10
|
-
const printHelp = () => {
|
|
11
|
-
console.log("fexapi-cli");
|
|
12
|
-
console.log("");
|
|
13
|
-
console.log("Usage:");
|
|
14
|
-
console.log(" fexapi init [--force]");
|
|
15
|
-
console.log(" fexapi generate");
|
|
16
|
-
console.log(" fexapi serve [--host <host>] [--port <number>]");
|
|
17
|
-
console.log(" fexapi [--host <host>] [--port <number>]");
|
|
18
|
-
console.log(" fexapi --help");
|
|
19
|
-
console.log("");
|
|
20
|
-
console.log("Examples:");
|
|
21
|
-
console.log(" fexapi init");
|
|
22
|
-
console.log(" fexapi init --force");
|
|
23
|
-
console.log(" fexapi generate");
|
|
24
|
-
console.log(" fexapi serve --host 127.0.0.1 --port 5000");
|
|
25
|
-
console.log(" fexapi --port 4000");
|
|
26
|
-
console.log("");
|
|
27
|
-
console.log("Package manager usage:");
|
|
28
|
-
console.log(" npx fexapi init");
|
|
29
|
-
console.log(" pnpm dlx fexapi init");
|
|
30
|
-
console.log(" yarn dlx fexapi init");
|
|
31
|
-
console.log("");
|
|
32
|
-
console.log("`fexapi init` creates:");
|
|
33
|
-
console.log(" fexapi.config.json");
|
|
34
|
-
console.log(" fexapi/schema.fexapi");
|
|
35
|
-
console.log("");
|
|
36
|
-
console.log("Then run:");
|
|
37
|
-
console.log(" fexapi generate");
|
|
38
|
-
console.log(" fexapi serve");
|
|
39
|
-
};
|
|
40
|
-
const findClosestPackageJson = (startDirectory) => {
|
|
41
|
-
let currentDirectory = startDirectory;
|
|
42
|
-
while (true) {
|
|
43
|
-
const candidate = (0, node_path_1.join)(currentDirectory, "package.json");
|
|
44
|
-
if ((0, node_fs_1.existsSync)(candidate)) {
|
|
45
|
-
return candidate;
|
|
46
|
-
}
|
|
47
|
-
const parentDirectory = (0, node_path_1.dirname)(currentDirectory);
|
|
48
|
-
if (parentDirectory === currentDirectory) {
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
currentDirectory = parentDirectory;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const readDependencyNames = (packageJsonPath) => {
|
|
55
|
-
const packageJsonText = (0, node_fs_1.readFileSync)(packageJsonPath, "utf-8");
|
|
56
|
-
const packageJson = JSON.parse(packageJsonText);
|
|
57
|
-
const dependencies = (packageJson.dependencies ?? {});
|
|
58
|
-
const devDependencies = (packageJson.devDependencies ?? {});
|
|
59
|
-
return new Set([
|
|
60
|
-
...Object.keys(dependencies),
|
|
61
|
-
...Object.keys(devDependencies),
|
|
62
|
-
]);
|
|
63
|
-
};
|
|
64
|
-
const readWorkspaceDependencyNames = (projectRoot) => {
|
|
65
|
-
const result = new Set();
|
|
66
|
-
const rootsToScan = [
|
|
67
|
-
(0, node_path_1.join)(projectRoot, "apps"),
|
|
68
|
-
(0, node_path_1.join)(projectRoot, "packages"),
|
|
69
|
-
];
|
|
70
|
-
for (const rootPath of rootsToScan) {
|
|
71
|
-
if (!(0, node_fs_1.existsSync)(rootPath)) {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
for (const entry of (0, node_fs_1.readdirSync)(rootPath)) {
|
|
75
|
-
const entryPath = (0, node_path_1.join)(rootPath, entry);
|
|
76
|
-
if (!(0, node_fs_1.statSync)(entryPath).isDirectory()) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const packageJsonPath = (0, node_path_1.join)(entryPath, "package.json");
|
|
80
|
-
if (!(0, node_fs_1.existsSync)(packageJsonPath)) {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const dependencyNames = readDependencyNames(packageJsonPath);
|
|
84
|
-
for (const dependencyName of dependencyNames) {
|
|
85
|
-
result.add(dependencyName);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return result;
|
|
90
|
-
};
|
|
91
|
-
const detectProject = (packageJsonPath, projectRoot) => {
|
|
92
|
-
const dependencyNames = readDependencyNames(packageJsonPath);
|
|
93
|
-
const frameworks = new Set();
|
|
94
|
-
const tooling = new Set();
|
|
95
|
-
if (dependencyNames.has("turbo") ||
|
|
96
|
-
(0, node_fs_1.existsSync)((0, node_path_1.join)(projectRoot, "turbo.json"))) {
|
|
97
|
-
tooling.add("turborepo");
|
|
98
|
-
}
|
|
99
|
-
if (dependencyNames.has("nx") || (0, node_fs_1.existsSync)((0, node_path_1.join)(projectRoot, "nx.json"))) {
|
|
100
|
-
tooling.add("nx");
|
|
101
|
-
}
|
|
102
|
-
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(projectRoot, "pnpm-workspace.yaml"))) {
|
|
103
|
-
tooling.add("pnpm-workspace");
|
|
104
|
-
}
|
|
105
|
-
if (tooling.has("turborepo") || tooling.has("pnpm-workspace")) {
|
|
106
|
-
const workspaceDeps = readWorkspaceDependencyNames(projectRoot);
|
|
107
|
-
for (const dependencyName of workspaceDeps) {
|
|
108
|
-
dependencyNames.add(dependencyName);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (dependencyNames.has("next")) {
|
|
112
|
-
frameworks.add("nextjs");
|
|
113
|
-
}
|
|
114
|
-
if (dependencyNames.has("react") || dependencyNames.has("react-dom")) {
|
|
115
|
-
frameworks.add("reactjs");
|
|
116
|
-
}
|
|
117
|
-
if (dependencyNames.has("vue")) {
|
|
118
|
-
frameworks.add("vue");
|
|
119
|
-
}
|
|
120
|
-
if (dependencyNames.has("nuxt")) {
|
|
121
|
-
frameworks.add("nuxt");
|
|
122
|
-
}
|
|
123
|
-
if (dependencyNames.has("svelte")) {
|
|
124
|
-
frameworks.add("svelte");
|
|
125
|
-
}
|
|
126
|
-
if (dependencyNames.has("@sveltejs/kit")) {
|
|
127
|
-
frameworks.add("sveltekit");
|
|
128
|
-
}
|
|
129
|
-
if (dependencyNames.has("@angular/core")) {
|
|
130
|
-
frameworks.add("angular");
|
|
131
|
-
}
|
|
132
|
-
if (dependencyNames.has("solid-js")) {
|
|
133
|
-
frameworks.add("solid");
|
|
134
|
-
}
|
|
135
|
-
if (dependencyNames.has("@remix-run/react") ||
|
|
136
|
-
dependencyNames.has("@remix-run/node")) {
|
|
137
|
-
frameworks.add("remix");
|
|
138
|
-
}
|
|
139
|
-
if (dependencyNames.has("astro")) {
|
|
140
|
-
frameworks.add("astro");
|
|
141
|
-
}
|
|
142
|
-
if (dependencyNames.has("vite")) {
|
|
143
|
-
tooling.add("vite");
|
|
144
|
-
}
|
|
145
|
-
const frameworkList = Array.from(frameworks);
|
|
146
|
-
const primaryFramework = frameworkList[0] ?? "unknown";
|
|
147
|
-
return {
|
|
148
|
-
primaryFramework,
|
|
149
|
-
frameworks: frameworkList.length > 0 ? frameworkList : ["unknown"],
|
|
150
|
-
tooling: Array.from(tooling),
|
|
151
|
-
};
|
|
152
|
-
};
|
|
153
|
-
const getSchemaTemplate = (framework) => {
|
|
154
|
-
const frameworkHint = framework === "nextjs"
|
|
155
|
-
? "# Framework: Next.js"
|
|
156
|
-
: framework === "reactjs"
|
|
157
|
-
? "# Framework: React"
|
|
158
|
-
: "# Framework: unknown";
|
|
159
|
-
return [
|
|
160
|
-
frameworkHint,
|
|
161
|
-
"# Server",
|
|
162
|
-
"port: 4000",
|
|
163
|
-
"",
|
|
164
|
-
"# Routes",
|
|
165
|
-
"# Format: METHOD /endpoint: field:type,field:type",
|
|
166
|
-
"GET /users: id:uuid,fullName:name,username:string,email:email,avatarUrl:url",
|
|
167
|
-
"GET /posts: id:uuid,title:string,body:string,createdAt:date",
|
|
168
|
-
].join("\n");
|
|
169
|
-
};
|
|
170
|
-
const parseInitOptions = (initArgs) => {
|
|
171
|
-
const validFlags = new Set(["--force"]);
|
|
172
|
-
const invalidFlags = initArgs.filter((value) => value.startsWith("-") && !validFlags.has(value));
|
|
173
|
-
if (invalidFlags.length > 0) {
|
|
174
|
-
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
175
|
-
}
|
|
176
|
-
return { force: initArgs.includes("--force") };
|
|
177
|
-
};
|
|
178
|
-
const parseGenerateOptions = (generateArgs) => {
|
|
179
|
-
const invalidFlags = generateArgs.filter((value) => value.startsWith("-"));
|
|
180
|
-
if (invalidFlags.length > 0) {
|
|
181
|
-
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
182
|
-
}
|
|
183
|
-
return {};
|
|
184
|
-
};
|
|
185
|
-
const resolveProjectRoot = () => {
|
|
186
|
-
const packageJsonPath = findClosestPackageJson(process.cwd());
|
|
187
|
-
if (!packageJsonPath) {
|
|
188
|
-
return undefined;
|
|
189
|
-
}
|
|
190
|
-
return (0, node_path_1.dirname)(packageJsonPath);
|
|
191
|
-
};
|
|
192
|
-
const initializeProject = ({ force }) => {
|
|
193
|
-
const packageJsonPath = findClosestPackageJson(process.cwd());
|
|
194
|
-
if (!packageJsonPath) {
|
|
195
|
-
console.error("Could not find package.json in this directory or parent directories.");
|
|
196
|
-
return 1;
|
|
197
|
-
}
|
|
198
|
-
const projectRoot = (0, node_path_1.dirname)(packageJsonPath);
|
|
199
|
-
const detectedProject = detectProject(packageJsonPath, projectRoot);
|
|
200
|
-
const fexapiDirectoryPath = (0, node_path_1.join)(projectRoot, "fexapi");
|
|
201
|
-
const schemaPath = (0, node_path_1.join)(fexapiDirectoryPath, "schema.fexapi");
|
|
202
|
-
const configPath = (0, node_path_1.join)(projectRoot, "fexapi.config.json");
|
|
203
|
-
(0, node_fs_1.mkdirSync)(fexapiDirectoryPath, { recursive: true });
|
|
204
|
-
const configExists = (0, node_fs_1.existsSync)(configPath);
|
|
205
|
-
const schemaExists = (0, node_fs_1.existsSync)(schemaPath);
|
|
206
|
-
const config = {
|
|
207
|
-
framework: detectedProject.primaryFramework,
|
|
208
|
-
frameworks: detectedProject.frameworks,
|
|
209
|
-
tooling: detectedProject.tooling,
|
|
210
|
-
schemaPath: "fexapi/schema.fexapi",
|
|
211
|
-
generatedPath: GENERATED_SPEC_RELATIVE_PATH,
|
|
212
|
-
createdAt: new Date().toISOString(),
|
|
213
|
-
};
|
|
214
|
-
if (!configExists || force) {
|
|
215
|
-
(0, node_fs_1.writeFileSync)(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
216
|
-
}
|
|
217
|
-
if (!schemaExists || force) {
|
|
218
|
-
(0, node_fs_1.writeFileSync)(schemaPath, `${getSchemaTemplate(detectedProject.primaryFramework)}\n`, "utf-8");
|
|
219
|
-
}
|
|
220
|
-
console.log(`Initialized Fexapi in ${projectRoot}`);
|
|
221
|
-
console.log(`Detected framework: ${detectedProject.primaryFramework}`);
|
|
222
|
-
console.log(`Detected frameworks: ${detectedProject.frameworks.join(", ")}`);
|
|
223
|
-
if (detectedProject.tooling.length > 0) {
|
|
224
|
-
console.log(`Detected tooling: ${detectedProject.tooling.join(", ")}`);
|
|
225
|
-
}
|
|
226
|
-
if (configExists && !force) {
|
|
227
|
-
console.log(`Exists ${configPath}`);
|
|
228
|
-
}
|
|
229
|
-
else if (configExists && force) {
|
|
230
|
-
console.log(`Overwritten ${configPath}`);
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
console.log(`Created ${configPath}`);
|
|
234
|
-
}
|
|
235
|
-
if (schemaExists && !force) {
|
|
236
|
-
console.log(`Exists ${schemaPath}`);
|
|
237
|
-
}
|
|
238
|
-
else if (schemaExists && force) {
|
|
239
|
-
console.log(`Overwritten ${schemaPath}`);
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
console.log(`Created ${schemaPath}`);
|
|
243
|
-
}
|
|
244
|
-
if (detectedProject.primaryFramework === "unknown") {
|
|
245
|
-
console.log("No known framework dependency found. Update fexapi.config.json and schema.fexapi if needed.");
|
|
246
|
-
}
|
|
247
|
-
return 0;
|
|
248
|
-
};
|
|
249
|
-
const generateFromSchema = () => {
|
|
250
|
-
const projectRoot = resolveProjectRoot();
|
|
251
|
-
if (!projectRoot) {
|
|
252
|
-
console.error("Could not find package.json in this directory or parent directories.");
|
|
253
|
-
return 1;
|
|
254
|
-
}
|
|
255
|
-
const schemaPath = (0, node_path_1.join)(projectRoot, "fexapi", "schema.fexapi");
|
|
256
|
-
const generatedPath = (0, node_path_1.join)(projectRoot, "fexapi", "generated.api.json");
|
|
257
|
-
const configPath = (0, node_path_1.join)(projectRoot, "fexapi.config.json");
|
|
258
|
-
if (!(0, node_fs_1.existsSync)(schemaPath)) {
|
|
259
|
-
console.error(`Schema file not found: ${schemaPath}`);
|
|
260
|
-
console.error("Run `fexapi init` first.");
|
|
261
|
-
return 1;
|
|
262
|
-
}
|
|
263
|
-
const schemaText = (0, node_fs_1.readFileSync)(schemaPath, "utf-8");
|
|
264
|
-
const parsed = (0, schema_1.parseFexapiSchema)(schemaText);
|
|
265
|
-
if (parsed.errors.length > 0 || !parsed.schema) {
|
|
266
|
-
console.error("Failed to generate API from schema.fexapi");
|
|
267
|
-
for (const error of parsed.errors) {
|
|
268
|
-
console.error(`- ${error}`);
|
|
269
|
-
}
|
|
270
|
-
return 1;
|
|
271
|
-
}
|
|
272
|
-
const generated = {
|
|
273
|
-
schemaVersion: 1,
|
|
274
|
-
generatedAt: new Date().toISOString(),
|
|
275
|
-
port: parsed.schema.port,
|
|
276
|
-
routes: parsed.schema.routes,
|
|
277
|
-
};
|
|
278
|
-
(0, node_fs_1.writeFileSync)(generatedPath, `${JSON.stringify(generated, null, 2)}\n`, "utf-8");
|
|
279
|
-
let existingConfig = {};
|
|
280
|
-
if ((0, node_fs_1.existsSync)(configPath)) {
|
|
281
|
-
try {
|
|
282
|
-
existingConfig = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
283
|
-
}
|
|
284
|
-
catch {
|
|
285
|
-
existingConfig = {};
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
const updatedConfig = {
|
|
289
|
-
...existingConfig,
|
|
290
|
-
schemaPath: "fexapi/schema.fexapi",
|
|
291
|
-
generatedPath: GENERATED_SPEC_RELATIVE_PATH,
|
|
292
|
-
lastGeneratedAt: new Date().toISOString(),
|
|
293
|
-
};
|
|
294
|
-
(0, node_fs_1.writeFileSync)(configPath, `${JSON.stringify(updatedConfig, null, 2)}\n`, "utf-8");
|
|
295
|
-
console.log(`Generated API spec at ${generatedPath}`);
|
|
296
|
-
console.log(`Routes generated: ${parsed.schema.routes.length}`);
|
|
297
|
-
console.log(`Configured server port: ${parsed.schema.port}`);
|
|
298
|
-
return 0;
|
|
299
|
-
};
|
|
300
|
-
const parseServeOptions = (serveArgs) => {
|
|
301
|
-
const getFlagValue = (flagName) => {
|
|
302
|
-
const index = serveArgs.indexOf(flagName);
|
|
303
|
-
if (index === -1) {
|
|
304
|
-
return undefined;
|
|
305
|
-
}
|
|
306
|
-
const value = serveArgs[index + 1];
|
|
307
|
-
if (!value || value.startsWith("-")) {
|
|
308
|
-
return { error: `Missing value for ${flagName}` };
|
|
309
|
-
}
|
|
310
|
-
return value;
|
|
311
|
-
};
|
|
312
|
-
const unknownFlags = serveArgs.filter((value) => value.startsWith("-") && value !== "--host" && value !== "--port");
|
|
313
|
-
if (unknownFlags.length > 0) {
|
|
314
|
-
return { error: `Unknown option(s): ${unknownFlags.join(", ")}` };
|
|
315
|
-
}
|
|
316
|
-
const hostValue = getFlagValue("--host");
|
|
317
|
-
if (hostValue && typeof hostValue !== "string") {
|
|
318
|
-
return hostValue;
|
|
319
|
-
}
|
|
320
|
-
const portValue = getFlagValue("--port");
|
|
321
|
-
if (portValue && typeof portValue !== "string") {
|
|
322
|
-
return portValue;
|
|
323
|
-
}
|
|
324
|
-
const host = hostValue ?? "127.0.0.1";
|
|
325
|
-
const port = portValue ? Number(portValue) : undefined;
|
|
326
|
-
if (port !== undefined &&
|
|
327
|
-
(!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
328
|
-
return { error: `Invalid port: ${portValue ?? ""}`.trim() };
|
|
329
|
-
}
|
|
330
|
-
return { host, port };
|
|
331
|
-
};
|
|
332
|
-
const loadGeneratedApiSpec = (projectRoot) => {
|
|
333
|
-
const generatedPath = (0, node_path_1.join)(projectRoot, GENERATED_SPEC_RELATIVE_PATH);
|
|
334
|
-
if (!(0, node_fs_1.existsSync)(generatedPath)) {
|
|
335
|
-
return undefined;
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
const parsed = JSON.parse((0, node_fs_1.readFileSync)(generatedPath, "utf-8"));
|
|
339
|
-
if (typeof parsed.port !== "number" || !Array.isArray(parsed.routes)) {
|
|
340
|
-
return undefined;
|
|
341
|
-
}
|
|
342
|
-
return {
|
|
343
|
-
port: parsed.port,
|
|
344
|
-
routes: parsed.routes,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
catch {
|
|
348
|
-
return undefined;
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
10
|
const [firstArg, ...restArgs] = args;
|
|
352
11
|
if (firstArg === "init") {
|
|
353
12
|
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
@@ -356,80 +15,58 @@ if (firstArg === "init") {
|
|
|
356
15
|
console.log("Use --force to overwrite existing files.");
|
|
357
16
|
process.exit(0);
|
|
358
17
|
}
|
|
359
|
-
const initOptions = parseInitOptions(restArgs);
|
|
18
|
+
const initOptions = (0, args_1.parseInitOptions)(restArgs);
|
|
360
19
|
if ("error" in initOptions) {
|
|
361
20
|
console.error(initOptions.error);
|
|
362
21
|
console.log("");
|
|
363
22
|
console.log("Usage: fexapi init [--force]");
|
|
364
23
|
process.exit(1);
|
|
365
24
|
}
|
|
366
|
-
|
|
367
|
-
process.exit(exitCode);
|
|
25
|
+
process.exit((0, init_1.initializeProject)({ force: initOptions.force }));
|
|
368
26
|
}
|
|
369
27
|
else if (firstArg === "generate") {
|
|
370
28
|
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
371
29
|
console.log("Usage: fexapi generate");
|
|
372
|
-
console.log("Reads fexapi/schema.fexapi and creates
|
|
30
|
+
console.log("Reads fexapi/schema.fexapi and creates generated API artifacts + migrations.");
|
|
373
31
|
process.exit(0);
|
|
374
32
|
}
|
|
375
|
-
const generateOptions = parseGenerateOptions(restArgs);
|
|
33
|
+
const generateOptions = (0, args_1.parseGenerateOptions)(restArgs);
|
|
376
34
|
if (generateOptions.error) {
|
|
377
35
|
console.error(generateOptions.error);
|
|
378
36
|
console.log("");
|
|
379
37
|
console.log("Usage: fexapi generate");
|
|
380
38
|
process.exit(1);
|
|
381
39
|
}
|
|
382
|
-
|
|
383
|
-
process.exit(exitCode);
|
|
40
|
+
process.exit((0, generate_1.generateFromSchema)());
|
|
384
41
|
}
|
|
385
|
-
else if (!firstArg ||
|
|
386
|
-
|
|
42
|
+
else if (!firstArg ||
|
|
43
|
+
firstArg === "serve" ||
|
|
44
|
+
firstArg === "run" ||
|
|
45
|
+
firstArg.startsWith("-")) {
|
|
46
|
+
const serveArgs = firstArg === "serve" || firstArg === "run" ? restArgs : args;
|
|
387
47
|
if (serveArgs.includes("--help") || serveArgs.includes("-h")) {
|
|
388
|
-
printHelp();
|
|
48
|
+
(0, help_1.printHelp)();
|
|
389
49
|
process.exit(0);
|
|
390
50
|
}
|
|
391
|
-
const options = parseServeOptions(serveArgs);
|
|
51
|
+
const options = (0, args_1.parseServeOptions)(serveArgs);
|
|
392
52
|
if ("error" in options) {
|
|
393
53
|
console.error(options.error);
|
|
394
54
|
console.log("");
|
|
395
|
-
printHelp();
|
|
55
|
+
(0, help_1.printHelp)();
|
|
396
56
|
process.exit(1);
|
|
397
57
|
}
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
: undefined;
|
|
402
|
-
const effectivePort = options.port ?? generatedSpec?.port ?? 4000;
|
|
403
|
-
if (generatedSpec) {
|
|
404
|
-
console.log(`Using generated schema routes (${generatedSpec.routes.length}) from ${GENERATED_SPEC_RELATIVE_PATH}`);
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
console.log("No generated schema found. Run `fexapi generate` to serve schema-defined endpoints.");
|
|
58
|
+
const exitCode = (0, serve_1.serveProject)(options);
|
|
59
|
+
if (exitCode !== 0) {
|
|
60
|
+
process.exit(exitCode);
|
|
408
61
|
}
|
|
409
|
-
const server = (0, server_1.startServer)({
|
|
410
|
-
host: options.host,
|
|
411
|
-
port: effectivePort,
|
|
412
|
-
apiSpec: generatedSpec,
|
|
413
|
-
});
|
|
414
|
-
const shutdown = () => {
|
|
415
|
-
server.close((error) => {
|
|
416
|
-
if (error) {
|
|
417
|
-
console.error("Error while shutting down server", error);
|
|
418
|
-
process.exit(1);
|
|
419
|
-
}
|
|
420
|
-
process.exit(0);
|
|
421
|
-
});
|
|
422
|
-
};
|
|
423
|
-
process.on("SIGINT", shutdown);
|
|
424
|
-
process.on("SIGTERM", shutdown);
|
|
425
62
|
}
|
|
426
63
|
else if (firstArg === "help") {
|
|
427
|
-
printHelp();
|
|
64
|
+
(0, help_1.printHelp)();
|
|
428
65
|
process.exit(0);
|
|
429
66
|
}
|
|
430
67
|
else {
|
|
431
68
|
console.error(`Unknown command: ${firstArg}`);
|
|
432
69
|
console.log("");
|
|
433
|
-
printHelp();
|
|
70
|
+
(0, help_1.printHelp)();
|
|
434
71
|
process.exit(1);
|
|
435
72
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DetectedProject, SupportedFramework } from "../types/project";
|
|
2
|
+
export declare const detectProject: (packageJsonPath: string, projectRoot: string) => DetectedProject;
|
|
3
|
+
export declare const getSchemaTemplate: (framework: SupportedFramework) => string;
|
|
4
|
+
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/project/detect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAyD5E,eAAO,MAAM,aAAa,GACxB,iBAAiB,MAAM,EACvB,aAAa,MAAM,KAClB,eAwDF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,WAAW,kBAAkB,KAAG,MAkBjE,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSchemaTemplate = exports.detectProject = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const readDependencyNames = (packageJsonPath) => {
|
|
7
|
+
const packageJsonText = (0, node_fs_1.readFileSync)(packageJsonPath, "utf-8");
|
|
8
|
+
const packageJson = JSON.parse(packageJsonText);
|
|
9
|
+
const dependencies = (packageJson.dependencies ?? {});
|
|
10
|
+
const devDependencies = (packageJson.devDependencies ?? {});
|
|
11
|
+
return new Set([
|
|
12
|
+
...Object.keys(dependencies),
|
|
13
|
+
...Object.keys(devDependencies),
|
|
14
|
+
]);
|
|
15
|
+
};
|
|
16
|
+
const readWorkspaceDependencyNames = (projectRoot) => {
|
|
17
|
+
const result = new Set();
|
|
18
|
+
const rootsToScan = [
|
|
19
|
+
(0, node_path_1.join)(projectRoot, "apps"),
|
|
20
|
+
(0, node_path_1.join)(projectRoot, "packages"),
|
|
21
|
+
];
|
|
22
|
+
for (const rootPath of rootsToScan) {
|
|
23
|
+
if (!(0, node_fs_1.existsSync)(rootPath)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
for (const entry of (0, node_fs_1.readdirSync)(rootPath)) {
|
|
27
|
+
const entryPath = (0, node_path_1.join)(rootPath, entry);
|
|
28
|
+
if (!(0, node_fs_1.statSync)(entryPath).isDirectory()) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const packageJsonPath = (0, node_path_1.join)(entryPath, "package.json");
|
|
32
|
+
if (!(0, node_fs_1.existsSync)(packageJsonPath)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const dependencyNames = readDependencyNames(packageJsonPath);
|
|
36
|
+
for (const dependencyName of dependencyNames) {
|
|
37
|
+
result.add(dependencyName);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
const detectProject = (packageJsonPath, projectRoot) => {
|
|
44
|
+
const dependencyNames = readDependencyNames(packageJsonPath);
|
|
45
|
+
const frameworks = new Set();
|
|
46
|
+
const tooling = new Set();
|
|
47
|
+
if (dependencyNames.has("turbo") ||
|
|
48
|
+
(0, node_fs_1.existsSync)((0, node_path_1.join)(projectRoot, "turbo.json"))) {
|
|
49
|
+
tooling.add("turborepo");
|
|
50
|
+
}
|
|
51
|
+
if (dependencyNames.has("nx") || (0, node_fs_1.existsSync)((0, node_path_1.join)(projectRoot, "nx.json"))) {
|
|
52
|
+
tooling.add("nx");
|
|
53
|
+
}
|
|
54
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(projectRoot, "pnpm-workspace.yaml"))) {
|
|
55
|
+
tooling.add("pnpm-workspace");
|
|
56
|
+
}
|
|
57
|
+
if (tooling.has("turborepo") || tooling.has("pnpm-workspace")) {
|
|
58
|
+
const workspaceDeps = readWorkspaceDependencyNames(projectRoot);
|
|
59
|
+
for (const dependencyName of workspaceDeps) {
|
|
60
|
+
dependencyNames.add(dependencyName);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (dependencyNames.has("next"))
|
|
64
|
+
frameworks.add("nextjs");
|
|
65
|
+
if (dependencyNames.has("react") || dependencyNames.has("react-dom"))
|
|
66
|
+
frameworks.add("reactjs");
|
|
67
|
+
if (dependencyNames.has("vue"))
|
|
68
|
+
frameworks.add("vue");
|
|
69
|
+
if (dependencyNames.has("nuxt"))
|
|
70
|
+
frameworks.add("nuxt");
|
|
71
|
+
if (dependencyNames.has("svelte"))
|
|
72
|
+
frameworks.add("svelte");
|
|
73
|
+
if (dependencyNames.has("@sveltejs/kit"))
|
|
74
|
+
frameworks.add("sveltekit");
|
|
75
|
+
if (dependencyNames.has("@angular/core"))
|
|
76
|
+
frameworks.add("angular");
|
|
77
|
+
if (dependencyNames.has("solid-js"))
|
|
78
|
+
frameworks.add("solid");
|
|
79
|
+
if (dependencyNames.has("@remix-run/react") ||
|
|
80
|
+
dependencyNames.has("@remix-run/node"))
|
|
81
|
+
frameworks.add("remix");
|
|
82
|
+
if (dependencyNames.has("astro"))
|
|
83
|
+
frameworks.add("astro");
|
|
84
|
+
if (dependencyNames.has("vite")) {
|
|
85
|
+
tooling.add("vite");
|
|
86
|
+
}
|
|
87
|
+
const frameworkList = Array.from(frameworks);
|
|
88
|
+
const primaryFramework = frameworkList[0] ?? "unknown";
|
|
89
|
+
return {
|
|
90
|
+
primaryFramework,
|
|
91
|
+
frameworks: frameworkList.length > 0 ? frameworkList : ["unknown"],
|
|
92
|
+
tooling: Array.from(tooling),
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
exports.detectProject = detectProject;
|
|
96
|
+
const getSchemaTemplate = (framework) => {
|
|
97
|
+
const frameworkHint = framework === "nextjs"
|
|
98
|
+
? "# Framework: Next.js"
|
|
99
|
+
: framework === "reactjs"
|
|
100
|
+
? "# Framework: React"
|
|
101
|
+
: "# Framework: unknown";
|
|
102
|
+
return [
|
|
103
|
+
frameworkHint,
|
|
104
|
+
"# Server",
|
|
105
|
+
"port: 4000",
|
|
106
|
+
"",
|
|
107
|
+
"# Routes",
|
|
108
|
+
"# Format: METHOD /endpoint: field:type,field:type",
|
|
109
|
+
"GET /users: id:uuid,fullName:name,username:string,email:email,avatarUrl:url",
|
|
110
|
+
"GET /posts: id:uuid,title:string,body:string,createdAt:date",
|
|
111
|
+
].join("\n");
|
|
112
|
+
};
|
|
113
|
+
exports.getSchemaTemplate = getSchemaTemplate;
|