zenko 0.1.10-beta.3 → 0.1.10

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 CHANGED
@@ -17,25 +17,30 @@ Unlike most OpenAPI generators, Zenko does not create a client. Instead you are
17
17
 
18
18
  ## Installation
19
19
 
20
- ### One-time Usage (Bun)
20
+ ### One-time Usage
21
21
 
22
22
  ```bash
23
- # Use directly with bunx (no installation required)
23
+ # Use directly with npx (no installation required)
24
+ npx zenko input.yaml output.ts
25
+
26
+ # Or with bunx
24
27
  bunx zenko input.yaml output.ts
25
28
  ```
26
29
 
27
- ### Install for Repeated Use (Bun runtime)
30
+ ### Install for Repeated Use
28
31
 
29
32
  ```bash
30
33
  # Install globally
34
+ npm install -g zenko
31
35
  bun install -g zenko
32
36
 
33
37
  # Or install locally
38
+ npm install zenko
34
39
  bun add zenko
40
+ yarn add zenko
41
+ pnpm add zenko
35
42
  ```
36
43
 
37
- > 💡 Need pure Node.js support? Install [`zenko-node`](../zenko-node/README.md) for an equivalent CLI and library powered by `js-yaml`.
38
-
39
44
  ## Usage
40
45
 
41
46
  ### Command Line
@@ -98,13 +103,18 @@ The config file controls generation for multiple specs and can also configure ty
98
103
  ### Programmatic Usage
99
104
 
100
105
  ```typescript
101
- import { generateFromDocument, type OpenAPISpec } from "zenko"
106
+ import { generate, type OpenAPISpec } from "zenko"
107
+ import * as fs from "fs"
108
+ import { load } from "js-yaml"
109
+
110
+ // Load your OpenAPI spec
111
+ const spec = load(fs.readFileSync("api.yaml", "utf8")) as OpenAPISpec
102
112
 
103
- const specText = await Bun.file("api.yaml").text()
104
- const spec = Bun.YAML.parse(specText) as OpenAPISpec
113
+ // Generate TypeScript code
114
+ const output = generate(spec)
105
115
 
106
- const { output } = generateFromDocument(spec)
107
- await Bun.write("types.ts", output)
116
+ // Write to file
117
+ fs.writeFileSync("types.ts", output)
108
118
  ```
109
119
 
110
120
  #### ES Modules
@@ -274,7 +284,7 @@ bun test
274
284
  bun run build
275
285
 
276
286
  # Test with example spec
277
- zenko ../zenko-core/src/resources/petstore.yaml output.ts
287
+ zenko src/resources/petstore.yaml output.ts
278
288
 
279
289
  # Format code
280
290
  bun run format
package/dist/cli.cjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
  "use strict";
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
@@ -24,10 +24,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli.ts
27
- var path2 = __toESM(require("path"), 1);
28
- var import_promises = require("fs/promises");
27
+ var fs = __toESM(require("fs"), 1);
28
+ var path = __toESM(require("path"), 1);
29
+ var import_url = require("url");
30
+ var import_js_yaml = require("js-yaml");
29
31
 
30
- // ../zenko-core/src/utils/topological-sort.ts
32
+ // src/utils/topological-sort.ts
31
33
  function topologicalSort(schemas) {
32
34
  const visited = /* @__PURE__ */ new Set();
33
35
  const visiting = /* @__PURE__ */ new Set();
@@ -76,7 +78,7 @@ function extractRefName(ref) {
76
78
  return ref.split("/").pop() || "Unknown";
77
79
  }
78
80
 
79
- // ../zenko-core/src/utils/property-name.ts
81
+ // src/utils/property-name.ts
80
82
  function isValidJSIdentifier(name) {
81
83
  if (!name) return false;
82
84
  const firstChar = name.at(0);
@@ -156,7 +158,7 @@ function formatPropertyName(name) {
156
158
  return isValidJSIdentifier(name) ? name : `"${name}"`;
157
159
  }
158
160
 
159
- // ../zenko-core/src/utils/string-utils.ts
161
+ // src/utils/string-utils.ts
160
162
  function toCamelCase(str) {
161
163
  return str.replace(/-([a-zA-Z])/g, (_, letter) => letter.toUpperCase()).replace(/-+$/, "");
162
164
  }
@@ -164,7 +166,7 @@ function capitalize(str) {
164
166
  return str.charAt(0).toUpperCase() + str.slice(1);
165
167
  }
166
168
 
167
- // ../zenko-core/src/utils/http-status.ts
169
+ // src/utils/http-status.ts
168
170
  var statusNameMap = {
169
171
  "400": "badRequest",
170
172
  "401": "unauthorized",
@@ -230,7 +232,7 @@ function isErrorStatus(status) {
230
232
  return code >= 400;
231
233
  }
232
234
 
233
- // ../zenko-core/src/utils/tree-shaking.ts
235
+ // src/utils/tree-shaking.ts
234
236
  function analyzeZenkoUsage(operations) {
235
237
  const usage = {
236
238
  usesHeaderFn: false,
@@ -270,7 +272,7 @@ function hasAnyErrors(errors) {
270
272
  return Boolean(errors && Object.keys(errors).length > 0);
271
273
  }
272
274
 
273
- // ../zenko-core/src/utils/collect-inline-types.ts
275
+ // src/utils/collect-inline-types.ts
274
276
  function collectInlineRequestTypes(operations, spec) {
275
277
  const requestTypesToGenerate = /* @__PURE__ */ new Map();
276
278
  const operationLookup = /* @__PURE__ */ new Map();
@@ -347,48 +349,8 @@ function collectInlineResponseTypes(operations, spec) {
347
349
  }
348
350
  return responseTypesToGenerate;
349
351
  }
350
- function collectInlineErrorTypes(operations, spec) {
351
- const errorTypesToGenerate = /* @__PURE__ */ new Map();
352
- const operationLookup = /* @__PURE__ */ new Map();
353
- for (const [, pathItem] of Object.entries(spec.paths || {})) {
354
- for (const [, operation] of Object.entries(pathItem)) {
355
- const op = operation;
356
- if (op.operationId) {
357
- operationLookup.set(op.operationId, op);
358
- }
359
- }
360
- }
361
- for (const [, pathItem] of Object.entries(spec.webhooks || {})) {
362
- for (const [, operation] of Object.entries(pathItem)) {
363
- const op = operation;
364
- if (op.operationId) {
365
- operationLookup.set(op.operationId, op);
366
- }
367
- }
368
- }
369
- for (const op of operations) {
370
- const operation = operationLookup.get(op.operationId);
371
- if (!operation) continue;
372
- const responses = operation.responses || {};
373
- for (const [statusCode, response] of Object.entries(responses)) {
374
- if (isErrorStatus(statusCode) && response.content) {
375
- const content = response.content;
376
- const jsonContent = content["application/json"];
377
- if (jsonContent && jsonContent.schema) {
378
- const schema = jsonContent.schema;
379
- const identifier = mapStatusToIdentifier(statusCode);
380
- const typeName = `${capitalize(toCamelCase(op.operationId))}${capitalize(identifier)}`;
381
- if (!schema.$ref || schema.allOf || schema.oneOf || schema.anyOf) {
382
- errorTypesToGenerate.set(typeName, schema);
383
- }
384
- }
385
- }
386
- }
387
- }
388
- return errorTypesToGenerate;
389
- }
390
352
 
391
- // ../zenko-core/src/utils/collect-referenced-schemas.ts
353
+ // src/utils/collect-referenced-schemas.ts
392
354
  function findContentType(content) {
393
355
  const contentTypes = Object.keys(content);
394
356
  if (contentTypes.includes("application/json")) {
@@ -526,7 +488,7 @@ function collectReferencedSchemas(operations, spec) {
526
488
  return referenced;
527
489
  }
528
490
 
529
- // ../zenko-core/src/utils/generate-helper-file.ts
491
+ // src/utils/generate-helper-file.ts
530
492
  function generateHelperFile() {
531
493
  const output = [];
532
494
  output.push("// Generated helper types for Zenko");
@@ -568,7 +530,7 @@ function generateHelperFile() {
568
530
  return output.join("\n");
569
531
  }
570
532
 
571
- // ../zenko-core/src/zenko.ts
533
+ // src/zenko.ts
572
534
  function generateWithMetadata(spec, options = {}) {
573
535
  const output = [];
574
536
  const generatedTypes = /* @__PURE__ */ new Set();
@@ -794,9 +756,6 @@ function generateWithMetadata(spec, options = {}) {
794
756
  }
795
757
  return result;
796
758
  }
797
- function generateFromDocument(spec, options = {}) {
798
- return generateWithMetadata(spec, options);
799
- }
800
759
  function generateRequestTypes(output, operations, spec, nameMap, schemaOptions) {
801
760
  const requestTypesToGenerate = collectInlineRequestTypes(operations, spec);
802
761
  if (requestTypesToGenerate.size > 0) {
@@ -819,8 +778,7 @@ function generateRequestTypes(output, operations, spec, nameMap, schemaOptions)
819
778
  }
820
779
  function generateResponseTypes(output, operations, spec, nameMap, schemaOptions) {
821
780
  const responseTypesToGenerate = collectInlineResponseTypes(operations, spec);
822
- const errorTypesToGenerate = collectInlineErrorTypes(operations, spec);
823
- if (responseTypesToGenerate.size > 0 || errorTypesToGenerate.size > 0) {
781
+ if (responseTypesToGenerate.size > 0) {
824
782
  output.push("// Generated Response Types");
825
783
  output.push("");
826
784
  for (const [typeName, schema] of responseTypesToGenerate) {
@@ -836,19 +794,6 @@ function generateResponseTypes(output, operations, spec, nameMap, schemaOptions)
836
794
  output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
837
795
  output.push("");
838
796
  }
839
- for (const [typeName, schema] of errorTypesToGenerate) {
840
- const generatedSchema = generateZodSchema(
841
- typeName,
842
- schema,
843
- /* @__PURE__ */ new Set(),
844
- schemaOptions,
845
- nameMap
846
- );
847
- output.push(generatedSchema);
848
- output.push("");
849
- output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
850
- output.push("");
851
- }
852
797
  }
853
798
  }
854
799
  function appendOperationField(buffer, key, value) {
@@ -914,12 +859,12 @@ function inferResponseType(contentType, statusCode) {
914
859
  function parseOperations(spec, nameMap) {
915
860
  const operations = [];
916
861
  if (spec.paths) {
917
- for (const [path3, pathItem] of Object.entries(spec.paths)) {
862
+ for (const [path2, pathItem] of Object.entries(spec.paths)) {
918
863
  for (const [method, operation] of Object.entries(pathItem)) {
919
864
  const normalizedMethod = method.toLowerCase();
920
865
  if (!isRequestMethod(normalizedMethod)) continue;
921
866
  if (!operation.operationId) continue;
922
- const pathParams = extractPathParams(path3);
867
+ const pathParams = extractPathParams(path2);
923
868
  const requestType = getRequestType(operation);
924
869
  const { successResponse, errors } = getResponseTypes(
925
870
  operation,
@@ -931,7 +876,7 @@ function parseOperations(spec, nameMap) {
931
876
  const queryParams = getQueryParams(resolvedParameters);
932
877
  operations.push({
933
878
  operationId: operation.operationId,
934
- path: path3,
879
+ path: path2,
935
880
  method: normalizedMethod,
936
881
  pathParams,
937
882
  queryParams,
@@ -949,8 +894,8 @@ function parseOperations(spec, nameMap) {
949
894
  const normalizedMethod = method.toLowerCase();
950
895
  if (!isRequestMethod(normalizedMethod)) continue;
951
896
  if (!operation.operationId) continue;
952
- const path3 = webhookName;
953
- const pathParams = extractPathParams(path3);
897
+ const path2 = webhookName;
898
+ const pathParams = extractPathParams(path2);
954
899
  const requestType = getRequestType(operation);
955
900
  const { successResponse, errors } = getResponseTypes(
956
901
  operation,
@@ -965,7 +910,7 @@ function parseOperations(spec, nameMap) {
965
910
  const queryParams = getQueryParams(resolvedParameters);
966
911
  operations.push({
967
912
  operationId: operation.operationId,
968
- path: path3,
913
+ path: path2,
969
914
  method: normalizedMethod,
970
915
  pathParams,
971
916
  queryParams,
@@ -1165,9 +1110,9 @@ function resolveParameter2(parameter, spec) {
1165
1110
  }
1166
1111
  return parameter;
1167
1112
  }
1168
- function extractPathParams(path3) {
1113
+ function extractPathParams(path2) {
1169
1114
  const params = [];
1170
- const matches = path3.match(/{([^}]+)}/g);
1115
+ const matches = path2.match(/{([^}]+)}/g);
1171
1116
  if (matches) {
1172
1117
  for (const match of matches) {
1173
1118
  const paramName = match.slice(1, -1);
@@ -1578,74 +1523,6 @@ function applyNumericBounds(schema, builder) {
1578
1523
  return builder;
1579
1524
  }
1580
1525
 
1581
- // src/loader.ts
1582
- var path = __toESM(require("path"), 1);
1583
- var import_url = require("url");
1584
- var YAML_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
1585
- var JSON_EXTENSIONS = /* @__PURE__ */ new Set([".json"]);
1586
- async function readFileText(filePath) {
1587
- const file = Bun.file(filePath);
1588
- if (!await file.exists()) {
1589
- throw new Error(`File not found: ${filePath}`);
1590
- }
1591
- return await file.text();
1592
- }
1593
- async function loadConfig(filePath) {
1594
- const extension = path.extname(filePath).toLowerCase();
1595
- if (JSON_EXTENSIONS.has(extension)) {
1596
- const content = await readFileText(filePath);
1597
- return JSON.parse(content);
1598
- }
1599
- if (YAML_EXTENSIONS.has(extension)) {
1600
- const content = await readFileText(filePath);
1601
- return Bun.YAML.parse(content);
1602
- }
1603
- const fileUrl = (0, import_url.pathToFileURL)(filePath).href;
1604
- const module2 = await import(fileUrl);
1605
- return module2.default ?? module2.config ?? module2;
1606
- }
1607
- async function loadSpec(filePath) {
1608
- const extension = path.extname(filePath).toLowerCase();
1609
- const content = await readFileText(filePath);
1610
- if (YAML_EXTENSIONS.has(extension)) {
1611
- const parsed = Bun.YAML.parse(content);
1612
- if (!parsed || typeof parsed !== "object") {
1613
- throw new Error(`YAML spec did not resolve to an object: ${filePath}`);
1614
- }
1615
- if (Array.isArray(parsed)) {
1616
- throw new Error(
1617
- `YAML spec produced multiple documents; provide a single document in ${filePath}`
1618
- );
1619
- }
1620
- return parsed;
1621
- }
1622
- if (JSON_EXTENSIONS.has(extension)) {
1623
- return JSON.parse(content);
1624
- }
1625
- throw new Error(
1626
- `Unsupported specification format for ${filePath}. Expected .yaml, .yml, or .json`
1627
- );
1628
- }
1629
- function normalizeGenerationOptions(entry, baseDir, defaults) {
1630
- const resolvedInput = path.isAbsolute(entry.input) ? entry.input : path.join(baseDir, entry.input);
1631
- const resolvedOutput = path.isAbsolute(entry.output) ? entry.output : path.join(baseDir, entry.output);
1632
- return {
1633
- resolvedInput,
1634
- resolvedOutput,
1635
- strictDates: entry.strictDates ?? defaults.strictDates,
1636
- strictNumeric: entry.strictNumeric ?? defaults.strictNumeric,
1637
- types: mergeTypesConfig(defaults.types, entry.types),
1638
- operationIds: entry.operationIds ?? defaults.operationIds
1639
- };
1640
- }
1641
- function mergeTypesConfig(baseConfig, entryConfig) {
1642
- if (!baseConfig && !entryConfig) return void 0;
1643
- return {
1644
- ...baseConfig,
1645
- ...entryConfig
1646
- };
1647
- }
1648
-
1649
1526
  // src/cli.ts
1650
1527
  async function main() {
1651
1528
  const args = process.argv.slice(2);
@@ -1671,8 +1548,8 @@ async function main() {
1671
1548
  return;
1672
1549
  }
1673
1550
  await generateSingle({
1674
- resolvedInput: path2.resolve(inputFile),
1675
- resolvedOutput: path2.resolve(outputFile),
1551
+ inputFile,
1552
+ outputFile,
1676
1553
  strictDates: parsed.strictDates,
1677
1554
  strictNumeric: parsed.strictNumeric
1678
1555
  });
@@ -1740,22 +1617,39 @@ function printHelp() {
1740
1617
  }
1741
1618
  async function runFromConfig(parsed) {
1742
1619
  const configPath = parsed.configPath;
1743
- const resolvedConfigPath = path2.resolve(configPath);
1744
- const configDocument = await loadConfig(resolvedConfigPath);
1745
- validateConfig(configDocument);
1746
- const config = configDocument;
1747
- const baseDir = path2.dirname(resolvedConfigPath);
1748
- const defaults = {
1749
- strictDates: parsed.strictDates,
1750
- strictNumeric: parsed.strictNumeric,
1751
- types: config.types,
1752
- operationIds: void 0
1753
- };
1620
+ const resolvedConfigPath = path.resolve(configPath);
1621
+ const config = await loadConfig(resolvedConfigPath);
1622
+ validateConfig(config);
1623
+ const baseDir = path.dirname(resolvedConfigPath);
1624
+ const baseTypesConfig = config.types;
1754
1625
  for (const entry of config.schemas) {
1755
- const options = normalizeGenerationOptions(entry, baseDir, defaults);
1756
- await generateSingle(options);
1626
+ const inputFile = resolvePath(entry.input, baseDir);
1627
+ const outputFile = resolvePath(entry.output, baseDir);
1628
+ const typesConfig = resolveTypesConfig(baseTypesConfig, entry.types);
1629
+ await generateSingle({
1630
+ inputFile,
1631
+ outputFile,
1632
+ strictDates: entry.strictDates ?? parsed.strictDates,
1633
+ strictNumeric: entry.strictNumeric ?? parsed.strictNumeric,
1634
+ typesConfig,
1635
+ operationIds: entry.operationIds
1636
+ });
1757
1637
  }
1758
1638
  }
1639
+ async function loadConfig(filePath) {
1640
+ const extension = path.extname(filePath).toLowerCase();
1641
+ if (extension === ".json") {
1642
+ const content = fs.readFileSync(filePath, "utf8");
1643
+ return JSON.parse(content);
1644
+ }
1645
+ if (extension === ".yaml" || extension === ".yml") {
1646
+ const content = fs.readFileSync(filePath, "utf8");
1647
+ return (0, import_js_yaml.load)(content);
1648
+ }
1649
+ const fileUrl = (0, import_url.pathToFileURL)(filePath).href;
1650
+ const module2 = await import(fileUrl);
1651
+ return module2.default ?? module2.config ?? module2;
1652
+ }
1759
1653
  function validateConfig(config) {
1760
1654
  if (!config || typeof config !== "object") {
1761
1655
  throw new Error("Config file must export an object");
@@ -1772,44 +1666,68 @@ function validateConfig(config) {
1772
1666
  }
1773
1667
  }
1774
1668
  }
1669
+ function resolvePath(filePath, baseDir) {
1670
+ return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
1671
+ }
1672
+ function resolveTypesConfig(baseConfig, entryConfig) {
1673
+ if (!baseConfig && !entryConfig) return void 0;
1674
+ return {
1675
+ ...baseConfig,
1676
+ ...entryConfig
1677
+ };
1678
+ }
1775
1679
  async function generateSingle(options) {
1776
1680
  const {
1777
- resolvedInput,
1778
- resolvedOutput,
1779
- strictDates = false,
1780
- strictNumeric = false,
1781
- types,
1681
+ inputFile,
1682
+ outputFile,
1683
+ strictDates,
1684
+ strictNumeric,
1685
+ typesConfig,
1782
1686
  operationIds
1783
1687
  } = options;
1784
- const spec = await loadSpec(resolvedInput);
1785
- const result = generateFromDocument(spec, {
1688
+ const resolvedInput = path.resolve(inputFile);
1689
+ const resolvedOutput = path.resolve(outputFile);
1690
+ const spec = readSpec(resolvedInput);
1691
+ const result = generateWithMetadata(spec, {
1786
1692
  strictDates,
1787
1693
  strictNumeric,
1788
- types,
1694
+ types: typesConfig,
1789
1695
  operationIds
1790
1696
  });
1791
- await (0, import_promises.mkdir)(path2.dirname(resolvedOutput), { recursive: true });
1792
- await Bun.write(resolvedOutput, result.output);
1697
+ fs.mkdirSync(path.dirname(resolvedOutput), { recursive: true });
1698
+ fs.writeFileSync(resolvedOutput, result.output);
1793
1699
  console.log(`\u2705 Generated TypeScript types in ${resolvedOutput}`);
1794
1700
  console.log(`\u{1F4C4} Processed ${Object.keys(spec.paths || {}).length} paths`);
1795
1701
  if (spec.webhooks) {
1796
1702
  console.log(`\u{1FA9D} Processed ${Object.keys(spec.webhooks).length} webhooks`);
1797
1703
  }
1798
1704
  if (result.helperFile) {
1799
- const helperPath = path2.isAbsolute(result.helperFile.path) ? result.helperFile.path : path2.resolve(path2.dirname(resolvedOutput), result.helperFile.path);
1800
- const absoluteResolvedOutput = path2.resolve(resolvedOutput);
1801
- const absoluteHelperPath = path2.resolve(helperPath);
1705
+ const helperPath = path.isAbsolute(result.helperFile.path) ? result.helperFile.path : path.resolve(path.dirname(resolvedOutput), result.helperFile.path);
1706
+ const absoluteResolvedOutput = path.resolve(resolvedOutput);
1707
+ const absoluteHelperPath = path.resolve(helperPath);
1802
1708
  if (absoluteResolvedOutput === absoluteHelperPath) {
1803
1709
  console.warn(
1804
1710
  `\u26A0\uFE0F Skipping helper file generation: would overwrite main output at ${absoluteResolvedOutput}`
1805
1711
  );
1806
1712
  return;
1807
1713
  }
1808
- await (0, import_promises.mkdir)(path2.dirname(helperPath), { recursive: true });
1809
- await Bun.write(helperPath, result.helperFile.content);
1714
+ fs.mkdirSync(path.dirname(helperPath), { recursive: true });
1715
+ fs.writeFileSync(helperPath, result.helperFile.content, {
1716
+ encoding: "utf8"
1717
+ });
1810
1718
  console.log(`\u{1F4E6} Generated helper types in ${helperPath}`);
1811
1719
  }
1812
1720
  }
1721
+ function readSpec(filePath) {
1722
+ if (!fs.existsSync(filePath)) {
1723
+ throw new Error(`Input file not found: ${filePath}`);
1724
+ }
1725
+ const content = fs.readFileSync(filePath, "utf8");
1726
+ if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
1727
+ return (0, import_js_yaml.load)(content);
1728
+ }
1729
+ return JSON.parse(content);
1730
+ }
1813
1731
  main().catch((error) => {
1814
1732
  console.error("\u274C Error:", error);
1815
1733
  process.exit(1);