@rexeus/typeweaver 0.9.2 โ†’ 0.10.0

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
@@ -34,6 +34,16 @@ bun add @rexeus/typeweaver-core
34
34
 
35
35
  Now you are ready to start building! Check out [Quickstart](#-get-started)
36
36
 
37
+ ## ๐Ÿท๏ธ Naming conventions
38
+
39
+ - `operationId` should use camelCase (preferred), for example `getUser`.
40
+ - PascalCase `operationId` values are supported for compatibility.
41
+ - snake_case and kebab-case `operationId` values are not supported.
42
+ - `resourceName` should preferably be a singular noun in camelCase, for example `user` or
43
+ `authSession`.
44
+ - Plural and PascalCase `resourceName` values are supported.
45
+ - snake_case and kebab-case `resourceName` values are not supported.
46
+
37
47
  ## ๐ŸŽฏ Why typeweaver?
38
48
 
39
49
  - ๐Ÿ“ **Define once, generate everything**: API contracts in Zod become clients, servers, validators,
@@ -91,7 +101,7 @@ bunx typeweaver generate --input ./api/spec/index.ts --output ./api/generated --
91
101
 
92
102
  - `--input, -i <path>`: Spec entrypoint file (required)
93
103
  - `--output, -o <path>`: Output directory for generated code (required)
94
- - `--config, -c <path>`: Configuration file path (optional)
104
+ - `--config, -c <path>`: Configuration file path (`.js`, `.mjs`, or `.cjs`, optional)
95
105
  - `--plugins, -p <plugins>`: Comma-separated list of plugins to use (e.g., "clients,hono" or "all"
96
106
  for all plugins)
97
107
  - `--format / --no-format`: Enable/disable code formatting with oxfmt (default: true)
@@ -99,9 +109,10 @@ bunx typeweaver generate --input ./api/spec/index.ts --output ./api/generated --
99
109
 
100
110
  ### ๐Ÿ“ Configuration File
101
111
 
102
- Create a config file (e.g. `typeweaver.config.js`) for more complex configurations:
112
+ Create a JavaScript config file (for example `typeweaver.config.mjs`) for more complex
113
+ configurations:
103
114
 
104
- ```javascript
115
+ ```js
105
116
  export default {
106
117
  input: "./api/spec/index.ts",
107
118
  output: "./api/generated",
@@ -114,11 +125,14 @@ export default {
114
125
  Then run:
115
126
 
116
127
  ```bash
117
- npx typeweaver generate --config ./typeweaver.config.js
128
+ npx typeweaver generate --config ./typeweaver.config.mjs
118
129
  ```
119
130
 
120
131
  > Replace `npx` with `pnpx`, `deno run -A npm:@rexeus/typeweaver`, or `bunx` depending on your
121
132
  > runtime.
133
+ >
134
+ > TypeScript config files (`.ts`, `.mts`, `.cts`) are no longer supported by the published CLI.
135
+ > Convert them to JavaScript first if needed.
122
136
 
123
137
  ## ๐ŸŒฑ Get Started
124
138
 
@@ -161,10 +175,10 @@ import {
161
175
  import { z } from "zod";
162
176
  import { sharedResponses } from "../shared/sharedResponses";
163
177
  import { userSchema } from "./userSchema";
164
- import UserNotFoundErrorDefinition from "./errors/UserNotFoundErrorDefinition";
178
+ import { UserNotFoundErrorDefinition } from "./errors/UserNotFoundErrorDefinition";
165
179
 
166
- export default defineOperation({
167
- operationId: "GetUser",
180
+ export const GetUserDefinition = defineOperation({
181
+ operationId: "getUser",
168
182
  method: HttpMethod.GET,
169
183
  path: "/users/:userId",
170
184
  summary: "Get a user by id",
@@ -192,9 +206,9 @@ export default defineOperation({
192
206
  ```typescript
193
207
  // api/spec/index.ts
194
208
  import { defineSpec } from "@rexeus/typeweaver-core";
195
- import GetUserDefinition from "./user/GetUserDefinition";
209
+ import { GetUserDefinition } from "./user/GetUserDefinition";
196
210
 
197
- export default defineSpec({
211
+ export const spec = defineSpec({
198
212
  resources: {
199
213
  user: {
200
214
  operations: [GetUserDefinition],
@@ -221,12 +235,12 @@ export const userSchema = z.object({
221
235
 
222
236
  ```typescript
223
237
  // api/spec/shared/sharedResponses.ts
224
- import ForbiddenErrorDefinition from "./ForbiddenErrorDefinition";
225
- import InternalServerErrorDefinition from "./InternalServerErrorDefinition";
226
- import TooManyRequestsErrorDefinition from "./TooManyRequestsErrorDefinition";
227
- import UnauthorizedErrorDefinition from "./UnauthorizedErrorDefinition";
228
- import UnsupportedMediaTypeErrorDefinition from "./UnsupportedMediaTypeErrorDefinition";
229
- import ValidationErrorDefinition from "./ValidationErrorDefinition";
238
+ import { ForbiddenErrorDefinition } from "./ForbiddenErrorDefinition";
239
+ import { InternalServerErrorDefinition } from "./InternalServerErrorDefinition";
240
+ import { TooManyRequestsErrorDefinition } from "./TooManyRequestsErrorDefinition";
241
+ import { UnauthorizedErrorDefinition } from "./UnauthorizedErrorDefinition";
242
+ import { UnsupportedMediaTypeErrorDefinition } from "./UnsupportedMediaTypeErrorDefinition";
243
+ import { ValidationErrorDefinition } from "./ValidationErrorDefinition";
230
244
 
231
245
  export const sharedResponses = [
232
246
  ForbiddenErrorDefinition,
@@ -3,12 +3,55 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { Command } from "commander";
6
- import { createPluginContextBuilder, createPluginRegistry, normalizeSpec } from "@rexeus/typeweaver-gen";
7
- import TypesPlugin from "@rexeus/typeweaver-types";
8
- import ejs from "ejs";
6
+ import { createPluginContextBuilder, createPluginRegistry, normalizeSpec, renderTemplate } from "@rexeus/typeweaver-gen";
7
+ import { TypesPlugin } from "@rexeus/typeweaver-types";
9
8
  import os from "node:os";
10
9
  import { Rolldown } from "tsdown";
11
10
  import { HttpMethod, HttpStatusCode } from "@rexeus/typeweaver-core";
11
+ //#region src/configLoader.ts
12
+ const SUPPORTED_CONFIG_EXTENSIONS = new Set([
13
+ ".js",
14
+ ".mjs",
15
+ ".cjs"
16
+ ]);
17
+ const UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS = new Set([
18
+ ".ts",
19
+ ".mts",
20
+ ".cts"
21
+ ]);
22
+ const getResolvedConfigPath = (configPath, currentWorkingDirectory = process.cwd()) => {
23
+ return path.isAbsolute(configPath) ? configPath : path.join(currentWorkingDirectory, configPath);
24
+ };
25
+ const assertSupportedConfigPath = (configPath) => {
26
+ const extension = path.extname(configPath).toLowerCase();
27
+ if (UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS.has(extension)) throw new Error(`TypeScript config files are no longer supported: '${configPath}'. Convert the config to .js, .mjs, or .cjs, or compile it before passing --config.`);
28
+ if (!SUPPORTED_CONFIG_EXTENSIONS.has(extension)) throw new Error(`Unsupported config file extension for '${configPath}'. TypeWeaver only accepts .js, .mjs, or .cjs config files.`);
29
+ };
30
+ const loadConfig = async (configPath) => {
31
+ assertSupportedConfigPath(configPath);
32
+ const loadedConfig = getConfigExport(await import(pathToFileURL(path.resolve(configPath)).toString()), configPath);
33
+ if (!isConfigObject(loadedConfig)) throw new Error(`Configuration file '${configPath}' must export a config object via 'export default' or 'export const config = ...'.`);
34
+ return loadedConfig;
35
+ };
36
+ const getConfigExport = (configModule, configPath) => {
37
+ const hasDefaultExport = Object.hasOwn(configModule, "default");
38
+ const hasNamedConfigExport = Object.hasOwn(configModule, "config");
39
+ if (hasDefaultExport && hasNamedConfigExport) throw new Error(`Configuration file '${configPath}' must choose a single export style: either 'export default' or 'export const config = ...', but not both.`);
40
+ if (hasDefaultExport) {
41
+ if (isNamespaceLikeConfigExport(configModule.default)) throw new Error(`Configuration file '${configPath}' default export must be the config object itself, not a module namespace-like wrapper. Export the config directly with 'export default { ... }' or use 'export const config = ...'.`);
42
+ return configModule.default;
43
+ }
44
+ if (hasNamedConfigExport) return configModule.config;
45
+ throw new Error(`Configuration file '${configPath}' must export its config via 'export default' or 'export const config = ...'.`);
46
+ };
47
+ const isConfigObject = (value) => {
48
+ return typeof value === "object" && value !== null && !Array.isArray(value);
49
+ };
50
+ const isNamespaceLikeConfigExport = (value) => {
51
+ if (!isConfigObject(value)) return false;
52
+ return Object.hasOwn(value, "default") || Object.hasOwn(value, "config");
53
+ };
54
+ //#endregion
12
55
  //#region src/generators/formatter.ts
13
56
  async function formatCode(outputDir, startDir) {
14
57
  const format = await loadFormatter();
@@ -68,7 +111,7 @@ function generateIndexFiles(templateDir, context) {
68
111
  }
69
112
  for (const [groupKey, entries] of groups) {
70
113
  if (existingBarrels.has(groupKey)) continue;
71
- const domainBarrelContent = ejs.render(template, { indexPaths: Array.from(entries).sort() });
114
+ const domainBarrelContent = renderTemplate(template, { indexPaths: Array.from(entries).sort() });
72
115
  const domainIndexPath = path.join(context.outputDir, groupKey, "index.ts");
73
116
  fs.mkdirSync(path.dirname(domainIndexPath), { recursive: true });
74
117
  fs.writeFileSync(domainIndexPath, domainBarrelContent);
@@ -76,7 +119,7 @@ function generateIndexFiles(templateDir, context) {
76
119
  const rootIndexPaths = new Set(rootFiles);
77
120
  for (const groupKey of groups.keys()) rootIndexPaths.add(`./${groupKey}`);
78
121
  for (const barrelKey of existingBarrels) rootIndexPaths.add(`./${barrelKey}`);
79
- const rootContent = ejs.render(template, { indexPaths: Array.from(rootIndexPaths).sort() });
122
+ const rootContent = renderTemplate(template, { indexPaths: Array.from(rootIndexPaths).sort() });
80
123
  fs.writeFileSync(path.join(context.outputDir, "index.ts"), rootContent);
81
124
  }
82
125
  //#endregion
@@ -109,17 +152,17 @@ async function loadPlugin(pluginName, strategies) {
109
152
  const possiblePaths = generatePluginPaths(pluginName, strategies);
110
153
  const attempts = [];
111
154
  for (const possiblePath of possiblePaths) try {
112
- const pluginPackage = await import(possiblePath);
113
- if (pluginPackage.default) return {
155
+ const PluginClass = findPluginConstructor(await import(possiblePath));
156
+ if (PluginClass) return {
114
157
  success: true,
115
158
  value: {
116
- plugin: new pluginPackage.default(),
159
+ plugin: new PluginClass(),
117
160
  source: possiblePath
118
161
  }
119
162
  };
120
163
  attempts.push({
121
164
  path: possiblePath,
122
- error: "No default export found"
165
+ error: "No plugin class export found"
123
166
  });
124
167
  } catch (error) {
125
168
  attempts.push({
@@ -135,6 +178,11 @@ async function loadPlugin(pluginName, strategies) {
135
178
  }
136
179
  };
137
180
  }
181
+ function findPluginConstructor(pluginModule) {
182
+ for (const [key, value] of Object.entries(pluginModule)) if (key !== "default" && typeof value === "function") return value;
183
+ const defaultExport = pluginModule.default;
184
+ if (typeof defaultExport === "function") return defaultExport;
185
+ }
138
186
  function generatePluginPaths(pluginName, strategies) {
139
187
  const paths = [];
140
188
  for (const strategy of strategies) switch (strategy) {
@@ -176,11 +224,10 @@ async function bundle(config) {
176
224
  fs.writeFileSync(wrapperFile, [
177
225
  `import * as specModule from ${JSON.stringify(wrapperImportSpecifier)};`,
178
226
  "const resolvedSpec =",
179
- " Reflect.get(specModule, \"default\") ??",
180
227
  " Reflect.get(specModule, \"spec\") ??",
228
+ " Reflect.get(specModule, \"default\") ??",
181
229
  " specModule;",
182
230
  "",
183
- "export default resolvedSpec;",
184
231
  "export const spec = resolvedSpec;",
185
232
  ""
186
233
  ].join("\n"));
@@ -256,7 +303,7 @@ const isSpecDefinition = (value) => {
256
303
  //#region src/generators/spec/specImporter.ts
257
304
  async function importDefinition(bundledSpecFile) {
258
305
  const specModule = await import(pathToFileURL(bundledSpecFile).toString());
259
- const definition = specModule.default ?? specModule.spec ?? specModule;
306
+ const definition = specModule.spec ?? specModule.default ?? specModule;
260
307
  if (!isSpecDefinition(definition)) throw new InvalidSpecEntrypointError(bundledSpecFile);
261
308
  return definition;
262
309
  }
@@ -275,8 +322,6 @@ async function loadSpec(config) {
275
322
  function writeSpecDeclarationFile(specOutputDir) {
276
323
  fs.writeFileSync(`${specOutputDir}/spec.d.ts`, [
277
324
  "import type { SpecDefinition } from \"@rexeus/typeweaver-core\";",
278
- "declare const _default: SpecDefinition;",
279
- "export default _default;",
280
325
  "export declare const spec: SpecDefinition;",
281
326
  ""
282
327
  ].join("\n"));
@@ -284,6 +329,16 @@ function writeSpecDeclarationFile(specOutputDir) {
284
329
  //#endregion
285
330
  //#region src/generators/Generator.ts
286
331
  const moduleDir$1 = path.dirname(fileURLToPath(import.meta.url));
332
+ const assertSafeCleanTarget = (outputDir, currentWorkingDirectory) => {
333
+ const trimmedOutputDir = outputDir.trim();
334
+ if (trimmedOutputDir.length === 0) throw new Error("Refusing to clean an empty output directory path. Pass a dedicated generated output directory instead.");
335
+ const resolvedWorkingDirectory = path.resolve(currentWorkingDirectory);
336
+ const resolvedOutputDir = path.resolve(resolvedWorkingDirectory, trimmedOutputDir);
337
+ if (resolvedOutputDir === path.parse(resolvedOutputDir).root) throw new Error(`Refusing to clean '${outputDir}' because it resolves to the filesystem root.`);
338
+ if (resolvedOutputDir === resolvedWorkingDirectory) throw new Error(`Refusing to clean '${outputDir}' because it resolves to the current working directory.`);
339
+ const protectedWorkspaceRoot = findProtectedWorkspaceRoot(resolvedWorkingDirectory);
340
+ if (protectedWorkspaceRoot !== void 0 && resolvedOutputDir === protectedWorkspaceRoot) throw new Error(`Refusing to clean '${outputDir}' because it resolves to the inferred workspace root '${protectedWorkspaceRoot}'. Choose a dedicated output subdirectory instead.`);
341
+ };
287
342
  /**
288
343
  * Main generator for typeweaver
289
344
  * Uses a plugin-based architecture for extensible code generation
@@ -306,10 +361,11 @@ var Generator = class {
306
361
  /**
307
362
  * Generate code using the plugin system
308
363
  */
309
- async generate(specFile, outputDir, config) {
364
+ async generate(specFile, outputDir, config, currentWorkingDirectory = process.cwd()) {
310
365
  console.info("Starting generation...");
311
- this.initializeDirectories(specFile, outputDir);
366
+ this.initializeDirectories(specFile, outputDir, currentWorkingDirectory);
312
367
  if (config?.clean ?? true) {
368
+ assertSafeCleanTarget(this.outputDir, currentWorkingDirectory);
313
369
  console.info("Cleaning output directory...");
314
370
  fs.rmSync(this.outputDir, {
315
371
  recursive: true,
@@ -356,13 +412,25 @@ var Generator = class {
356
412
  console.info("Generation complete!");
357
413
  console.info(`Generated files: ${this.contextBuilder.getGeneratedFiles().length}`);
358
414
  }
359
- initializeDirectories(specFile, outputDir) {
360
- this.inputFile = specFile;
361
- this.outputDir = outputDir;
362
- this.responsesOutputDir = path.join(outputDir, "responses");
415
+ initializeDirectories(specFile, outputDir, currentWorkingDirectory) {
416
+ this.inputFile = path.resolve(currentWorkingDirectory, specFile);
417
+ this.outputDir = path.resolve(currentWorkingDirectory, outputDir);
418
+ this.responsesOutputDir = path.join(this.outputDir, "responses");
363
419
  this.specOutputDir = path.join(this.outputDir, "spec");
364
420
  }
365
421
  };
422
+ const findProtectedWorkspaceRoot = (startDirectory) => {
423
+ let currentDirectory = startDirectory;
424
+ while (true) {
425
+ if (hasWorkspaceMarker(currentDirectory)) return currentDirectory;
426
+ const parentDirectory = path.dirname(currentDirectory);
427
+ if (parentDirectory === currentDirectory) return;
428
+ currentDirectory = parentDirectory;
429
+ }
430
+ };
431
+ const hasWorkspaceMarker = (directory) => {
432
+ return ["pnpm-workspace.yaml", ".git"].some((marker) => fs.existsSync(path.join(directory, marker)));
433
+ };
366
434
  //#endregion
367
435
  //#region src/cli.ts
368
436
  const moduleDir = path.dirname(fileURLToPath(import.meta.url));
@@ -370,13 +438,12 @@ const packageJson = JSON.parse(fs.readFileSync(path.join(moduleDir, "../package.
370
438
  const program = new Command();
371
439
  const execDir = process.cwd();
372
440
  program.name("@rexeus/typeweaver").description("Type-safe API framework with code generation for TypeScript").version(packageJson.version);
373
- program.command("generate").description("Generate types, validators, and clients from an API spec").option("-i, --input <inputPath>", "path to spec entrypoint file").option("-o, --output <outputDir>", "output directory for generated files").option("-c, --config <configFile>", "path to configuration file").option("-p, --plugins <plugins>", "comma-separated list of plugins to use").option("--format", "format generated code with oxfmt (default: true)").option("--no-format", "disable code formatting").option("--clean", "clean output directory before generation (default: true)").option("--no-clean", "disable cleaning output directory").action(async (options) => {
441
+ program.command("generate").description("Generate types, validators, and clients from an API spec").option("-i, --input <inputPath>", "path to spec entrypoint file").option("-o, --output <outputDir>", "output directory for generated files").option("-c, --config <configFile>", "path to a .js, .mjs, or .cjs configuration file").option("-p, --plugins <plugins>", "comma-separated list of plugins to use").option("--format", "format generated code with oxfmt (default: true)").option("--no-format", "disable code formatting").option("--clean", "clean output directory before generation (default: true)").option("--no-clean", "disable cleaning output directory").action(async (options) => {
374
442
  let config = {};
375
443
  if (options.config) {
376
- const configPath = path.isAbsolute(options.config) ? options.config : path.join(execDir, options.config);
444
+ const configPath = getResolvedConfigPath(options.config, execDir);
377
445
  try {
378
- const configModule = await import(pathToFileURL(configPath).toString());
379
- config = configModule.default ?? configModule;
446
+ config = await loadConfig(configPath);
380
447
  console.info(`Loaded configuration from ${configPath}`);
381
448
  } catch (error) {
382
449
  console.error(`Failed to load configuration file: ${options.config}`);
@@ -398,7 +465,7 @@ program.command("generate").description("Generate types, validators, and clients
398
465
  };
399
466
  if (options.plugins) finalConfig.plugins = options.plugins.split(",").map((p) => p.trim());
400
467
  else if (config.plugins) finalConfig.plugins = config.plugins;
401
- return new Generator().generate(resolvedInputPath, resolvedOutputDir, finalConfig);
468
+ return new Generator().generate(resolvedInputPath, resolvedOutputDir, finalConfig, execDir);
402
469
  });
403
470
  program.command("init").description("Initialize a new typeweaver project (coming soon)").action(() => {
404
471
  console.log("The init command is coming soon!");
package/dist/cli.cjs CHANGED
@@ -28,13 +28,54 @@ let node_url = require("node:url");
28
28
  let commander = require("commander");
29
29
  let _rexeus_typeweaver_gen = require("@rexeus/typeweaver-gen");
30
30
  let _rexeus_typeweaver_types = require("@rexeus/typeweaver-types");
31
- _rexeus_typeweaver_types = __toESM(_rexeus_typeweaver_types);
32
- let ejs = require("ejs");
33
- ejs = __toESM(ejs);
34
31
  let node_os = require("node:os");
35
32
  node_os = __toESM(node_os);
36
33
  let tsdown = require("tsdown");
37
34
  let _rexeus_typeweaver_core = require("@rexeus/typeweaver-core");
35
+ //#region src/configLoader.ts
36
+ const SUPPORTED_CONFIG_EXTENSIONS = new Set([
37
+ ".js",
38
+ ".mjs",
39
+ ".cjs"
40
+ ]);
41
+ const UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS = new Set([
42
+ ".ts",
43
+ ".mts",
44
+ ".cts"
45
+ ]);
46
+ const getResolvedConfigPath = (configPath, currentWorkingDirectory = process.cwd()) => {
47
+ return node_path.default.isAbsolute(configPath) ? configPath : node_path.default.join(currentWorkingDirectory, configPath);
48
+ };
49
+ const assertSupportedConfigPath = (configPath) => {
50
+ const extension = node_path.default.extname(configPath).toLowerCase();
51
+ if (UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS.has(extension)) throw new Error(`TypeScript config files are no longer supported: '${configPath}'. Convert the config to .js, .mjs, or .cjs, or compile it before passing --config.`);
52
+ if (!SUPPORTED_CONFIG_EXTENSIONS.has(extension)) throw new Error(`Unsupported config file extension for '${configPath}'. TypeWeaver only accepts .js, .mjs, or .cjs config files.`);
53
+ };
54
+ const loadConfig = async (configPath) => {
55
+ assertSupportedConfigPath(configPath);
56
+ const loadedConfig = getConfigExport(await import((0, node_url.pathToFileURL)(node_path.default.resolve(configPath)).toString()), configPath);
57
+ if (!isConfigObject(loadedConfig)) throw new Error(`Configuration file '${configPath}' must export a config object via 'export default' or 'export const config = ...'.`);
58
+ return loadedConfig;
59
+ };
60
+ const getConfigExport = (configModule, configPath) => {
61
+ const hasDefaultExport = Object.hasOwn(configModule, "default");
62
+ const hasNamedConfigExport = Object.hasOwn(configModule, "config");
63
+ if (hasDefaultExport && hasNamedConfigExport) throw new Error(`Configuration file '${configPath}' must choose a single export style: either 'export default' or 'export const config = ...', but not both.`);
64
+ if (hasDefaultExport) {
65
+ if (isNamespaceLikeConfigExport(configModule.default)) throw new Error(`Configuration file '${configPath}' default export must be the config object itself, not a module namespace-like wrapper. Export the config directly with 'export default { ... }' or use 'export const config = ...'.`);
66
+ return configModule.default;
67
+ }
68
+ if (hasNamedConfigExport) return configModule.config;
69
+ throw new Error(`Configuration file '${configPath}' must export its config via 'export default' or 'export const config = ...'.`);
70
+ };
71
+ const isConfigObject = (value) => {
72
+ return typeof value === "object" && value !== null && !Array.isArray(value);
73
+ };
74
+ const isNamespaceLikeConfigExport = (value) => {
75
+ if (!isConfigObject(value)) return false;
76
+ return Object.hasOwn(value, "default") || Object.hasOwn(value, "config");
77
+ };
78
+ //#endregion
38
79
  //#region src/generators/formatter.ts
39
80
  async function formatCode(outputDir, startDir) {
40
81
  const format = await loadFormatter();
@@ -94,7 +135,7 @@ function generateIndexFiles(templateDir, context) {
94
135
  }
95
136
  for (const [groupKey, entries] of groups) {
96
137
  if (existingBarrels.has(groupKey)) continue;
97
- const domainBarrelContent = ejs.default.render(template, { indexPaths: Array.from(entries).sort() });
138
+ const domainBarrelContent = (0, _rexeus_typeweaver_gen.renderTemplate)(template, { indexPaths: Array.from(entries).sort() });
98
139
  const domainIndexPath = node_path.default.join(context.outputDir, groupKey, "index.ts");
99
140
  node_fs.default.mkdirSync(node_path.default.dirname(domainIndexPath), { recursive: true });
100
141
  node_fs.default.writeFileSync(domainIndexPath, domainBarrelContent);
@@ -102,7 +143,7 @@ function generateIndexFiles(templateDir, context) {
102
143
  const rootIndexPaths = new Set(rootFiles);
103
144
  for (const groupKey of groups.keys()) rootIndexPaths.add(`./${groupKey}`);
104
145
  for (const barrelKey of existingBarrels) rootIndexPaths.add(`./${barrelKey}`);
105
- const rootContent = ejs.default.render(template, { indexPaths: Array.from(rootIndexPaths).sort() });
146
+ const rootContent = (0, _rexeus_typeweaver_gen.renderTemplate)(template, { indexPaths: Array.from(rootIndexPaths).sort() });
106
147
  node_fs.default.writeFileSync(node_path.default.join(context.outputDir, "index.ts"), rootContent);
107
148
  }
108
149
  //#endregion
@@ -135,17 +176,17 @@ async function loadPlugin(pluginName, strategies) {
135
176
  const possiblePaths = generatePluginPaths(pluginName, strategies);
136
177
  const attempts = [];
137
178
  for (const possiblePath of possiblePaths) try {
138
- const pluginPackage = await import(possiblePath);
139
- if (pluginPackage.default) return {
179
+ const PluginClass = findPluginConstructor(await import(possiblePath));
180
+ if (PluginClass) return {
140
181
  success: true,
141
182
  value: {
142
- plugin: new pluginPackage.default(),
183
+ plugin: new PluginClass(),
143
184
  source: possiblePath
144
185
  }
145
186
  };
146
187
  attempts.push({
147
188
  path: possiblePath,
148
- error: "No default export found"
189
+ error: "No plugin class export found"
149
190
  });
150
191
  } catch (error) {
151
192
  attempts.push({
@@ -161,6 +202,11 @@ async function loadPlugin(pluginName, strategies) {
161
202
  }
162
203
  };
163
204
  }
205
+ function findPluginConstructor(pluginModule) {
206
+ for (const [key, value] of Object.entries(pluginModule)) if (key !== "default" && typeof value === "function") return value;
207
+ const defaultExport = pluginModule.default;
208
+ if (typeof defaultExport === "function") return defaultExport;
209
+ }
164
210
  function generatePluginPaths(pluginName, strategies) {
165
211
  const paths = [];
166
212
  for (const strategy of strategies) switch (strategy) {
@@ -202,11 +248,10 @@ async function bundle(config) {
202
248
  node_fs.default.writeFileSync(wrapperFile, [
203
249
  `import * as specModule from ${JSON.stringify(wrapperImportSpecifier)};`,
204
250
  "const resolvedSpec =",
205
- " Reflect.get(specModule, \"default\") ??",
206
251
  " Reflect.get(specModule, \"spec\") ??",
252
+ " Reflect.get(specModule, \"default\") ??",
207
253
  " specModule;",
208
254
  "",
209
- "export default resolvedSpec;",
210
255
  "export const spec = resolvedSpec;",
211
256
  ""
212
257
  ].join("\n"));
@@ -282,7 +327,7 @@ const isSpecDefinition = (value) => {
282
327
  //#region src/generators/spec/specImporter.ts
283
328
  async function importDefinition(bundledSpecFile) {
284
329
  const specModule = await import((0, node_url.pathToFileURL)(bundledSpecFile).toString());
285
- const definition = specModule.default ?? specModule.spec ?? specModule;
330
+ const definition = specModule.spec ?? specModule.default ?? specModule;
286
331
  if (!isSpecDefinition(definition)) throw new InvalidSpecEntrypointError(bundledSpecFile);
287
332
  return definition;
288
333
  }
@@ -301,8 +346,6 @@ async function loadSpec(config) {
301
346
  function writeSpecDeclarationFile(specOutputDir) {
302
347
  node_fs.default.writeFileSync(`${specOutputDir}/spec.d.ts`, [
303
348
  "import type { SpecDefinition } from \"@rexeus/typeweaver-core\";",
304
- "declare const _default: SpecDefinition;",
305
- "export default _default;",
306
349
  "export declare const spec: SpecDefinition;",
307
350
  ""
308
351
  ].join("\n"));
@@ -310,6 +353,16 @@ function writeSpecDeclarationFile(specOutputDir) {
310
353
  //#endregion
311
354
  //#region src/generators/Generator.ts
312
355
  const moduleDir$1 = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
356
+ const assertSafeCleanTarget = (outputDir, currentWorkingDirectory) => {
357
+ const trimmedOutputDir = outputDir.trim();
358
+ if (trimmedOutputDir.length === 0) throw new Error("Refusing to clean an empty output directory path. Pass a dedicated generated output directory instead.");
359
+ const resolvedWorkingDirectory = node_path.default.resolve(currentWorkingDirectory);
360
+ const resolvedOutputDir = node_path.default.resolve(resolvedWorkingDirectory, trimmedOutputDir);
361
+ if (resolvedOutputDir === node_path.default.parse(resolvedOutputDir).root) throw new Error(`Refusing to clean '${outputDir}' because it resolves to the filesystem root.`);
362
+ if (resolvedOutputDir === resolvedWorkingDirectory) throw new Error(`Refusing to clean '${outputDir}' because it resolves to the current working directory.`);
363
+ const protectedWorkspaceRoot = findProtectedWorkspaceRoot(resolvedWorkingDirectory);
364
+ if (protectedWorkspaceRoot !== void 0 && resolvedOutputDir === protectedWorkspaceRoot) throw new Error(`Refusing to clean '${outputDir}' because it resolves to the inferred workspace root '${protectedWorkspaceRoot}'. Choose a dedicated output subdirectory instead.`);
365
+ };
313
366
  /**
314
367
  * Main generator for typeweaver
315
368
  * Uses a plugin-based architecture for extensible code generation
@@ -325,17 +378,18 @@ var Generator = class {
325
378
  outputDir = "";
326
379
  specOutputDir = "";
327
380
  responsesOutputDir = "";
328
- constructor(requiredPlugins = [new _rexeus_typeweaver_types.default()], strategies) {
381
+ constructor(requiredPlugins = [new _rexeus_typeweaver_types.TypesPlugin()], strategies) {
329
382
  this.requiredPlugins = requiredPlugins;
330
383
  this.strategies = strategies ?? ["npm", "local"];
331
384
  }
332
385
  /**
333
386
  * Generate code using the plugin system
334
387
  */
335
- async generate(specFile, outputDir, config) {
388
+ async generate(specFile, outputDir, config, currentWorkingDirectory = process.cwd()) {
336
389
  console.info("Starting generation...");
337
- this.initializeDirectories(specFile, outputDir);
390
+ this.initializeDirectories(specFile, outputDir, currentWorkingDirectory);
338
391
  if (config?.clean ?? true) {
392
+ assertSafeCleanTarget(this.outputDir, currentWorkingDirectory);
339
393
  console.info("Cleaning output directory...");
340
394
  node_fs.default.rmSync(this.outputDir, {
341
395
  recursive: true,
@@ -382,13 +436,25 @@ var Generator = class {
382
436
  console.info("Generation complete!");
383
437
  console.info(`Generated files: ${this.contextBuilder.getGeneratedFiles().length}`);
384
438
  }
385
- initializeDirectories(specFile, outputDir) {
386
- this.inputFile = specFile;
387
- this.outputDir = outputDir;
388
- this.responsesOutputDir = node_path.default.join(outputDir, "responses");
439
+ initializeDirectories(specFile, outputDir, currentWorkingDirectory) {
440
+ this.inputFile = node_path.default.resolve(currentWorkingDirectory, specFile);
441
+ this.outputDir = node_path.default.resolve(currentWorkingDirectory, outputDir);
442
+ this.responsesOutputDir = node_path.default.join(this.outputDir, "responses");
389
443
  this.specOutputDir = node_path.default.join(this.outputDir, "spec");
390
444
  }
391
445
  };
446
+ const findProtectedWorkspaceRoot = (startDirectory) => {
447
+ let currentDirectory = startDirectory;
448
+ while (true) {
449
+ if (hasWorkspaceMarker(currentDirectory)) return currentDirectory;
450
+ const parentDirectory = node_path.default.dirname(currentDirectory);
451
+ if (parentDirectory === currentDirectory) return;
452
+ currentDirectory = parentDirectory;
453
+ }
454
+ };
455
+ const hasWorkspaceMarker = (directory) => {
456
+ return ["pnpm-workspace.yaml", ".git"].some((marker) => node_fs.default.existsSync(node_path.default.join(directory, marker)));
457
+ };
392
458
  //#endregion
393
459
  //#region src/cli.ts
394
460
  const moduleDir = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
@@ -396,13 +462,12 @@ const packageJson = JSON.parse(node_fs.default.readFileSync(node_path.default.jo
396
462
  const program = new commander.Command();
397
463
  const execDir = process.cwd();
398
464
  program.name("@rexeus/typeweaver").description("Type-safe API framework with code generation for TypeScript").version(packageJson.version);
399
- program.command("generate").description("Generate types, validators, and clients from an API spec").option("-i, --input <inputPath>", "path to spec entrypoint file").option("-o, --output <outputDir>", "output directory for generated files").option("-c, --config <configFile>", "path to configuration file").option("-p, --plugins <plugins>", "comma-separated list of plugins to use").option("--format", "format generated code with oxfmt (default: true)").option("--no-format", "disable code formatting").option("--clean", "clean output directory before generation (default: true)").option("--no-clean", "disable cleaning output directory").action(async (options) => {
465
+ program.command("generate").description("Generate types, validators, and clients from an API spec").option("-i, --input <inputPath>", "path to spec entrypoint file").option("-o, --output <outputDir>", "output directory for generated files").option("-c, --config <configFile>", "path to a .js, .mjs, or .cjs configuration file").option("-p, --plugins <plugins>", "comma-separated list of plugins to use").option("--format", "format generated code with oxfmt (default: true)").option("--no-format", "disable code formatting").option("--clean", "clean output directory before generation (default: true)").option("--no-clean", "disable cleaning output directory").action(async (options) => {
400
466
  let config = {};
401
467
  if (options.config) {
402
- const configPath = node_path.default.isAbsolute(options.config) ? options.config : node_path.default.join(execDir, options.config);
468
+ const configPath = getResolvedConfigPath(options.config, execDir);
403
469
  try {
404
- const configModule = await import((0, node_url.pathToFileURL)(configPath).toString());
405
- config = configModule.default ?? configModule;
470
+ config = await loadConfig(configPath);
406
471
  console.info(`Loaded configuration from ${configPath}`);
407
472
  } catch (error) {
408
473
  console.error(`Failed to load configuration file: ${options.config}`);
@@ -424,7 +489,7 @@ program.command("generate").description("Generate types, validators, and clients
424
489
  };
425
490
  if (options.plugins) finalConfig.plugins = options.plugins.split(",").map((p) => p.trim());
426
491
  else if (config.plugins) finalConfig.plugins = config.plugins;
427
- return new Generator().generate(resolvedInputPath, resolvedOutputDir, finalConfig);
492
+ return new Generator().generate(resolvedInputPath, resolvedOutputDir, finalConfig, execDir);
428
493
  });
429
494
  program.command("init").description("Initialize a new typeweaver project (coming soon)").action(() => {
430
495
  console.log("The init command is coming soon!");
package/dist/cli.mjs CHANGED
@@ -2,12 +2,55 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { Command } from "commander";
5
- import { createPluginContextBuilder, createPluginRegistry, normalizeSpec } from "@rexeus/typeweaver-gen";
6
- import TypesPlugin from "@rexeus/typeweaver-types";
7
- import ejs from "ejs";
5
+ import { createPluginContextBuilder, createPluginRegistry, normalizeSpec, renderTemplate } from "@rexeus/typeweaver-gen";
6
+ import { TypesPlugin } from "@rexeus/typeweaver-types";
8
7
  import os from "node:os";
9
8
  import { Rolldown } from "tsdown";
10
9
  import { HttpMethod, HttpStatusCode } from "@rexeus/typeweaver-core";
10
+ //#region src/configLoader.ts
11
+ const SUPPORTED_CONFIG_EXTENSIONS = new Set([
12
+ ".js",
13
+ ".mjs",
14
+ ".cjs"
15
+ ]);
16
+ const UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS = new Set([
17
+ ".ts",
18
+ ".mts",
19
+ ".cts"
20
+ ]);
21
+ const getResolvedConfigPath = (configPath, currentWorkingDirectory = process.cwd()) => {
22
+ return path.isAbsolute(configPath) ? configPath : path.join(currentWorkingDirectory, configPath);
23
+ };
24
+ const assertSupportedConfigPath = (configPath) => {
25
+ const extension = path.extname(configPath).toLowerCase();
26
+ if (UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS.has(extension)) throw new Error(`TypeScript config files are no longer supported: '${configPath}'. Convert the config to .js, .mjs, or .cjs, or compile it before passing --config.`);
27
+ if (!SUPPORTED_CONFIG_EXTENSIONS.has(extension)) throw new Error(`Unsupported config file extension for '${configPath}'. TypeWeaver only accepts .js, .mjs, or .cjs config files.`);
28
+ };
29
+ const loadConfig = async (configPath) => {
30
+ assertSupportedConfigPath(configPath);
31
+ const loadedConfig = getConfigExport(await import(pathToFileURL(path.resolve(configPath)).toString()), configPath);
32
+ if (!isConfigObject(loadedConfig)) throw new Error(`Configuration file '${configPath}' must export a config object via 'export default' or 'export const config = ...'.`);
33
+ return loadedConfig;
34
+ };
35
+ const getConfigExport = (configModule, configPath) => {
36
+ const hasDefaultExport = Object.hasOwn(configModule, "default");
37
+ const hasNamedConfigExport = Object.hasOwn(configModule, "config");
38
+ if (hasDefaultExport && hasNamedConfigExport) throw new Error(`Configuration file '${configPath}' must choose a single export style: either 'export default' or 'export const config = ...', but not both.`);
39
+ if (hasDefaultExport) {
40
+ if (isNamespaceLikeConfigExport(configModule.default)) throw new Error(`Configuration file '${configPath}' default export must be the config object itself, not a module namespace-like wrapper. Export the config directly with 'export default { ... }' or use 'export const config = ...'.`);
41
+ return configModule.default;
42
+ }
43
+ if (hasNamedConfigExport) return configModule.config;
44
+ throw new Error(`Configuration file '${configPath}' must export its config via 'export default' or 'export const config = ...'.`);
45
+ };
46
+ const isConfigObject = (value) => {
47
+ return typeof value === "object" && value !== null && !Array.isArray(value);
48
+ };
49
+ const isNamespaceLikeConfigExport = (value) => {
50
+ if (!isConfigObject(value)) return false;
51
+ return Object.hasOwn(value, "default") || Object.hasOwn(value, "config");
52
+ };
53
+ //#endregion
11
54
  //#region src/generators/formatter.ts
12
55
  async function formatCode(outputDir, startDir) {
13
56
  const format = await loadFormatter();
@@ -67,7 +110,7 @@ function generateIndexFiles(templateDir, context) {
67
110
  }
68
111
  for (const [groupKey, entries] of groups) {
69
112
  if (existingBarrels.has(groupKey)) continue;
70
- const domainBarrelContent = ejs.render(template, { indexPaths: Array.from(entries).sort() });
113
+ const domainBarrelContent = renderTemplate(template, { indexPaths: Array.from(entries).sort() });
71
114
  const domainIndexPath = path.join(context.outputDir, groupKey, "index.ts");
72
115
  fs.mkdirSync(path.dirname(domainIndexPath), { recursive: true });
73
116
  fs.writeFileSync(domainIndexPath, domainBarrelContent);
@@ -75,7 +118,7 @@ function generateIndexFiles(templateDir, context) {
75
118
  const rootIndexPaths = new Set(rootFiles);
76
119
  for (const groupKey of groups.keys()) rootIndexPaths.add(`./${groupKey}`);
77
120
  for (const barrelKey of existingBarrels) rootIndexPaths.add(`./${barrelKey}`);
78
- const rootContent = ejs.render(template, { indexPaths: Array.from(rootIndexPaths).sort() });
121
+ const rootContent = renderTemplate(template, { indexPaths: Array.from(rootIndexPaths).sort() });
79
122
  fs.writeFileSync(path.join(context.outputDir, "index.ts"), rootContent);
80
123
  }
81
124
  //#endregion
@@ -108,17 +151,17 @@ async function loadPlugin(pluginName, strategies) {
108
151
  const possiblePaths = generatePluginPaths(pluginName, strategies);
109
152
  const attempts = [];
110
153
  for (const possiblePath of possiblePaths) try {
111
- const pluginPackage = await import(possiblePath);
112
- if (pluginPackage.default) return {
154
+ const PluginClass = findPluginConstructor(await import(possiblePath));
155
+ if (PluginClass) return {
113
156
  success: true,
114
157
  value: {
115
- plugin: new pluginPackage.default(),
158
+ plugin: new PluginClass(),
116
159
  source: possiblePath
117
160
  }
118
161
  };
119
162
  attempts.push({
120
163
  path: possiblePath,
121
- error: "No default export found"
164
+ error: "No plugin class export found"
122
165
  });
123
166
  } catch (error) {
124
167
  attempts.push({
@@ -134,6 +177,11 @@ async function loadPlugin(pluginName, strategies) {
134
177
  }
135
178
  };
136
179
  }
180
+ function findPluginConstructor(pluginModule) {
181
+ for (const [key, value] of Object.entries(pluginModule)) if (key !== "default" && typeof value === "function") return value;
182
+ const defaultExport = pluginModule.default;
183
+ if (typeof defaultExport === "function") return defaultExport;
184
+ }
137
185
  function generatePluginPaths(pluginName, strategies) {
138
186
  const paths = [];
139
187
  for (const strategy of strategies) switch (strategy) {
@@ -175,11 +223,10 @@ async function bundle(config) {
175
223
  fs.writeFileSync(wrapperFile, [
176
224
  `import * as specModule from ${JSON.stringify(wrapperImportSpecifier)};`,
177
225
  "const resolvedSpec =",
178
- " Reflect.get(specModule, \"default\") ??",
179
226
  " Reflect.get(specModule, \"spec\") ??",
227
+ " Reflect.get(specModule, \"default\") ??",
180
228
  " specModule;",
181
229
  "",
182
- "export default resolvedSpec;",
183
230
  "export const spec = resolvedSpec;",
184
231
  ""
185
232
  ].join("\n"));
@@ -255,7 +302,7 @@ const isSpecDefinition = (value) => {
255
302
  //#region src/generators/spec/specImporter.ts
256
303
  async function importDefinition(bundledSpecFile) {
257
304
  const specModule = await import(pathToFileURL(bundledSpecFile).toString());
258
- const definition = specModule.default ?? specModule.spec ?? specModule;
305
+ const definition = specModule.spec ?? specModule.default ?? specModule;
259
306
  if (!isSpecDefinition(definition)) throw new InvalidSpecEntrypointError(bundledSpecFile);
260
307
  return definition;
261
308
  }
@@ -274,8 +321,6 @@ async function loadSpec(config) {
274
321
  function writeSpecDeclarationFile(specOutputDir) {
275
322
  fs.writeFileSync(`${specOutputDir}/spec.d.ts`, [
276
323
  "import type { SpecDefinition } from \"@rexeus/typeweaver-core\";",
277
- "declare const _default: SpecDefinition;",
278
- "export default _default;",
279
324
  "export declare const spec: SpecDefinition;",
280
325
  ""
281
326
  ].join("\n"));
@@ -283,6 +328,16 @@ function writeSpecDeclarationFile(specOutputDir) {
283
328
  //#endregion
284
329
  //#region src/generators/Generator.ts
285
330
  const moduleDir$1 = path.dirname(fileURLToPath(import.meta.url));
331
+ const assertSafeCleanTarget = (outputDir, currentWorkingDirectory) => {
332
+ const trimmedOutputDir = outputDir.trim();
333
+ if (trimmedOutputDir.length === 0) throw new Error("Refusing to clean an empty output directory path. Pass a dedicated generated output directory instead.");
334
+ const resolvedWorkingDirectory = path.resolve(currentWorkingDirectory);
335
+ const resolvedOutputDir = path.resolve(resolvedWorkingDirectory, trimmedOutputDir);
336
+ if (resolvedOutputDir === path.parse(resolvedOutputDir).root) throw new Error(`Refusing to clean '${outputDir}' because it resolves to the filesystem root.`);
337
+ if (resolvedOutputDir === resolvedWorkingDirectory) throw new Error(`Refusing to clean '${outputDir}' because it resolves to the current working directory.`);
338
+ const protectedWorkspaceRoot = findProtectedWorkspaceRoot(resolvedWorkingDirectory);
339
+ if (protectedWorkspaceRoot !== void 0 && resolvedOutputDir === protectedWorkspaceRoot) throw new Error(`Refusing to clean '${outputDir}' because it resolves to the inferred workspace root '${protectedWorkspaceRoot}'. Choose a dedicated output subdirectory instead.`);
340
+ };
286
341
  /**
287
342
  * Main generator for typeweaver
288
343
  * Uses a plugin-based architecture for extensible code generation
@@ -305,10 +360,11 @@ var Generator = class {
305
360
  /**
306
361
  * Generate code using the plugin system
307
362
  */
308
- async generate(specFile, outputDir, config) {
363
+ async generate(specFile, outputDir, config, currentWorkingDirectory = process.cwd()) {
309
364
  console.info("Starting generation...");
310
- this.initializeDirectories(specFile, outputDir);
365
+ this.initializeDirectories(specFile, outputDir, currentWorkingDirectory);
311
366
  if (config?.clean ?? true) {
367
+ assertSafeCleanTarget(this.outputDir, currentWorkingDirectory);
312
368
  console.info("Cleaning output directory...");
313
369
  fs.rmSync(this.outputDir, {
314
370
  recursive: true,
@@ -355,13 +411,25 @@ var Generator = class {
355
411
  console.info("Generation complete!");
356
412
  console.info(`Generated files: ${this.contextBuilder.getGeneratedFiles().length}`);
357
413
  }
358
- initializeDirectories(specFile, outputDir) {
359
- this.inputFile = specFile;
360
- this.outputDir = outputDir;
361
- this.responsesOutputDir = path.join(outputDir, "responses");
414
+ initializeDirectories(specFile, outputDir, currentWorkingDirectory) {
415
+ this.inputFile = path.resolve(currentWorkingDirectory, specFile);
416
+ this.outputDir = path.resolve(currentWorkingDirectory, outputDir);
417
+ this.responsesOutputDir = path.join(this.outputDir, "responses");
362
418
  this.specOutputDir = path.join(this.outputDir, "spec");
363
419
  }
364
420
  };
421
+ const findProtectedWorkspaceRoot = (startDirectory) => {
422
+ let currentDirectory = startDirectory;
423
+ while (true) {
424
+ if (hasWorkspaceMarker(currentDirectory)) return currentDirectory;
425
+ const parentDirectory = path.dirname(currentDirectory);
426
+ if (parentDirectory === currentDirectory) return;
427
+ currentDirectory = parentDirectory;
428
+ }
429
+ };
430
+ const hasWorkspaceMarker = (directory) => {
431
+ return ["pnpm-workspace.yaml", ".git"].some((marker) => fs.existsSync(path.join(directory, marker)));
432
+ };
365
433
  //#endregion
366
434
  //#region src/cli.ts
367
435
  const moduleDir = path.dirname(fileURLToPath(import.meta.url));
@@ -369,13 +437,12 @@ const packageJson = JSON.parse(fs.readFileSync(path.join(moduleDir, "../package.
369
437
  const program = new Command();
370
438
  const execDir = process.cwd();
371
439
  program.name("@rexeus/typeweaver").description("Type-safe API framework with code generation for TypeScript").version(packageJson.version);
372
- program.command("generate").description("Generate types, validators, and clients from an API spec").option("-i, --input <inputPath>", "path to spec entrypoint file").option("-o, --output <outputDir>", "output directory for generated files").option("-c, --config <configFile>", "path to configuration file").option("-p, --plugins <plugins>", "comma-separated list of plugins to use").option("--format", "format generated code with oxfmt (default: true)").option("--no-format", "disable code formatting").option("--clean", "clean output directory before generation (default: true)").option("--no-clean", "disable cleaning output directory").action(async (options) => {
440
+ program.command("generate").description("Generate types, validators, and clients from an API spec").option("-i, --input <inputPath>", "path to spec entrypoint file").option("-o, --output <outputDir>", "output directory for generated files").option("-c, --config <configFile>", "path to a .js, .mjs, or .cjs configuration file").option("-p, --plugins <plugins>", "comma-separated list of plugins to use").option("--format", "format generated code with oxfmt (default: true)").option("--no-format", "disable code formatting").option("--clean", "clean output directory before generation (default: true)").option("--no-clean", "disable cleaning output directory").action(async (options) => {
373
441
  let config = {};
374
442
  if (options.config) {
375
- const configPath = path.isAbsolute(options.config) ? options.config : path.join(execDir, options.config);
443
+ const configPath = getResolvedConfigPath(options.config, execDir);
376
444
  try {
377
- const configModule = await import(pathToFileURL(configPath).toString());
378
- config = configModule.default ?? configModule;
445
+ config = await loadConfig(configPath);
379
446
  console.info(`Loaded configuration from ${configPath}`);
380
447
  } catch (error) {
381
448
  console.error(`Failed to load configuration file: ${options.config}`);
@@ -397,7 +464,7 @@ program.command("generate").description("Generate types, validators, and clients
397
464
  };
398
465
  if (options.plugins) finalConfig.plugins = options.plugins.split(",").map((p) => p.trim());
399
466
  else if (config.plugins) finalConfig.plugins = config.plugins;
400
- return new Generator().generate(resolvedInputPath, resolvedOutputDir, finalConfig);
467
+ return new Generator().generate(resolvedInputPath, resolvedOutputDir, finalConfig, execDir);
401
468
  });
402
469
  program.command("init").description("Initialize a new typeweaver project (coming soon)").action(() => {
403
470
  console.log("The init command is coming soon!");
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["moduleDir"],"sources":["../src/generators/formatter.ts","../src/generators/indexFileGenerator.ts","../src/generators/errors/PluginLoadingFailure.ts","../src/generators/pluginLoader.ts","../src/generators/spec/specBundler.ts","../src/generators/spec/InvalidSpecEntrypointError.ts","../src/generators/spec/specGuards.ts","../src/generators/spec/specImporter.ts","../src/generators/specLoader.ts","../src/generators/Generator.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\ntype FormatFn = (filename: string, source: string) => Promise<{ code: string }>;\n\nexport async function formatCode(\n outputDir: string,\n startDir?: string\n): Promise<void> {\n const format = await loadFormatter();\n if (!format) {\n return;\n }\n\n const targetDir = startDir ?? outputDir;\n await formatDirectory(targetDir, format);\n}\n\nasync function loadFormatter(): Promise<FormatFn | undefined> {\n try {\n const oxfmt = await import(\"oxfmt\");\n return oxfmt.format;\n } catch {\n console.warn(\n \"oxfmt not installed - skipping formatting. Install with: npm install -D oxfmt\"\n );\n return undefined;\n }\n}\n\nasync function formatDirectory(\n targetDir: string,\n format: FormatFn\n): Promise<void> {\n const contents = fs.readdirSync(targetDir, { withFileTypes: true });\n\n for (const content of contents) {\n if (content.isFile()) {\n const filePath = path.join(targetDir, content.name);\n const unformatted = fs.readFileSync(filePath, \"utf8\");\n const { code } = await format(filePath, unformatted);\n fs.writeFileSync(filePath, code);\n } else if (content.isDirectory()) {\n await formatDirectory(path.join(targetDir, content.name), format);\n }\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { GeneratorContext } from \"@rexeus/typeweaver-gen\";\nimport ejs from \"ejs\";\n\nexport function generateIndexFiles(\n templateDir: string,\n context: GeneratorContext\n): void {\n const templateFilePath = path.join(templateDir, \"Index.ejs\");\n const template = fs.readFileSync(templateFilePath, \"utf8\");\n\n const generatedFiles = context.getGeneratedFiles();\n const groups = new Map<string, Set<string>>();\n const rootFiles = new Set<string>();\n const existingBarrels = new Set<string>();\n\n for (const file of generatedFiles) {\n const stripped = file.replace(/\\.ts$/, \"\");\n const firstSlash = stripped.indexOf(\"/\");\n\n if (firstSlash === -1) {\n rootFiles.add(`./${stripped}`);\n continue;\n }\n\n const firstSegment = stripped.slice(0, firstSlash);\n\n if (firstSegment === \"lib\") {\n const secondSlash = stripped.indexOf(\"/\", firstSlash + 1);\n const groupKey =\n secondSlash === -1 ? stripped : stripped.slice(0, secondSlash);\n\n const entryName = stripped.slice(groupKey.length + 1);\n\n if (entryName === \"index\") {\n existingBarrels.add(groupKey);\n } else {\n if (!groups.has(groupKey)) {\n groups.set(groupKey, new Set());\n }\n groups.get(groupKey)!.add(`./${entryName}`);\n }\n } else {\n const entryName = stripped.slice(firstSlash + 1);\n\n if (entryName === \"index\") {\n existingBarrels.add(firstSegment);\n } else {\n if (!groups.has(firstSegment)) {\n groups.set(firstSegment, new Set());\n }\n groups.get(firstSegment)!.add(`./${entryName}`);\n }\n }\n }\n\n for (const [groupKey, entries] of groups) {\n if (existingBarrels.has(groupKey)) {\n continue;\n }\n\n const domainBarrelContent = ejs.render(template, {\n indexPaths: Array.from(entries).sort(),\n });\n\n const domainIndexPath = path.join(context.outputDir, groupKey, \"index.ts\");\n fs.mkdirSync(path.dirname(domainIndexPath), { recursive: true });\n fs.writeFileSync(domainIndexPath, domainBarrelContent);\n }\n\n const rootIndexPaths = new Set<string>(rootFiles);\n for (const groupKey of groups.keys()) {\n rootIndexPaths.add(`./${groupKey}`);\n }\n for (const barrelKey of existingBarrels) {\n rootIndexPaths.add(`./${barrelKey}`);\n }\n\n const rootContent = ejs.render(template, {\n indexPaths: Array.from(rootIndexPaths).sort(),\n });\n\n fs.writeFileSync(path.join(context.outputDir, \"index.ts\"), rootContent);\n}\n","export type PluginLoadError = {\n readonly pluginName: string;\n readonly attempts: readonly {\n readonly path: string;\n readonly error: string;\n }[];\n};\n\nexport class PluginLoadingFailure extends Error implements PluginLoadError {\n public constructor(\n public readonly pluginName: string,\n public readonly attempts: readonly {\n readonly path: string;\n readonly error: string;\n }[]\n ) {\n super(`Failed to load plugin '${pluginName}'`);\n Object.setPrototypeOf(this, PluginLoadingFailure.prototype);\n }\n}\n","import type {\n PluginRegistryApi,\n TypeweaverConfig,\n TypeweaverPlugin,\n} from \"@rexeus/typeweaver-gen\";\nimport TypesPlugin from \"@rexeus/typeweaver-types\";\nimport { PluginLoadingFailure } from \"./errors/PluginLoadingFailure\";\nimport type { PluginLoadError } from \"./errors/PluginLoadingFailure\";\n\nexport type PluginResolutionStrategy = \"npm\" | \"local\" | \"scoped\";\n\nexport type PluginLoadResult = {\n plugin: TypeweaverPlugin;\n source: string;\n};\n\ntype LoadResult<T, E> =\n | { success: true; value: T }\n | { success: false; error: E };\n\nexport async function loadPlugins(\n registry: PluginRegistryApi,\n requiredPlugins: [TypesPlugin],\n strategies: PluginResolutionStrategy[],\n config?: TypeweaverConfig\n): Promise<void> {\n for (const requiredPlugin of requiredPlugins) {\n registry.register(requiredPlugin);\n }\n\n if (!config?.plugins) {\n return;\n }\n\n const successful: PluginLoadResult[] = [];\n\n for (const plugin of config.plugins) {\n let result: LoadResult<PluginLoadResult, PluginLoadError>;\n if (typeof plugin === \"string\") {\n result = await loadPlugin(plugin, strategies);\n } else {\n result = await loadPlugin(plugin[0], strategies);\n }\n\n if (result.success === false) {\n throw new PluginLoadingFailure(\n result.error.pluginName,\n result.error.attempts\n );\n }\n\n successful.push(result.value);\n registry.register(result.value.plugin);\n }\n\n reportSuccessfulLoads(successful);\n}\n\nasync function loadPlugin(\n pluginName: string,\n strategies: PluginResolutionStrategy[]\n): Promise<LoadResult<PluginLoadResult, PluginLoadError>> {\n const possiblePaths = generatePluginPaths(pluginName, strategies);\n const attempts: { path: string; error: string }[] = [];\n\n for (const possiblePath of possiblePaths) {\n try {\n const pluginPackage = await import(possiblePath);\n if (pluginPackage.default) {\n return {\n success: true,\n value: {\n plugin: new pluginPackage.default(),\n source: possiblePath,\n },\n };\n }\n attempts.push({\n path: possiblePath,\n error: \"No default export found\",\n });\n } catch (error) {\n attempts.push({\n path: possiblePath,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return {\n success: false,\n error: {\n pluginName,\n attempts,\n },\n };\n}\n\nfunction generatePluginPaths(\n pluginName: string,\n strategies: PluginResolutionStrategy[]\n): string[] {\n const paths: string[] = [];\n\n for (const strategy of strategies) {\n switch (strategy) {\n case \"npm\":\n paths.push(`@rexeus/typeweaver-${pluginName}`);\n paths.push(`@rexeus/${pluginName}`);\n break;\n case \"local\":\n paths.push(pluginName);\n break;\n }\n }\n\n return paths;\n}\n\nfunction reportSuccessfulLoads(successful: PluginLoadResult[]): void {\n if (successful.length > 0) {\n console.info(`Successfully loaded ${successful.length} plugin(s):`);\n for (const result of successful) {\n console.info(` - ${result.plugin.name} (from ${result.source})`);\n }\n }\n}\n","import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { Rolldown } from \"tsdown\";\n\nconst WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\\\/]/;\nconst WINDOWS_UNC_PATH_PATTERN = /^\\\\\\\\/;\n\nexport type SpecBundlerConfig = {\n readonly inputFile: string;\n readonly specOutputDir: string;\n};\n\nexport function createWrapperImportSpecifier(\n wrapperFile: string,\n inputFile: string\n): string {\n const absoluteInputFile = resolveBundledInputFile(inputFile);\n const useWindowsPathSemantics = usesWindowsPathSemantics(\n wrapperFile,\n absoluteInputFile\n );\n const pathModule = useWindowsPathSemantics ? path.win32 : path.posix;\n const wrapperDir = useWindowsPathSemantics\n ? pathModule.dirname(wrapperFile)\n : resolveRealFilePath(pathModule.dirname(wrapperFile));\n const resolvedInputFile = useWindowsPathSemantics\n ? absoluteInputFile\n : resolveRealFilePath(absoluteInputFile);\n const relativeInputFile = pathModule\n .relative(wrapperDir, resolvedInputFile)\n .replaceAll(pathModule.sep, \"/\");\n\n if (relativeInputFile.startsWith(\".\") || relativeInputFile.startsWith(\"..\")) {\n return relativeInputFile;\n }\n\n return `./${relativeInputFile}`;\n}\n\nexport async function bundle(config: SpecBundlerConfig): Promise<string> {\n const tempDir = fs.mkdtempSync(\n path.join(os.tmpdir(), \"typeweaver-spec-loader-\")\n );\n const wrapperFile = path.join(tempDir, \"spec-entrypoint.ts\");\n const bundledSpecFile = path.join(config.specOutputDir, \"spec.js\");\n const wrapperImportSpecifier = createWrapperImportSpecifier(\n wrapperFile,\n config.inputFile\n );\n\n fs.writeFileSync(\n wrapperFile,\n [\n `import * as specModule from ${JSON.stringify(wrapperImportSpecifier)};`,\n \"const resolvedSpec =\",\n ' Reflect.get(specModule, \"default\") ??',\n ' Reflect.get(specModule, \"spec\") ??',\n \" specModule;\",\n \"\",\n \"export default resolvedSpec;\",\n \"export const spec = resolvedSpec;\",\n \"\",\n ].join(\"\\n\")\n );\n\n try {\n await Rolldown.build({\n cwd: tempDir,\n input: wrapperFile,\n treeshake: true,\n experimental: {\n attachDebugInfo: \"none\",\n },\n external: (source: string) => {\n if (source.startsWith(\"node:\")) {\n return true;\n }\n\n return !source.startsWith(\".\") && !path.isAbsolute(source);\n },\n output: {\n file: bundledSpecFile,\n format: \"esm\",\n },\n });\n } finally {\n fs.rmSync(tempDir, { recursive: true, force: true });\n }\n\n if (!fs.existsSync(bundledSpecFile)) {\n throw new Error(\n `Failed to bundle spec entrypoint '${config.inputFile}' to '${bundledSpecFile}'.`\n );\n }\n\n return bundledSpecFile;\n}\n\nfunction resolveBundledInputFile(inputFile: string): string {\n if (path.isAbsolute(inputFile)) {\n return inputFile;\n }\n\n if (WINDOWS_ABSOLUTE_PATH_PATTERN.test(inputFile)) {\n return path.win32.normalize(inputFile);\n }\n\n if (WINDOWS_UNC_PATH_PATTERN.test(inputFile)) {\n return path.win32.normalize(inputFile);\n }\n\n return path.resolve(inputFile);\n}\n\nfunction usesWindowsPathSemantics(...filePaths: string[]): boolean {\n return filePaths.some(filePath => {\n return (\n WINDOWS_ABSOLUTE_PATH_PATTERN.test(filePath) ||\n WINDOWS_UNC_PATH_PATTERN.test(filePath)\n );\n });\n}\n\nfunction resolveRealFilePath(filePath: string): string {\n if (!fs.existsSync(filePath)) {\n return filePath;\n }\n\n return fs.realpathSync.native(filePath);\n}\n","export class InvalidSpecEntrypointError extends Error {\n public constructor(specEntrypoint: string) {\n super(\n `Spec entrypoint '${specEntrypoint}' must export a SpecDefinition as its default export, named 'spec' export, or module namespace.`\n );\n this.name = \"InvalidSpecEntrypointError\";\n }\n}\n","import { HttpMethod, HttpStatusCode } from \"@rexeus/typeweaver-core\";\nimport type { SpecDefinition } from \"@rexeus/typeweaver-core\";\n\nconst validHttpStatusCodes = new Set<HttpStatusCode>(\n Object.values(HttpStatusCode).filter(\n (statusCode): statusCode is HttpStatusCode => typeof statusCode === \"number\"\n )\n);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n};\n\nconst isResponseDefinition = (value: unknown): boolean => {\n if (!isRecord(value)) {\n return false;\n }\n\n return (\n typeof value.name === \"string\" &&\n value.name.length > 0 &&\n typeof value.description === \"string\" &&\n value.description.length > 0 &&\n validHttpStatusCodes.has(value.statusCode as HttpStatusCode)\n );\n};\n\nconst isOperationDefinition = (value: unknown): boolean => {\n if (!isRecord(value) || !Array.isArray(value.responses)) {\n return false;\n }\n\n return (\n typeof value.operationId === \"string\" &&\n value.operationId.length > 0 &&\n typeof value.path === \"string\" &&\n value.path.length > 0 &&\n typeof value.summary === \"string\" &&\n value.summary.length > 0 &&\n Object.values(HttpMethod).includes(value.method as HttpMethod) &&\n isRecord(value.request) &&\n value.responses.length > 0 &&\n value.responses.every(response => isResponseDefinition(response))\n );\n};\n\nconst isResourceDefinition = (value: unknown): boolean => {\n return (\n isRecord(value) &&\n Array.isArray(value.operations) &&\n value.operations.every(isOperationDefinition)\n );\n};\n\nexport const isSpecDefinition = (value: unknown): value is SpecDefinition => {\n if (!isRecord(value) || !isRecord(value.resources)) {\n return false;\n }\n\n return Object.values(value.resources).every(isResourceDefinition);\n};\n","import { pathToFileURL } from \"node:url\";\nimport type { SpecDefinition } from \"@rexeus/typeweaver-core\";\nimport { InvalidSpecEntrypointError } from \"./InvalidSpecEntrypointError\";\nimport { isSpecDefinition } from \"./specGuards\";\n\nexport async function importDefinition(\n bundledSpecFile: string\n): Promise<SpecDefinition> {\n const moduleUrl = pathToFileURL(bundledSpecFile).toString();\n const specModule = (await import(moduleUrl)) as {\n readonly default?: unknown;\n readonly spec?: unknown;\n };\n const definition = specModule.default ?? specModule.spec ?? specModule;\n\n if (!isSpecDefinition(definition)) {\n throw new InvalidSpecEntrypointError(bundledSpecFile);\n }\n\n return definition;\n}\n","import fs from \"node:fs\";\nimport type { SpecDefinition } from \"@rexeus/typeweaver-core\";\nimport { normalizeSpec } from \"@rexeus/typeweaver-gen\";\nimport type { NormalizedSpec } from \"@rexeus/typeweaver-gen\";\nimport { bundle } from \"./spec/specBundler\";\nimport { importDefinition } from \"./spec/specImporter\";\n\nexport type SpecLoaderConfig = {\n readonly inputFile: string;\n readonly specOutputDir: string;\n};\n\nexport type LoadedSpec = {\n readonly definition: SpecDefinition;\n readonly normalizedSpec: NormalizedSpec;\n};\n\nexport async function loadSpec(config: SpecLoaderConfig): Promise<LoadedSpec> {\n fs.mkdirSync(config.specOutputDir, { recursive: true });\n\n const bundledSpecFile = await bundle(config);\n writeSpecDeclarationFile(config.specOutputDir);\n\n const definition = await importDefinition(bundledSpecFile);\n const normalizedSpec = normalizeSpec(definition);\n\n return {\n definition,\n normalizedSpec,\n };\n}\n\nfunction writeSpecDeclarationFile(specOutputDir: string): void {\n fs.writeFileSync(\n `${specOutputDir}/spec.d.ts`,\n [\n 'import type { SpecDefinition } from \"@rexeus/typeweaver-core\";',\n \"declare const _default: SpecDefinition;\",\n \"export default _default;\",\n \"export declare const spec: SpecDefinition;\",\n \"\",\n ].join(\"\\n\")\n );\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n createPluginContextBuilder,\n createPluginRegistry,\n} from \"@rexeus/typeweaver-gen\";\nimport type { PluginConfig, TypeweaverConfig } from \"@rexeus/typeweaver-gen\";\nimport TypesPlugin from \"@rexeus/typeweaver-types\";\nimport { formatCode } from \"./formatter\";\nimport { generateIndexFiles } from \"./indexFileGenerator\";\nimport { loadPlugins } from \"./pluginLoader\";\nimport { loadSpec } from \"./specLoader\";\nimport type { PluginResolutionStrategy } from \"./pluginLoader\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\n/**\n * Main generator for typeweaver\n * Uses a plugin-based architecture for extensible code generation\n */\nexport class Generator {\n public readonly coreDir = \"@rexeus/typeweaver-core\";\n public readonly templateDir = path.join(moduleDir, \"templates\");\n\n private readonly registry = createPluginRegistry();\n private readonly contextBuilder = createPluginContextBuilder();\n private readonly requiredPlugins: [TypesPlugin];\n private readonly strategies: PluginResolutionStrategy[];\n\n private inputFile = \"\";\n private outputDir = \"\";\n private specOutputDir = \"\";\n private responsesOutputDir = \"\";\n\n public constructor(\n requiredPlugins: [TypesPlugin] = [new TypesPlugin()],\n strategies?: PluginResolutionStrategy[]\n ) {\n this.requiredPlugins = requiredPlugins;\n this.strategies = strategies ?? [\"npm\", \"local\"];\n }\n\n /**\n * Generate code using the plugin system\n */\n public async generate(\n specFile: string,\n outputDir: string,\n config?: TypeweaverConfig\n ): Promise<void> {\n console.info(\"Starting generation...\");\n\n this.initializeDirectories(specFile, outputDir);\n\n if (config?.clean ?? true) {\n console.info(\"Cleaning output directory...\");\n fs.rmSync(this.outputDir, { recursive: true, force: true });\n }\n\n fs.mkdirSync(this.outputDir, { recursive: true });\n fs.mkdirSync(this.responsesOutputDir, { recursive: true });\n fs.mkdirSync(this.specOutputDir, { recursive: true });\n\n await loadPlugins(\n this.registry,\n this.requiredPlugins,\n this.strategies,\n config\n );\n\n console.info(\n `Bundling spec from '${this.inputFile}' to '${this.specOutputDir}'...`\n );\n let { normalizedSpec } = await loadSpec({\n inputFile: this.inputFile,\n specOutputDir: this.specOutputDir,\n });\n\n const pluginContext = this.contextBuilder.createPluginContext({\n outputDir: this.outputDir,\n inputDir: path.dirname(this.inputFile),\n config: (config ?? {}) as PluginConfig,\n });\n\n console.info(\"Initializing plugins...\");\n for (const registration of this.registry.getAll()) {\n if (registration.plugin.initialize) {\n await registration.plugin.initialize(pluginContext);\n }\n }\n\n console.info(\"Collecting resources...\");\n for (const registration of this.registry.getAll()) {\n if (registration.plugin.collectResources) {\n normalizedSpec =\n await registration.plugin.collectResources(normalizedSpec);\n }\n }\n\n const generatorContext = this.contextBuilder.createGeneratorContext({\n outputDir: this.outputDir,\n inputDir: path.dirname(this.inputFile),\n config: (config ?? {}) as PluginConfig,\n normalizedSpec,\n templateDir: this.templateDir,\n coreDir: this.coreDir,\n responsesOutputDir: this.responsesOutputDir,\n specOutputDir: this.specOutputDir,\n });\n\n console.info(\"Generating code...\");\n for (const registration of this.registry.getAll()) {\n console.info(`Running plugin: ${registration.plugin.name}`);\n if (registration.plugin.generate) {\n await registration.plugin.generate(generatorContext);\n }\n }\n\n generateIndexFiles(this.templateDir, generatorContext);\n\n console.info(\"Finalizing plugins...\");\n for (const registration of this.registry.getAll()) {\n if (registration.plugin.finalize) {\n await registration.plugin.finalize(pluginContext);\n }\n }\n\n if (config?.format ?? true) {\n await formatCode(this.outputDir);\n }\n\n console.info(\"Generation complete!\");\n console.info(\n `Generated files: ${this.contextBuilder.getGeneratedFiles().length}`\n );\n }\n\n private initializeDirectories(specFile: string, outputDir: string): void {\n this.inputFile = specFile;\n this.outputDir = outputDir;\n this.responsesOutputDir = path.join(outputDir, \"responses\");\n this.specOutputDir = path.join(this.outputDir, \"spec\");\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport type { TypeweaverConfig } from \"@rexeus/typeweaver-gen\";\nimport { Command } from \"commander\";\nimport { Generator } from \"./generators/Generator\";\nimport type { CommandOptions as CommanderOptions } from \"commander\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\nconst packageJson = JSON.parse(\n fs.readFileSync(path.join(moduleDir, \"../package.json\"), \"utf-8\")\n) as {\n readonly version: string;\n readonly name: string;\n readonly description: string;\n};\n\ntype CommandOptions = CommanderOptions & {\n input?: string;\n output?: string;\n config?: string;\n plugins?: string;\n format?: boolean;\n clean?: boolean;\n};\n\nconst program = new Command();\nconst execDir = process.cwd();\n\nprogram\n .name(\"@rexeus/typeweaver\")\n .description(\"Type-safe API framework with code generation for TypeScript\")\n .version(packageJson.version);\n\nprogram\n .command(\"generate\")\n .description(\"Generate types, validators, and clients from an API spec\")\n .option(\"-i, --input <inputPath>\", \"path to spec entrypoint file\")\n .option(\"-o, --output <outputDir>\", \"output directory for generated files\")\n .option(\"-c, --config <configFile>\", \"path to configuration file\")\n .option(\"-p, --plugins <plugins>\", \"comma-separated list of plugins to use\")\n .option(\"--format\", \"format generated code with oxfmt (default: true)\")\n .option(\"--no-format\", \"disable code formatting\")\n .option(\"--clean\", \"clean output directory before generation (default: true)\")\n .option(\"--no-clean\", \"disable cleaning output directory\")\n .action(async (options: CommandOptions) => {\n let config: Partial<TypeweaverConfig> = {};\n\n // Load configuration file if provided\n if (options.config) {\n const configPath = path.isAbsolute(options.config)\n ? options.config\n : path.join(execDir, options.config);\n\n try {\n const configUrl = pathToFileURL(configPath).toString();\n const configModule = await import(configUrl);\n config = configModule.default ?? configModule;\n console.info(`Loaded configuration from ${configPath}`);\n } catch (error) {\n console.error(`Failed to load configuration file: ${options.config}`);\n console.error(error);\n process.exit(1);\n }\n }\n\n // Override with CLI options\n const inputPath = options.input ?? config.input;\n const outputDir = options.output ?? config.output;\n // Validate required options\n if (!inputPath) {\n throw new Error(\n \"No input spec entrypoint provided. Use --input or specify in config file.\"\n );\n }\n if (!outputDir) {\n throw new Error(\n \"No output directory provided. Use --output or specify in config file.\"\n );\n }\n\n // Resolve paths\n const resolvedInputPath = path.isAbsolute(inputPath)\n ? inputPath\n : path.join(execDir, inputPath);\n const resolvedOutputDir = path.isAbsolute(outputDir)\n ? outputDir\n : path.join(execDir, outputDir);\n\n // Build final configuration\n const finalConfig: TypeweaverConfig = {\n input: resolvedInputPath,\n output: resolvedOutputDir,\n format: options.format ?? config.format ?? true,\n clean: options.clean ?? config.clean ?? true,\n };\n\n // Handle plugins\n if (options.plugins) {\n // Parse comma-separated plugins from CLI\n finalConfig.plugins = options.plugins.split(\",\").map(p => p.trim());\n } else if (config.plugins) {\n // Use plugins from config file\n finalConfig.plugins = config.plugins;\n }\n // If no plugins specified, Generator will use defaults\n\n // Run generation\n const generator = new Generator();\n return generator.generate(\n resolvedInputPath,\n resolvedOutputDir,\n finalConfig\n );\n });\n\n// Add future commands placeholder\nprogram\n .command(\"init\")\n .description(\"Initialize a new typeweaver project (coming soon)\")\n .action(() => {\n console.log(\"The init command is coming soon!\");\n });\n\nprogram.parse(process.argv);\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["moduleDir"],"sources":["../src/configLoader.ts","../src/generators/formatter.ts","../src/generators/indexFileGenerator.ts","../src/generators/errors/PluginLoadingFailure.ts","../src/generators/pluginLoader.ts","../src/generators/spec/specBundler.ts","../src/generators/spec/InvalidSpecEntrypointError.ts","../src/generators/spec/specGuards.ts","../src/generators/spec/specImporter.ts","../src/generators/specLoader.ts","../src/generators/Generator.ts","../src/cli.ts"],"sourcesContent":["import path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { TypeweaverConfig } from \"@rexeus/typeweaver-gen\";\n\nconst SUPPORTED_CONFIG_EXTENSIONS = new Set([\".js\", \".mjs\", \".cjs\"]);\nconst UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS = new Set([\n \".ts\",\n \".mts\",\n \".cts\",\n]);\n\nexport const getResolvedConfigPath = (\n configPath: string,\n currentWorkingDirectory: string = process.cwd()\n): string => {\n return path.isAbsolute(configPath)\n ? configPath\n : path.join(currentWorkingDirectory, configPath);\n};\n\nexport const assertSupportedConfigPath = (configPath: string): void => {\n const extension = path.extname(configPath).toLowerCase();\n\n if (UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS.has(extension)) {\n throw new Error(\n `TypeScript config files are no longer supported: '${configPath}'. Convert the config to .js, .mjs, or .cjs, or compile it before passing --config.`\n );\n }\n\n if (!SUPPORTED_CONFIG_EXTENSIONS.has(extension)) {\n throw new Error(\n `Unsupported config file extension for '${configPath}'. TypeWeaver only accepts .js, .mjs, or .cjs config files.`\n );\n }\n};\n\nexport const loadConfig = async (\n configPath: string\n): Promise<Partial<TypeweaverConfig>> => {\n assertSupportedConfigPath(configPath);\n\n const resolvedPath = path.resolve(configPath);\n const configUrl = pathToFileURL(resolvedPath).toString();\n const configModule = await import(configUrl);\n const loadedConfig = getConfigExport(configModule, configPath);\n\n if (!isConfigObject(loadedConfig)) {\n throw new Error(\n `Configuration file '${configPath}' must export a config object via 'export default' or 'export const config = ...'.`\n );\n }\n\n return loadedConfig;\n};\n\nconst getConfigExport = (\n configModule: Record<string, unknown>,\n configPath: string\n): unknown => {\n const hasDefaultExport = Object.hasOwn(configModule, \"default\");\n const hasNamedConfigExport = Object.hasOwn(configModule, \"config\");\n\n if (hasDefaultExport && hasNamedConfigExport) {\n throw new Error(\n `Configuration file '${configPath}' must choose a single export style: either 'export default' or 'export const config = ...', but not both.`\n );\n }\n\n if (hasDefaultExport) {\n if (isNamespaceLikeConfigExport(configModule.default)) {\n throw new Error(\n `Configuration file '${configPath}' default export must be the config object itself, not a module namespace-like wrapper. Export the config directly with 'export default { ... }' or use 'export const config = ...'.`\n );\n }\n\n return configModule.default;\n }\n\n if (hasNamedConfigExport) {\n return configModule.config;\n }\n\n throw new Error(\n `Configuration file '${configPath}' must export its config via 'export default' or 'export const config = ...'.`\n );\n};\n\nconst isConfigObject = (value: unknown): value is Partial<TypeweaverConfig> => {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n};\n\nconst isNamespaceLikeConfigExport = (value: unknown): boolean => {\n if (!isConfigObject(value)) {\n return false;\n }\n\n return Object.hasOwn(value, \"default\") || Object.hasOwn(value, \"config\");\n};\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\ntype FormatFn = (filename: string, source: string) => Promise<{ code: string }>;\n\nexport async function formatCode(\n outputDir: string,\n startDir?: string\n): Promise<void> {\n const format = await loadFormatter();\n if (!format) {\n return;\n }\n\n const targetDir = startDir ?? outputDir;\n await formatDirectory(targetDir, format);\n}\n\nasync function loadFormatter(): Promise<FormatFn | undefined> {\n try {\n const oxfmt = await import(\"oxfmt\");\n return oxfmt.format;\n } catch {\n console.warn(\n \"oxfmt not installed - skipping formatting. Install with: npm install -D oxfmt\"\n );\n return undefined;\n }\n}\n\nasync function formatDirectory(\n targetDir: string,\n format: FormatFn\n): Promise<void> {\n const contents = fs.readdirSync(targetDir, { withFileTypes: true });\n\n for (const content of contents) {\n if (content.isFile()) {\n const filePath = path.join(targetDir, content.name);\n const unformatted = fs.readFileSync(filePath, \"utf8\");\n const { code } = await format(filePath, unformatted);\n fs.writeFileSync(filePath, code);\n } else if (content.isDirectory()) {\n await formatDirectory(path.join(targetDir, content.name), format);\n }\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { renderTemplate } from \"@rexeus/typeweaver-gen\";\nimport type { GeneratorContext } from \"@rexeus/typeweaver-gen\";\n\nexport function generateIndexFiles(\n templateDir: string,\n context: GeneratorContext\n): void {\n const templateFilePath = path.join(templateDir, \"Index.ejs\");\n const template = fs.readFileSync(templateFilePath, \"utf8\");\n\n const generatedFiles = context.getGeneratedFiles();\n const groups = new Map<string, Set<string>>();\n const rootFiles = new Set<string>();\n const existingBarrels = new Set<string>();\n\n for (const file of generatedFiles) {\n const stripped = file.replace(/\\.ts$/, \"\");\n const firstSlash = stripped.indexOf(\"/\");\n\n if (firstSlash === -1) {\n rootFiles.add(`./${stripped}`);\n continue;\n }\n\n const firstSegment = stripped.slice(0, firstSlash);\n\n if (firstSegment === \"lib\") {\n const secondSlash = stripped.indexOf(\"/\", firstSlash + 1);\n const groupKey =\n secondSlash === -1 ? stripped : stripped.slice(0, secondSlash);\n\n const entryName = stripped.slice(groupKey.length + 1);\n\n if (entryName === \"index\") {\n existingBarrels.add(groupKey);\n } else {\n if (!groups.has(groupKey)) {\n groups.set(groupKey, new Set());\n }\n groups.get(groupKey)!.add(`./${entryName}`);\n }\n } else {\n const entryName = stripped.slice(firstSlash + 1);\n\n if (entryName === \"index\") {\n existingBarrels.add(firstSegment);\n } else {\n if (!groups.has(firstSegment)) {\n groups.set(firstSegment, new Set());\n }\n groups.get(firstSegment)!.add(`./${entryName}`);\n }\n }\n }\n\n for (const [groupKey, entries] of groups) {\n if (existingBarrels.has(groupKey)) {\n continue;\n }\n\n const domainBarrelContent = renderTemplate(template, {\n indexPaths: Array.from(entries).sort(),\n });\n\n const domainIndexPath = path.join(context.outputDir, groupKey, \"index.ts\");\n fs.mkdirSync(path.dirname(domainIndexPath), { recursive: true });\n fs.writeFileSync(domainIndexPath, domainBarrelContent);\n }\n\n const rootIndexPaths = new Set<string>(rootFiles);\n for (const groupKey of groups.keys()) {\n rootIndexPaths.add(`./${groupKey}`);\n }\n for (const barrelKey of existingBarrels) {\n rootIndexPaths.add(`./${barrelKey}`);\n }\n\n const rootContent = renderTemplate(template, {\n indexPaths: Array.from(rootIndexPaths).sort(),\n });\n\n fs.writeFileSync(path.join(context.outputDir, \"index.ts\"), rootContent);\n}\n","export type PluginLoadError = {\n readonly pluginName: string;\n readonly attempts: readonly {\n readonly path: string;\n readonly error: string;\n }[];\n};\n\nexport class PluginLoadingFailure extends Error implements PluginLoadError {\n public constructor(\n public readonly pluginName: string,\n public readonly attempts: readonly {\n readonly path: string;\n readonly error: string;\n }[]\n ) {\n super(`Failed to load plugin '${pluginName}'`);\n Object.setPrototypeOf(this, PluginLoadingFailure.prototype);\n }\n}\n","import type {\n PluginRegistryApi,\n TypeweaverConfig,\n TypeweaverPlugin,\n} from \"@rexeus/typeweaver-gen\";\nimport { TypesPlugin } from \"@rexeus/typeweaver-types\";\nimport { PluginLoadingFailure } from \"./errors/PluginLoadingFailure\";\nimport type { PluginLoadError } from \"./errors/PluginLoadingFailure\";\n\nexport type PluginResolutionStrategy = \"npm\" | \"local\" | \"scoped\";\n\nexport type PluginLoadResult = {\n plugin: TypeweaverPlugin;\n source: string;\n};\n\ntype LoadResult<T, E> =\n | { success: true; value: T }\n | { success: false; error: E };\n\nexport async function loadPlugins(\n registry: PluginRegistryApi,\n requiredPlugins: [TypesPlugin],\n strategies: PluginResolutionStrategy[],\n config?: TypeweaverConfig\n): Promise<void> {\n for (const requiredPlugin of requiredPlugins) {\n registry.register(requiredPlugin);\n }\n\n if (!config?.plugins) {\n return;\n }\n\n const successful: PluginLoadResult[] = [];\n\n for (const plugin of config.plugins) {\n let result: LoadResult<PluginLoadResult, PluginLoadError>;\n if (typeof plugin === \"string\") {\n result = await loadPlugin(plugin, strategies);\n } else {\n result = await loadPlugin(plugin[0], strategies);\n }\n\n if (result.success === false) {\n throw new PluginLoadingFailure(\n result.error.pluginName,\n result.error.attempts\n );\n }\n\n successful.push(result.value);\n registry.register(result.value.plugin);\n }\n\n reportSuccessfulLoads(successful);\n}\n\nasync function loadPlugin(\n pluginName: string,\n strategies: PluginResolutionStrategy[]\n): Promise<LoadResult<PluginLoadResult, PluginLoadError>> {\n const possiblePaths = generatePluginPaths(pluginName, strategies);\n const attempts: { path: string; error: string }[] = [];\n\n for (const possiblePath of possiblePaths) {\n try {\n const pluginPackage = await import(possiblePath);\n const PluginClass = findPluginConstructor(pluginPackage);\n if (PluginClass) {\n return {\n success: true,\n value: {\n plugin: new PluginClass(),\n source: possiblePath,\n },\n };\n }\n attempts.push({\n path: possiblePath,\n error: \"No plugin class export found\",\n });\n } catch (error) {\n attempts.push({\n path: possiblePath,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return {\n success: false,\n error: {\n pluginName,\n attempts,\n },\n };\n}\n\nfunction findPluginConstructor(\n pluginModule: Record<string, unknown>\n): (new () => TypeweaverPlugin) | undefined {\n for (const [key, value] of Object.entries(pluginModule)) {\n if (key !== \"default\" && typeof value === \"function\") {\n return value as new () => TypeweaverPlugin;\n }\n }\n\n // Fall back to default export for third-party plugin compatibility\n const defaultExport = pluginModule.default;\n if (typeof defaultExport === \"function\") {\n return defaultExport as new () => TypeweaverPlugin;\n }\n\n return undefined;\n}\n\nfunction generatePluginPaths(\n pluginName: string,\n strategies: PluginResolutionStrategy[]\n): string[] {\n const paths: string[] = [];\n\n for (const strategy of strategies) {\n switch (strategy) {\n case \"npm\":\n paths.push(`@rexeus/typeweaver-${pluginName}`);\n paths.push(`@rexeus/${pluginName}`);\n break;\n case \"local\":\n paths.push(pluginName);\n break;\n }\n }\n\n return paths;\n}\n\nfunction reportSuccessfulLoads(successful: PluginLoadResult[]): void {\n if (successful.length > 0) {\n console.info(`Successfully loaded ${successful.length} plugin(s):`);\n for (const result of successful) {\n console.info(` - ${result.plugin.name} (from ${result.source})`);\n }\n }\n}\n","import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { Rolldown } from \"tsdown\";\n\nconst WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\\\/]/;\nconst WINDOWS_UNC_PATH_PATTERN = /^\\\\\\\\/;\n\nexport type SpecBundlerConfig = {\n readonly inputFile: string;\n readonly specOutputDir: string;\n};\n\nexport function createWrapperImportSpecifier(\n wrapperFile: string,\n inputFile: string\n): string {\n const absoluteInputFile = resolveBundledInputFile(inputFile);\n const useWindowsPathSemantics = usesWindowsPathSemantics(\n wrapperFile,\n absoluteInputFile\n );\n const pathModule = useWindowsPathSemantics ? path.win32 : path.posix;\n const wrapperDir = useWindowsPathSemantics\n ? pathModule.dirname(wrapperFile)\n : resolveRealFilePath(pathModule.dirname(wrapperFile));\n const resolvedInputFile = useWindowsPathSemantics\n ? absoluteInputFile\n : resolveRealFilePath(absoluteInputFile);\n const relativeInputFile = pathModule\n .relative(wrapperDir, resolvedInputFile)\n .replaceAll(pathModule.sep, \"/\");\n\n if (relativeInputFile.startsWith(\".\") || relativeInputFile.startsWith(\"..\")) {\n return relativeInputFile;\n }\n\n return `./${relativeInputFile}`;\n}\n\nexport async function bundle(config: SpecBundlerConfig): Promise<string> {\n const tempDir = fs.mkdtempSync(\n path.join(os.tmpdir(), \"typeweaver-spec-loader-\")\n );\n const wrapperFile = path.join(tempDir, \"spec-entrypoint.ts\");\n const bundledSpecFile = path.join(config.specOutputDir, \"spec.js\");\n const wrapperImportSpecifier = createWrapperImportSpecifier(\n wrapperFile,\n config.inputFile\n );\n\n fs.writeFileSync(\n wrapperFile,\n [\n `import * as specModule from ${JSON.stringify(wrapperImportSpecifier)};`,\n \"const resolvedSpec =\",\n ' Reflect.get(specModule, \"spec\") ??',\n ' Reflect.get(specModule, \"default\") ??',\n \" specModule;\",\n \"\",\n \"export const spec = resolvedSpec;\",\n \"\",\n ].join(\"\\n\")\n );\n\n try {\n await Rolldown.build({\n cwd: tempDir,\n input: wrapperFile,\n treeshake: true,\n experimental: {\n attachDebugInfo: \"none\",\n },\n external: (source: string) => {\n if (source.startsWith(\"node:\")) {\n return true;\n }\n\n return !source.startsWith(\".\") && !path.isAbsolute(source);\n },\n output: {\n file: bundledSpecFile,\n format: \"esm\",\n },\n });\n } finally {\n fs.rmSync(tempDir, { recursive: true, force: true });\n }\n\n if (!fs.existsSync(bundledSpecFile)) {\n throw new Error(\n `Failed to bundle spec entrypoint '${config.inputFile}' to '${bundledSpecFile}'.`\n );\n }\n\n return bundledSpecFile;\n}\n\nfunction resolveBundledInputFile(inputFile: string): string {\n if (path.isAbsolute(inputFile)) {\n return inputFile;\n }\n\n if (WINDOWS_ABSOLUTE_PATH_PATTERN.test(inputFile)) {\n return path.win32.normalize(inputFile);\n }\n\n if (WINDOWS_UNC_PATH_PATTERN.test(inputFile)) {\n return path.win32.normalize(inputFile);\n }\n\n return path.resolve(inputFile);\n}\n\nfunction usesWindowsPathSemantics(...filePaths: string[]): boolean {\n return filePaths.some(filePath => {\n return (\n WINDOWS_ABSOLUTE_PATH_PATTERN.test(filePath) ||\n WINDOWS_UNC_PATH_PATTERN.test(filePath)\n );\n });\n}\n\nfunction resolveRealFilePath(filePath: string): string {\n if (!fs.existsSync(filePath)) {\n return filePath;\n }\n\n return fs.realpathSync.native(filePath);\n}\n","export class InvalidSpecEntrypointError extends Error {\n public constructor(specEntrypoint: string) {\n super(\n `Spec entrypoint '${specEntrypoint}' must export a SpecDefinition as its default export, named 'spec' export, or module namespace.`\n );\n this.name = \"InvalidSpecEntrypointError\";\n }\n}\n","import { HttpMethod, HttpStatusCode } from \"@rexeus/typeweaver-core\";\nimport type { SpecDefinition } from \"@rexeus/typeweaver-core\";\n\nconst validHttpStatusCodes = new Set<HttpStatusCode>(\n Object.values(HttpStatusCode).filter(\n (statusCode): statusCode is HttpStatusCode => typeof statusCode === \"number\"\n )\n);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n};\n\nconst isResponseDefinition = (value: unknown): boolean => {\n if (!isRecord(value)) {\n return false;\n }\n\n return (\n typeof value.name === \"string\" &&\n value.name.length > 0 &&\n typeof value.description === \"string\" &&\n value.description.length > 0 &&\n validHttpStatusCodes.has(value.statusCode as HttpStatusCode)\n );\n};\n\nconst isOperationDefinition = (value: unknown): boolean => {\n if (!isRecord(value) || !Array.isArray(value.responses)) {\n return false;\n }\n\n return (\n typeof value.operationId === \"string\" &&\n value.operationId.length > 0 &&\n typeof value.path === \"string\" &&\n value.path.length > 0 &&\n typeof value.summary === \"string\" &&\n value.summary.length > 0 &&\n Object.values(HttpMethod).includes(value.method as HttpMethod) &&\n isRecord(value.request) &&\n value.responses.length > 0 &&\n value.responses.every(response => isResponseDefinition(response))\n );\n};\n\nconst isResourceDefinition = (value: unknown): boolean => {\n return (\n isRecord(value) &&\n Array.isArray(value.operations) &&\n value.operations.every(isOperationDefinition)\n );\n};\n\nexport const isSpecDefinition = (value: unknown): value is SpecDefinition => {\n if (!isRecord(value) || !isRecord(value.resources)) {\n return false;\n }\n\n return Object.values(value.resources).every(isResourceDefinition);\n};\n","import { pathToFileURL } from \"node:url\";\nimport type { SpecDefinition } from \"@rexeus/typeweaver-core\";\nimport { InvalidSpecEntrypointError } from \"./InvalidSpecEntrypointError\";\nimport { isSpecDefinition } from \"./specGuards\";\n\nexport async function importDefinition(\n bundledSpecFile: string\n): Promise<SpecDefinition> {\n const moduleUrl = pathToFileURL(bundledSpecFile).toString();\n const specModule = (await import(moduleUrl)) as {\n readonly spec?: unknown;\n readonly default?: unknown;\n };\n const definition = specModule.spec ?? specModule.default ?? specModule;\n\n if (!isSpecDefinition(definition)) {\n throw new InvalidSpecEntrypointError(bundledSpecFile);\n }\n\n return definition;\n}\n","import fs from \"node:fs\";\nimport type { SpecDefinition } from \"@rexeus/typeweaver-core\";\nimport { normalizeSpec } from \"@rexeus/typeweaver-gen\";\nimport type { NormalizedSpec } from \"@rexeus/typeweaver-gen\";\nimport { bundle } from \"./spec/specBundler\";\nimport { importDefinition } from \"./spec/specImporter\";\n\nexport type SpecLoaderConfig = {\n readonly inputFile: string;\n readonly specOutputDir: string;\n};\n\nexport type LoadedSpec = {\n readonly definition: SpecDefinition;\n readonly normalizedSpec: NormalizedSpec;\n};\n\nexport async function loadSpec(config: SpecLoaderConfig): Promise<LoadedSpec> {\n fs.mkdirSync(config.specOutputDir, { recursive: true });\n\n const bundledSpecFile = await bundle(config);\n writeSpecDeclarationFile(config.specOutputDir);\n\n const definition = await importDefinition(bundledSpecFile);\n const normalizedSpec = normalizeSpec(definition);\n\n return {\n definition,\n normalizedSpec,\n };\n}\n\nfunction writeSpecDeclarationFile(specOutputDir: string): void {\n fs.writeFileSync(\n `${specOutputDir}/spec.d.ts`,\n [\n 'import type { SpecDefinition } from \"@rexeus/typeweaver-core\";',\n \"export declare const spec: SpecDefinition;\",\n \"\",\n ].join(\"\\n\")\n );\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n createPluginContextBuilder,\n createPluginRegistry,\n} from \"@rexeus/typeweaver-gen\";\nimport type { PluginConfig, TypeweaverConfig } from \"@rexeus/typeweaver-gen\";\nimport { TypesPlugin } from \"@rexeus/typeweaver-types\";\nimport { formatCode } from \"./formatter\";\nimport { generateIndexFiles } from \"./indexFileGenerator\";\nimport { loadPlugins } from \"./pluginLoader\";\nimport { loadSpec } from \"./specLoader\";\nimport type { PluginResolutionStrategy } from \"./pluginLoader\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\nexport const assertSafeCleanTarget = (\n outputDir: string,\n currentWorkingDirectory: string\n): void => {\n const trimmedOutputDir = outputDir.trim();\n if (trimmedOutputDir.length === 0) {\n throw new Error(\n \"Refusing to clean an empty output directory path. Pass a dedicated generated output directory instead.\"\n );\n }\n\n const resolvedWorkingDirectory = path.resolve(currentWorkingDirectory);\n const resolvedOutputDir = path.resolve(\n resolvedWorkingDirectory,\n trimmedOutputDir\n );\n const filesystemRoot = path.parse(resolvedOutputDir).root;\n\n if (resolvedOutputDir === filesystemRoot) {\n throw new Error(\n `Refusing to clean '${outputDir}' because it resolves to the filesystem root.`\n );\n }\n\n if (resolvedOutputDir === resolvedWorkingDirectory) {\n throw new Error(\n `Refusing to clean '${outputDir}' because it resolves to the current working directory.`\n );\n }\n\n const protectedWorkspaceRoot = findProtectedWorkspaceRoot(\n resolvedWorkingDirectory\n );\n\n if (\n protectedWorkspaceRoot !== undefined &&\n resolvedOutputDir === protectedWorkspaceRoot\n ) {\n throw new Error(\n `Refusing to clean '${outputDir}' because it resolves to the inferred workspace root '${protectedWorkspaceRoot}'. Choose a dedicated output subdirectory instead.`\n );\n }\n};\n\n/**\n * Main generator for typeweaver\n * Uses a plugin-based architecture for extensible code generation\n */\nexport class Generator {\n public readonly coreDir = \"@rexeus/typeweaver-core\";\n public readonly templateDir = path.join(moduleDir, \"templates\");\n\n private readonly registry = createPluginRegistry();\n private readonly contextBuilder = createPluginContextBuilder();\n private readonly requiredPlugins: [TypesPlugin];\n private readonly strategies: PluginResolutionStrategy[];\n\n private inputFile = \"\";\n private outputDir = \"\";\n private specOutputDir = \"\";\n private responsesOutputDir = \"\";\n\n public constructor(\n requiredPlugins: [TypesPlugin] = [new TypesPlugin()],\n strategies?: PluginResolutionStrategy[]\n ) {\n this.requiredPlugins = requiredPlugins;\n this.strategies = strategies ?? [\"npm\", \"local\"];\n }\n\n /**\n * Generate code using the plugin system\n */\n public async generate(\n specFile: string,\n outputDir: string,\n config?: TypeweaverConfig,\n currentWorkingDirectory: string = process.cwd()\n ): Promise<void> {\n console.info(\"Starting generation...\");\n\n this.initializeDirectories(specFile, outputDir, currentWorkingDirectory);\n\n if (config?.clean ?? true) {\n assertSafeCleanTarget(this.outputDir, currentWorkingDirectory);\n console.info(\"Cleaning output directory...\");\n fs.rmSync(this.outputDir, { recursive: true, force: true });\n }\n\n fs.mkdirSync(this.outputDir, { recursive: true });\n fs.mkdirSync(this.responsesOutputDir, { recursive: true });\n fs.mkdirSync(this.specOutputDir, { recursive: true });\n\n await loadPlugins(\n this.registry,\n this.requiredPlugins,\n this.strategies,\n config\n );\n\n console.info(\n `Bundling spec from '${this.inputFile}' to '${this.specOutputDir}'...`\n );\n let { normalizedSpec } = await loadSpec({\n inputFile: this.inputFile,\n specOutputDir: this.specOutputDir,\n });\n\n const pluginContext = this.contextBuilder.createPluginContext({\n outputDir: this.outputDir,\n inputDir: path.dirname(this.inputFile),\n config: (config ?? {}) as PluginConfig,\n });\n\n console.info(\"Initializing plugins...\");\n for (const registration of this.registry.getAll()) {\n if (registration.plugin.initialize) {\n await registration.plugin.initialize(pluginContext);\n }\n }\n\n console.info(\"Collecting resources...\");\n for (const registration of this.registry.getAll()) {\n if (registration.plugin.collectResources) {\n normalizedSpec =\n await registration.plugin.collectResources(normalizedSpec);\n }\n }\n\n const generatorContext = this.contextBuilder.createGeneratorContext({\n outputDir: this.outputDir,\n inputDir: path.dirname(this.inputFile),\n config: (config ?? {}) as PluginConfig,\n normalizedSpec,\n templateDir: this.templateDir,\n coreDir: this.coreDir,\n responsesOutputDir: this.responsesOutputDir,\n specOutputDir: this.specOutputDir,\n });\n\n console.info(\"Generating code...\");\n for (const registration of this.registry.getAll()) {\n console.info(`Running plugin: ${registration.plugin.name}`);\n if (registration.plugin.generate) {\n await registration.plugin.generate(generatorContext);\n }\n }\n\n generateIndexFiles(this.templateDir, generatorContext);\n\n console.info(\"Finalizing plugins...\");\n for (const registration of this.registry.getAll()) {\n if (registration.plugin.finalize) {\n await registration.plugin.finalize(pluginContext);\n }\n }\n\n if (config?.format ?? true) {\n await formatCode(this.outputDir);\n }\n\n console.info(\"Generation complete!\");\n console.info(\n `Generated files: ${this.contextBuilder.getGeneratedFiles().length}`\n );\n }\n\n private initializeDirectories(\n specFile: string,\n outputDir: string,\n currentWorkingDirectory: string\n ): void {\n this.inputFile = path.resolve(currentWorkingDirectory, specFile);\n this.outputDir = path.resolve(currentWorkingDirectory, outputDir);\n this.responsesOutputDir = path.join(this.outputDir, \"responses\");\n this.specOutputDir = path.join(this.outputDir, \"spec\");\n }\n}\n\nconst findProtectedWorkspaceRoot = (\n startDirectory: string\n): string | undefined => {\n let currentDirectory = startDirectory;\n\n while (true) {\n if (hasWorkspaceMarker(currentDirectory)) {\n return currentDirectory;\n }\n\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n\n currentDirectory = parentDirectory;\n }\n};\n\nconst hasWorkspaceMarker = (directory: string): boolean => {\n return [\"pnpm-workspace.yaml\", \".git\"].some(marker =>\n fs.existsSync(path.join(directory, marker))\n );\n};\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { TypeweaverConfig } from \"@rexeus/typeweaver-gen\";\nimport { Command } from \"commander\";\nimport { getResolvedConfigPath, loadConfig } from \"./configLoader\";\nimport { Generator } from \"./generators/Generator\";\nimport type { CommandOptions as CommanderOptions } from \"commander\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\nconst packageJson = JSON.parse(\n fs.readFileSync(path.join(moduleDir, \"../package.json\"), \"utf-8\")\n) as {\n readonly version: string;\n readonly name: string;\n readonly description: string;\n};\n\ntype CommandOptions = CommanderOptions & {\n input?: string;\n output?: string;\n config?: string;\n plugins?: string;\n format?: boolean;\n clean?: boolean;\n};\n\nconst program = new Command();\nconst execDir = process.cwd();\n\nprogram\n .name(\"@rexeus/typeweaver\")\n .description(\"Type-safe API framework with code generation for TypeScript\")\n .version(packageJson.version);\n\nprogram\n .command(\"generate\")\n .description(\"Generate types, validators, and clients from an API spec\")\n .option(\"-i, --input <inputPath>\", \"path to spec entrypoint file\")\n .option(\"-o, --output <outputDir>\", \"output directory for generated files\")\n .option(\n \"-c, --config <configFile>\",\n \"path to a .js, .mjs, or .cjs configuration file\"\n )\n .option(\"-p, --plugins <plugins>\", \"comma-separated list of plugins to use\")\n .option(\"--format\", \"format generated code with oxfmt (default: true)\")\n .option(\"--no-format\", \"disable code formatting\")\n .option(\"--clean\", \"clean output directory before generation (default: true)\")\n .option(\"--no-clean\", \"disable cleaning output directory\")\n .action(async (options: CommandOptions) => {\n let config: Partial<TypeweaverConfig> = {};\n\n // Load configuration file if provided\n if (options.config) {\n const configPath = getResolvedConfigPath(options.config, execDir);\n\n try {\n config = await loadConfig(configPath);\n console.info(`Loaded configuration from ${configPath}`);\n } catch (error) {\n console.error(`Failed to load configuration file: ${options.config}`);\n console.error(error);\n process.exit(1);\n }\n }\n\n // Override with CLI options\n const inputPath = options.input ?? config.input;\n const outputDir = options.output ?? config.output;\n // Validate required options\n if (!inputPath) {\n throw new Error(\n \"No input spec entrypoint provided. Use --input or specify in config file.\"\n );\n }\n if (!outputDir) {\n throw new Error(\n \"No output directory provided. Use --output or specify in config file.\"\n );\n }\n\n // Resolve paths\n const resolvedInputPath = path.isAbsolute(inputPath)\n ? inputPath\n : path.join(execDir, inputPath);\n const resolvedOutputDir = path.isAbsolute(outputDir)\n ? outputDir\n : path.join(execDir, outputDir);\n\n // Build final configuration\n const finalConfig: TypeweaverConfig = {\n input: resolvedInputPath,\n output: resolvedOutputDir,\n format: options.format ?? config.format ?? true,\n clean: options.clean ?? config.clean ?? true,\n };\n\n // Handle plugins\n if (options.plugins) {\n // Parse comma-separated plugins from CLI\n finalConfig.plugins = options.plugins.split(\",\").map(p => p.trim());\n } else if (config.plugins) {\n // Use plugins from config file\n finalConfig.plugins = config.plugins;\n }\n // If no plugins specified, Generator will use defaults\n\n // Run generation\n const generator = new Generator();\n return generator.generate(\n resolvedInputPath,\n resolvedOutputDir,\n finalConfig,\n execDir\n );\n });\n\n// Add future commands placeholder\nprogram\n .command(\"init\")\n .description(\"Initialize a new typeweaver project (coming soon)\")\n .action(() => {\n console.log(\"The init command is coming soon!\");\n });\n\nprogram.parse(process.argv);\n"],"mappings":"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"}
package/dist/entry.mjs CHANGED
@@ -17,8 +17,7 @@ const getRuntimeDisplayName = (runtime) => {
17
17
  const main = async () => {
18
18
  const runtime = detectRuntime();
19
19
  console.info(`Running on ${getRuntimeDisplayName(runtime)}`);
20
- if (runtime === "node") await import("./tsx-loader-DtL8gLLb.mjs");
21
- await import("./cli-CF2Dbqut.mjs");
20
+ await import("./cli-BlGgs--F.mjs");
22
21
  };
23
22
  main().catch((error) => {
24
23
  console.error("Failed to start TypeWeaver CLI:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "description": "๐Ÿงตโœจ Typeweaver CLI. Entry point into the Typeweaver framework to scaffold, validate, and generate API assets.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -52,17 +52,15 @@
52
52
  "homepage": "https://github.com/rexeus/typeweaver#readme",
53
53
  "dependencies": {
54
54
  "commander": "^14.0.3",
55
- "ejs": "^5.0.1",
56
55
  "oxc-transform": "^0.121.0",
57
56
  "tsdown": "^0.21.7",
58
- "tsx": "^4.21.0",
59
- "@rexeus/typeweaver-clients": "^0.9.2",
60
- "@rexeus/typeweaver-aws-cdk": "^0.9.2",
61
- "@rexeus/typeweaver-hono": "^0.9.2",
62
- "@rexeus/typeweaver-core": "^0.9.2",
63
- "@rexeus/typeweaver-gen": "^0.9.2",
64
- "@rexeus/typeweaver-server": "^0.9.2",
65
- "@rexeus/typeweaver-types": "^0.9.2"
57
+ "@rexeus/typeweaver-aws-cdk": "^0.10.0",
58
+ "@rexeus/typeweaver-clients": "^0.10.0",
59
+ "@rexeus/typeweaver-core": "^0.10.0",
60
+ "@rexeus/typeweaver-gen": "^0.10.0",
61
+ "@rexeus/typeweaver-server": "^0.10.0",
62
+ "@rexeus/typeweaver-types": "^0.10.0",
63
+ "@rexeus/typeweaver-hono": "^0.10.0"
66
64
  },
67
65
  "peerDependencies": {
68
66
  "oxfmt": ">=0.30.0"
@@ -73,8 +71,8 @@
73
71
  }
74
72
  },
75
73
  "devDependencies": {
76
- "case": "^1.6.3",
77
74
  "hono": "^4.12.9",
75
+ "tsx": "^4.21.0",
78
76
  "zod": "^4.3.6"
79
77
  },
80
78
  "scripts": {
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import "tsx";
3
- export {};