@hyper-fetch/cli 7.2.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/.eslintrc.json +11 -0
- package/README.md +63 -0
- package/dist/cli.cjs.js +3087 -0
- package/dist/cli.cjs.js.map +7 -0
- package/dist/index.d.ts +100 -0
- package/package.json +77 -0
- package/src/cli/index.ts +54 -0
- package/src/codegen/index.ts +1 -0
- package/src/codegen/openapi/generator.ts +338 -0
- package/src/codegen/openapi/http-methods.enum.ts +10 -0
- package/src/codegen/openapi/index.ts +3 -0
- package/src/codegen/openapi/openapi.types.ts +14 -0
- package/src/codegen/openapi/operations.ts +22 -0
- package/src/codegen/openapi/utils.ts +34 -0
- package/src/commands/add.ts +90 -0
- package/src/commands/generate.ts +104 -0
- package/src/commands/init.ts +191 -0
- package/src/config/get-config.ts +59 -0
- package/src/config/get-ts-alias.ts +25 -0
- package/src/config/schema.ts +22 -0
- package/src/index.ts +1 -0
- package/src/preflights/preflight-add.ts +43 -0
- package/src/preflights/preflight-generate.ts +46 -0
- package/src/utils/errors.ts +13 -0
- package/src/utils/handle-error.ts +35 -0
- package/src/utils/highlighter.ts +8 -0
- package/src/utils/logger.ts +20 -0
- package/src/utils/resolve-import.ts +14 -0
- package/src/utils/show-help.ts +37 -0
- package/src/utils/spinner.ts +13 -0
- package/src/utils/zod-to-table.ts +16 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import { input, select, confirm } from "@inquirer/prompts";
|
|
7
|
+
|
|
8
|
+
import { handleError } from "utils/handle-error";
|
|
9
|
+
import { preFlightGenerate } from "preflights/preflight-generate";
|
|
10
|
+
import { OpenapiRequestGenerator } from "codegen/openapi/generator";
|
|
11
|
+
import { spinner } from "utils/spinner";
|
|
12
|
+
import { logger } from "utils/logger";
|
|
13
|
+
import { showHelp } from "utils/show-help";
|
|
14
|
+
|
|
15
|
+
export const generateOptionsSchema = z.object({
|
|
16
|
+
template: z.enum(["openapi", "swagger"]).describe("API provider template to use"),
|
|
17
|
+
url: z.string().describe("The URL to generate the schema from"),
|
|
18
|
+
fileName: z.string().describe("The output file for the SDK."),
|
|
19
|
+
cwd: z.string().describe("The working directory. defaults to the current directory."),
|
|
20
|
+
overwrite: z.boolean().optional().describe("Overwrite existing files."),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const generate = new Command()
|
|
24
|
+
.name("Generate")
|
|
25
|
+
.description("Generate SDK from a schema, url or service")
|
|
26
|
+
.option("-t, --template <template>", "API provider template to use")
|
|
27
|
+
.option("-u, --url <url>", "The URL to generate the schema from")
|
|
28
|
+
.option("-f, --fileName <fileName>", "The output file for the SDK.")
|
|
29
|
+
.option("-o, --overwrite", "overwrite existing files.")
|
|
30
|
+
.option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd())
|
|
31
|
+
.option("-h, --help <help>", "display help for command")
|
|
32
|
+
.action(async (opts: z.infer<typeof generateOptionsSchema>) => {
|
|
33
|
+
try {
|
|
34
|
+
const help = process.argv.includes("--help") || process.argv.includes("-h");
|
|
35
|
+
|
|
36
|
+
if (help) {
|
|
37
|
+
return showHelp(generateOptionsSchema);
|
|
38
|
+
}
|
|
39
|
+
const { config } = await preFlightGenerate(opts);
|
|
40
|
+
|
|
41
|
+
if (!opts.template) {
|
|
42
|
+
opts.template = await select({
|
|
43
|
+
message: "What source we want to use?",
|
|
44
|
+
choices: [
|
|
45
|
+
{ name: "OpenAPI (v3)", value: "openapi" },
|
|
46
|
+
{ name: "Swagger", value: "swagger" },
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!opts.url) {
|
|
52
|
+
opts.url = await input({
|
|
53
|
+
message: "Enter the URL to generate the schema from:",
|
|
54
|
+
validate: (value) => {
|
|
55
|
+
const result = z.url("Please enter a valid URL.").safeParse(value);
|
|
56
|
+
if (result.success) return true;
|
|
57
|
+
return result.error.message;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!opts.fileName) {
|
|
63
|
+
opts.fileName = await input({
|
|
64
|
+
message: "Enter the file name for the SDK:",
|
|
65
|
+
default: `api-${opts.template}.sdk.ts`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const fileExists = fs.existsSync(path.join(opts.cwd, opts.fileName));
|
|
70
|
+
if (opts.overwrite === undefined && fileExists) {
|
|
71
|
+
opts.overwrite = await confirm({ message: "Overwrite existing files?" });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (opts.overwrite === false && fileExists) {
|
|
75
|
+
return logger.info(`File ${opts.fileName} already exists. Use --overwrite to overwrite.`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const options = generateOptionsSchema.parse(opts);
|
|
79
|
+
|
|
80
|
+
const componentSpinner = spinner(`Writing ${options.fileName}...`).start();
|
|
81
|
+
switch (options.template) {
|
|
82
|
+
case "openapi": {
|
|
83
|
+
const openapiSchema = await OpenapiRequestGenerator.getSchemaFromUrl({ url: options.url!, config });
|
|
84
|
+
const generator = new OpenapiRequestGenerator(openapiSchema);
|
|
85
|
+
await generator.generateFile({ fileName: options.fileName, config });
|
|
86
|
+
componentSpinner.succeed();
|
|
87
|
+
return process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
case "swagger": {
|
|
90
|
+
const openapiSchema = await OpenapiRequestGenerator.getSchemaFromUrl({ url: options.url!, config });
|
|
91
|
+
const generator = new OpenapiRequestGenerator(openapiSchema);
|
|
92
|
+
await generator.generateFile({ fileName: options.fileName, config });
|
|
93
|
+
componentSpinner.succeed();
|
|
94
|
+
return process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
default: {
|
|
97
|
+
componentSpinner.fail();
|
|
98
|
+
throw new Error(`Invalid template: ${options.template}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
handleError(err);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { input, select } from "@inquirer/prompts";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { existsSync, mkdir, readFileSync, writeFile } from "fs-extra";
|
|
6
|
+
|
|
7
|
+
import { handleError } from "../utils/handle-error";
|
|
8
|
+
import { spinner } from "../utils/spinner";
|
|
9
|
+
import { configSchema, Config } from "config/schema";
|
|
10
|
+
import { logger } from "utils/logger";
|
|
11
|
+
import { getTsConfigAliasPrefix } from "config/get-ts-alias";
|
|
12
|
+
import { showHelp } from "utils/show-help";
|
|
13
|
+
|
|
14
|
+
const initOptionsSchema = z.object({
|
|
15
|
+
yes: z.boolean().optional().describe("skip confirmation prompt."),
|
|
16
|
+
cwd: z.string().describe("the working directory. defaults to the current directory."),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
type Step = {
|
|
20
|
+
name: string;
|
|
21
|
+
action: (config: Partial<Config>) => Promise<Partial<Config> | void>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const init = new Command()
|
|
25
|
+
.name("Init")
|
|
26
|
+
.description("Initialize HyperFetch Client configuration.")
|
|
27
|
+
.option("-y, --yes", "skip confirmation prompt.", false)
|
|
28
|
+
.option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd())
|
|
29
|
+
.option("-h, --help <help>", "display help for command")
|
|
30
|
+
.action(async (opts) => {
|
|
31
|
+
try {
|
|
32
|
+
const help = process.argv.includes("--help") || process.argv.includes("-h");
|
|
33
|
+
|
|
34
|
+
if (help) {
|
|
35
|
+
return showHelp(initOptionsSchema);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { cwd, yes } = initOptionsSchema.parse(opts);
|
|
39
|
+
|
|
40
|
+
let config: Partial<Config> = {};
|
|
41
|
+
|
|
42
|
+
let mainPath: string;
|
|
43
|
+
let apiDir: string;
|
|
44
|
+
|
|
45
|
+
if (yes) {
|
|
46
|
+
mainPath = "src";
|
|
47
|
+
apiDir = "api";
|
|
48
|
+
} else {
|
|
49
|
+
// 1. ask for the path to the main directory
|
|
50
|
+
mainPath = await select({
|
|
51
|
+
message: "Select the main directory for your project:",
|
|
52
|
+
choices: [
|
|
53
|
+
{ name: "src", value: "src" },
|
|
54
|
+
{ name: "app", value: "app" },
|
|
55
|
+
{ name: "other", value: "other" },
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (mainPath === "other") {
|
|
60
|
+
mainPath = await input({
|
|
61
|
+
message: "Enter the path to the main directory:",
|
|
62
|
+
validate: (value) => {
|
|
63
|
+
if (!value) return "Path cannot be empty.";
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. text field - asking for the directory of api
|
|
70
|
+
apiDir = await input({
|
|
71
|
+
message: "Enter the name of the API directory:",
|
|
72
|
+
default: "api",
|
|
73
|
+
validate: (value) => {
|
|
74
|
+
if (!value) return "Directory name cannot be empty.";
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const fullPath = path.join(cwd, mainPath, apiDir);
|
|
81
|
+
const relativePath = path.join(mainPath, apiDir);
|
|
82
|
+
const configPath = path.join(cwd, "api.json");
|
|
83
|
+
|
|
84
|
+
const aliasPrefix = (await getTsConfigAliasPrefix(cwd)) || "";
|
|
85
|
+
const alias = aliasPrefix ? `${aliasPrefix}/` : "";
|
|
86
|
+
|
|
87
|
+
const defaultAliases: Config["aliases"] = {
|
|
88
|
+
api: `${alias}${apiDir}`,
|
|
89
|
+
hooks: `${alias}hooks`,
|
|
90
|
+
ui: `${alias}components/ui`,
|
|
91
|
+
components: `${alias}components`,
|
|
92
|
+
lib: `${alias}lib`,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const defaultConfig: Omit<Config, "resolvedPaths"> = {
|
|
96
|
+
tsx: true,
|
|
97
|
+
aliases: defaultAliases,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
logger.break();
|
|
101
|
+
|
|
102
|
+
const steps: Step[] = [
|
|
103
|
+
{
|
|
104
|
+
name: `Initialize API directory at ${relativePath}`,
|
|
105
|
+
action: async () => {
|
|
106
|
+
if (!existsSync(fullPath)) {
|
|
107
|
+
await mkdir(fullPath, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "Setup configuration",
|
|
113
|
+
action: async (currentConfig) => {
|
|
114
|
+
if (existsSync(configPath)) {
|
|
115
|
+
const existingConfig = JSON.parse(readFileSync(configPath, "utf8"));
|
|
116
|
+
const { success, error, data } = configSchema.omit({ resolvedPaths: true }).safeParse({
|
|
117
|
+
...defaultConfig,
|
|
118
|
+
...existingConfig,
|
|
119
|
+
aliases: {
|
|
120
|
+
...defaultConfig.aliases,
|
|
121
|
+
...existingConfig.aliases,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (error) {
|
|
126
|
+
logger.break();
|
|
127
|
+
logger.error(`Invalid configuration found in ${configPath}.`);
|
|
128
|
+
handleError(error);
|
|
129
|
+
logger.break();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (success) {
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return currentConfig;
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "Setup Aliases",
|
|
141
|
+
action: async (currentConfig) => {
|
|
142
|
+
if (currentConfig.aliases) {
|
|
143
|
+
return currentConfig;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
...currentConfig,
|
|
148
|
+
aliases: defaultAliases,
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: `Create api.json configuration file`,
|
|
154
|
+
action: async (currentConfig) => {
|
|
155
|
+
const finalConfig = {
|
|
156
|
+
...defaultConfig,
|
|
157
|
+
...currentConfig,
|
|
158
|
+
aliases: {
|
|
159
|
+
...defaultConfig.aliases,
|
|
160
|
+
...currentConfig.aliases,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
configSchema.omit({ resolvedPaths: true }).parse(finalConfig);
|
|
165
|
+
|
|
166
|
+
await writeFile(configPath, JSON.stringify(finalConfig, null, 2));
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
logger.break();
|
|
172
|
+
logger.info("Starting HyperFetch CLI initialization...");
|
|
173
|
+
|
|
174
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
175
|
+
for (const step of steps) {
|
|
176
|
+
const s = spinner(step.name).start();
|
|
177
|
+
// eslint-disable-next-line no-await-in-loop
|
|
178
|
+
const result = await step.action(config);
|
|
179
|
+
if (result) {
|
|
180
|
+
config = result;
|
|
181
|
+
}
|
|
182
|
+
s.succeed();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
logger.break();
|
|
186
|
+
logger.info("Configuration has been generated successfully! 🎉");
|
|
187
|
+
logger.info(`You can now find your configuration at ${configPath}`);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
handleError(err);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { loadConfig } from "tsconfig-paths";
|
|
4
|
+
|
|
5
|
+
import { highlighter } from "utils/highlighter";
|
|
6
|
+
import { resolveImport } from "utils/resolve-import";
|
|
7
|
+
import { logger } from "../utils/logger";
|
|
8
|
+
import { handleError } from "../utils/handle-error";
|
|
9
|
+
import { configSchema, Config } from "config/schema";
|
|
10
|
+
|
|
11
|
+
export async function resolveConfigPaths(cwd: string, config: Omit<Config, "resolvedPaths">): Promise<Config> {
|
|
12
|
+
// Read tsconfig.json.
|
|
13
|
+
const tsConfig = await loadConfig(cwd);
|
|
14
|
+
|
|
15
|
+
if (tsConfig.resultType === "failed") {
|
|
16
|
+
throw new Error(`Failed to load ${config.tsx ? "tsconfig" : "jsconfig"}.json. ${tsConfig.message ?? ""}`.trim());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return configSchema.parse({
|
|
20
|
+
...config,
|
|
21
|
+
resolvedPaths: {
|
|
22
|
+
cwd,
|
|
23
|
+
api: await resolveImport(config.aliases.api, tsConfig),
|
|
24
|
+
components: await resolveImport(config.aliases.components, tsConfig),
|
|
25
|
+
lib: await resolveImport(config.aliases.lib, tsConfig),
|
|
26
|
+
hooks: await resolveImport(config.aliases.hooks, tsConfig),
|
|
27
|
+
ui: await resolveImport(config.aliases.ui, tsConfig),
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function getConfig(cwd: string): Promise<Config | null> {
|
|
33
|
+
// Check for existing api.json file.
|
|
34
|
+
if (!fs.existsSync(path.resolve(cwd, "api.json"))) {
|
|
35
|
+
logger.break();
|
|
36
|
+
logger.error(
|
|
37
|
+
`An invalid ${highlighter.info("api.json")} file was found at ${highlighter.info(
|
|
38
|
+
cwd,
|
|
39
|
+
)}.\nBefore you can add or generate SDKs, you must create a valid ${highlighter.info(
|
|
40
|
+
"api.json",
|
|
41
|
+
)} file by running the ${highlighter.info("init")} command.`,
|
|
42
|
+
);
|
|
43
|
+
logger.break();
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
const { data, error } = await configSchema
|
|
47
|
+
.omit({ resolvedPaths: true }) // We enrich it later
|
|
48
|
+
.safeParseAsync(JSON.parse(fs.readFileSync(path.resolve(cwd, "api.json"), "utf8")));
|
|
49
|
+
|
|
50
|
+
if (error) {
|
|
51
|
+
handleError(error);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!data) {
|
|
55
|
+
throw new Error(`Invalid configuration found in ${highlighter.info(cwd)}.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return resolveConfigPaths(cwd, data);
|
|
59
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { loadConfig } from "tsconfig-paths";
|
|
2
|
+
|
|
3
|
+
export async function getTsConfigAliasPrefix(cwd: string) {
|
|
4
|
+
const tsConfig = await loadConfig(cwd);
|
|
5
|
+
|
|
6
|
+
if (tsConfig?.resultType === "failed" || !Object.entries(tsConfig?.paths).length) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// This assume that the first alias is the prefix.
|
|
11
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
12
|
+
for (const [alias, paths] of Object.entries(tsConfig.paths)) {
|
|
13
|
+
if (
|
|
14
|
+
paths.includes("./*") ||
|
|
15
|
+
paths.includes("./src/*") ||
|
|
16
|
+
paths.includes("./app/*") ||
|
|
17
|
+
paths.includes("./resources/js/*") // Laravel.
|
|
18
|
+
) {
|
|
19
|
+
return alias.replace(/\/\*$/, "") ?? null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Use the first alias as the prefix.
|
|
24
|
+
return Object.keys(tsConfig?.paths)?.[0].replace(/\/\*$/, "") ?? null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const configSchema = z.object({
|
|
4
|
+
tsx: z.boolean(),
|
|
5
|
+
aliases: z.object({
|
|
6
|
+
api: z.string(),
|
|
7
|
+
hooks: z.string(),
|
|
8
|
+
ui: z.string(),
|
|
9
|
+
components: z.string(),
|
|
10
|
+
lib: z.string(),
|
|
11
|
+
}),
|
|
12
|
+
resolvedPaths: z.object({
|
|
13
|
+
cwd: z.string(),
|
|
14
|
+
api: z.string(),
|
|
15
|
+
hooks: z.string(),
|
|
16
|
+
ui: z.string(),
|
|
17
|
+
components: z.string(),
|
|
18
|
+
lib: z.string(),
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export type Config = z.infer<typeof configSchema>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "codegen";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { addOptionsSchema } from "commands/add";
|
|
6
|
+
import * as ERRORS from "utils/errors";
|
|
7
|
+
import { getConfig } from "config/get-config";
|
|
8
|
+
import { highlighter } from "utils/highlighter";
|
|
9
|
+
import { logger } from "utils/logger";
|
|
10
|
+
|
|
11
|
+
export async function preFlightAdd(options: z.infer<typeof addOptionsSchema>) {
|
|
12
|
+
const errors: Record<string, boolean> = {};
|
|
13
|
+
|
|
14
|
+
// Ensure target directory exists.
|
|
15
|
+
// Check for empty project. We assume if no package.json exists, the project is empty.
|
|
16
|
+
if (!fs.existsSync(options.cwd) || !fs.existsSync(path.resolve(options.cwd, "package.json"))) {
|
|
17
|
+
errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT] = true;
|
|
18
|
+
return {
|
|
19
|
+
errors,
|
|
20
|
+
config: null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const config = await getConfig(options.cwd);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
errors,
|
|
29
|
+
config: config!,
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
logger.break();
|
|
33
|
+
logger.error(
|
|
34
|
+
`An invalid ${highlighter.info("api.json")} file was found at ${highlighter.info(
|
|
35
|
+
options.cwd,
|
|
36
|
+
)}.\nBefore you can add SDKs, you must create a valid ${highlighter.info(
|
|
37
|
+
"api.json",
|
|
38
|
+
)} file by running the ${highlighter.info("init")} command.`,
|
|
39
|
+
);
|
|
40
|
+
logger.break();
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { generateOptionsSchema } from "commands/generate";
|
|
6
|
+
import * as ERRORS from "utils/errors";
|
|
7
|
+
import { getConfig } from "config/get-config";
|
|
8
|
+
import { Config } from "config/schema";
|
|
9
|
+
import { highlighter } from "utils/highlighter";
|
|
10
|
+
import { logger } from "utils/logger";
|
|
11
|
+
import { handleError } from "utils/handle-error";
|
|
12
|
+
|
|
13
|
+
export async function preFlightGenerate(options: z.infer<typeof generateOptionsSchema>): Promise<{
|
|
14
|
+
errors: Record<string, boolean>;
|
|
15
|
+
config: Config;
|
|
16
|
+
}> {
|
|
17
|
+
const errors: Record<string, boolean> = {};
|
|
18
|
+
|
|
19
|
+
// Ensure target directory exists.
|
|
20
|
+
// Check for empty project. We assume if no package.json exists, the project is empty.
|
|
21
|
+
if (!fs.existsSync(options.cwd) || !fs.existsSync(path.resolve(options.cwd, "package.json"))) {
|
|
22
|
+
errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT] = true;
|
|
23
|
+
|
|
24
|
+
handleError(errors);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const config = await getConfig(options.cwd);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
errors,
|
|
32
|
+
config: config!,
|
|
33
|
+
};
|
|
34
|
+
} catch (error) {
|
|
35
|
+
logger.break();
|
|
36
|
+
logger.error(
|
|
37
|
+
`An invalid ${highlighter.info("api.json")} file was found at ${highlighter.info(
|
|
38
|
+
options.cwd,
|
|
39
|
+
)}.\nBefore you can add SDKs, you must create a valid ${highlighter.info(
|
|
40
|
+
"api.json",
|
|
41
|
+
)} file by running the ${highlighter.info("init")} command.`,
|
|
42
|
+
);
|
|
43
|
+
logger.break();
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const MISSING_DIR_OR_EMPTY_PROJECT = "1";
|
|
2
|
+
export const EXISTING_CONFIG = "2";
|
|
3
|
+
export const MISSING_CONFIG = "3";
|
|
4
|
+
export const FAILED_CONFIG_READ = "4";
|
|
5
|
+
export const TAILWIND_NOT_CONFIGURED = "5";
|
|
6
|
+
export const IMPORT_ALIAS_MISSING = "6";
|
|
7
|
+
export const UNSUPPORTED_FRAMEWORK = "7";
|
|
8
|
+
export const COMPONENT_URL_NOT_FOUND = "8";
|
|
9
|
+
export const COMPONENT_URL_UNAUTHORIZED = "9";
|
|
10
|
+
export const COMPONENT_URL_FORBIDDEN = "10";
|
|
11
|
+
export const COMPONENT_URL_BAD_REQUEST = "11";
|
|
12
|
+
export const COMPONENT_URL_INTERNAL_SERVER_ERROR = "12";
|
|
13
|
+
export const BUILD_MISSING_REGISTRY_FILE = "13";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { logger } from "utils/logger";
|
|
5
|
+
|
|
6
|
+
export function handleError(error: unknown) {
|
|
7
|
+
logger.break();
|
|
8
|
+
logger.error(`Something went wrong. Please check the error below for more details.`);
|
|
9
|
+
logger.error(`If the problem persists, please open an issue on GitHub.`);
|
|
10
|
+
logger.error("");
|
|
11
|
+
if (typeof error === "string") {
|
|
12
|
+
logger.error(error);
|
|
13
|
+
logger.break();
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (error instanceof z.ZodError) {
|
|
18
|
+
logger.error("Validation failed:");
|
|
19
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
20
|
+
for (const [key, value] of Object.entries(error.flatten().fieldErrors)) {
|
|
21
|
+
logger.error(`- ${chalk.cyan(key)}: ${value}`);
|
|
22
|
+
}
|
|
23
|
+
logger.break();
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (error instanceof Error) {
|
|
28
|
+
logger.error(error.message);
|
|
29
|
+
logger.break();
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
logger.break();
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
|
|
4
|
+
export const logger = {
|
|
5
|
+
info(...args: unknown[]) {
|
|
6
|
+
console.log(chalk.cyan("ℹ"), chalk.blue.bold("info"), ...args);
|
|
7
|
+
},
|
|
8
|
+
warn(...args: unknown[]) {
|
|
9
|
+
console.log(chalk.yellow("⚠"), chalk.yellow.bold("warn"), ...args);
|
|
10
|
+
},
|
|
11
|
+
error(...args: unknown[]) {
|
|
12
|
+
console.log(chalk.red("✖"), chalk.red.bold("error"), ...args);
|
|
13
|
+
},
|
|
14
|
+
success(...args: unknown[]) {
|
|
15
|
+
console.log(chalk.green("✔"), chalk.green.bold("success"), ...args);
|
|
16
|
+
},
|
|
17
|
+
break() {
|
|
18
|
+
console.log("");
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createMatchPath, type ConfigLoaderSuccessResult } from "tsconfig-paths";
|
|
2
|
+
|
|
3
|
+
export async function resolveImport(
|
|
4
|
+
importPath: string,
|
|
5
|
+
config: Pick<ConfigLoaderSuccessResult, "absoluteBaseUrl" | "paths">,
|
|
6
|
+
) {
|
|
7
|
+
return createMatchPath(config.absoluteBaseUrl, config.paths)(importPath, undefined, () => true, [
|
|
8
|
+
".ts",
|
|
9
|
+
".tsx",
|
|
10
|
+
".jsx",
|
|
11
|
+
".js",
|
|
12
|
+
".css",
|
|
13
|
+
]);
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
|
|
4
|
+
import { zodToTable } from "./zod-to-table";
|
|
5
|
+
|
|
6
|
+
export const showHelp = (schema: z.ZodType) => {
|
|
7
|
+
const rows = zodToTable(schema).map(({ name, description }) => [`--${name}`, description || ""]);
|
|
8
|
+
|
|
9
|
+
const maxOptionLength = rows.reduce((max, row) => Math.max(max, row[0].length), 0);
|
|
10
|
+
const terminalWidth = process.stdout.columns;
|
|
11
|
+
|
|
12
|
+
const tableOptions: Table.TableConstructorOptions = {
|
|
13
|
+
head: ["Option", "Description"],
|
|
14
|
+
style: {
|
|
15
|
+
head: ["blue"],
|
|
16
|
+
},
|
|
17
|
+
wordWrap: true,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
if (terminalWidth) {
|
|
21
|
+
// Accounting for borders and padding.
|
|
22
|
+
const tableOverhead = 7;
|
|
23
|
+
const optionColWidth = maxOptionLength + 2;
|
|
24
|
+
const descriptionColWidth = terminalWidth - optionColWidth - tableOverhead;
|
|
25
|
+
|
|
26
|
+
if (descriptionColWidth > 10) {
|
|
27
|
+
tableOptions.colWidths = [optionColWidth, descriptionColWidth];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const table = new Table(tableOptions);
|
|
32
|
+
|
|
33
|
+
table.push(...rows);
|
|
34
|
+
|
|
35
|
+
// eslint-disable-next-line no-console
|
|
36
|
+
console.log(table.toString());
|
|
37
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const zodToTable = (schema: z.ZodType): { name: string; description: string | undefined }[] => {
|
|
4
|
+
if (schema instanceof z.ZodObject) {
|
|
5
|
+
const { shape } = schema;
|
|
6
|
+
return Object.keys(shape).map((key) => {
|
|
7
|
+
const fieldSchema = shape[key];
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
name: key,
|
|
11
|
+
description: fieldSchema.description,
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return [];
|
|
16
|
+
};
|