@igniter-js/cli 0.4.92 → 0.4.95
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 +35 -6
- package/dist/index.mjs +751 -67
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.mjs
CHANGED
|
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
|
-
import { Command as
|
|
10
|
+
import { Command as Command10 } from "commander";
|
|
11
11
|
|
|
12
12
|
// src/commands/init/index.ts
|
|
13
13
|
import { Command } from "commander";
|
|
@@ -123,11 +123,11 @@ function registerHandlebarsHelpers() {
|
|
|
123
123
|
handlebars.registerHelper("capitalizeSlug", function(slug) {
|
|
124
124
|
return slug.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
125
125
|
});
|
|
126
|
-
handlebars.registerHelper("get", function(obj,
|
|
127
|
-
if (!
|
|
126
|
+
handlebars.registerHelper("get", function(obj, path25) {
|
|
127
|
+
if (!path25 || typeof path25 !== "string") {
|
|
128
128
|
return void 0;
|
|
129
129
|
}
|
|
130
|
-
const pathArray =
|
|
130
|
+
const pathArray = path25.split(".");
|
|
131
131
|
let current = obj;
|
|
132
132
|
for (let i = 0; i < pathArray.length; i++) {
|
|
133
133
|
if (current === null || current === void 0) {
|
|
@@ -148,7 +148,7 @@ function registerHandlebarsHelpers() {
|
|
|
148
148
|
});
|
|
149
149
|
handlebars.registerHelper("filterPlugins", function(plugins = []) {
|
|
150
150
|
if (!Array.isArray(plugins)) return [];
|
|
151
|
-
return plugins.filter((
|
|
151
|
+
return plugins.filter((p14) => p14 !== "next-cookies");
|
|
152
152
|
});
|
|
153
153
|
handlebars.registerHelper(
|
|
154
154
|
"generatePluginImports",
|
|
@@ -162,7 +162,7 @@ function registerHandlebarsHelpers() {
|
|
|
162
162
|
if (str === "open-api") return "openAPI";
|
|
163
163
|
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
164
164
|
};
|
|
165
|
-
const regularPlugins = plugins.filter((
|
|
165
|
+
const regularPlugins = plugins.filter((p14) => p14 !== "next-cookies").map(camelCase);
|
|
166
166
|
const hasNextCookies = plugins.includes("next-cookies");
|
|
167
167
|
const importStatements = [];
|
|
168
168
|
if (regularPlugins.length > 0) {
|
|
@@ -334,9 +334,9 @@ var BaseStarter = class {
|
|
|
334
334
|
}
|
|
335
335
|
const outputPath = join2(projectDir, template.outputPath);
|
|
336
336
|
const outputDir = join2(outputPath, "..");
|
|
337
|
-
await import("fs").then((
|
|
338
|
-
if (!
|
|
339
|
-
|
|
337
|
+
await import("fs").then((fs9) => {
|
|
338
|
+
if (!fs9.existsSync(outputDir)) {
|
|
339
|
+
fs9.mkdirSync(outputDir, { recursive: true });
|
|
340
340
|
}
|
|
341
341
|
});
|
|
342
342
|
await writeFile(outputPath, renderedContent);
|
|
@@ -971,9 +971,9 @@ var BaseAddOn = class {
|
|
|
971
971
|
}
|
|
972
972
|
const outputPath = join3(projectDir, template.outputPath);
|
|
973
973
|
const outputDir = join3(outputPath, "..");
|
|
974
|
-
await import("fs").then((
|
|
975
|
-
if (!
|
|
976
|
-
|
|
974
|
+
await import("fs").then((fs9) => {
|
|
975
|
+
if (!fs9.existsSync(outputDir)) {
|
|
976
|
+
fs9.mkdirSync(outputDir, { recursive: true });
|
|
977
977
|
}
|
|
978
978
|
});
|
|
979
979
|
await writeFile2(outputPath, renderedContent);
|
|
@@ -1838,9 +1838,9 @@ var addOnRegistry = AddOnRegistry.create().register(new RedisStoreAddOn()).regis
|
|
|
1838
1838
|
|
|
1839
1839
|
// src/core/file-system.ts
|
|
1840
1840
|
import * as fs2 from "fs/promises";
|
|
1841
|
-
async function isPathEmpty(
|
|
1841
|
+
async function isPathEmpty(path25) {
|
|
1842
1842
|
try {
|
|
1843
|
-
const files = await fs2.readdir(
|
|
1843
|
+
const files = await fs2.readdir(path25);
|
|
1844
1844
|
return files.length === 0;
|
|
1845
1845
|
} catch (error) {
|
|
1846
1846
|
if (error.code === "ENOENT") {
|
|
@@ -2173,14 +2173,14 @@ var ProjectGenerator = class {
|
|
|
2173
2173
|
dockerSetup.message(
|
|
2174
2174
|
`Checking and stopping currently running containers...`
|
|
2175
2175
|
);
|
|
2176
|
-
const
|
|
2177
|
-
const
|
|
2178
|
-
const
|
|
2179
|
-
const composeFilePath =
|
|
2176
|
+
const fs9 = await import("fs/promises");
|
|
2177
|
+
const path25 = await import("path");
|
|
2178
|
+
const yaml2 = await import("js-yaml");
|
|
2179
|
+
const composeFilePath = path25.join(this.targetDir, "docker-compose.yml");
|
|
2180
2180
|
let ports = [];
|
|
2181
2181
|
try {
|
|
2182
|
-
const composeContent = await
|
|
2183
|
-
const doc =
|
|
2182
|
+
const composeContent = await fs9.readFile(composeFilePath, "utf8");
|
|
2183
|
+
const doc = yaml2.load(composeContent);
|
|
2184
2184
|
const services = doc?.services || {};
|
|
2185
2185
|
for (const svcKey of Object.keys(services)) {
|
|
2186
2186
|
const service = services[svcKey];
|
|
@@ -2287,7 +2287,7 @@ var initCommand = new Command().command("init").description("Create a new Ignite
|
|
|
2287
2287
|
).option("--no-git", "Skip git repository initialization").option("--no-install", "Skip automatic dependency installation").option("--no-docker", "Skip Docker Compose setup").action(handleInitAction);
|
|
2288
2288
|
|
|
2289
2289
|
// src/commands/generate/index.ts
|
|
2290
|
-
import { Command as
|
|
2290
|
+
import { Command as Command8 } from "commander";
|
|
2291
2291
|
|
|
2292
2292
|
// src/commands/generate/feature/index.ts
|
|
2293
2293
|
import { Command as Command2 } from "commander";
|
|
@@ -2900,8 +2900,8 @@ async function handleGenerateFeatureAction(name, options) {
|
|
|
2900
2900
|
p7.outro("Nothing to do.");
|
|
2901
2901
|
process.exit(1);
|
|
2902
2902
|
}
|
|
2903
|
-
const
|
|
2904
|
-
|
|
2903
|
+
const spinner8 = p7.spinner();
|
|
2904
|
+
spinner8.start(`Scaffolding feature '${featureSlug}'...`);
|
|
2905
2905
|
try {
|
|
2906
2906
|
if (provider && selection) {
|
|
2907
2907
|
await FeatureWorkspace.ensureStructure(featureDir);
|
|
@@ -2914,13 +2914,13 @@ async function handleGenerateFeatureAction(name, options) {
|
|
|
2914
2914
|
await FeatureWorkspace.ensureStructure(featureDir);
|
|
2915
2915
|
await generateEmptyFeature(featureSlug, featureDir, templateEngine);
|
|
2916
2916
|
}
|
|
2917
|
-
|
|
2917
|
+
spinner8.stop(`Feature '${featureSlug}' created successfully!`);
|
|
2918
2918
|
p7.log.success(
|
|
2919
2919
|
`Scaffolded feature '${featureSlug}'. Remember to register the controller in your router.`
|
|
2920
2920
|
);
|
|
2921
2921
|
p7.outro("Feature generation complete!");
|
|
2922
2922
|
} catch (error) {
|
|
2923
|
-
|
|
2923
|
+
spinner8.stop("Failed to scaffold the feature.");
|
|
2924
2924
|
const message = error instanceof Error ? error.message : String(error);
|
|
2925
2925
|
p7.log.error(`Feature generation failed: ${message}`);
|
|
2926
2926
|
process.exit(1);
|
|
@@ -2929,7 +2929,7 @@ async function handleGenerateFeatureAction(name, options) {
|
|
|
2929
2929
|
async function resolveSchemaProvider({
|
|
2930
2930
|
registry,
|
|
2931
2931
|
options,
|
|
2932
|
-
cancel:
|
|
2932
|
+
cancel: cancel5
|
|
2933
2933
|
}) {
|
|
2934
2934
|
if (options.schema) {
|
|
2935
2935
|
const provider2 = registry.findBySchemaOption(options.schema);
|
|
@@ -2962,7 +2962,7 @@ async function resolveSchemaProvider({
|
|
|
2962
2962
|
initialValue: "none"
|
|
2963
2963
|
});
|
|
2964
2964
|
if (p7.isCancel(choice)) {
|
|
2965
|
-
|
|
2965
|
+
cancel5("Feature generation cancelled.");
|
|
2966
2966
|
}
|
|
2967
2967
|
if (choice === "none") {
|
|
2968
2968
|
return { provider: null, selection: null };
|
|
@@ -3063,13 +3063,13 @@ var OpenAPIGenerator = class _OpenAPIGenerator {
|
|
|
3063
3063
|
for (const [controllerKey, controller] of Object.entries(router.controllers)) {
|
|
3064
3064
|
for (const [actionKey, action] of Object.entries(controller.actions)) {
|
|
3065
3065
|
const actionName = action.name || actionKey;
|
|
3066
|
-
let
|
|
3067
|
-
|
|
3068
|
-
if (
|
|
3069
|
-
|
|
3066
|
+
let path25 = `/${controller.path}/${action.path}`;
|
|
3067
|
+
path25 = path25.replace(/\/{2,}/g, "/");
|
|
3068
|
+
if (path25.length > 1 && path25.endsWith("/")) {
|
|
3069
|
+
path25 = path25.slice(0, -1);
|
|
3070
3070
|
}
|
|
3071
|
-
if (!paths[
|
|
3072
|
-
paths[
|
|
3071
|
+
if (!paths[path25]) {
|
|
3072
|
+
paths[path25] = {};
|
|
3073
3073
|
}
|
|
3074
3074
|
const operation = {
|
|
3075
3075
|
summary: action.description || actionName,
|
|
@@ -3131,7 +3131,7 @@ var OpenAPIGenerator = class _OpenAPIGenerator {
|
|
|
3131
3131
|
if (action.isStream) {
|
|
3132
3132
|
operation.description = (operation.description ? operation.description + "\n\n" : "") + "This endpoint supports Server-Sent Events (SSE) for real-time updates. It functions as a standard GET request initially, then maintains an open connection for streaming data.";
|
|
3133
3133
|
}
|
|
3134
|
-
paths[
|
|
3134
|
+
paths[path25][action.method.toLowerCase()] = operation;
|
|
3135
3135
|
}
|
|
3136
3136
|
}
|
|
3137
3137
|
return paths;
|
|
@@ -3307,10 +3307,10 @@ async function generateDocsWatchMode(routerPath, outputDir) {
|
|
|
3307
3307
|
async function handleGenerateDocsAction(options) {
|
|
3308
3308
|
try {
|
|
3309
3309
|
p9.intro("Generate OpenAPI documentation");
|
|
3310
|
-
const
|
|
3311
|
-
|
|
3310
|
+
const spinner8 = p9.spinner();
|
|
3311
|
+
spinner8.start("Generating OpenAPI documentation...");
|
|
3312
3312
|
const result = await generateDocsWatchMode(options.router, options.output);
|
|
3313
|
-
|
|
3313
|
+
spinner8.stop("OpenAPI documentation generated successfully.");
|
|
3314
3314
|
p9.log.success(
|
|
3315
3315
|
`Docs generated in ${(result.durationMs / 1e3).toFixed(2)}s (${result.sizeKb.toFixed(1)} KB)`
|
|
3316
3316
|
);
|
|
@@ -3354,10 +3354,10 @@ async function generateSchemaWatchMode(routerPath, outputPath) {
|
|
|
3354
3354
|
async function handleGenerateSchemaAction({ router: routerPath, output: outputPath }) {
|
|
3355
3355
|
try {
|
|
3356
3356
|
p10.intro("Generate Igniter.js Client Schema");
|
|
3357
|
-
const
|
|
3358
|
-
|
|
3357
|
+
const spinner8 = p10.spinner();
|
|
3358
|
+
spinner8.start("Generating schema...");
|
|
3359
3359
|
const result = await generateSchemaWatchMode(routerPath, outputPath);
|
|
3360
|
-
|
|
3360
|
+
spinner8.stop("Schema generated successfully.");
|
|
3361
3361
|
p10.log.success(
|
|
3362
3362
|
`Schema generated in ${(result.durationMs / 1e3).toFixed(2)}s (${result.controllers} controllers, ${result.actions} actions).`
|
|
3363
3363
|
);
|
|
@@ -3432,8 +3432,8 @@ async function handleGenerateControllerAction(name, options) {
|
|
|
3432
3432
|
"feature",
|
|
3433
3433
|
"empty.controller.hbs"
|
|
3434
3434
|
);
|
|
3435
|
-
const
|
|
3436
|
-
|
|
3435
|
+
const spinner8 = p11.spinner();
|
|
3436
|
+
spinner8.start(
|
|
3437
3437
|
`Creating controller '${controllerSlug}' inside feature '${featureSlug}'...`
|
|
3438
3438
|
);
|
|
3439
3439
|
try {
|
|
@@ -3446,13 +3446,13 @@ async function handleGenerateControllerAction(name, options) {
|
|
|
3446
3446
|
},
|
|
3447
3447
|
controllerPath
|
|
3448
3448
|
);
|
|
3449
|
-
|
|
3449
|
+
spinner8.stop("Controller created successfully!");
|
|
3450
3450
|
p11.log.success(
|
|
3451
3451
|
`Created controller '${controllerSlug}' in feature '${featureSlug}'.`
|
|
3452
3452
|
);
|
|
3453
3453
|
p11.outro("Controller generation complete!");
|
|
3454
3454
|
} catch (error) {
|
|
3455
|
-
|
|
3455
|
+
spinner8.stop("Failed to create the controller.");
|
|
3456
3456
|
const message = error instanceof Error ? error.message : String(error);
|
|
3457
3457
|
p11.log.error(`Controller generation failed: ${message}`);
|
|
3458
3458
|
process.exit(1);
|
|
@@ -3510,8 +3510,8 @@ async function handleGenerateProcedureAction(name, options) {
|
|
|
3510
3510
|
"feature",
|
|
3511
3511
|
"procedure.hbs"
|
|
3512
3512
|
);
|
|
3513
|
-
const
|
|
3514
|
-
|
|
3513
|
+
const spinner8 = p12.spinner();
|
|
3514
|
+
spinner8.start(
|
|
3515
3515
|
`Creating procedure '${procedureSlug}' inside feature '${featureSlug}'...`
|
|
3516
3516
|
);
|
|
3517
3517
|
if (await FeatureWorkspace.fileExists(path22.join(featureDir, "procedures", ".gitkeep"))) {
|
|
@@ -3527,13 +3527,13 @@ async function handleGenerateProcedureAction(name, options) {
|
|
|
3527
3527
|
},
|
|
3528
3528
|
procedurePath
|
|
3529
3529
|
);
|
|
3530
|
-
|
|
3530
|
+
spinner8.stop("Procedure created successfully!");
|
|
3531
3531
|
p12.log.success(
|
|
3532
3532
|
`Created procedure '${procedureSlug}' in feature '${featureSlug}'.`
|
|
3533
3533
|
);
|
|
3534
3534
|
p12.outro("Procedure generation complete!");
|
|
3535
3535
|
} catch (error) {
|
|
3536
|
-
|
|
3536
|
+
spinner8.stop("Failed to create the procedure.");
|
|
3537
3537
|
const message = error instanceof Error ? error.message : String(error);
|
|
3538
3538
|
p12.log.error(`Procedure generation failed: ${message}`);
|
|
3539
3539
|
process.exit(1);
|
|
@@ -3543,15 +3543,699 @@ async function handleGenerateProcedureAction(name, options) {
|
|
|
3543
3543
|
// src/commands/generate/procedure/index.ts
|
|
3544
3544
|
var procedureCommand = new Command6().command("procedure").description("Scaffold a new procedure within a feature").argument("[name]", "Name of the procedure (e.g., 'profile')").option("-f, --feature <feature>", "Target feature name").action(handleGenerateProcedureAction);
|
|
3545
3545
|
|
|
3546
|
+
// src/commands/generate/caller/index.ts
|
|
3547
|
+
import { Command as Command7 } from "commander";
|
|
3548
|
+
|
|
3549
|
+
// src/commands/generate/caller/action.ts
|
|
3550
|
+
import { promises as fs7 } from "fs";
|
|
3551
|
+
import path23 from "path";
|
|
3552
|
+
import * as p13 from "@clack/prompts";
|
|
3553
|
+
import yaml from "js-yaml";
|
|
3554
|
+
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
3555
|
+
var SUPPORTED_METHODS = ["get", "post", "put", "patch", "delete", "head"];
|
|
3556
|
+
async function handleGenerateCallerAction(options) {
|
|
3557
|
+
p13.intro("Generate Igniter Caller");
|
|
3558
|
+
try {
|
|
3559
|
+
const callerName = await resolveCallerName(options.name);
|
|
3560
|
+
const source = await resolveSource(options);
|
|
3561
|
+
const spinner8 = p13.spinner();
|
|
3562
|
+
spinner8.start("Loading OpenAPI spec...");
|
|
3563
|
+
const parsedDoc = await loadOpenApiDocument(source);
|
|
3564
|
+
const host = deriveHostname(source, parsedDoc) || "api";
|
|
3565
|
+
const outputDir = path23.resolve(
|
|
3566
|
+
process.cwd(),
|
|
3567
|
+
options.output ?? path23.join("src", "callers", host)
|
|
3568
|
+
);
|
|
3569
|
+
const prefix = Casing.toPascalCase(callerName);
|
|
3570
|
+
const converter = new SchemaConverter(prefix, parsedDoc.components);
|
|
3571
|
+
const componentsCode = converter.renderComponentSchemas();
|
|
3572
|
+
const schemaConstName = `${Casing.toCamelCase(callerName)}CallerSchemas`;
|
|
3573
|
+
const schemaTypeName = `${prefix}CallerSchemas`;
|
|
3574
|
+
const { builderCode, typeAliases } = buildSchemaBuilder(
|
|
3575
|
+
parsedDoc,
|
|
3576
|
+
converter,
|
|
3577
|
+
schemaConstName
|
|
3578
|
+
);
|
|
3579
|
+
const schemaFileContents = [
|
|
3580
|
+
"/* eslint-disable */",
|
|
3581
|
+
"/* prettier-ignore */",
|
|
3582
|
+
"/**",
|
|
3583
|
+
" * @generated by @igniter-js/cli",
|
|
3584
|
+
" * This file was automatically created from your OpenAPI spec.",
|
|
3585
|
+
" * Do not edit manually; regenerate via `igniter generate caller`.",
|
|
3586
|
+
" */",
|
|
3587
|
+
'import { z } from "zod";',
|
|
3588
|
+
'import { IgniterCallerSchema } from "@igniter-js/caller";',
|
|
3589
|
+
componentsCode,
|
|
3590
|
+
builderCode,
|
|
3591
|
+
`export type ${schemaTypeName} = typeof ${schemaConstName};`,
|
|
3592
|
+
typeAliases,
|
|
3593
|
+
""
|
|
3594
|
+
].filter(Boolean).join("\n");
|
|
3595
|
+
const baseUrl = inferBaseUrl(source, parsedDoc);
|
|
3596
|
+
const callerVar = `${Casing.toCamelCase(callerName)}Caller`;
|
|
3597
|
+
const callerFileContents = [
|
|
3598
|
+
"/**",
|
|
3599
|
+
` * Preconfigured IgniterCaller for ${prefix}.`,
|
|
3600
|
+
" * Regenerate via `igniter generate caller` when the OpenAPI spec changes.",
|
|
3601
|
+
" */",
|
|
3602
|
+
'import { IgniterCaller } from "@igniter-js/caller";',
|
|
3603
|
+
`import { ${schemaConstName} } from "./schema";`,
|
|
3604
|
+
"",
|
|
3605
|
+
"export const " + callerVar + " = IgniterCaller.create()",
|
|
3606
|
+
baseUrl ? ` .withBaseUrl("${baseUrl}")` : "",
|
|
3607
|
+
" .withSchemas(" + schemaConstName + ', { mode: "strict" })',
|
|
3608
|
+
" .build();",
|
|
3609
|
+
""
|
|
3610
|
+
].filter(Boolean).join("\n");
|
|
3611
|
+
await fs7.mkdir(outputDir, { recursive: true });
|
|
3612
|
+
await fs7.writeFile(path23.join(outputDir, "schema.ts"), schemaFileContents, "utf8");
|
|
3613
|
+
await fs7.writeFile(path23.join(outputDir, "index.ts"), callerFileContents, "utf8");
|
|
3614
|
+
spinner8.stop("Caller generated successfully.");
|
|
3615
|
+
p13.log.success(
|
|
3616
|
+
`Schemas and caller created at ${path23.relative(process.cwd(), outputDir)}`
|
|
3617
|
+
);
|
|
3618
|
+
p13.log.info(`Exported caller: ${callerVar} (schemas: ${schemaConstName})`);
|
|
3619
|
+
} catch (error) {
|
|
3620
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3621
|
+
p13.log.error(message);
|
|
3622
|
+
process.exit(1);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
async function resolveCallerName(name) {
|
|
3626
|
+
if (name) {
|
|
3627
|
+
return name.trim();
|
|
3628
|
+
}
|
|
3629
|
+
const answer = await p13.text({
|
|
3630
|
+
message: "Caller name (e.g., facebook, billing)",
|
|
3631
|
+
validate: (value) => value && value.trim().length > 0 ? void 0 : "Name is required"
|
|
3632
|
+
});
|
|
3633
|
+
if (p13.isCancel(answer)) {
|
|
3634
|
+
p13.cancel("Caller generation cancelled.");
|
|
3635
|
+
process.exit(0);
|
|
3636
|
+
}
|
|
3637
|
+
return String(answer);
|
|
3638
|
+
}
|
|
3639
|
+
async function resolveSource(options) {
|
|
3640
|
+
if (options.url && options.path) {
|
|
3641
|
+
throw new Error("Use either --url or --path, not both.");
|
|
3642
|
+
}
|
|
3643
|
+
if (options.url) {
|
|
3644
|
+
return { type: "url", url: options.url };
|
|
3645
|
+
}
|
|
3646
|
+
if (options.path) {
|
|
3647
|
+
return { type: "path", path: options.path };
|
|
3648
|
+
}
|
|
3649
|
+
const sourceChoice = await p13.select({
|
|
3650
|
+
message: "Where is your OpenAPI spec?",
|
|
3651
|
+
options: [
|
|
3652
|
+
{ label: "Remote URL", value: "url" },
|
|
3653
|
+
{ label: "Local file path", value: "path" }
|
|
3654
|
+
],
|
|
3655
|
+
initialValue: "url"
|
|
3656
|
+
});
|
|
3657
|
+
if (p13.isCancel(sourceChoice)) {
|
|
3658
|
+
p13.cancel("Caller generation cancelled.");
|
|
3659
|
+
process.exit(0);
|
|
3660
|
+
}
|
|
3661
|
+
if (sourceChoice === "url") {
|
|
3662
|
+
const url = await p13.text({
|
|
3663
|
+
message: "OpenAPI URL",
|
|
3664
|
+
placeholder: "https://api.example.com/openapi.json",
|
|
3665
|
+
validate: (value) => !value ? "URL is required" : void 0
|
|
3666
|
+
});
|
|
3667
|
+
if (p13.isCancel(url)) {
|
|
3668
|
+
p13.cancel("Caller generation cancelled.");
|
|
3669
|
+
process.exit(0);
|
|
3670
|
+
}
|
|
3671
|
+
return { type: "url", url: String(url) };
|
|
3672
|
+
}
|
|
3673
|
+
const filePath = await p13.text({
|
|
3674
|
+
message: "Path to OpenAPI file",
|
|
3675
|
+
placeholder: "./openapi.json",
|
|
3676
|
+
validate: (value) => !value ? "Path is required" : void 0
|
|
3677
|
+
});
|
|
3678
|
+
if (p13.isCancel(filePath)) {
|
|
3679
|
+
p13.cancel("Caller generation cancelled.");
|
|
3680
|
+
process.exit(0);
|
|
3681
|
+
}
|
|
3682
|
+
return { type: "path", path: String(filePath) };
|
|
3683
|
+
}
|
|
3684
|
+
async function loadOpenApiDocument(source) {
|
|
3685
|
+
const raw = await loadRawContent(source);
|
|
3686
|
+
const parsed = parseOpenApi(raw);
|
|
3687
|
+
const version = parsed.openapi ?? "";
|
|
3688
|
+
if (typeof version !== "string" || !version.startsWith("3.")) {
|
|
3689
|
+
throw new Error("Only OpenAPI 3.x documents are supported.");
|
|
3690
|
+
}
|
|
3691
|
+
const bundled = await SwaggerParser.bundle(parsed);
|
|
3692
|
+
return bundled;
|
|
3693
|
+
}
|
|
3694
|
+
async function loadRawContent(source) {
|
|
3695
|
+
if (source.type === "url") {
|
|
3696
|
+
const response = await fetch(source.url);
|
|
3697
|
+
if (!response.ok) {
|
|
3698
|
+
throw new Error(
|
|
3699
|
+
`Failed to fetch OpenAPI document: ${response.status} ${response.statusText}`
|
|
3700
|
+
);
|
|
3701
|
+
}
|
|
3702
|
+
return await response.text();
|
|
3703
|
+
}
|
|
3704
|
+
const filePath = path23.resolve(process.cwd(), source.path);
|
|
3705
|
+
return await fs7.readFile(filePath, "utf8");
|
|
3706
|
+
}
|
|
3707
|
+
function parseOpenApi(content) {
|
|
3708
|
+
try {
|
|
3709
|
+
return JSON.parse(content);
|
|
3710
|
+
} catch {
|
|
3711
|
+
const parsed = yaml.load(content);
|
|
3712
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3713
|
+
throw new Error("Could not parse OpenAPI document (JSON/YAML).");
|
|
3714
|
+
}
|
|
3715
|
+
return parsed;
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
function deriveHostname(source, doc) {
|
|
3719
|
+
if (source.type === "url") {
|
|
3720
|
+
try {
|
|
3721
|
+
const url = new URL(source.url);
|
|
3722
|
+
return url.hostname.replace(/[^a-zA-Z0-9.-]/g, "") || null;
|
|
3723
|
+
} catch {
|
|
3724
|
+
return null;
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
const serverUrl = doc.servers?.[0]?.url;
|
|
3728
|
+
if (serverUrl) {
|
|
3729
|
+
try {
|
|
3730
|
+
const url = new URL(serverUrl);
|
|
3731
|
+
return url.hostname.replace(/[^a-zA-Z0-9.-]/g, "") || null;
|
|
3732
|
+
} catch {
|
|
3733
|
+
return null;
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
return null;
|
|
3737
|
+
}
|
|
3738
|
+
function inferBaseUrl(source, doc) {
|
|
3739
|
+
if (source.type === "url") {
|
|
3740
|
+
const serverUrl2 = doc.servers?.[0]?.url;
|
|
3741
|
+
if (serverUrl2) {
|
|
3742
|
+
return serverUrl2;
|
|
3743
|
+
}
|
|
3744
|
+
return new URL(source.url).origin;
|
|
3745
|
+
}
|
|
3746
|
+
const serverUrl = doc.servers?.[0]?.url;
|
|
3747
|
+
return serverUrl ?? null;
|
|
3748
|
+
}
|
|
3749
|
+
var SchemaConverter = class {
|
|
3750
|
+
constructor(prefix, components) {
|
|
3751
|
+
this.generated = /* @__PURE__ */ new Map();
|
|
3752
|
+
this.inProgress = /* @__PURE__ */ new Set();
|
|
3753
|
+
this.prefix = prefix;
|
|
3754
|
+
this.components = components;
|
|
3755
|
+
}
|
|
3756
|
+
renderComponentSchemas() {
|
|
3757
|
+
const entries = [];
|
|
3758
|
+
const names = this.listComponents();
|
|
3759
|
+
for (const name of names) {
|
|
3760
|
+
const identifier = this.componentIdentifier(name);
|
|
3761
|
+
const expression = this.convertWithCache(name);
|
|
3762
|
+
const registryKey = this.registryKey(name);
|
|
3763
|
+
entries.push(
|
|
3764
|
+
[
|
|
3765
|
+
"/**",
|
|
3766
|
+
` * Schema: ${registryKey}`,
|
|
3767
|
+
` * Source: openapi#/components/schemas/${name}`,
|
|
3768
|
+
" */",
|
|
3769
|
+
`const ${identifier} = ${expression};`,
|
|
3770
|
+
""
|
|
3771
|
+
].join("\n")
|
|
3772
|
+
);
|
|
3773
|
+
}
|
|
3774
|
+
return entries.length ? `${entries.join("\n")}
|
|
3775
|
+
` : "";
|
|
3776
|
+
}
|
|
3777
|
+
listComponents() {
|
|
3778
|
+
return Object.keys(this.components?.schemas ?? {}).sort();
|
|
3779
|
+
}
|
|
3780
|
+
registryKey(name) {
|
|
3781
|
+
return Casing.toPascalCase(name);
|
|
3782
|
+
}
|
|
3783
|
+
componentName(name) {
|
|
3784
|
+
return this.componentIdentifier(name);
|
|
3785
|
+
}
|
|
3786
|
+
convert(schema) {
|
|
3787
|
+
if ("$ref" in schema) {
|
|
3788
|
+
const refName = this.refName(schema.$ref);
|
|
3789
|
+
const identifier = this.componentIdentifier(refName);
|
|
3790
|
+
this.convertWithCache(refName);
|
|
3791
|
+
return this.wrapLazyIfNeeded(refName, identifier);
|
|
3792
|
+
}
|
|
3793
|
+
const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
3794
|
+
const nullable = isNullable(schema);
|
|
3795
|
+
if (schema.oneOf?.length) {
|
|
3796
|
+
const parts = schema.oneOf.map((item) => this.convert(item));
|
|
3797
|
+
return wrapNullable(`z.union([${parts.join(", ")}])`, nullable);
|
|
3798
|
+
}
|
|
3799
|
+
if (schema.anyOf?.length) {
|
|
3800
|
+
const parts = schema.anyOf.map((item) => this.convert(item));
|
|
3801
|
+
return wrapNullable(`z.union([${parts.join(", ")}])`, nullable);
|
|
3802
|
+
}
|
|
3803
|
+
if (schema.allOf?.length) {
|
|
3804
|
+
const [first, ...rest] = schema.allOf;
|
|
3805
|
+
let expr = this.convert(first);
|
|
3806
|
+
for (const part of rest) {
|
|
3807
|
+
expr = `z.intersection(${expr}, ${this.convert(part)})`;
|
|
3808
|
+
}
|
|
3809
|
+
return wrapNullable(expr, nullable);
|
|
3810
|
+
}
|
|
3811
|
+
switch (schemaType) {
|
|
3812
|
+
case "string":
|
|
3813
|
+
if (schema.enum) {
|
|
3814
|
+
const values = schema.enum.map((v) => JSON.stringify(String(v)));
|
|
3815
|
+
return wrapNullable(`z.enum([${values.join(", ")}])`, nullable);
|
|
3816
|
+
}
|
|
3817
|
+
return wrapNullable("z.string()", nullable);
|
|
3818
|
+
case "number":
|
|
3819
|
+
case "integer": {
|
|
3820
|
+
let expr = "z.number()";
|
|
3821
|
+
if (schemaType === "integer") {
|
|
3822
|
+
expr = `${expr}.int()`;
|
|
3823
|
+
}
|
|
3824
|
+
return wrapNullable(expr, nullable);
|
|
3825
|
+
}
|
|
3826
|
+
case "boolean":
|
|
3827
|
+
return wrapNullable("z.boolean()", nullable);
|
|
3828
|
+
case "array": {
|
|
3829
|
+
const arraySchema = schema;
|
|
3830
|
+
const items = arraySchema.items ? this.convert(arraySchema.items) : "z.unknown()";
|
|
3831
|
+
return wrapNullable(`z.array(${items})`, nullable);
|
|
3832
|
+
}
|
|
3833
|
+
case "object": {
|
|
3834
|
+
const properties = schema.properties ?? {};
|
|
3835
|
+
const required = new Set(schema.required ?? []);
|
|
3836
|
+
const entries = Object.entries(properties).map(([key, value]) => {
|
|
3837
|
+
const valueExpr = this.convert(value);
|
|
3838
|
+
return `${JSON.stringify(key)}: ${required.has(key) ? valueExpr : `${valueExpr}.optional()`}`;
|
|
3839
|
+
});
|
|
3840
|
+
let base = `z.object({${entries.length ? ` ${entries.join(", ")} ` : ""}})`;
|
|
3841
|
+
if (schema.additionalProperties) {
|
|
3842
|
+
const apValue = schema.additionalProperties === true ? "z.unknown()" : this.convert(schema.additionalProperties);
|
|
3843
|
+
base = `${base}.catchall(${apValue})`;
|
|
3844
|
+
}
|
|
3845
|
+
return wrapNullable(base, nullable);
|
|
3846
|
+
}
|
|
3847
|
+
default:
|
|
3848
|
+
return "z.unknown()";
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
convertWithCache(name) {
|
|
3852
|
+
const identifier = this.componentIdentifier(name);
|
|
3853
|
+
if (this.generated.has(name)) {
|
|
3854
|
+
return this.generated.get(name);
|
|
3855
|
+
}
|
|
3856
|
+
if (this.inProgress.has(name)) {
|
|
3857
|
+
return `z.lazy(() => ${identifier})`;
|
|
3858
|
+
}
|
|
3859
|
+
const schema = this.components?.schemas?.[name];
|
|
3860
|
+
if (!schema) {
|
|
3861
|
+
return "z.unknown()";
|
|
3862
|
+
}
|
|
3863
|
+
this.inProgress.add(name);
|
|
3864
|
+
const expression = this.convert(schema);
|
|
3865
|
+
this.inProgress.delete(name);
|
|
3866
|
+
this.generated.set(name, expression);
|
|
3867
|
+
return expression;
|
|
3868
|
+
}
|
|
3869
|
+
componentIdentifier(name) {
|
|
3870
|
+
return `${this.prefix}${Casing.toPascalCase(name)}Schema`;
|
|
3871
|
+
}
|
|
3872
|
+
refName(ref) {
|
|
3873
|
+
const [, component] = ref.split("#/components/schemas/");
|
|
3874
|
+
if (!component) {
|
|
3875
|
+
return "UnknownSchema";
|
|
3876
|
+
}
|
|
3877
|
+
return component;
|
|
3878
|
+
}
|
|
3879
|
+
wrapLazyIfNeeded(name, identifier) {
|
|
3880
|
+
if (this.inProgress.has(name)) {
|
|
3881
|
+
return `z.lazy(() => ${identifier})`;
|
|
3882
|
+
}
|
|
3883
|
+
return identifier;
|
|
3884
|
+
}
|
|
3885
|
+
};
|
|
3886
|
+
function buildSchemaBuilder(doc, converter, schemaConstName) {
|
|
3887
|
+
const usedNames = /* @__PURE__ */ new Map();
|
|
3888
|
+
const operations = [];
|
|
3889
|
+
const pathBlocks = [];
|
|
3890
|
+
let needsVoid = false;
|
|
3891
|
+
const sortedPaths = Object.keys(doc.paths || {}).sort();
|
|
3892
|
+
for (const rawPath of sortedPaths) {
|
|
3893
|
+
const pathItem = doc.paths?.[rawPath];
|
|
3894
|
+
if (!pathItem) continue;
|
|
3895
|
+
const methodBlocks = [];
|
|
3896
|
+
for (const method of SUPPORTED_METHODS) {
|
|
3897
|
+
const operation = pathItem[method];
|
|
3898
|
+
if (!operation) continue;
|
|
3899
|
+
const normalizedPath2 = normalizePath(rawPath);
|
|
3900
|
+
const requestBody = buildRequestBody(
|
|
3901
|
+
operation.requestBody,
|
|
3902
|
+
converter,
|
|
3903
|
+
doc
|
|
3904
|
+
);
|
|
3905
|
+
const responses = buildResponses(operation.responses, converter, doc);
|
|
3906
|
+
const typeBaseName = ensureUniqueTypeName(
|
|
3907
|
+
buildOperationTypeName(rawPath, method, operation.operationId),
|
|
3908
|
+
usedNames
|
|
3909
|
+
);
|
|
3910
|
+
operations.push({
|
|
3911
|
+
path: normalizedPath2,
|
|
3912
|
+
method: method.toUpperCase(),
|
|
3913
|
+
request: Boolean(requestBody),
|
|
3914
|
+
responses: responses.entries,
|
|
3915
|
+
typeName: typeBaseName
|
|
3916
|
+
});
|
|
3917
|
+
const docBlock = buildMethodDocBlock({
|
|
3918
|
+
method: method.toUpperCase(),
|
|
3919
|
+
path: normalizedPath2,
|
|
3920
|
+
summary: operation.summary ?? operation.description,
|
|
3921
|
+
tags: operation.tags,
|
|
3922
|
+
operationId: operation.operationId,
|
|
3923
|
+
requestLabel: requestBody?.label,
|
|
3924
|
+
responses: responses.entries,
|
|
3925
|
+
source: `openapi#/paths/${encodeJsonPointerPath(rawPath)}/${method}`
|
|
3926
|
+
});
|
|
3927
|
+
const methodCall = buildMethodCall(method, {
|
|
3928
|
+
request: requestBody?.expression,
|
|
3929
|
+
responses: responses.code,
|
|
3930
|
+
doc: operation.summary ?? operation.description,
|
|
3931
|
+
tags: operation.tags,
|
|
3932
|
+
operationId: operation.operationId
|
|
3933
|
+
});
|
|
3934
|
+
methodBlocks.push(indent(`${docBlock}
|
|
3935
|
+
${methodCall}`, 3));
|
|
3936
|
+
needsVoid = needsVoid || responses.usesVoid;
|
|
3937
|
+
}
|
|
3938
|
+
if (!methodBlocks.length) continue;
|
|
3939
|
+
const normalizedPath = normalizePath(rawPath);
|
|
3940
|
+
const pathLines = [
|
|
3941
|
+
` .path(${JSON.stringify(normalizedPath)}, (path) =>`,
|
|
3942
|
+
" path",
|
|
3943
|
+
methodBlocks.join("\n"),
|
|
3944
|
+
" )"
|
|
3945
|
+
];
|
|
3946
|
+
pathBlocks.push(pathLines.join("\n"));
|
|
3947
|
+
}
|
|
3948
|
+
const builderLines = [];
|
|
3949
|
+
builderLines.push(`export const ${schemaConstName} = IgniterCallerSchema.create()`);
|
|
3950
|
+
const componentNames = converter.listComponents();
|
|
3951
|
+
for (const name of componentNames) {
|
|
3952
|
+
const registryKey = converter.registryKey(name);
|
|
3953
|
+
const identifier = converter.componentName(name);
|
|
3954
|
+
builderLines.push(` .schema(${JSON.stringify(registryKey)}, ${identifier})`);
|
|
3955
|
+
}
|
|
3956
|
+
if (needsVoid) {
|
|
3957
|
+
builderLines.push(` .schema("Void", z.void(), { internal: true })`);
|
|
3958
|
+
}
|
|
3959
|
+
if (pathBlocks.length) {
|
|
3960
|
+
builderLines.push(pathBlocks.join("\n"));
|
|
3961
|
+
}
|
|
3962
|
+
builderLines.push(" .build()");
|
|
3963
|
+
return {
|
|
3964
|
+
builderCode: `${builderLines.join("\n")}
|
|
3965
|
+
`,
|
|
3966
|
+
typeAliases: buildTypeAliases(operations, schemaConstName)
|
|
3967
|
+
};
|
|
3968
|
+
}
|
|
3969
|
+
function buildMethodDocBlock(params) {
|
|
3970
|
+
const lines = [];
|
|
3971
|
+
lines.push("/**");
|
|
3972
|
+
lines.push(` * ${params.method} ${params.path}`);
|
|
3973
|
+
if (params.summary) {
|
|
3974
|
+
lines.push(` * Summary: ${params.summary}`);
|
|
3975
|
+
}
|
|
3976
|
+
if (params.tags?.length) {
|
|
3977
|
+
lines.push(` * Tags: ${params.tags.join(", ")}`);
|
|
3978
|
+
}
|
|
3979
|
+
if (params.operationId) {
|
|
3980
|
+
lines.push(` * OperationId: ${params.operationId}`);
|
|
3981
|
+
}
|
|
3982
|
+
if (params.requestLabel) {
|
|
3983
|
+
lines.push(` * Request: ${params.requestLabel}`);
|
|
3984
|
+
}
|
|
3985
|
+
if (params.responses.length) {
|
|
3986
|
+
lines.push(" * Responses:");
|
|
3987
|
+
for (const response of params.responses) {
|
|
3988
|
+
lines.push(` * - ${response.status}: ${response.label}`);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
lines.push(` * Source: ${params.source}`);
|
|
3992
|
+
lines.push(" */");
|
|
3993
|
+
return lines.join("\n");
|
|
3994
|
+
}
|
|
3995
|
+
function buildMethodCall(method, params) {
|
|
3996
|
+
const lines = [];
|
|
3997
|
+
lines.push(`.${method}({`);
|
|
3998
|
+
if (params.request) {
|
|
3999
|
+
lines.push(` request: ${params.request},`);
|
|
4000
|
+
}
|
|
4001
|
+
lines.push(" responses: {");
|
|
4002
|
+
lines.push(params.responses);
|
|
4003
|
+
lines.push(" },");
|
|
4004
|
+
if (params.doc) {
|
|
4005
|
+
lines.push(` doc: ${JSON.stringify(params.doc)},`);
|
|
4006
|
+
}
|
|
4007
|
+
if (params.tags?.length) {
|
|
4008
|
+
lines.push(` tags: ${JSON.stringify(params.tags)},`);
|
|
4009
|
+
}
|
|
4010
|
+
if (params.operationId) {
|
|
4011
|
+
lines.push(` operationId: ${JSON.stringify(params.operationId)},`);
|
|
4012
|
+
}
|
|
4013
|
+
lines.push("})");
|
|
4014
|
+
return lines.join("\n");
|
|
4015
|
+
}
|
|
4016
|
+
function buildResponses(responses, converter, doc) {
|
|
4017
|
+
const builder = new CodeBuilder();
|
|
4018
|
+
const entries = [];
|
|
4019
|
+
let usesVoid = false;
|
|
4020
|
+
if (!responses || !Object.keys(responses).length) {
|
|
4021
|
+
const expression = `path.ref("Void").schema`;
|
|
4022
|
+
builder.line(indent(`200: ${expression},`, 2));
|
|
4023
|
+
entries.push({
|
|
4024
|
+
status: "200",
|
|
4025
|
+
statusLiteral: "200",
|
|
4026
|
+
label: "Void"
|
|
4027
|
+
});
|
|
4028
|
+
return { code: builder.toString(), entries, usesVoid: true };
|
|
4029
|
+
}
|
|
4030
|
+
const sorted = Object.keys(responses).sort();
|
|
4031
|
+
for (const status of sorted) {
|
|
4032
|
+
const responseOrRef = responses[status];
|
|
4033
|
+
if (!responseOrRef) continue;
|
|
4034
|
+
const resolved = resolveResponse(responseOrRef, doc);
|
|
4035
|
+
const schema = resolveSchemaFromResponse(resolved);
|
|
4036
|
+
const resolvedSchema = resolveSchemaExpression(schema, converter);
|
|
4037
|
+
builder.line(indent(`${formatStatusKey(status)}: ${resolvedSchema.expression},`, 2));
|
|
4038
|
+
entries.push({
|
|
4039
|
+
status: String(status),
|
|
4040
|
+
statusLiteral: formatStatusType(status),
|
|
4041
|
+
label: resolvedSchema.label
|
|
4042
|
+
});
|
|
4043
|
+
usesVoid = usesVoid || resolvedSchema.usesVoid;
|
|
4044
|
+
}
|
|
4045
|
+
return { code: builder.toString(), entries, usesVoid };
|
|
4046
|
+
}
|
|
4047
|
+
function buildRequestBody(requestBody, converter, doc) {
|
|
4048
|
+
if (!requestBody) return null;
|
|
4049
|
+
const resolved = "$ref" in requestBody ? resolveRequestBody(requestBody, doc) : requestBody;
|
|
4050
|
+
if (!resolved?.content) return null;
|
|
4051
|
+
const schema = selectJsonSchema(resolved.content);
|
|
4052
|
+
if (!schema) return null;
|
|
4053
|
+
return resolveSchemaExpression(schema, converter);
|
|
4054
|
+
}
|
|
4055
|
+
function resolveSchemaExpression(schema, converter) {
|
|
4056
|
+
if (!schema) {
|
|
4057
|
+
return {
|
|
4058
|
+
expression: `path.ref("Void").schema`,
|
|
4059
|
+
label: "Void",
|
|
4060
|
+
usesVoid: true
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
const refName = extractSchemaRef(schema);
|
|
4064
|
+
if (refName) {
|
|
4065
|
+
const registryKey = converter.registryKey(refName);
|
|
4066
|
+
const base = `path.ref(${JSON.stringify(registryKey)}).schema`;
|
|
4067
|
+
return {
|
|
4068
|
+
expression: wrapNullable(base, isNullable(schema)),
|
|
4069
|
+
label: registryKey,
|
|
4070
|
+
usesVoid: false
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
const arrayRefName = extractArraySchemaRef(schema);
|
|
4074
|
+
if (arrayRefName) {
|
|
4075
|
+
const registryKey = converter.registryKey(arrayRefName);
|
|
4076
|
+
const base = `path.ref(${JSON.stringify(registryKey)}).array()`;
|
|
4077
|
+
return {
|
|
4078
|
+
expression: wrapNullable(base, isNullable(schema)),
|
|
4079
|
+
label: `${registryKey}[]`,
|
|
4080
|
+
usesVoid: false
|
|
4081
|
+
};
|
|
4082
|
+
}
|
|
4083
|
+
return {
|
|
4084
|
+
expression: converter.convert(schema),
|
|
4085
|
+
label: "InlineSchema",
|
|
4086
|
+
usesVoid: false
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
function buildTypeAliases(operations, schemaConstName) {
|
|
4090
|
+
if (!operations.length) return "";
|
|
4091
|
+
const lines = [];
|
|
4092
|
+
lines.push("// Derived types");
|
|
4093
|
+
for (const operation of operations) {
|
|
4094
|
+
const pathLiteral = JSON.stringify(operation.path);
|
|
4095
|
+
const methodLiteral = JSON.stringify(operation.method);
|
|
4096
|
+
if (operation.request) {
|
|
4097
|
+
lines.push(
|
|
4098
|
+
`export type ${operation.typeName}Request = ReturnType<typeof ${schemaConstName}.$Infer.Request<${pathLiteral}, ${methodLiteral}>>;`
|
|
4099
|
+
);
|
|
4100
|
+
}
|
|
4101
|
+
lines.push(
|
|
4102
|
+
`export type ${operation.typeName}Responses = ReturnType<typeof ${schemaConstName}.$Infer.Responses<${pathLiteral}, ${methodLiteral}>>;`
|
|
4103
|
+
);
|
|
4104
|
+
for (const response of operation.responses) {
|
|
4105
|
+
const suffix = formatStatusSuffix(response.status);
|
|
4106
|
+
lines.push(
|
|
4107
|
+
`export type ${operation.typeName}Response${suffix} = ReturnType<typeof ${schemaConstName}.$Infer.Response<${pathLiteral}, ${methodLiteral}, ${response.statusLiteral}>>;`
|
|
4108
|
+
);
|
|
4109
|
+
}
|
|
4110
|
+
lines.push("");
|
|
4111
|
+
}
|
|
4112
|
+
return lines.join("\n");
|
|
4113
|
+
}
|
|
4114
|
+
function buildOperationTypeName(rawPath, method, operationId) {
|
|
4115
|
+
if (operationId) {
|
|
4116
|
+
return Casing.toPascalCase(operationId);
|
|
4117
|
+
}
|
|
4118
|
+
const normalizedPath = normalizePath(rawPath);
|
|
4119
|
+
const segments = normalizedPath.split("/").filter(Boolean).map((segment) => segment.startsWith(":") ? `by-${segment.slice(1)}` : segment);
|
|
4120
|
+
return Casing.toPascalCase([...segments, method].join("-"));
|
|
4121
|
+
}
|
|
4122
|
+
function ensureUniqueTypeName(baseName, used) {
|
|
4123
|
+
const count = used.get(baseName) ?? 0;
|
|
4124
|
+
if (count === 0) {
|
|
4125
|
+
used.set(baseName, 1);
|
|
4126
|
+
return baseName;
|
|
4127
|
+
}
|
|
4128
|
+
const next = `${baseName}${count + 1}`;
|
|
4129
|
+
used.set(baseName, count + 1);
|
|
4130
|
+
return next;
|
|
4131
|
+
}
|
|
4132
|
+
function encodeJsonPointerPath(pathname) {
|
|
4133
|
+
return pathname.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
4134
|
+
}
|
|
4135
|
+
function extractSchemaRef(schema) {
|
|
4136
|
+
if (!schema || typeof schema !== "object") return null;
|
|
4137
|
+
if (!("$ref" in schema)) return null;
|
|
4138
|
+
return extractComponentSchemaName(schema.$ref);
|
|
4139
|
+
}
|
|
4140
|
+
function extractArraySchemaRef(schema) {
|
|
4141
|
+
if (!schema || typeof schema !== "object") return null;
|
|
4142
|
+
if (schema.type !== "array") return null;
|
|
4143
|
+
const items = schema.items;
|
|
4144
|
+
if (!items || typeof items !== "object") return null;
|
|
4145
|
+
if (!("$ref" in items)) return null;
|
|
4146
|
+
return extractComponentSchemaName(items.$ref);
|
|
4147
|
+
}
|
|
4148
|
+
function extractComponentSchemaName(ref) {
|
|
4149
|
+
const match = ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
4150
|
+
return match ? match[1] : null;
|
|
4151
|
+
}
|
|
4152
|
+
function selectJsonSchema(content) {
|
|
4153
|
+
if (content["application/json"]?.schema) {
|
|
4154
|
+
return content["application/json"].schema;
|
|
4155
|
+
}
|
|
4156
|
+
const first = Object.values(content)[0];
|
|
4157
|
+
return first?.schema;
|
|
4158
|
+
}
|
|
4159
|
+
function resolveSchemaFromResponse(response) {
|
|
4160
|
+
if (!response?.content) {
|
|
4161
|
+
return void 0;
|
|
4162
|
+
}
|
|
4163
|
+
return selectJsonSchema(response.content);
|
|
4164
|
+
}
|
|
4165
|
+
function normalizePath(pathname) {
|
|
4166
|
+
return pathname.replace(/{(.*?)}/g, ":$1");
|
|
4167
|
+
}
|
|
4168
|
+
function indent(value, depth) {
|
|
4169
|
+
const pad = " ".repeat(depth);
|
|
4170
|
+
return value.split("\n").map((line) => line ? pad + line : line).join("\n");
|
|
4171
|
+
}
|
|
4172
|
+
function isNullable(schema) {
|
|
4173
|
+
if (!schema || typeof schema !== "object") return false;
|
|
4174
|
+
return Boolean(schema.nullable);
|
|
4175
|
+
}
|
|
4176
|
+
function wrapNullable(expr, nullable) {
|
|
4177
|
+
return nullable ? `${expr}.nullable()` : expr;
|
|
4178
|
+
}
|
|
4179
|
+
function formatStatusKey(status) {
|
|
4180
|
+
const trimmed = String(status).trim();
|
|
4181
|
+
const numeric = Number(trimmed);
|
|
4182
|
+
if (!Number.isNaN(numeric) && `${numeric}` === trimmed) {
|
|
4183
|
+
return trimmed;
|
|
4184
|
+
}
|
|
4185
|
+
return JSON.stringify(trimmed);
|
|
4186
|
+
}
|
|
4187
|
+
function formatStatusType(status) {
|
|
4188
|
+
return formatStatusKey(status);
|
|
4189
|
+
}
|
|
4190
|
+
function formatStatusSuffix(status) {
|
|
4191
|
+
const safe = String(status).replace(/[^a-zA-Z0-9]+/g, "-");
|
|
4192
|
+
const pascal = Casing.toPascalCase(safe);
|
|
4193
|
+
return pascal || "Unknown";
|
|
4194
|
+
}
|
|
4195
|
+
function resolveResponse(response, doc) {
|
|
4196
|
+
if ("$ref" in response) {
|
|
4197
|
+
return getComponent(doc, response.$ref, "responses");
|
|
4198
|
+
}
|
|
4199
|
+
return response;
|
|
4200
|
+
}
|
|
4201
|
+
function resolveRequestBody(requestBody, doc) {
|
|
4202
|
+
return getComponent(doc, requestBody.$ref, "requestBodies");
|
|
4203
|
+
}
|
|
4204
|
+
function getComponent(doc, ref, type) {
|
|
4205
|
+
const match = ref.match(/^#\/components\/([^/]+)\/(.+)$/);
|
|
4206
|
+
if (!match) return void 0;
|
|
4207
|
+
const [, category, name] = match;
|
|
4208
|
+
if (category !== type) return void 0;
|
|
4209
|
+
const component = doc.components?.[type]?.[name];
|
|
4210
|
+
return component;
|
|
4211
|
+
}
|
|
4212
|
+
var CodeBuilder = class {
|
|
4213
|
+
constructor() {
|
|
4214
|
+
this.lines = [];
|
|
4215
|
+
}
|
|
4216
|
+
line(text4) {
|
|
4217
|
+
this.lines.push(text4);
|
|
4218
|
+
}
|
|
4219
|
+
toString() {
|
|
4220
|
+
return this.lines.join("\n");
|
|
4221
|
+
}
|
|
4222
|
+
};
|
|
4223
|
+
|
|
4224
|
+
// src/commands/generate/caller/index.ts
|
|
4225
|
+
var callerCommand = new Command7().command("caller").description("Generate Igniter Caller schemas from an OpenAPI spec").option("--name <name>", "Name used to prefix generated schemas and caller export").option("--url <url>", "URL to the OpenAPI document").option("--path <path>", "Local path to the OpenAPI document").option(
|
|
4226
|
+
"--output <path>",
|
|
4227
|
+
"Output directory (defaults to src/callers/<hostname>)"
|
|
4228
|
+
).action(handleGenerateCallerAction);
|
|
4229
|
+
|
|
3546
4230
|
// src/commands/generate/index.ts
|
|
3547
|
-
var generateCommand = new
|
|
4231
|
+
var generateCommand = new Command8().command("generate").description("Scaffold new features or generate client schema").addCommand(featureCommand).addCommand(controllerCommand).addCommand(procedureCommand).addCommand(docsCommand).addCommand(schemaCommand).addCommand(callerCommand);
|
|
3548
4232
|
|
|
3549
4233
|
// src/commands/dev/index.ts
|
|
3550
|
-
import { Command as
|
|
4234
|
+
import { Command as Command9 } from "commander";
|
|
3551
4235
|
|
|
3552
4236
|
// src/commands/dev/action.ts
|
|
3553
|
-
import * as
|
|
3554
|
-
import * as
|
|
4237
|
+
import * as path24 from "path";
|
|
4238
|
+
import * as fs8 from "fs";
|
|
3555
4239
|
import { spawn as spawn2 } from "child_process";
|
|
3556
4240
|
import chokidar from "chokidar";
|
|
3557
4241
|
import { render } from "ink";
|
|
@@ -3586,14 +4270,14 @@ function DevUI({ igniterLogs, appLogs, onExit }) {
|
|
|
3586
4270
|
};
|
|
3587
4271
|
const renderLogs = (logs, maxLines = 50) => {
|
|
3588
4272
|
const visibleLogs = logs.slice(-maxLines);
|
|
3589
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, visibleLogs.length === 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "No logs yet...") : visibleLogs.map((
|
|
4273
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, visibleLogs.length === 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "No logs yet...") : visibleLogs.map((log12, index) => {
|
|
3590
4274
|
const color2 = {
|
|
3591
4275
|
info: "cyan",
|
|
3592
4276
|
success: "green",
|
|
3593
4277
|
error: "red",
|
|
3594
4278
|
warn: "yellow"
|
|
3595
|
-
}[
|
|
3596
|
-
return /* @__PURE__ */ React.createElement(Text, { key: `${
|
|
4279
|
+
}[log12.type];
|
|
4280
|
+
return /* @__PURE__ */ React.createElement(Text, { key: `${log12.timestamp.getTime()}-${index}`, color: color2 }, formatLogEntry(log12));
|
|
3597
4281
|
}));
|
|
3598
4282
|
};
|
|
3599
4283
|
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Igniter.js Development Mode")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React.createElement(
|
|
@@ -3655,19 +4339,19 @@ async function regenerateSchemaAndDocs(routerPath, outputPath, docsOutputDir, ad
|
|
|
3655
4339
|
}
|
|
3656
4340
|
async function handleDevAction(options) {
|
|
3657
4341
|
const packageManager = detectPackageManager();
|
|
3658
|
-
const routerPath =
|
|
4342
|
+
const routerPath = path24.resolve(process.cwd(), options.router || "src/igniter.router.ts");
|
|
3659
4343
|
const outputPath = options.output || "src/igniter.schema.ts";
|
|
3660
4344
|
const docsOutputDir = options.docsOutput || "./src/docs";
|
|
3661
4345
|
const devCommand2 = options.cmd || getDefaultDevCommand(packageManager);
|
|
3662
|
-
if (!
|
|
4346
|
+
if (!fs8.existsSync(routerPath)) {
|
|
3663
4347
|
console.error(`Router file not found: ${routerPath}`);
|
|
3664
4348
|
process.exit(1);
|
|
3665
4349
|
}
|
|
3666
4350
|
const igniterLogs = [];
|
|
3667
4351
|
const appLogs = [];
|
|
3668
4352
|
let rerenderFn = null;
|
|
3669
|
-
const addIgniterLog = (
|
|
3670
|
-
igniterLogs.push(
|
|
4353
|
+
const addIgniterLog = (log12) => {
|
|
4354
|
+
igniterLogs.push(log12);
|
|
3671
4355
|
if (igniterLogs.length > 1e3) {
|
|
3672
4356
|
igniterLogs.shift();
|
|
3673
4357
|
}
|
|
@@ -3675,8 +4359,8 @@ async function handleDevAction(options) {
|
|
|
3675
4359
|
rerenderFn();
|
|
3676
4360
|
}
|
|
3677
4361
|
};
|
|
3678
|
-
const addAppLog = (
|
|
3679
|
-
appLogs.push(
|
|
4362
|
+
const addAppLog = (log12) => {
|
|
4363
|
+
appLogs.push(log12);
|
|
3680
4364
|
if (appLogs.length > 1e3) {
|
|
3681
4365
|
appLogs.shift();
|
|
3682
4366
|
}
|
|
@@ -3706,13 +4390,13 @@ async function handleDevAction(options) {
|
|
|
3706
4390
|
});
|
|
3707
4391
|
process.exit(1);
|
|
3708
4392
|
}
|
|
3709
|
-
const featuresDir =
|
|
4393
|
+
const featuresDir = path24.join(process.cwd(), "src", "features");
|
|
3710
4394
|
const watchPaths = [
|
|
3711
4395
|
routerPath,
|
|
3712
4396
|
// Watch router file directly
|
|
3713
4397
|
featuresDir
|
|
3714
4398
|
// Watch features directory recursively
|
|
3715
|
-
].filter((
|
|
4399
|
+
].filter((p14) => fs8.existsSync(p14));
|
|
3716
4400
|
if (watchPaths.length === 0) {
|
|
3717
4401
|
addIgniterLog({
|
|
3718
4402
|
type: "warn",
|
|
@@ -3723,7 +4407,7 @@ async function handleDevAction(options) {
|
|
|
3723
4407
|
}
|
|
3724
4408
|
addIgniterLog({
|
|
3725
4409
|
type: "info",
|
|
3726
|
-
message: `Watching for changes in: ${watchPaths.map((
|
|
4410
|
+
message: `Watching for changes in: ${watchPaths.map((p14) => path24.relative(process.cwd(), p14)).join(", ")}`,
|
|
3727
4411
|
timestamp: /* @__PURE__ */ new Date()
|
|
3728
4412
|
});
|
|
3729
4413
|
let regenerateTimeout = null;
|
|
@@ -3743,7 +4427,7 @@ async function handleDevAction(options) {
|
|
|
3743
4427
|
ignoreInitial: true
|
|
3744
4428
|
});
|
|
3745
4429
|
watcher.on("change", (filePath) => {
|
|
3746
|
-
const relativePath =
|
|
4430
|
+
const relativePath = path24.relative(process.cwd(), filePath);
|
|
3747
4431
|
if (regenerateTimeout) {
|
|
3748
4432
|
clearTimeout(regenerateTimeout);
|
|
3749
4433
|
}
|
|
@@ -3867,10 +4551,10 @@ async function handleDevAction(options) {
|
|
|
3867
4551
|
}
|
|
3868
4552
|
|
|
3869
4553
|
// src/commands/dev/index.ts
|
|
3870
|
-
var devCommand = new
|
|
4554
|
+
var devCommand = new Command9().command("dev").description("Start development mode with automatic schema and docs regeneration").option("--router <path>", "Path to the router file", "src/igniter.router.ts").option("--output <path>", "Output path for the schema file", "src/igniter.schema.ts").option("--docs-output <dir>", "Output directory for the OpenAPI spec", "./src/docs").option("--cmd <command>", "Custom command to start the development server").action(handleDevAction);
|
|
3871
4555
|
|
|
3872
4556
|
// src/index.ts
|
|
3873
|
-
var program = new
|
|
4557
|
+
var program = new Command10();
|
|
3874
4558
|
program.name("igniter").description("The next-generation command-line interface for Igniter.js").version("0.0.1");
|
|
3875
4559
|
program.addCommand(initCommand);
|
|
3876
4560
|
program.addCommand(generateCommand);
|