next-typed-paths 0.1.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 ADDED
@@ -0,0 +1,249 @@
1
+ # Next Typed Routes
2
+
3
+ Type-safe Next.js App Router route builder with automatic generation from your file system.
4
+
5
+ ## Features
6
+
7
+ - šŸ”’ **Fully Type-Safe**: Get autocomplete and type checking for all your routes
8
+ - šŸ”„ **Auto-Generated**: Scans your Next.js app directory and generates routes automatically
9
+ - šŸ‘€ **Live Updates**: Watch mode regenerates routes when files change
10
+ - āš™ļø **Configurable**: Support for config files and CLI options
11
+ - šŸ“¦ **Zero Runtime Cost**: All types are compile-time only
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install next-typed-paths
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ### 1. Generate Routes
22
+
23
+ ```bash
24
+ npx next-typed-paths generate --input ./src/app/api --output ./src/generated/routes.ts
25
+ ```
26
+
27
+ ### 2. Use in Your Code
28
+
29
+ ```typescript
30
+ import { routes } from "./generated/routes";
31
+
32
+ // Type-safe route building
33
+ const userRoute = routes.api.users.$userId("123"); // "/api/users/123"
34
+ const listRoute = routes.api.users.$(); // "/api/users"
35
+ ```
36
+
37
+ ## Configuration
38
+
39
+ Create a `routes.config.ts` file in your project root:
40
+
41
+ ```typescript
42
+ import type { RouteConfig } from "next-typed-paths";
43
+
44
+ const routeConfig: RouteConfig = {
45
+ input: "./src/app/api",
46
+ output: "./src/generated/routes.ts",
47
+ watch: false,
48
+ basePrefix: "/api",
49
+ paramTypes: {
50
+ userId: "string",
51
+ postId: "number",
52
+ },
53
+ imports: ["import { z } from 'zod';"],
54
+ };
55
+
56
+ export default routeConfig;
57
+ ```
58
+
59
+ ### Configuration Options
60
+
61
+ - **`input`** (`string`, required): The directory path to scan for route files. This should point to your Next.js API routes directory (e.g., `./src/app/api` or `./src/app`). You can use next-typed-paths for just your REST API backend or also for any page routes that return UI.
62
+
63
+ - **`output`** (`string`, required): The file path where the generated TypeScript routes file will be written. This file will contain all your type-safe route builders.
64
+
65
+ - **`watch`** (`boolean`, optional): When set to `true`, the generator will run in watch mode and automatically regenerate routes whenever files change in the input directory. Defaults to `false`.
66
+
67
+ - **`basePrefix`** (`string`, optional): A prefix that will be prepended to all generated routes. For example, if your API routes are under `/api`, set this to `"/api"` so generated routes include this prefix. Defaults to `""`.
68
+
69
+ - **`paramTypes`** (`Record<string, string>`, optional): A mapping of parameter names to their TypeScript types. This allows you to specify custom types for dynamic route parameters instead of the default `string` type. For example, `{ userId: "number", postId: "string" }` will type the `userId` parameter as a number. Defaults to `{}`. You can even specify custom stricter types such as database table, user type, etc. Any unspecified route params will have their type defaulted to `string`.
70
+
71
+ - **`imports`** (`string[]`, optional): An array of import statements to include at the top of the generated routes file. Useful if your route builders need to reference custom types or utilities. For example, `["import { z } from 'zod';", "import type { User } from './types';"]`. Defaults to `[]`.
72
+
73
+ ## CLI Commands
74
+
75
+ ### Generate Routes
76
+
77
+ ```bash
78
+ npx next-typed-paths generate [options]
79
+ ```
80
+
81
+ Options:
82
+
83
+ - `-i, --input <path>`: Input directory to scan (default: "./app/api")
84
+ - `-o, --output <path>`: Output file path (default: "./generated/routes.ts")
85
+ - `-w, --watch`: Watch for changes and regenerate
86
+ - `-c, --config <path>`: Path to config file
87
+
88
+ ### Watch Mode
89
+
90
+ ```bash
91
+ npx next-typed-paths generate --watch
92
+ ```
93
+
94
+ This will watch your app directory and automatically regenerate routes when files change.
95
+
96
+ ### Integration with Development Workflow
97
+
98
+ You can integrate the route generator into your development workflow to automatically regenerate routes alongside your dev server. For example, if you are using [Nx](https://github.com/nrwl/nx), you can run both the Next.js dev server and the route generator in parallel:
99
+
100
+ ```json
101
+ // project.json
102
+ {
103
+ "name": "your-next-app",
104
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
105
+ "sourceRoot": "apps/your-next-app",
106
+ "projectType": "application",
107
+ "targets": {
108
+ "dev": {
109
+ "executor": "nx:run-commands",
110
+ "options": {
111
+ "commands": ["next dev", "npx next-typed-paths generate --watch"],
112
+ "parallel": true
113
+ }
114
+ },
115
+ "build": {
116
+ "executor": "@nx/next:build",
117
+ "outputs": ["{options.outputPath}"],
118
+ "options": {
119
+ "outputPath": "dist/apps/your-next-app"
120
+ }
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ With `"parallel": true`, both commands run simultaneously:
127
+
128
+ - `next dev` starts your Next.js development server
129
+ - `npx next-typed-paths generate --watch` watches for route file changes and regenerates types
130
+
131
+ This ensures your route types stay in sync with your file system as you develop.
132
+
133
+ ## How It Works
134
+
135
+ The generator scans your Next.js app directory structure:
136
+
137
+ ```
138
+ app/api/
139
+ ā”œā”€ā”€ users/
140
+ │ ā”œā”€ā”€ route.ts → routes.api.users.$()
141
+ │ └── [userId]/
142
+ │ └── route.ts → routes.api.users.$userId(id)
143
+ └── posts/
144
+ ā”œā”€ā”€ route.ts → routes.api.posts.$()
145
+ └── [postId]/
146
+ ā”œā”€ā”€ route.ts → routes.api.posts.$postId(id)
147
+ └── comments/
148
+ └── route.ts → routes.api.posts.$postId(id).comments()
149
+ ```
150
+
151
+ It uses the directory structure to generate in realtime a typed schema of the available routes in your Next.Js application. You are still responsible for ensuring you use the route in the correct way (i.e. correct HTTP method and query params), however, the route and path params are typed for you.
152
+
153
+ ## Examples
154
+
155
+ ### Basic Usage
156
+
157
+ ```typescript
158
+ import { routes } from "./generated/routes";
159
+
160
+ // Static routes
161
+ routes.api.auth.login(); // "/api/auth/login"
162
+
163
+ // Dynamic routes
164
+ routes.api.users.$userId("123"); // "/api/users/123"
165
+
166
+ // Nested dynamic routes
167
+ routes.api.posts.$postId("456").comments(); // "/api/posts/456/comments"
168
+
169
+ // Access parent route
170
+ routes.api.users.$userId("123").$(); // "/api/users/123"
171
+ ```
172
+
173
+ ### With Next.js
174
+
175
+ By no means are the following examples an indication you are pinned to using certain libraries (e.g. Axios, Tanstack Query). Rather I provide some examples within the context of some common patterns.
176
+
177
+ #### In Client Components
178
+
179
+ ```typescript
180
+ 'use client';
181
+
182
+ import { useState } from "react";
183
+
184
+ import axios from "axios";
185
+
186
+ import { User } from "@/common/types";
187
+ import { routes } from '@/generated/routes';
188
+
189
+ export const UsersList = () => {
190
+ const [users, setUsers] = useState<User[]>([]);
191
+
192
+ useEffect(() => {
193
+ // Type-safe API calls from the client
194
+ axios.get(routes.api.users.$())
195
+ .then(res => res.data)
196
+ .then(setUsers);
197
+ }, []);
198
+
199
+ return <div>{/* render users */}</div>;
200
+ };
201
+ ```
202
+
203
+ #### In Server Components
204
+
205
+ ```typescript
206
+ const UserProfile = async ({ userId }: { userId: string }) => {
207
+ // Call your API with type-safe routes
208
+ const { data: user } = await axios.get(routes.api.users.$userId(userId));
209
+ return <div>{user.name}</div>;
210
+ };
211
+ ```
212
+
213
+ #### For redirects
214
+
215
+ ```typescript
216
+ import { redirect } from "next/navigation";
217
+
218
+ const handleLogin = (userId: string) => {
219
+ redirect(routes.api.auth.callback.$());
220
+ };
221
+ ```
222
+
223
+ #### Building URLs for links
224
+
225
+ ```typescript
226
+ const UserLink = ({ userId }: { userId: string }) => {
227
+ return <a href={routes.api.users.$userId(userId)}>View Profile</a>;
228
+ };
229
+ ```
230
+
231
+ #### With [TanStack Query](https://github.com/TanStack/query)
232
+
233
+ ```typescript
234
+ import { useQuery } from '@tanstack/react-query';
235
+
236
+ const UserProfile = ({ userId }: { userId: string }) => {
237
+ const { data: user, isLoading } = useQuery({
238
+ queryKey: ['user', userId],
239
+ queryFn: () => axios.get(routes.api.users.$userId(userId)).then(res => res.data),
240
+ });
241
+
242
+ if (isLoading) return <div>Loading...</div>;
243
+ return <div>{user?.name}</div>;
244
+ };
245
+ ```
246
+
247
+ ## License
248
+
249
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for Next.js route generation
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;GAEG"}
package/dist/cli.js ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for Next.js route generation
4
+ */
5
+ import { Command } from "commander";
6
+ import { existsSync } from "fs";
7
+ import { writeFile } from "fs/promises";
8
+ import { dirname, join } from "path";
9
+ import { loadConfig, mergeConfig } from "./config";
10
+ import { CLI_NAME, CONFIG_FILE_NAME, DEFAULT_BASE_PREFIX, DEFAULT_INPUT_DIR, DEFAULT_OUTPUT_FILE, PACKAGE_NAME, PACKAGE_VERSION, } from "./constants";
11
+ import { mkdirIfNotExists } from "./file";
12
+ import { generateRouteFile } from "./generator";
13
+ import { generateRouteStructure } from "./scanner";
14
+ import { startWatcher } from "./watcher";
15
+ const program = new Command();
16
+ program
17
+ .name(CLI_NAME)
18
+ .description("Generate type-safe routes from Next.js app directory structure")
19
+ .version(PACKAGE_VERSION);
20
+ /**
21
+ * Generate routes from directory structure
22
+ */
23
+ const generateRoutes = async (config) => {
24
+ try {
25
+ console.log("šŸ” Scanning directory:", config.input);
26
+ // Scan directory structure
27
+ const structure = await generateRouteStructure(config.input);
28
+ // Generate TypeScript code
29
+ const code = generateRouteFile(structure, config);
30
+ // Ensure output directory exists
31
+ const outputDir = dirname(config.output);
32
+ await mkdirIfNotExists(outputDir);
33
+ // Write output file
34
+ await writeFile(config.output, code, "utf-8");
35
+ console.log("āœ… Routes generated successfully:");
36
+ console.log(" šŸ“ Input:", config.input);
37
+ console.log(" šŸ“„ Output:", config.output);
38
+ }
39
+ catch (error) {
40
+ console.error("āŒ Error generating routes:", error);
41
+ throw error;
42
+ }
43
+ };
44
+ /**
45
+ * Generate command
46
+ */
47
+ program
48
+ .command("generate")
49
+ .description("Generate route file from Next.js app directory")
50
+ .option("-i, --input <path>", "Input directory to scan")
51
+ .option("-o, --output <path>", "Output file path")
52
+ .option("-w, --watch", "Watch for changes and regenerate")
53
+ .option("-c, --config <path>", "Path to config file")
54
+ .option("-p, --prefix <prefix>", "Base prefix for all routes")
55
+ .action(async (options) => {
56
+ try {
57
+ // Load base config from file
58
+ const baseConfig = await loadConfig(options.config);
59
+ // Merge with CLI options
60
+ const config = mergeConfig(baseConfig, {
61
+ input: options.input,
62
+ output: options.output,
63
+ watch: options.watch,
64
+ basePrefix: options.prefix,
65
+ });
66
+ // Generate initial routes
67
+ await generateRoutes(config);
68
+ // Start watch mode if requested
69
+ if (config.watch) {
70
+ startWatcher(config, async () => {
71
+ await generateRoutes(config);
72
+ });
73
+ }
74
+ }
75
+ catch (error) {
76
+ console.error(error);
77
+ process.exit(1);
78
+ }
79
+ });
80
+ /**
81
+ * Init command - create example config file
82
+ */
83
+ program
84
+ .command("init")
85
+ .description("Create a routes.config.ts file with defaults")
86
+ .action(async () => {
87
+ const configContent = `import type { RouteConfig } from "${PACKAGE_NAME}";
88
+
89
+ const config: RouteConfig = {
90
+ input: "${DEFAULT_INPUT_DIR}",
91
+ output: "${DEFAULT_OUTPUT_FILE}",
92
+ watch: false,
93
+ basePrefix: "${DEFAULT_BASE_PREFIX}",
94
+ paramTypes: {
95
+ // Add custom parameter types here
96
+ // Example: userId: 'string', postId: 'number'
97
+ },
98
+ };
99
+
100
+ export default config;
101
+ `;
102
+ const configPath = join(process.cwd(), `${CONFIG_FILE_NAME}.ts`);
103
+ if (existsSync(configPath)) {
104
+ console.log(`āš ļø ${CONFIG_FILE_NAME}.ts already exists`);
105
+ return;
106
+ }
107
+ await writeFile(configPath, configContent, "utf-8");
108
+ console.log(`āœ… Created ${CONFIG_FILE_NAME}.ts`);
109
+ });
110
+ program.parse();
111
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Configuration loader using cosmiconfig
3
+ */
4
+ import { RouteConfig } from "./types";
5
+ /**
6
+ * Default configuration
7
+ */
8
+ export declare const defaultConfig: RouteConfig;
9
+ /**
10
+ * Load configuration from file or use defaults
11
+ */
12
+ export declare const loadConfig: (configPath?: string) => Promise<RouteConfig>;
13
+ /**
14
+ * Merge config with CLI options
15
+ */
16
+ export declare const mergeConfig: (baseConfig: RouteConfig, options: Partial<RouteConfig>) => RouteConfig;
17
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAKtC;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,WAM3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,WAAW,CAgBzE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,YAAY,WAAW,EAAE,SAAS,OAAO,CAAC,WAAW,CAAC,KAAG,WAKpF,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Configuration loader using cosmiconfig
3
+ */
4
+ import { cosmiconfig } from "cosmiconfig";
5
+ import { DEFAULT_INPUT_DIR, DEFAULT_OUTPUT_FILE, DEFAULT_BASE_PREFIX, CONFIG_MODULE_NAME } from "./constants";
6
+ const explorer = cosmiconfig(CONFIG_MODULE_NAME);
7
+ /**
8
+ * Default configuration
9
+ */
10
+ export const defaultConfig = {
11
+ input: DEFAULT_INPUT_DIR,
12
+ output: DEFAULT_OUTPUT_FILE,
13
+ watch: false,
14
+ basePrefix: DEFAULT_BASE_PREFIX,
15
+ paramTypes: {},
16
+ };
17
+ /**
18
+ * Load configuration from file or use defaults
19
+ */
20
+ export const loadConfig = async (configPath) => {
21
+ try {
22
+ const result = configPath ? await explorer.load(configPath) : await explorer.search();
23
+ if (result && result.config) {
24
+ return {
25
+ ...defaultConfig,
26
+ ...result.config,
27
+ };
28
+ }
29
+ }
30
+ catch (error) {
31
+ // Config file not found or invalid, use defaults
32
+ console.warn("No config file found, using defaults");
33
+ }
34
+ return defaultConfig;
35
+ };
36
+ /**
37
+ * Merge config with CLI options
38
+ */
39
+ export const mergeConfig = (baseConfig, options) => {
40
+ return {
41
+ ...baseConfig,
42
+ ...Object.fromEntries(Object.entries(options).filter(([_, v]) => v !== undefined)),
43
+ };
44
+ };
45
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE9G,MAAM,QAAQ,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAAC;AAEjD;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,KAAK,EAAE,iBAAiB;IACxB,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE,KAAK;IACZ,UAAU,EAAE,mBAAmB;IAC/B,UAAU,EAAE,EAAE;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,UAAmB,EAAwB,EAAE;IAC5E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;QAEtF,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAC5B,OAAO;gBACL,GAAG,aAAa;gBAChB,GAAG,MAAM,CAAC,MAAM;aACjB,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iDAAiD;QACjD,OAAO,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IACvD,CAAC;IAED,OAAO,aAAa,CAAC;AACvB,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,UAAuB,EAAE,OAA6B,EAAe,EAAE;IACjG,OAAO;QACL,GAAG,UAAU;QACb,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;KACnF,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Constants used throughout the package
3
+ */
4
+ export declare const PACKAGE_NAME = "next-typed-paths";
5
+ export declare const PACKAGE_VERSION: any;
6
+ export declare const CLI_NAME = "next-typed-paths";
7
+ export declare const CONFIG_MODULE_NAME = "routes";
8
+ export declare const CONFIG_FILE_NAME = "routes.config";
9
+ export declare const DEFAULT_INPUT_DIR = "./app/api";
10
+ export declare const DEFAULT_OUTPUT_FILE = "./generated/routes.ts";
11
+ export declare const DEFAULT_BASE_PREFIX = "/api";
12
+ export declare const WATCH_DEBOUNCE_MS = 300;
13
+ export declare const ROUTE_FILE_EXTENSIONS: string[];
14
+ export declare const ROUTE_FILE_NAME = "route";
15
+ export declare const PAGE_FILE_NAME = "page";
16
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,eAAO,MAAM,YAAY,qBAAqB,CAAC;AAC/C,eAAO,MAAM,eAAe,KAAiB,CAAC;AAC9C,eAAO,MAAM,QAAQ,qBAAe,CAAC;AACrC,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAEhD,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAC3D,eAAO,MAAM,mBAAmB,SAAS,CAAC;AAE1C,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,qBAAqB,UAAiC,CAAC;AACpE,eAAO,MAAM,eAAe,UAAU,CAAC;AACvC,eAAO,MAAM,cAAc,SAAS,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Constants used throughout the package
3
+ */
4
+ import { dirname, join } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { readFileSync } from "fs";
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
9
+ const packageVersion = packageJson.version;
10
+ export const PACKAGE_NAME = "next-typed-paths";
11
+ export const PACKAGE_VERSION = packageVersion;
12
+ export const CLI_NAME = PACKAGE_NAME;
13
+ export const CONFIG_MODULE_NAME = "routes";
14
+ export const CONFIG_FILE_NAME = "routes.config";
15
+ export const DEFAULT_INPUT_DIR = "./app/api";
16
+ export const DEFAULT_OUTPUT_FILE = "./generated/routes.ts";
17
+ export const DEFAULT_BASE_PREFIX = "/api";
18
+ export const WATCH_DEBOUNCE_MS = 300;
19
+ export const ROUTE_FILE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
20
+ export const ROUTE_FILE_NAME = "route";
21
+ export const PAGE_FILE_NAME = "page";
22
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAElC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAC1F,MAAM,cAAc,GAAG,WAAW,CAAC,OAAO,CAAC;AAE3C,MAAM,CAAC,MAAM,YAAY,GAAG,kBAAkB,CAAC;AAC/C,MAAM,CAAC,MAAM,eAAe,GAAG,cAAc,CAAC;AAC9C,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AACrC,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAQ,CAAC;AAC3C,MAAM,CAAC,MAAM,gBAAgB,GAAG,eAAe,CAAC;AAEhD,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAC7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,uBAAuB,CAAC;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAE1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AACrC,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC;AACvC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC"}
package/dist/file.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Make sure a directory exists, creating it if necessary
3
+ *
4
+ * @param dirPath - The path of the directory to ensure exists
5
+ */
6
+ export declare const mkdirIfNotExists: (dirPath: string) => Promise<void>;
7
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,IAAI,CAIpE,CAAC"}
package/dist/file.js ADDED
@@ -0,0 +1,13 @@
1
+ import { mkdir } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ /**
4
+ * Make sure a directory exists, creating it if necessary
5
+ *
6
+ * @param dirPath - The path of the directory to ensure exists
7
+ */
8
+ export const mkdirIfNotExists = async (dirPath) => {
9
+ if (!existsSync(dirPath)) {
10
+ await mkdir(dirPath, { recursive: true });
11
+ }
12
+ };
13
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAEhC;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;IACvE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Code generator for route files
3
+ */
4
+ import { RouteNode, RouteConfig } from "./types";
5
+ /**
6
+ * Generate complete route file content
7
+ */
8
+ export declare const generateRouteFile: (structure: RouteNode, config: RouteConfig) => string;
9
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAkDjD;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,WAAW,SAAS,EAAE,QAAQ,WAAW,KAAG,MA8B7E,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Code generator for route files
3
+ */
4
+ import { PACKAGE_NAME } from "./constants";
5
+ /**
6
+ * Generate TypeScript code for route structure constant
7
+ */
8
+ const generateStructureCode = (structure, indent = 2) => {
9
+ const indentStr = " ".repeat(indent);
10
+ const lines = [];
11
+ // Sort entries to put $param and $route first
12
+ const entries = Object.entries(structure);
13
+ const metadataEntries = entries.filter(([key]) => key.startsWith("$"));
14
+ const regularEntries = entries.filter(([key]) => !key.startsWith("$"));
15
+ const sortedEntries = [...metadataEntries, ...regularEntries];
16
+ for (const [key, value] of sortedEntries) {
17
+ // Check if key needs to be quoted (contains special characters)
18
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(key);
19
+ const quotedKey = needsQuotes ? `"${key}"` : key;
20
+ if (typeof value === "object" && value !== null) {
21
+ const childLines = generateStructureCode(value, indent + 2);
22
+ if (childLines) {
23
+ lines.push(`${indentStr}${quotedKey}: {`);
24
+ lines.push(childLines);
25
+ lines.push(`${indentStr}},`);
26
+ }
27
+ }
28
+ else if (typeof value === "boolean") {
29
+ lines.push(`${indentStr}${quotedKey}: ${value},`);
30
+ }
31
+ else if (typeof value === "string") {
32
+ lines.push(`${indentStr}${quotedKey}: "${value}",`);
33
+ }
34
+ }
35
+ return lines.join("\n");
36
+ };
37
+ /**
38
+ * Generate parameter type map from config
39
+ */
40
+ const generateParamTypeMap = (paramTypes) => {
41
+ if (!paramTypes || Object.keys(paramTypes).length === 0) {
42
+ return "{}";
43
+ }
44
+ const entries = Object.entries(paramTypes).map(([key, type]) => ` ${key}: ${type};`);
45
+ return `{\n${entries.join("\n")}\n}`;
46
+ };
47
+ /**
48
+ * Generate complete route file content
49
+ */
50
+ export const generateRouteFile = (structure, config) => {
51
+ const structureCode = generateStructureCode(structure);
52
+ const paramTypeMap = generateParamTypeMap(config.paramTypes);
53
+ const basePrefix = config.basePrefix || "";
54
+ const customImports = config.imports?.join("\n") || "";
55
+ return `/**
56
+ * Auto-generated Next.js route builder
57
+ * Generated from: ${config.input}
58
+ *
59
+ * DO NOT EDIT THIS FILE MANUALLY - it will be regenerated
60
+ */
61
+
62
+ import { createRouteBuilder } from "${PACKAGE_NAME}/runtime";
63
+ import type { RouteBuilderObject, GetParamType, HasChildren, RouteBuilder } from "${PACKAGE_NAME}/types";
64
+ ${customImports ? "\n" + customImports + "\n" : ""}
65
+ // Route structure definition
66
+ const ROUTE_STRUCTURE = {
67
+ ${structureCode}
68
+ } as const;
69
+
70
+ // Parameter type mappings
71
+ type ParamTypeMap = ${paramTypeMap};
72
+
73
+ // Type-safe route builder with parameter types
74
+ export type Routes = RouteBuilderObject<typeof ROUTE_STRUCTURE, ParamTypeMap>;
75
+
76
+ // Route builder instance
77
+ export const routes = createRouteBuilder(ROUTE_STRUCTURE, [], "${basePrefix}") as Routes;
78
+ `;
79
+ };
80
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Main entry point for the package
3
+ */
4
+ export { defaultConfig, loadConfig, mergeConfig } from "./config";
5
+ export * from "./constants";
6
+ export { generateRouteFile } from "./generator";
7
+ export { buildRoutePath, createRouteBuilder } from "./runtime";
8
+ export { generateRouteStructure, scanDirectory } from "./scanner";
9
+ export type { GetParamType, HasChildren, RouteBuilder, RouteBuilderObject, RouteConfig, RouteNode } from "./types";
10
+ export { startWatcher } from "./watcher";
11
+ export type { RegenerateCallback } from "./watcher";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAClE,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAClE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACnH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Main entry point for the package
3
+ */
4
+ export { defaultConfig, loadConfig, mergeConfig } from "./config";
5
+ export * from "./constants";
6
+ export { generateRouteFile } from "./generator";
7
+ export { buildRoutePath, createRouteBuilder } from "./runtime";
8
+ export { generateRouteStructure, scanDirectory } from "./scanner";
9
+ export { startWatcher } from "./watcher";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAClE,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Core route builder runtime
3
+ */
4
+ /**
5
+ * Build a typed API route path from segments
6
+ */
7
+ export declare const buildRoutePath: (segments: (string | number)[], basePrefix?: string) => string;
8
+ /**
9
+ * Recursively build route builder functions from route structure
10
+ */
11
+ export declare const createRouteBuilder: <T extends Record<string, any>>(structure: T, basePath?: (string | number)[], basePrefix?: string) => any;
12
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAAE,aAAY,MAAW,KAAG,MAGvF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9D,WAAW,CAAC,EACZ,WAAU,CAAC,MAAM,GAAG,MAAM,CAAC,EAAO,EAClC,aAAY,MAAW,KACtB,GAuDF,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Core route builder runtime
3
+ */
4
+ /**
5
+ * Build a typed API route path from segments
6
+ */
7
+ export const buildRoutePath = (segments, basePrefix = "") => {
8
+ const path = segments.map((s) => String(s)).join("/");
9
+ return basePrefix ? `${basePrefix}/${path}` : `/${path}`;
10
+ };
11
+ /**
12
+ * Recursively build route builder functions from route structure
13
+ */
14
+ export const createRouteBuilder = (structure, basePath = [], basePrefix = "") => {
15
+ const builder = {};
16
+ for (const [key, value] of Object.entries(structure)) {
17
+ // Skip metadata keys, but not route segment keys like $documentId
18
+ if (key === "$param" || key === "$route")
19
+ continue;
20
+ const currentPath = [...basePath, key];
21
+ if (typeof value === "object") {
22
+ const hasRoute = value.$route === true;
23
+ const hasParam = "$param" in value;
24
+ // Check if there are children (non-metadata keys)
25
+ const childKeys = Object.keys(value).filter((k) => k !== "$param" && k !== "$route");
26
+ const hasChildren = childKeys.length > 0;
27
+ if (hasParam) {
28
+ // This level has a parameter
29
+ builder[key] = (param) => {
30
+ const paramPath = [...currentPath.slice(0, -1), param];
31
+ if (hasChildren) {
32
+ // Has children, build them with the parameter in the path
33
+ const children = createRouteBuilder(value, paramPath, basePrefix);
34
+ // If this level is also a route, add a $ method
35
+ if (hasRoute) {
36
+ Object.assign(children, { $: () => buildRoutePath(paramPath, basePrefix) });
37
+ }
38
+ return children;
39
+ }
40
+ else if (hasRoute) {
41
+ // Leaf route with parameter
42
+ return buildRoutePath(paramPath, basePrefix);
43
+ }
44
+ return buildRoutePath(paramPath, basePrefix);
45
+ };
46
+ }
47
+ else if (hasRoute && !hasChildren) {
48
+ // Leaf route with no children or params
49
+ builder[key] = () => buildRoutePath(currentPath, basePrefix);
50
+ }
51
+ else {
52
+ // Has children, recurse
53
+ const children = createRouteBuilder(value, currentPath, basePrefix);
54
+ if (hasRoute) {
55
+ // Also a route itself
56
+ Object.assign(children, { $: () => buildRoutePath(currentPath, basePrefix) });
57
+ }
58
+ builder[key] = children;
59
+ }
60
+ }
61
+ }
62
+ return builder;
63
+ };
64
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Scanner for Next.js app directory structure
3
+ */
4
+ import { RouteNode } from "./types";
5
+ /**
6
+ * Recursively scan a directory and build route structure
7
+ */
8
+ export declare const scanDirectory: (dirPath: string, basePath?: string) => Promise<RouteNode>;
9
+ /**
10
+ * Scan Next.js app directory and generate route structure
11
+ */
12
+ export declare const generateRouteStructure: (inputDir: string) => Promise<RouteNode>;
13
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAoCpC;;GAEG;AACH,eAAO,MAAM,aAAa,GAAU,SAAS,MAAM,EAAE,WAAU,MAAW,KAAG,OAAO,CAAC,SAAS,CAsC7F,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,SAAS,CAGhF,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Scanner for Next.js app directory structure
3
+ */
4
+ import { existsSync } from "fs";
5
+ import { readdir } from "fs/promises";
6
+ import { join, resolve } from "path";
7
+ import { ROUTE_FILE_EXTENSIONS, ROUTE_FILE_NAME, PAGE_FILE_NAME } from "./constants";
8
+ /**
9
+ * Check if a directory contains a route.ts or page.ts file
10
+ */
11
+ const hasRouteFile = async (dirPath) => {
12
+ const fileNames = [ROUTE_FILE_NAME, PAGE_FILE_NAME];
13
+ const checks = await Promise.all(fileNames.flatMap((fileName) => ROUTE_FILE_EXTENSIONS.map(async (ext) => {
14
+ const filePath = join(dirPath, `${fileName}${ext}`);
15
+ return existsSync(filePath);
16
+ })));
17
+ return checks.some((exists) => exists);
18
+ };
19
+ /**
20
+ * Extract parameter name from Next.js dynamic segment [paramName]
21
+ */
22
+ const extractParamName = (segment) => {
23
+ const match = segment.match(/^\[(.+)\]$/);
24
+ return match ? (match[1] ?? null) : null;
25
+ };
26
+ /**
27
+ * Convert parameter name from Next.js format to camelCase with $ prefix
28
+ * e.g., [userId] -> $userId, [user-id] -> $userId
29
+ */
30
+ const formatParamName = (paramName) => {
31
+ // Convert kebab-case to camelCase
32
+ const camelCase = paramName.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
33
+ return `$${camelCase}`;
34
+ };
35
+ /**
36
+ * Recursively scan a directory and build route structure
37
+ */
38
+ export const scanDirectory = async (dirPath, basePath = "") => {
39
+ const node = {};
40
+ if (!existsSync(dirPath)) {
41
+ throw new Error(`Directory does not exist: ${dirPath}`);
42
+ }
43
+ // Check if this directory itself has a route
44
+ if (await hasRouteFile(dirPath)) {
45
+ node.$route = true;
46
+ }
47
+ // Read directory contents
48
+ const entries = await readdir(dirPath, { withFileTypes: true });
49
+ for (const entry of entries) {
50
+ // Skip non-directories and special directories
51
+ if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules") {
52
+ continue;
53
+ }
54
+ const entryPath = join(dirPath, entry.name);
55
+ const paramName = extractParamName(entry.name);
56
+ if (paramName) {
57
+ // Dynamic segment [paramName]
58
+ const formattedName = formatParamName(paramName);
59
+ const childNode = await scanDirectory(entryPath, `${basePath}/${entry.name}`);
60
+ childNode.$param = paramName;
61
+ node[formattedName] = childNode;
62
+ }
63
+ else {
64
+ // Static segment
65
+ const childNode = await scanDirectory(entryPath, `${basePath}/${entry.name}`);
66
+ node[entry.name] = childNode;
67
+ }
68
+ }
69
+ return node;
70
+ };
71
+ /**
72
+ * Scan Next.js app directory and generate route structure
73
+ */
74
+ export const generateRouteStructure = async (inputDir) => {
75
+ const resolvedPath = resolve(inputDir);
76
+ return scanDirectory(resolvedPath);
77
+ };
78
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Type definitions for route builder
3
+ */
4
+ export type HasChildren<T> = keyof Omit<T, "$param" | "$route"> extends never ? false : true;
5
+ export type GetParamType<P extends string, TMap = {}> = P extends keyof TMap ? TMap[P] : string;
6
+ export type RouteBuilder<T, TMap = {}> = T extends {
7
+ $param: infer P extends string;
8
+ } ? HasChildren<T> extends true ? (param: GetParamType<P, TMap>) => RouteBuilderObject<Omit<T, "$param">, TMap> & (T extends {
9
+ $route: true;
10
+ } ? {
11
+ $: () => string;
12
+ } : {}) : T extends {
13
+ $route: true;
14
+ } ? (param: GetParamType<P, TMap>) => string : (param: GetParamType<P, TMap>) => RouteBuilderObject<Omit<T, "$param">, TMap> : T extends {
15
+ $route: true;
16
+ } ? () => string : T extends object ? RouteBuilderObject<T, TMap> : never;
17
+ export type RouteBuilderObject<T, TMap = {}> = {
18
+ [K in keyof T as K extends "$param" | "$route" ? never : K]: RouteBuilder<T[K], TMap>;
19
+ };
20
+ /**
21
+ * Route structure node
22
+ */
23
+ export interface RouteNode {
24
+ $param?: string;
25
+ $route?: boolean;
26
+ [key: string]: any;
27
+ }
28
+ /**
29
+ * Configuration options for route generation
30
+ */
31
+ export interface RouteConfig {
32
+ /** Input directory to scan (e.g., "./app/api") */
33
+ input: string;
34
+ /** Output file path for generated routes */
35
+ output: string;
36
+ /** Watch for changes and regenerate */
37
+ watch?: boolean;
38
+ /** Base prefix for all routes (e.g., "/api") */
39
+ basePrefix?: string;
40
+ /**
41
+ * Parameter type mappings
42
+ * Allows for defining custom types for route parameters,
43
+ * which will be used in the generated route builder for
44
+ * type safety.
45
+ *
46
+ * Any parameter not defined here will default to string type.
47
+ */
48
+ paramTypes?: Record<string, string>;
49
+ /** Additional imports to include in generated file */
50
+ imports?: string[];
51
+ }
52
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC,SAAS,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC;AAG7F,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,GAAG,EAAE,IAAI,CAAC,SAAS,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAGhG,MAAM,MAAM,YAAY,CAAC,CAAC,EAAE,IAAI,GAAG,EAAE,IAAI,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC,SAAS,MAAM,CAAA;CAAE,GACjF,WAAW,CAAC,CAAC,CAAC,SAAS,IAAI,GACzB,CACE,KAAK,EAAE,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,KACzB,kBAAkB,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,CAAC,EAAE,MAAM,MAAM,CAAA;CAAE,GAAG,EAAE,CAAC,GAC1G,CAAC,SAAS;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GACxB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,MAAM,GACxC,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,kBAAkB,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,GACjF,CAAC,SAAS;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GACxB,MAAM,MAAM,GACZ,CAAC,SAAS,MAAM,GACd,kBAAkB,CAAC,CAAC,EAAE,IAAI,CAAC,GAC3B,KAAK,CAAC;AAEd,MAAM,MAAM,kBAAkB,CAAC,CAAC,EAAE,IAAI,GAAG,EAAE,IAAI;KAC5C,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;CACtF,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Type definitions for route builder
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * File watcher for live route regeneration
3
+ */
4
+ import { RouteConfig } from "./types";
5
+ export type RegenerateCallback = () => Promise<void>;
6
+ /**
7
+ * Start watching the input directory for changes
8
+ */
9
+ export declare const startWatcher: (config: RouteConfig, onRegenerate: RegenerateCallback) => void;
10
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,MAAM,MAAM,kBAAkB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAErD;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,WAAW,EAAE,cAAc,kBAAkB,KAAG,IA6DpF,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * File watcher for live route regeneration
3
+ */
4
+ import { watch } from "chokidar";
5
+ import { basename, relative, resolve } from "path";
6
+ import { ROUTE_FILE_NAME, WATCH_DEBOUNCE_MS } from "./constants";
7
+ /**
8
+ * Start watching the input directory for changes
9
+ */
10
+ export const startWatcher = (config, onRegenerate) => {
11
+ const inputPath = resolve(config.input);
12
+ console.log(`šŸ‘€ Watching for changes in: ${inputPath}`);
13
+ const watcher = watch(inputPath, {
14
+ ignored: /(^|[\/\\])\../, // ignore dotfiles
15
+ persistent: true,
16
+ ignoreInitial: true,
17
+ });
18
+ let regenerateTimer = null;
19
+ const scheduleRegenerate = () => {
20
+ // Debounce rapid changes
21
+ if (regenerateTimer) {
22
+ clearTimeout(regenerateTimer);
23
+ }
24
+ regenerateTimer = setTimeout(async () => {
25
+ console.log("\nšŸ”„ Changes detected, regenerating routes...");
26
+ try {
27
+ await onRegenerate();
28
+ console.log("āœ… Routes regenerated successfully");
29
+ }
30
+ catch (error) {
31
+ console.error("āŒ Failed to regenerate routes:", error);
32
+ }
33
+ }, WATCH_DEBOUNCE_MS);
34
+ };
35
+ watcher
36
+ .on("add", (filePath) => {
37
+ if (basename(filePath).startsWith(`${ROUTE_FILE_NAME}.`)) {
38
+ console.log(`šŸ“ New route file: ${relative(inputPath, filePath)}`);
39
+ scheduleRegenerate();
40
+ }
41
+ })
42
+ .on("unlink", (filePath) => {
43
+ if (basename(filePath).startsWith(`${ROUTE_FILE_NAME}.`)) {
44
+ console.log(`šŸ—‘ļø Removed route file: ${relative(inputPath, filePath)}`);
45
+ scheduleRegenerate();
46
+ }
47
+ })
48
+ .on("addDir", (dirPath) => {
49
+ console.log(`šŸ“ New directory: ${relative(inputPath, dirPath)}`);
50
+ scheduleRegenerate();
51
+ })
52
+ .on("unlinkDir", (dirPath) => {
53
+ console.log(`šŸ—‘ļø Removed directory: ${relative(inputPath, dirPath)}`);
54
+ scheduleRegenerate();
55
+ })
56
+ .on("error", (error) => {
57
+ console.error("āŒ Watcher error:", error);
58
+ });
59
+ // Handle graceful shutdown
60
+ process.on("SIGINT", () => {
61
+ console.log("\nšŸ‘‹ Stopping watcher...");
62
+ watcher.close();
63
+ process.exit(0);
64
+ });
65
+ };
66
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "next-typed-paths",
3
+ "version": "0.1.0",
4
+ "description": "A realtime typesafe Next.Js route generation tool",
5
+ "keywords": [
6
+ "nextjs",
7
+ "routes",
8
+ "typescript",
9
+ "type-safe",
10
+ "app-router"
11
+ ],
12
+ "homepage": "https://github.com/hugs7/next-typed-paths#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/hugs7/next-typed-paths/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/hugs7/next-typed-paths.git"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Hugo Burton",
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "bin": {
26
+ "next-typed-paths": "./dist/cli.js"
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "prepublishOnly": "npm run build",
42
+ "test": "vitest"
43
+ },
44
+ "dependencies": {
45
+ "chokidar": "^5.0.0",
46
+ "commander": "^14.0.3",
47
+ "cosmiconfig": "^9.0.0",
48
+ "zod": "^4.3.6"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.2.1",
52
+ "typescript": "^5.9.3",
53
+ "vitest": "^4.0.18"
54
+ },
55
+ "peerDependencies": {
56
+ "typescript": ">=5.0.0"
57
+ }
58
+ }