@yak-io/nextjs 0.7.1 → 0.8.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/dist/cli/generate-manifest.cjs +266 -0
- package/dist/cli/generate-manifest.cjs.map +7 -0
- package/dist/cli/generate-manifest.js +169 -259
- package/dist/cli/generate-manifest.js.map +7 -0
- package/dist/index.client.cjs +86 -0
- package/dist/index.client.cjs.map +7 -0
- package/dist/index.client.js +65 -2
- package/dist/index.client.js.map +7 -0
- package/dist/index.server.cjs +364 -0
- package/dist/index.server.cjs.map +7 -0
- package/dist/index.server.js +335 -2
- package/dist/index.server.js.map +7 -0
- package/package.json +7 -5
- package/dist/client/YakProvider.js +0 -57
- package/dist/client/useYak.js +0 -4
- package/dist/server/createNextYakConfigHandler.js +0 -1
- package/dist/server/createNextYakHandler.js +0 -161
- package/dist/server/createNextYakToolsHandler.js +0 -1
- package/dist/server/index.js +0 -3
- package/dist/server/route-manifest-adapter.js +0 -75
- package/dist/server/scan-routes.js +0 -208
- package/dist/types/config.js +0 -1
- package/dist/types/messaging.js +0 -1
- package/dist/types/routes.js +0 -1
- package/dist/types/tools.js +0 -1
|
@@ -1,245 +1,166 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* This CLI tool scans a Next.js app directory and generates a manifest
|
|
6
|
-
* of all available page routes that can be injected into the chat context.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* yak-nextjs generate-manifest [options]
|
|
10
|
-
*
|
|
11
|
-
* Options:
|
|
12
|
-
* --app-dir <path> Path to Next.js app directory (default: ./src/app)
|
|
13
|
-
* --pages-dir <path> Path to Next.js pages directory (optional, scanned in addition to app-dir)
|
|
14
|
-
* --output <path> Output file path (default: ./src/yak-routes-manifest.json)
|
|
15
|
-
*/
|
|
2
|
+
|
|
3
|
+
// src/cli/generate-manifest.ts
|
|
16
4
|
import * as fs from "node:fs";
|
|
17
5
|
import * as path from "node:path";
|
|
18
|
-
/**
|
|
19
|
-
* Extract static metadata (title and description) from a Next.js page file.
|
|
20
|
-
* Parses `export const metadata = { title: "...", description: "..." }` patterns.
|
|
21
|
-
* Does not support dynamic generateMetadata functions.
|
|
22
|
-
*/
|
|
23
6
|
function extractMetadata(filePath) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const metadataBlock = metadataMatch[1];
|
|
32
|
-
const result = {};
|
|
33
|
-
// Extract title - handle both single and double quotes
|
|
34
|
-
const titleMatch = metadataBlock?.match(/title\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
35
|
-
if (titleMatch?.[1]) {
|
|
36
|
-
result.title = titleMatch[1];
|
|
37
|
-
}
|
|
38
|
-
// Extract description - handle both single and double quotes
|
|
39
|
-
const descMatch = metadataBlock?.match(/description\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
40
|
-
if (descMatch?.[1]) {
|
|
41
|
-
result.description = descMatch[1];
|
|
42
|
-
}
|
|
43
|
-
return result;
|
|
7
|
+
try {
|
|
8
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
9
|
+
const metadataMatch = content.match(
|
|
10
|
+
/export\s+const\s+metadata\s*(?::\s*Metadata\s*)?=\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/s
|
|
11
|
+
);
|
|
12
|
+
if (!metadataMatch) {
|
|
13
|
+
return {};
|
|
44
14
|
}
|
|
45
|
-
|
|
46
|
-
|
|
15
|
+
const metadataBlock = metadataMatch[1];
|
|
16
|
+
const result = {};
|
|
17
|
+
const titleMatch = metadataBlock?.match(/title\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
18
|
+
if (titleMatch?.[1]) {
|
|
19
|
+
result.title = titleMatch[1];
|
|
47
20
|
}
|
|
21
|
+
const descMatch = metadataBlock?.match(/description\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
22
|
+
if (descMatch?.[1]) {
|
|
23
|
+
result.description = descMatch[1];
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
} catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
48
29
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Check if a path segment is a route group (organizational only, not part of URL)
|
|
51
|
-
*/
|
|
52
30
|
function isRouteGroup(segment) {
|
|
53
|
-
|
|
31
|
+
return segment.startsWith("(") && segment.endsWith(")");
|
|
54
32
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Check if a path segment is an optional catch-all (e.g., [[...slug]])
|
|
57
|
-
* These segments match the base path without any additional segments
|
|
58
|
-
*/
|
|
59
33
|
function isOptionalCatchAll(segment) {
|
|
60
|
-
|
|
34
|
+
return segment.startsWith("[[...") && segment.endsWith("]]");
|
|
61
35
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Check if a path segment is a required catch-all (e.g., [...slug])
|
|
64
|
-
* These segments require at least one additional path segment
|
|
65
|
-
*/
|
|
66
36
|
function isRequiredCatchAll(segment) {
|
|
67
|
-
|
|
37
|
+
return segment.startsWith("[...") && segment.endsWith("]") && !segment.startsWith("[[");
|
|
68
38
|
}
|
|
69
|
-
/**
|
|
70
|
-
* Check if a path segment is a dynamic segment (e.g., [id])
|
|
71
|
-
*/
|
|
72
39
|
function isDynamicSegment(segment) {
|
|
73
|
-
|
|
74
|
-
segment.endsWith("]") &&
|
|
75
|
-
!isOptionalCatchAll(segment) &&
|
|
76
|
-
!isRequiredCatchAll(segment));
|
|
40
|
+
return segment.startsWith("[") && segment.endsWith("]") && !isOptionalCatchAll(segment) && !isRequiredCatchAll(segment);
|
|
77
41
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Normalize a dynamic segment for display
|
|
80
|
-
* e.g., [id] → :id, [postId] → :postId
|
|
81
|
-
*/
|
|
82
42
|
function normalizeDynamicSegment(segment) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return `:${paramName}`;
|
|
43
|
+
const paramName = segment.slice(1, -1);
|
|
44
|
+
return `:${paramName}`;
|
|
86
45
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Normalize a route path for display (excludes route groups and handles dynamic segments)
|
|
89
|
-
*/
|
|
90
46
|
function normalizeRoutePath(segments) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
return seg;
|
|
105
|
-
});
|
|
106
|
-
if (urlSegments.length === 0)
|
|
107
|
-
return "/";
|
|
108
|
-
return `/${urlSegments.join("/")}`;
|
|
47
|
+
const urlSegments = segments.filter((seg) => !isRouteGroup(seg) && !isOptionalCatchAll(seg)).map((seg) => {
|
|
48
|
+
if (isDynamicSegment(seg)) {
|
|
49
|
+
return normalizeDynamicSegment(seg);
|
|
50
|
+
}
|
|
51
|
+
if (isRequiredCatchAll(seg)) {
|
|
52
|
+
const paramName = seg.slice(4, -1);
|
|
53
|
+
return `:${paramName}+`;
|
|
54
|
+
}
|
|
55
|
+
return seg;
|
|
56
|
+
});
|
|
57
|
+
if (urlSegments.length === 0) return "/";
|
|
58
|
+
return `/${urlSegments.join("/")}`;
|
|
109
59
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Check if file is a page file
|
|
112
|
-
*/
|
|
113
60
|
function isPageFile(filename) {
|
|
114
|
-
|
|
61
|
+
return filename === "page.tsx" || filename === "page.js";
|
|
115
62
|
}
|
|
116
|
-
/**
|
|
117
|
-
* Recursively scan a directory for Next.js page routes
|
|
118
|
-
*/
|
|
119
63
|
function scanDirectory(dirPath, segments = []) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
// Recursively scan subdirectories
|
|
131
|
-
routes.push(...scanDirectory(fullPath, [...segments, entry.name]));
|
|
132
|
-
}
|
|
133
|
-
else if (entry.isFile() && isPageFile(entry.name)) {
|
|
134
|
-
const metadata = extractMetadata(fullPath);
|
|
135
|
-
routes.push({
|
|
136
|
-
path: normalizeRoutePath(segments),
|
|
137
|
-
title: metadata.title,
|
|
138
|
-
description: metadata.description,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
64
|
+
const routes = [];
|
|
65
|
+
try {
|
|
66
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
if (entry.name.startsWith("_") || entry.name === "api") {
|
|
71
|
+
continue;
|
|
141
72
|
}
|
|
73
|
+
routes.push(...scanDirectory(fullPath, [...segments, entry.name]));
|
|
74
|
+
} else if (entry.isFile() && isPageFile(entry.name)) {
|
|
75
|
+
const metadata = extractMetadata(fullPath);
|
|
76
|
+
routes.push({
|
|
77
|
+
path: normalizeRoutePath(segments),
|
|
78
|
+
title: metadata.title,
|
|
79
|
+
description: metadata.description
|
|
80
|
+
});
|
|
81
|
+
}
|
|
142
82
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(`Error scanning directory ${dirPath}:`, error);
|
|
85
|
+
}
|
|
86
|
+
return routes;
|
|
147
87
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
"500",
|
|
158
|
-
"middleware",
|
|
159
|
-
"_middleware",
|
|
88
|
+
var VALID_PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".md", ".mdx"]);
|
|
89
|
+
var SPECIAL_PAGE_FILENAMES = /* @__PURE__ */ new Set([
|
|
90
|
+
"_app",
|
|
91
|
+
"_document",
|
|
92
|
+
"_error",
|
|
93
|
+
"404",
|
|
94
|
+
"500",
|
|
95
|
+
"middleware",
|
|
96
|
+
"_middleware"
|
|
160
97
|
]);
|
|
161
|
-
/**
|
|
162
|
-
* Recursively scan a pages directory for Next.js page routes
|
|
163
|
-
*/
|
|
164
98
|
function scanPagesDirectory(dirPath, segments = []) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
routes.push(...scanPagesDirectory(fullPath, [...segments, entry.name]));
|
|
176
|
-
}
|
|
177
|
-
else if (entry.isFile()) {
|
|
178
|
-
const extension = path.extname(entry.name);
|
|
179
|
-
if (!VALID_PAGE_EXTENSIONS.has(extension)) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
const baseName = entry.name.slice(0, -extension.length);
|
|
183
|
-
if (!baseName || SPECIAL_PAGE_FILENAMES.has(baseName) || baseName.startsWith("_")) {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
const routeSegments = buildPagesSegments(segments, baseName);
|
|
187
|
-
const metadata = extractMetadata(fullPath);
|
|
188
|
-
routes.push({
|
|
189
|
-
path: normalizeRoutePath(routeSegments),
|
|
190
|
-
title: metadata.title,
|
|
191
|
-
description: metadata.description,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
99
|
+
const routes = [];
|
|
100
|
+
try {
|
|
101
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
104
|
+
if (entry.isDirectory()) {
|
|
105
|
+
if (entry.name.startsWith("_") || entry.name === "api") {
|
|
106
|
+
continue;
|
|
194
107
|
}
|
|
108
|
+
routes.push(...scanPagesDirectory(fullPath, [...segments, entry.name]));
|
|
109
|
+
} else if (entry.isFile()) {
|
|
110
|
+
const extension = path.extname(entry.name);
|
|
111
|
+
if (!VALID_PAGE_EXTENSIONS.has(extension)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const baseName = entry.name.slice(0, -extension.length);
|
|
115
|
+
if (!baseName || SPECIAL_PAGE_FILENAMES.has(baseName) || baseName.startsWith("_")) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const routeSegments = buildPagesSegments(segments, baseName);
|
|
119
|
+
const metadata = extractMetadata(fullPath);
|
|
120
|
+
routes.push({
|
|
121
|
+
path: normalizeRoutePath(routeSegments),
|
|
122
|
+
title: metadata.title,
|
|
123
|
+
description: metadata.description
|
|
124
|
+
});
|
|
125
|
+
}
|
|
195
126
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(`Error scanning directory ${dirPath}:`, error);
|
|
129
|
+
}
|
|
130
|
+
return routes;
|
|
200
131
|
}
|
|
201
132
|
function buildPagesSegments(segments, baseName) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
133
|
+
const routeSegments = [...segments];
|
|
134
|
+
if (baseName !== "index") {
|
|
135
|
+
routeSegments.push(baseName);
|
|
136
|
+
}
|
|
137
|
+
return routeSegments;
|
|
207
138
|
}
|
|
208
|
-
/**
|
|
209
|
-
* Parse command-line arguments
|
|
210
|
-
*/
|
|
211
139
|
function parseArgs() {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
else if (arg === "--output" && args[i + 1]) {
|
|
232
|
-
output = args[i + 1];
|
|
233
|
-
i++;
|
|
234
|
-
}
|
|
140
|
+
const args = process.argv.slice(2);
|
|
141
|
+
let appDir = "./src/app";
|
|
142
|
+
let pagesDir;
|
|
143
|
+
let output = "./src/yak.routes.ts";
|
|
144
|
+
let help = false;
|
|
145
|
+
for (let i = 0; i < args.length; i++) {
|
|
146
|
+
const arg = args[i];
|
|
147
|
+
if (arg === "--help" || arg === "-h") {
|
|
148
|
+
help = true;
|
|
149
|
+
} else if (arg === "--app-dir" && args[i + 1]) {
|
|
150
|
+
appDir = args[i + 1];
|
|
151
|
+
i++;
|
|
152
|
+
} else if (arg === "--pages-dir" && args[i + 1]) {
|
|
153
|
+
pagesDir = args[i + 1];
|
|
154
|
+
i++;
|
|
155
|
+
} else if (arg === "--output" && args[i + 1]) {
|
|
156
|
+
output = args[i + 1];
|
|
157
|
+
i++;
|
|
235
158
|
}
|
|
236
|
-
|
|
159
|
+
}
|
|
160
|
+
return { appDir, pagesDir, output, help };
|
|
237
161
|
}
|
|
238
|
-
/**
|
|
239
|
-
* Display help message
|
|
240
|
-
*/
|
|
241
162
|
function showHelp() {
|
|
242
|
-
|
|
163
|
+
console.log(`
|
|
243
164
|
Yak Next.js Route Manifest Generator
|
|
244
165
|
|
|
245
166
|
Scans a Next.js app directory and generates a TypeScript module with route definitions.
|
|
@@ -259,16 +180,13 @@ Examples:
|
|
|
259
180
|
yak-nextjs generate-manifest --output ./src/generated/yak.routes.ts
|
|
260
181
|
`);
|
|
261
182
|
}
|
|
262
|
-
/**
|
|
263
|
-
* Generate TypeScript module content
|
|
264
|
-
*/
|
|
265
183
|
function generateTypeScriptModule(routes) {
|
|
266
|
-
|
|
267
|
-
|
|
184
|
+
const routesJson = JSON.stringify(routes, null, 2);
|
|
185
|
+
return `/**
|
|
268
186
|
* Auto-generated by yak-nextjs generate-manifest
|
|
269
187
|
* DO NOT EDIT - This file is regenerated at build time
|
|
270
188
|
*
|
|
271
|
-
* Generated: ${new Date().toISOString()}
|
|
189
|
+
* Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
272
190
|
*/
|
|
273
191
|
|
|
274
192
|
import type { RouteInfo } from "@yak-io/nextjs/server";
|
|
@@ -280,54 +198,46 @@ import type { RouteInfo } from "@yak-io/nextjs/server";
|
|
|
280
198
|
export const routes: RouteInfo[] = ${routesJson} as const;
|
|
281
199
|
`;
|
|
282
200
|
}
|
|
283
|
-
/**
|
|
284
|
-
* Main entry point
|
|
285
|
-
*/
|
|
286
201
|
function main() {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
routes.push(...scanPagesDirectory(pagesDirPath, []));
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
console.log(` Pages directory: ${pagesDir} (not found, skipping)`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
if (routes.length === 0) {
|
|
315
|
-
console.error("❌ Error: No routes found. Check your directory paths.");
|
|
316
|
-
process.exit(1);
|
|
317
|
-
}
|
|
318
|
-
// Deduplicate routes by path (app router takes precedence)
|
|
319
|
-
const uniqueRoutes = Array.from(new Map(routes.map((r) => [r.path, r])).values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
320
|
-
const outputPath = path.resolve(process.cwd(), output);
|
|
321
|
-
// Ensure output directory exists
|
|
322
|
-
const outputDir = path.dirname(outputPath);
|
|
323
|
-
if (!fs.existsSync(outputDir)) {
|
|
324
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
202
|
+
const { appDir, pagesDir, output, help } = parseArgs();
|
|
203
|
+
if (help) {
|
|
204
|
+
showHelp();
|
|
205
|
+
process.exit(0);
|
|
206
|
+
}
|
|
207
|
+
console.log("\u{1F50D} Scanning Next.js page routes...");
|
|
208
|
+
const routes = [];
|
|
209
|
+
const appDirPath = path.resolve(process.cwd(), appDir);
|
|
210
|
+
if (fs.existsSync(appDirPath)) {
|
|
211
|
+
console.log(` App directory: ${appDir}`);
|
|
212
|
+
routes.push(...scanDirectory(appDirPath, []));
|
|
213
|
+
} else {
|
|
214
|
+
console.log(` App directory: ${appDir} (not found, skipping)`);
|
|
215
|
+
}
|
|
216
|
+
if (pagesDir) {
|
|
217
|
+
const pagesDirPath = path.resolve(process.cwd(), pagesDir);
|
|
218
|
+
if (fs.existsSync(pagesDirPath)) {
|
|
219
|
+
console.log(` Pages directory: ${pagesDir}`);
|
|
220
|
+
routes.push(...scanPagesDirectory(pagesDirPath, []));
|
|
221
|
+
} else {
|
|
222
|
+
console.log(` Pages directory: ${pagesDir} (not found, skipping)`);
|
|
325
223
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
224
|
+
}
|
|
225
|
+
if (routes.length === 0) {
|
|
226
|
+
console.error("\u274C Error: No routes found. Check your directory paths.");
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const uniqueRoutes = Array.from(new Map(routes.map((r) => [r.path, r])).values()).sort(
|
|
230
|
+
(a, b) => a.path.localeCompare(b.path)
|
|
231
|
+
);
|
|
232
|
+
const outputPath = path.resolve(process.cwd(), output);
|
|
233
|
+
const outputDir = path.dirname(outputPath);
|
|
234
|
+
if (!fs.existsSync(outputDir)) {
|
|
235
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
236
|
+
}
|
|
237
|
+
const content = generateTypeScriptModule(uniqueRoutes);
|
|
238
|
+
fs.writeFileSync(outputPath, content, "utf-8");
|
|
239
|
+
console.log(`\u2705 Generated route manifest with ${uniqueRoutes.length} routes`);
|
|
240
|
+
console.log(` Output: ${outputPath}`);
|
|
331
241
|
}
|
|
332
|
-
// Run the CLI
|
|
333
242
|
main();
|
|
243
|
+
//# sourceMappingURL=generate-manifest.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/cli/generate-manifest.ts"],
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n\n/**\n * Yak Next.js Route Manifest Generator\n *\n * This CLI tool scans a Next.js app directory and generates a manifest\n * of all available page routes that can be injected into the chat context.\n *\n * Usage:\n * yak-nextjs generate-manifest [options]\n *\n * Options:\n * --app-dir <path> Path to Next.js app directory (default: ./src/app)\n * --pages-dir <path> Path to Next.js pages directory (optional, scanned in addition to app-dir)\n * --output <path> Output file path (default: ./src/yak-routes-manifest.json)\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { RouteInfo } from \"@yak-io/javascript/server\";\n\n/**\n * Extract static metadata (title and description) from a Next.js page file.\n * Parses `export const metadata = { title: \"...\", description: \"...\" }` patterns.\n * Does not support dynamic generateMetadata functions.\n */\nfunction extractMetadata(filePath: string): { title?: string; description?: string } {\n try {\n const content = fs.readFileSync(filePath, \"utf-8\");\n\n // Match `export const metadata` object\n const metadataMatch = content.match(\n /export\\s+const\\s+metadata\\s*(?::\\s*Metadata\\s*)?=\\s*\\{([^}]*(?:\\{[^}]*\\}[^}]*)*)\\}/s\n );\n\n if (!metadataMatch) {\n return {};\n }\n\n const metadataBlock = metadataMatch[1];\n const result: { title?: string; description?: string } = {};\n\n // Extract title - handle both single and double quotes\n const titleMatch = metadataBlock?.match(/title\\s*:\\s*[\"'`]([^\"'`]+)[\"'`]/);\n if (titleMatch?.[1]) {\n result.title = titleMatch[1];\n }\n\n // Extract description - handle both single and double quotes\n const descMatch = metadataBlock?.match(/description\\s*:\\s*[\"'`]([^\"'`]+)[\"'`]/);\n if (descMatch?.[1]) {\n result.description = descMatch[1];\n }\n\n return result;\n } catch {\n return {};\n }\n}\n\n/**\n * Check if a path segment is a route group (organizational only, not part of URL)\n */\nfunction isRouteGroup(segment: string): boolean {\n return segment.startsWith(\"(\") && segment.endsWith(\")\");\n}\n\n/**\n * Check if a path segment is an optional catch-all (e.g., [[...slug]])\n * These segments match the base path without any additional segments\n */\nfunction isOptionalCatchAll(segment: string): boolean {\n return segment.startsWith(\"[[...\") && segment.endsWith(\"]]\");\n}\n\n/**\n * Check if a path segment is a required catch-all (e.g., [...slug])\n * These segments require at least one additional path segment\n */\nfunction isRequiredCatchAll(segment: string): boolean {\n return segment.startsWith(\"[...\") && segment.endsWith(\"]\") && !segment.startsWith(\"[[\");\n}\n\n/**\n * Check if a path segment is a dynamic segment (e.g., [id])\n */\nfunction isDynamicSegment(segment: string): boolean {\n return (\n segment.startsWith(\"[\") &&\n segment.endsWith(\"]\") &&\n !isOptionalCatchAll(segment) &&\n !isRequiredCatchAll(segment)\n );\n}\n\n/**\n * Normalize a dynamic segment for display\n * e.g., [id] \u2192 :id, [postId] \u2192 :postId\n */\nfunction normalizeDynamicSegment(segment: string): string {\n // Extract the parameter name from [name] format\n const paramName = segment.slice(1, -1);\n return `:${paramName}`;\n}\n\n/**\n * Normalize a route path for display (excludes route groups and handles dynamic segments)\n */\nfunction normalizeRoutePath(segments: string[]): string {\n // Filter out route groups - they don't appear in URLs\n // Filter out optional catch-all segments - they match the base path\n const urlSegments = segments\n .filter((seg) => !isRouteGroup(seg) && !isOptionalCatchAll(seg))\n .map((seg) => {\n if (isDynamicSegment(seg)) {\n return normalizeDynamicSegment(seg);\n }\n if (isRequiredCatchAll(seg)) {\n // For required catch-all, extract the name: [...slug] \u2192 :slug+\n const paramName = seg.slice(4, -1);\n return `:${paramName}+`;\n }\n return seg;\n });\n if (urlSegments.length === 0) return \"/\";\n return `/${urlSegments.join(\"/\")}`;\n}\n\n/**\n * Check if file is a page file\n */\nfunction isPageFile(filename: string): boolean {\n return filename === \"page.tsx\" || filename === \"page.js\";\n}\n\n/**\n * Recursively scan a directory for Next.js page routes\n */\nfunction scanDirectory(dirPath: string, segments: string[] = []): RouteInfo[] {\n const routes: RouteInfo[] = [];\n\n try {\n const entries = fs.readdirSync(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry.name);\n\n if (entry.isDirectory()) {\n // Skip special Next.js directories and api routes\n if (entry.name.startsWith(\"_\") || entry.name === \"api\") {\n continue;\n }\n\n // Recursively scan subdirectories\n routes.push(...scanDirectory(fullPath, [...segments, entry.name]));\n } else if (entry.isFile() && isPageFile(entry.name)) {\n const metadata = extractMetadata(fullPath);\n\n routes.push({\n path: normalizeRoutePath(segments),\n title: metadata.title,\n description: metadata.description,\n });\n }\n }\n } catch (error) {\n console.error(`Error scanning directory ${dirPath}:`, error);\n }\n\n return routes;\n}\n\n/**\n * File discovery helpers for legacy `pages/` directories\n */\nconst VALID_PAGE_EXTENSIONS = new Set([\".js\", \".jsx\", \".ts\", \".tsx\", \".md\", \".mdx\"]);\nconst SPECIAL_PAGE_FILENAMES = new Set([\n \"_app\",\n \"_document\",\n \"_error\",\n \"404\",\n \"500\",\n \"middleware\",\n \"_middleware\",\n]);\n\n/**\n * Recursively scan a pages directory for Next.js page routes\n */\nfunction scanPagesDirectory(dirPath: string, segments: string[] = []): RouteInfo[] {\n const routes: RouteInfo[] = [];\n\n try {\n const entries = fs.readdirSync(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry.name);\n\n if (entry.isDirectory()) {\n // Skip special directories and api routes\n if (entry.name.startsWith(\"_\") || entry.name === \"api\") {\n continue;\n }\n\n routes.push(...scanPagesDirectory(fullPath, [...segments, entry.name]));\n } else if (entry.isFile()) {\n const extension = path.extname(entry.name);\n if (!VALID_PAGE_EXTENSIONS.has(extension)) {\n continue;\n }\n\n const baseName = entry.name.slice(0, -extension.length);\n if (!baseName || SPECIAL_PAGE_FILENAMES.has(baseName) || baseName.startsWith(\"_\")) {\n continue;\n }\n\n const routeSegments = buildPagesSegments(segments, baseName);\n const metadata = extractMetadata(fullPath);\n\n routes.push({\n path: normalizeRoutePath(routeSegments),\n title: metadata.title,\n description: metadata.description,\n });\n }\n }\n } catch (error) {\n console.error(`Error scanning directory ${dirPath}:`, error);\n }\n\n return routes;\n}\n\nfunction buildPagesSegments(segments: string[], baseName: string): string[] {\n const routeSegments = [...segments];\n if (baseName !== \"index\") {\n routeSegments.push(baseName);\n }\n return routeSegments;\n}\n\n/**\n * Parse command-line arguments\n */\nfunction parseArgs(): {\n appDir: string;\n pagesDir: string | undefined;\n output: string;\n help: boolean;\n} {\n const args = process.argv.slice(2);\n let appDir = \"./src/app\";\n let pagesDir: string | undefined;\n // Default to ./src/yak.routes.ts - TypeScript module that gets bundled\n let output = \"./src/yak.routes.ts\";\n let help = false;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === \"--help\" || arg === \"-h\") {\n help = true;\n } else if (arg === \"--app-dir\" && args[i + 1]) {\n appDir = args[i + 1] as string;\n i++;\n } else if (arg === \"--pages-dir\" && args[i + 1]) {\n pagesDir = args[i + 1] as string;\n i++;\n } else if (arg === \"--output\" && args[i + 1]) {\n output = args[i + 1] as string;\n i++;\n }\n }\n\n return { appDir, pagesDir, output, help };\n}\n\n/**\n * Display help message\n */\nfunction showHelp(): void {\n console.log(`\nYak Next.js Route Manifest Generator\n\nScans a Next.js app directory and generates a TypeScript module with route definitions.\n\nUsage:\n yak-nextjs generate-manifest [options]\n\nOptions:\n --app-dir <path> Path to Next.js app directory (default: ./src/app)\n --pages-dir <path> Path to Next.js pages directory (optional)\n --output <path> Output file path (default: ./src/yak.routes.ts)\n --help, -h Show this help message\n\nExamples:\n yak-nextjs generate-manifest\n yak-nextjs generate-manifest --app-dir ./app\n yak-nextjs generate-manifest --output ./src/generated/yak.routes.ts\n `);\n}\n\n/**\n * Generate TypeScript module content\n */\nfunction generateTypeScriptModule(routes: RouteInfo[]): string {\n const routesJson = JSON.stringify(routes, null, 2);\n\n return `/**\n * Auto-generated by yak-nextjs generate-manifest\n * DO NOT EDIT - This file is regenerated at build time\n * \n * Generated: ${new Date().toISOString()}\n */\n\nimport type { RouteInfo } from \"@yak-io/nextjs/server\";\n\n/**\n * All scanned routes from your Next.js application.\n * Use with createRouteManifestAdapter for filtering options.\n */\nexport const routes: RouteInfo[] = ${routesJson} as const;\n`;\n}\n\n/**\n * Main entry point\n */\nfunction main(): void {\n const { appDir, pagesDir, output, help } = parseArgs();\n\n if (help) {\n showHelp();\n process.exit(0);\n }\n\n console.log(\"\uD83D\uDD0D Scanning Next.js page routes...\");\n\n const routes: RouteInfo[] = [];\n\n // Scan app directory\n const appDirPath = path.resolve(process.cwd(), appDir);\n if (fs.existsSync(appDirPath)) {\n console.log(` App directory: ${appDir}`);\n routes.push(...scanDirectory(appDirPath, []));\n } else {\n console.log(` App directory: ${appDir} (not found, skipping)`);\n }\n\n // Scan pages directory if provided\n if (pagesDir) {\n const pagesDirPath = path.resolve(process.cwd(), pagesDir);\n if (fs.existsSync(pagesDirPath)) {\n console.log(` Pages directory: ${pagesDir}`);\n routes.push(...scanPagesDirectory(pagesDirPath, []));\n } else {\n console.log(` Pages directory: ${pagesDir} (not found, skipping)`);\n }\n }\n\n if (routes.length === 0) {\n console.error(\"\u274C Error: No routes found. Check your directory paths.\");\n process.exit(1);\n }\n\n // Deduplicate routes by path (app router takes precedence)\n const uniqueRoutes = Array.from(new Map(routes.map((r) => [r.path, r])).values()).sort((a, b) =>\n a.path.localeCompare(b.path)\n );\n\n const outputPath = path.resolve(process.cwd(), output);\n\n // Ensure output directory exists\n const outputDir = path.dirname(outputPath);\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true });\n }\n\n // Write TypeScript module\n const content = generateTypeScriptModule(uniqueRoutes);\n fs.writeFileSync(outputPath, content, \"utf-8\");\n\n console.log(`\u2705 Generated route manifest with ${uniqueRoutes.length} routes`);\n console.log(` Output: ${outputPath}`);\n}\n\n// Run the CLI\nmain();\n"],
|
|
5
|
+
"mappings": ";;;AAiBA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAQtB,SAAS,gBAAgB,UAA4D;AACnF,MAAI;AACF,UAAM,UAAa,gBAAa,UAAU,OAAO;AAGjD,UAAM,gBAAgB,QAAQ;AAAA,MAC5B;AAAA,IACF;AAEA,QAAI,CAAC,eAAe;AAClB,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,gBAAgB,cAAc,CAAC;AACrC,UAAM,SAAmD,CAAC;AAG1D,UAAM,aAAa,eAAe,MAAM,iCAAiC;AACzE,QAAI,aAAa,CAAC,GAAG;AACnB,aAAO,QAAQ,WAAW,CAAC;AAAA,IAC7B;AAGA,UAAM,YAAY,eAAe,MAAM,uCAAuC;AAC9E,QAAI,YAAY,CAAC,GAAG;AAClB,aAAO,cAAc,UAAU,CAAC;AAAA,IAClC;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAKA,SAAS,aAAa,SAA0B;AAC9C,SAAO,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG;AACxD;AAMA,SAAS,mBAAmB,SAA0B;AACpD,SAAO,QAAQ,WAAW,OAAO,KAAK,QAAQ,SAAS,IAAI;AAC7D;AAMA,SAAS,mBAAmB,SAA0B;AACpD,SAAO,QAAQ,WAAW,MAAM,KAAK,QAAQ,SAAS,GAAG,KAAK,CAAC,QAAQ,WAAW,IAAI;AACxF;AAKA,SAAS,iBAAiB,SAA0B;AAClD,SACE,QAAQ,WAAW,GAAG,KACtB,QAAQ,SAAS,GAAG,KACpB,CAAC,mBAAmB,OAAO,KAC3B,CAAC,mBAAmB,OAAO;AAE/B;AAMA,SAAS,wBAAwB,SAAyB;AAExD,QAAM,YAAY,QAAQ,MAAM,GAAG,EAAE;AACrC,SAAO,IAAI,SAAS;AACtB;AAKA,SAAS,mBAAmB,UAA4B;AAGtD,QAAM,cAAc,SACjB,OAAO,CAAC,QAAQ,CAAC,aAAa,GAAG,KAAK,CAAC,mBAAmB,GAAG,CAAC,EAC9D,IAAI,CAAC,QAAQ;AACZ,QAAI,iBAAiB,GAAG,GAAG;AACzB,aAAO,wBAAwB,GAAG;AAAA,IACpC;AACA,QAAI,mBAAmB,GAAG,GAAG;AAE3B,YAAM,YAAY,IAAI,MAAM,GAAG,EAAE;AACjC,aAAO,IAAI,SAAS;AAAA,IACtB;AACA,WAAO;AAAA,EACT,CAAC;AACH,MAAI,YAAY,WAAW,EAAG,QAAO;AACrC,SAAO,IAAI,YAAY,KAAK,GAAG,CAAC;AAClC;AAKA,SAAS,WAAW,UAA2B;AAC7C,SAAO,aAAa,cAAc,aAAa;AACjD;AAKA,SAAS,cAAc,SAAiB,WAAqB,CAAC,GAAgB;AAC5E,QAAM,SAAsB,CAAC;AAE7B,MAAI;AACF,UAAM,UAAa,eAAY,SAAS,EAAE,eAAe,KAAK,CAAC;AAE/D,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAgB,UAAK,SAAS,MAAM,IAAI;AAE9C,UAAI,MAAM,YAAY,GAAG;AAEvB,YAAI,MAAM,KAAK,WAAW,GAAG,KAAK,MAAM,SAAS,OAAO;AACtD;AAAA,QACF;AAGA,eAAO,KAAK,GAAG,cAAc,UAAU,CAAC,GAAG,UAAU,MAAM,IAAI,CAAC,CAAC;AAAA,MACnE,WAAW,MAAM,OAAO,KAAK,WAAW,MAAM,IAAI,GAAG;AACnD,cAAM,WAAW,gBAAgB,QAAQ;AAEzC,eAAO,KAAK;AAAA,UACV,MAAM,mBAAmB,QAAQ;AAAA,UACjC,OAAO,SAAS;AAAA,UAChB,aAAa,SAAS;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,4BAA4B,OAAO,KAAK,KAAK;AAAA,EAC7D;AAEA,SAAO;AACT;AAKA,IAAM,wBAAwB,oBAAI,IAAI,CAAC,OAAO,QAAQ,OAAO,QAAQ,OAAO,MAAM,CAAC;AACnF,IAAM,yBAAyB,oBAAI,IAAI;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKD,SAAS,mBAAmB,SAAiB,WAAqB,CAAC,GAAgB;AACjF,QAAM,SAAsB,CAAC;AAE7B,MAAI;AACF,UAAM,UAAa,eAAY,SAAS,EAAE,eAAe,KAAK,CAAC;AAE/D,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAgB,UAAK,SAAS,MAAM,IAAI;AAE9C,UAAI,MAAM,YAAY,GAAG;AAEvB,YAAI,MAAM,KAAK,WAAW,GAAG,KAAK,MAAM,SAAS,OAAO;AACtD;AAAA,QACF;AAEA,eAAO,KAAK,GAAG,mBAAmB,UAAU,CAAC,GAAG,UAAU,MAAM,IAAI,CAAC,CAAC;AAAA,MACxE,WAAW,MAAM,OAAO,GAAG;AACzB,cAAM,YAAiB,aAAQ,MAAM,IAAI;AACzC,YAAI,CAAC,sBAAsB,IAAI,SAAS,GAAG;AACzC;AAAA,QACF;AAEA,cAAM,WAAW,MAAM,KAAK,MAAM,GAAG,CAAC,UAAU,MAAM;AACtD,YAAI,CAAC,YAAY,uBAAuB,IAAI,QAAQ,KAAK,SAAS,WAAW,GAAG,GAAG;AACjF;AAAA,QACF;AAEA,cAAM,gBAAgB,mBAAmB,UAAU,QAAQ;AAC3D,cAAM,WAAW,gBAAgB,QAAQ;AAEzC,eAAO,KAAK;AAAA,UACV,MAAM,mBAAmB,aAAa;AAAA,UACtC,OAAO,SAAS;AAAA,UAChB,aAAa,SAAS;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,4BAA4B,OAAO,KAAK,KAAK;AAAA,EAC7D;AAEA,SAAO;AACT;AAEA,SAAS,mBAAmB,UAAoB,UAA4B;AAC1E,QAAM,gBAAgB,CAAC,GAAG,QAAQ;AAClC,MAAI,aAAa,SAAS;AACxB,kBAAc,KAAK,QAAQ;AAAA,EAC7B;AACA,SAAO;AACT;AAKA,SAAS,YAKP;AACA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,MAAI,SAAS;AACb,MAAI;AAEJ,MAAI,SAAS;AACb,MAAI,OAAO;AAEX,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAElB,QAAI,QAAQ,YAAY,QAAQ,MAAM;AACpC,aAAO;AAAA,IACT,WAAW,QAAQ,eAAe,KAAK,IAAI,CAAC,GAAG;AAC7C,eAAS,KAAK,IAAI,CAAC;AACnB;AAAA,IACF,WAAW,QAAQ,iBAAiB,KAAK,IAAI,CAAC,GAAG;AAC/C,iBAAW,KAAK,IAAI,CAAC;AACrB;AAAA,IACF,WAAW,QAAQ,cAAc,KAAK,IAAI,CAAC,GAAG;AAC5C,eAAS,KAAK,IAAI,CAAC;AACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,UAAU,QAAQ,KAAK;AAC1C;AAKA,SAAS,WAAiB;AACxB,UAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAkBX;AACH;AAKA,SAAS,yBAAyB,QAA6B;AAC7D,QAAM,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC;AAEjD,SAAO;AAAA;AAAA;AAAA;AAAA,iBAIO,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCASH,UAAU;AAAA;AAE/C;AAKA,SAAS,OAAa;AACpB,QAAM,EAAE,QAAQ,UAAU,QAAQ,KAAK,IAAI,UAAU;AAErD,MAAI,MAAM;AACR,aAAS;AACT,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,2CAAoC;AAEhD,QAAM,SAAsB,CAAC;AAG7B,QAAM,aAAkB,aAAQ,QAAQ,IAAI,GAAG,MAAM;AACrD,MAAO,cAAW,UAAU,GAAG;AAC7B,YAAQ,IAAI,qBAAqB,MAAM,EAAE;AACzC,WAAO,KAAK,GAAG,cAAc,YAAY,CAAC,CAAC,CAAC;AAAA,EAC9C,OAAO;AACL,YAAQ,IAAI,qBAAqB,MAAM,wBAAwB;AAAA,EACjE;AAGA,MAAI,UAAU;AACZ,UAAM,eAAoB,aAAQ,QAAQ,IAAI,GAAG,QAAQ;AACzD,QAAO,cAAW,YAAY,GAAG;AAC/B,cAAQ,IAAI,uBAAuB,QAAQ,EAAE;AAC7C,aAAO,KAAK,GAAG,mBAAmB,cAAc,CAAC,CAAC,CAAC;AAAA,IACrD,OAAO;AACL,cAAQ,IAAI,uBAAuB,QAAQ,wBAAwB;AAAA,IACrE;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,YAAQ,MAAM,4DAAuD;AACrE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,eAAe,MAAM,KAAK,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE;AAAA,IAAK,CAAC,GAAG,MACzF,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,EAC7B;AAEA,QAAM,aAAkB,aAAQ,QAAQ,IAAI,GAAG,MAAM;AAGrD,QAAM,YAAiB,aAAQ,UAAU;AACzC,MAAI,CAAI,cAAW,SAAS,GAAG;AAC7B,IAAG,aAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,EAC7C;AAGA,QAAM,UAAU,yBAAyB,YAAY;AACrD,EAAG,iBAAc,YAAY,SAAS,OAAO;AAE7C,UAAQ,IAAI,wCAAmC,aAAa,MAAM,SAAS;AAC3E,UAAQ,IAAI,cAAc,UAAU,EAAE;AACxC;AAGA,KAAK;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/index.client.ts
|
|
23
|
+
var index_client_exports = {};
|
|
24
|
+
__export(index_client_exports, {
|
|
25
|
+
YakProvider: () => YakProvider,
|
|
26
|
+
useYak: () => import_react.useYak
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_client_exports);
|
|
29
|
+
__reExport(index_client_exports, require("@yak-io/react"), module.exports);
|
|
30
|
+
|
|
31
|
+
// src/client/useYak.ts
|
|
32
|
+
var import_react = require("@yak-io/react");
|
|
33
|
+
|
|
34
|
+
// src/client/YakProvider.tsx
|
|
35
|
+
var import_react2 = require("@yak-io/react");
|
|
36
|
+
var import_navigation = require("next/navigation");
|
|
37
|
+
var import_react3 = require("react");
|
|
38
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
39
|
+
function isAbsoluteUrl(path) {
|
|
40
|
+
return /^https?:\/\//i.test(path);
|
|
41
|
+
}
|
|
42
|
+
async function defaultGetConfig() {
|
|
43
|
+
const res = await fetch("/api/yak", { credentials: "include" });
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
throw new Error(`Failed to fetch config: ${res.status}`);
|
|
46
|
+
}
|
|
47
|
+
return res.json();
|
|
48
|
+
}
|
|
49
|
+
async function defaultOnToolCall(name, args) {
|
|
50
|
+
const res = await fetch("/api/yak", {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json" },
|
|
53
|
+
credentials: "include",
|
|
54
|
+
body: JSON.stringify({ name, args })
|
|
55
|
+
});
|
|
56
|
+
const data = await res.json();
|
|
57
|
+
if (!data.ok) {
|
|
58
|
+
throw new Error(data.error ?? "Tool execution failed");
|
|
59
|
+
}
|
|
60
|
+
return data.result;
|
|
61
|
+
}
|
|
62
|
+
function YakProvider(props) {
|
|
63
|
+
const router = (0, import_navigation.useRouter)();
|
|
64
|
+
const handleRedirect = (0, import_react3.useCallback)(
|
|
65
|
+
(path) => {
|
|
66
|
+
if (isAbsoluteUrl(path)) {
|
|
67
|
+
window.location.assign(path);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
router.push(path);
|
|
71
|
+
},
|
|
72
|
+
[router]
|
|
73
|
+
);
|
|
74
|
+
const getConfig = (0, import_react3.useMemo)(() => props.getConfig ?? defaultGetConfig, [props.getConfig]);
|
|
75
|
+
const onToolCall = (0, import_react3.useMemo)(() => props.onToolCall ?? defaultOnToolCall, [props.onToolCall]);
|
|
76
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
77
|
+
import_react2.YakProvider,
|
|
78
|
+
{
|
|
79
|
+
...props,
|
|
80
|
+
getConfig,
|
|
81
|
+
onToolCall,
|
|
82
|
+
onRedirect: props.onRedirect ?? handleRedirect
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=index.client.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.client.ts", "../src/client/useYak.ts", "../src/client/YakProvider.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nexport * from \"@yak-io/react\";\nexport type { YakChatAPI } from \"./client/useYak.js\";\nexport { useYak } from \"./client/useYak.js\";\nexport type { YakProviderProps } from \"./client/YakProvider.js\";\nexport { YakProvider } from \"./client/YakProvider.js\";\n", "\"use client\";\n\nimport { useYak } from \"@yak-io/react\";\n\n// Re-export useYak for convenience\nexport { useYak };\n\n/**\n * The object returned by {@link useYak} \u2014 the imperative handle for controlling\n * chat and voice (`open`, `close`, `openWithPrompt`, `voiceStart`, `voiceStop`,\n * `voiceToggle`) plus reactive state (`isOpen`, `isReady`, `chatLoading`,\n * `voiceState`, `voiceLoading`, \u2026).\n */\nexport type YakChatAPI = ReturnType<typeof useYak>;\n", "\"use client\";\n\nimport {\n YakProvider as CoreYakProvider,\n type YakProviderProps as CoreYakProviderProps,\n} from \"@yak-io/react\";\nimport { useRouter } from \"next/navigation\";\nimport type React from \"react\";\nimport { useCallback, useMemo } from \"react\";\n\nexport type YakProviderProps = CoreYakProviderProps;\n\nfunction isAbsoluteUrl(path: string): boolean {\n return /^https?:\\/\\//i.test(path);\n}\n\n/**\n * Default getConfig that fetches from /api/yak\n */\nasync function defaultGetConfig() {\n const res = await fetch(\"/api/yak\", { credentials: \"include\" });\n if (!res.ok) {\n throw new Error(`Failed to fetch config: ${res.status}`);\n }\n return res.json();\n}\n\n/**\n * Default onToolCall that POSTs to /api/yak\n */\nasync function defaultOnToolCall(name: string, args: unknown) {\n const res = await fetch(\"/api/yak\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify({ name, args }),\n });\n const data = await res.json();\n if (!data.ok) {\n throw new Error(data.error ?? \"Tool execution failed\");\n }\n return data.result;\n}\n\n/**\n * Next-aware YakProvider that falls back to client-side navigation\n * and provides sensible defaults for getConfig and onToolCall.\n *\n * By default:\n * - `getConfig` fetches from `/api/yak` (GET)\n * - `onToolCall` POSTs to `/api/yak`\n *\n * These defaults assume you've set up the API handler via `createNextYakHandler`.\n */\nexport function YakProvider(props: YakProviderProps): React.JSX.Element {\n const router = useRouter();\n\n const handleRedirect = useCallback(\n (path: string) => {\n if (isAbsoluteUrl(path)) {\n window.location.assign(path);\n return;\n }\n\n router.push(path);\n },\n [router]\n );\n\n const getConfig = useMemo(() => props.getConfig ?? defaultGetConfig, [props.getConfig]);\n\n const onToolCall = useMemo(() => props.onToolCall ?? defaultOnToolCall, [props.onToolCall]);\n\n return (\n <CoreYakProvider\n {...props}\n getConfig={getConfig}\n onToolCall={onToolCall}\n onRedirect={props.onRedirect ?? handleRedirect}\n />\n );\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,iCAAc,0BAFd;;;ACEA,mBAAuB;;;ACAvB,IAAAA,gBAGO;AACP,wBAA0B;AAE1B,IAAAA,gBAAqC;AAkEjC;AA9DJ,SAAS,cAAc,MAAuB;AAC5C,SAAO,gBAAgB,KAAK,IAAI;AAClC;AAKA,eAAe,mBAAmB;AAChC,QAAM,MAAM,MAAM,MAAM,YAAY,EAAE,aAAa,UAAU,CAAC;AAC9D,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,2BAA2B,IAAI,MAAM,EAAE;AAAA,EACzD;AACA,SAAO,IAAI,KAAK;AAClB;AAKA,eAAe,kBAAkB,MAAc,MAAe;AAC5D,QAAM,MAAM,MAAM,MAAM,YAAY;AAAA,IAClC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,aAAa;AAAA,IACb,MAAM,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;AAAA,EACrC,CAAC;AACD,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,KAAK,IAAI;AACZ,UAAM,IAAI,MAAM,KAAK,SAAS,uBAAuB;AAAA,EACvD;AACA,SAAO,KAAK;AACd;AAYO,SAAS,YAAY,OAA4C;AACtE,QAAM,aAAS,6BAAU;AAEzB,QAAM,qBAAiB;AAAA,IACrB,CAAC,SAAiB;AAChB,UAAI,cAAc,IAAI,GAAG;AACvB,eAAO,SAAS,OAAO,IAAI;AAC3B;AAAA,MACF;AAEA,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,gBAAY,uBAAQ,MAAM,MAAM,aAAa,kBAAkB,CAAC,MAAM,SAAS,CAAC;AAEtF,QAAM,iBAAa,uBAAQ,MAAM,MAAM,cAAc,mBAAmB,CAAC,MAAM,UAAU,CAAC;AAE1F,SACE;AAAA,IAAC,cAAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA;AAAA,MACA,YAAY,MAAM,cAAc;AAAA;AAAA,EAClC;AAEJ;",
|
|
6
|
+
"names": ["import_react", "CoreYakProvider"]
|
|
7
|
+
}
|