@yak-io/nextjs 0.7.0 → 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
package/dist/server/index.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export { createNextYakConfigHandler, createNextYakHandler, createNextYakToolsHandler, loadRouteManifest, loadRoutes, } from "./createNextYakHandler.js";
|
|
2
|
-
export { createRouteManifestAdapter } from "./route-manifest-adapter.js";
|
|
3
|
-
export { scanRoutes } from "./scan-routes.js";
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Check if a route path matches a pattern.
|
|
3
|
-
* Supports exact matches and prefix patterns ending with `*`.
|
|
4
|
-
*/
|
|
5
|
-
function matchesPattern(pattern, routePath) {
|
|
6
|
-
if (pattern.endsWith("/*")) {
|
|
7
|
-
const prefix = pattern.slice(0, -1); // Remove the *
|
|
8
|
-
return routePath === prefix.slice(0, -1) || routePath.startsWith(prefix);
|
|
9
|
-
}
|
|
10
|
-
return pattern === routePath;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Filter routes based on allowed and disallowed patterns.
|
|
14
|
-
*/
|
|
15
|
-
function filterRoutes(routes, allowedRoutes, disallowedRoutes) {
|
|
16
|
-
return routes.filter((route) => {
|
|
17
|
-
const path = route.path;
|
|
18
|
-
// If allowedRoutes is set, route must match at least one pattern
|
|
19
|
-
if (allowedRoutes && allowedRoutes.length > 0) {
|
|
20
|
-
const isAllowed = allowedRoutes.some((pattern) => matchesPattern(pattern, path));
|
|
21
|
-
if (!isAllowed) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
// If disallowedRoutes is set, route must not match any pattern
|
|
26
|
-
if (disallowedRoutes && disallowedRoutes.length > 0) {
|
|
27
|
-
const isDisallowed = disallowedRoutes.some((pattern) => matchesPattern(pattern, path));
|
|
28
|
-
if (isDisallowed) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return true;
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Create a route source adapter from an imported route manifest.
|
|
37
|
-
*
|
|
38
|
-
* This adapter allows you to use generated route manifests with optional
|
|
39
|
-
* filtering to control which routes are exposed to the chatbot.
|
|
40
|
-
*
|
|
41
|
-
* @example Basic usage - all routes
|
|
42
|
-
* ```ts
|
|
43
|
-
* import { createNextYakHandler } from "@yak-io/nextjs/server";
|
|
44
|
-
* import { createRouteManifestAdapter } from "@yak-io/nextjs/server";
|
|
45
|
-
* import { routes } from "@/yak.routes";
|
|
46
|
-
*
|
|
47
|
-
* export const { GET, POST } = createNextYakHandler({
|
|
48
|
-
* routes: createRouteManifestAdapter({ routes }),
|
|
49
|
-
* });
|
|
50
|
-
* ```
|
|
51
|
-
*
|
|
52
|
-
* @example With filtering
|
|
53
|
-
* ```ts
|
|
54
|
-
* import { createNextYakHandler } from "@yak-io/nextjs/server";
|
|
55
|
-
* import { createRouteManifestAdapter } from "@yak-io/nextjs/server";
|
|
56
|
-
* import { routes } from "@/yak.routes";
|
|
57
|
-
*
|
|
58
|
-
* export const { GET, POST } = createNextYakHandler({
|
|
59
|
-
* routes: createRouteManifestAdapter({
|
|
60
|
-
* routes,
|
|
61
|
-
* allowedRoutes: ["/docs/*", "/pricing", "/"],
|
|
62
|
-
* disallowedRoutes: ["/docs/internal/*"],
|
|
63
|
-
* }),
|
|
64
|
-
* });
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
export function createRouteManifestAdapter(config) {
|
|
68
|
-
const { routes, id = "manifest", allowedRoutes, disallowedRoutes } = config;
|
|
69
|
-
return {
|
|
70
|
-
id,
|
|
71
|
-
getRoutes: async () => {
|
|
72
|
-
return filterRoutes(routes, allowedRoutes, disallowedRoutes);
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
/**
|
|
4
|
-
* Extract static metadata (title and description) from a Next.js page file.
|
|
5
|
-
* Parses `export const metadata = { title: "...", description: "..." }` patterns.
|
|
6
|
-
* Does not support dynamic generateMetadata functions.
|
|
7
|
-
*/
|
|
8
|
-
function extractMetadata(filePath) {
|
|
9
|
-
try {
|
|
10
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
11
|
-
// Match `export const metadata` object
|
|
12
|
-
const metadataMatch = content.match(/export\s+const\s+metadata\s*(?::\s*Metadata\s*)?=\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/s);
|
|
13
|
-
if (!metadataMatch) {
|
|
14
|
-
return {};
|
|
15
|
-
}
|
|
16
|
-
const metadataBlock = metadataMatch[1];
|
|
17
|
-
const result = {};
|
|
18
|
-
// Extract title - handle both single and double quotes
|
|
19
|
-
const titleMatch = metadataBlock?.match(/title\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
20
|
-
if (titleMatch?.[1]) {
|
|
21
|
-
result.title = titleMatch[1];
|
|
22
|
-
}
|
|
23
|
-
// Extract description - handle both single and double quotes
|
|
24
|
-
const descMatch = metadataBlock?.match(/description\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
25
|
-
if (descMatch?.[1]) {
|
|
26
|
-
result.description = descMatch[1];
|
|
27
|
-
}
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return {};
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Check if a path segment is a route group (organizational only, not part of URL)
|
|
36
|
-
*/
|
|
37
|
-
function isRouteGroup(segment) {
|
|
38
|
-
return segment.startsWith("(") && segment.endsWith(")");
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Check if a path segment is an optional catch-all (e.g., [[...slug]])
|
|
42
|
-
* These segments match the base path without any additional segments
|
|
43
|
-
*/
|
|
44
|
-
function isOptionalCatchAll(segment) {
|
|
45
|
-
return segment.startsWith("[[...") && segment.endsWith("]]");
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Check if a path segment is a required catch-all (e.g., [...slug])
|
|
49
|
-
* These segments require at least one additional path segment
|
|
50
|
-
*/
|
|
51
|
-
function isRequiredCatchAll(segment) {
|
|
52
|
-
return segment.startsWith("[...") && segment.endsWith("]") && !segment.startsWith("[[");
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Check if a path segment is a dynamic segment (e.g., [id])
|
|
56
|
-
*/
|
|
57
|
-
function isDynamicSegment(segment) {
|
|
58
|
-
return (segment.startsWith("[") &&
|
|
59
|
-
segment.endsWith("]") &&
|
|
60
|
-
!isOptionalCatchAll(segment) &&
|
|
61
|
-
!isRequiredCatchAll(segment));
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Normalize a dynamic segment for display
|
|
65
|
-
* e.g., [id] → :id, [postId] → :postId
|
|
66
|
-
*/
|
|
67
|
-
function normalizeDynamicSegment(segment) {
|
|
68
|
-
// Extract the parameter name from [name] format
|
|
69
|
-
const paramName = segment.slice(1, -1);
|
|
70
|
-
return `:${paramName}`;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Normalize a route path for display (excludes route groups and handles dynamic segments)
|
|
74
|
-
*/
|
|
75
|
-
function normalizeRoutePath(segments) {
|
|
76
|
-
// Filter out route groups - they don't appear in URLs
|
|
77
|
-
// Filter out optional catch-all segments - they match the base path
|
|
78
|
-
const urlSegments = segments
|
|
79
|
-
.filter((seg) => !isRouteGroup(seg) && !isOptionalCatchAll(seg))
|
|
80
|
-
.map((seg) => {
|
|
81
|
-
if (isDynamicSegment(seg)) {
|
|
82
|
-
return normalizeDynamicSegment(seg);
|
|
83
|
-
}
|
|
84
|
-
if (isRequiredCatchAll(seg)) {
|
|
85
|
-
// For required catch-all, extract the name: [...slug] → :slug+
|
|
86
|
-
const paramName = seg.slice(4, -1);
|
|
87
|
-
return `:${paramName}+`;
|
|
88
|
-
}
|
|
89
|
-
return seg;
|
|
90
|
-
});
|
|
91
|
-
if (urlSegments.length === 0)
|
|
92
|
-
return "/";
|
|
93
|
-
return `/${urlSegments.join("/")}`;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Check if file is a page file
|
|
97
|
-
*/
|
|
98
|
-
function isPageFile(filename) {
|
|
99
|
-
return filename === "page.tsx" || filename === "page.js";
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Recursively scan a directory for Next.js page routes
|
|
103
|
-
*/
|
|
104
|
-
function scanAppDirectory(dirPath, segments = []) {
|
|
105
|
-
const routes = [];
|
|
106
|
-
try {
|
|
107
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
108
|
-
for (const entry of entries) {
|
|
109
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
110
|
-
if (entry.isDirectory()) {
|
|
111
|
-
// Skip special Next.js directories and api routes
|
|
112
|
-
if (entry.name.startsWith("_") || entry.name === "api") {
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
// Recursively scan subdirectories
|
|
116
|
-
routes.push(...scanAppDirectory(fullPath, [...segments, entry.name]));
|
|
117
|
-
}
|
|
118
|
-
else if (entry.isFile() && isPageFile(entry.name)) {
|
|
119
|
-
const metadata = extractMetadata(fullPath);
|
|
120
|
-
routes.push({
|
|
121
|
-
path: normalizeRoutePath(segments),
|
|
122
|
-
title: metadata.title,
|
|
123
|
-
description: metadata.description,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
console.error(`Error scanning directory ${dirPath}:`, error);
|
|
130
|
-
}
|
|
131
|
-
return routes;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* File discovery helpers for legacy `pages/` directories
|
|
135
|
-
*/
|
|
136
|
-
const VALID_PAGE_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".md", ".mdx"]);
|
|
137
|
-
const SPECIAL_PAGE_FILENAMES = new Set([
|
|
138
|
-
"_app",
|
|
139
|
-
"_document",
|
|
140
|
-
"_error",
|
|
141
|
-
"404",
|
|
142
|
-
"500",
|
|
143
|
-
"middleware",
|
|
144
|
-
"_middleware",
|
|
145
|
-
]);
|
|
146
|
-
function scanPagesDirectory(dirPath, segments = []) {
|
|
147
|
-
const routes = [];
|
|
148
|
-
try {
|
|
149
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
150
|
-
for (const entry of entries) {
|
|
151
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
152
|
-
if (entry.isDirectory()) {
|
|
153
|
-
// Skip special directories and api routes
|
|
154
|
-
if (entry.name.startsWith("_") || entry.name === "api") {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
routes.push(...scanPagesDirectory(fullPath, [...segments, entry.name]));
|
|
158
|
-
}
|
|
159
|
-
else if (entry.isFile()) {
|
|
160
|
-
const extension = path.extname(entry.name);
|
|
161
|
-
if (!VALID_PAGE_EXTENSIONS.has(extension)) {
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
const baseName = entry.name.slice(0, -extension.length);
|
|
165
|
-
if (!baseName || SPECIAL_PAGE_FILENAMES.has(baseName) || baseName.startsWith("_")) {
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
const routeSegments = buildPagesSegments(segments, baseName);
|
|
169
|
-
const metadata = extractMetadata(fullPath);
|
|
170
|
-
routes.push({
|
|
171
|
-
path: normalizeRoutePath(routeSegments),
|
|
172
|
-
title: metadata.title,
|
|
173
|
-
description: metadata.description,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
console.error(`Error scanning directory ${dirPath}:`, error);
|
|
180
|
-
}
|
|
181
|
-
return routes;
|
|
182
|
-
}
|
|
183
|
-
function buildPagesSegments(segments, baseName) {
|
|
184
|
-
const routeSegments = [...segments];
|
|
185
|
-
if (baseName !== "index") {
|
|
186
|
-
routeSegments.push(baseName);
|
|
187
|
-
}
|
|
188
|
-
return routeSegments;
|
|
189
|
-
}
|
|
190
|
-
function normalizeScanOptions(options) {
|
|
191
|
-
return {
|
|
192
|
-
directoryType: options?.directoryType ?? "app",
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Scan a Next.js app or pages directory and return page route information
|
|
197
|
-
*/
|
|
198
|
-
export function scanRoutes(directory, options) {
|
|
199
|
-
const normalizedOptions = normalizeScanOptions(options);
|
|
200
|
-
const targetDir = path.resolve(process.cwd(), directory);
|
|
201
|
-
if (!fs.existsSync(targetDir)) {
|
|
202
|
-
throw new Error(`App directory not found: ${targetDir}`);
|
|
203
|
-
}
|
|
204
|
-
const routes = normalizedOptions.directoryType === "pages"
|
|
205
|
-
? scanPagesDirectory(targetDir, [])
|
|
206
|
-
: scanAppDirectory(targetDir, []);
|
|
207
|
-
return routes.sort((a, b) => a.path.localeCompare(b.path));
|
|
208
|
-
}
|
package/dist/types/config.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/messaging.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/routes.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/tools.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|