@zubyjs/next 1.0.83
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 +446 -0
- package/index.d.ts +77 -0
- package/index.js +300 -0
- package/nextApiRequest.d.ts +6 -0
- package/nextApiRequest.js +79 -0
- package/nextApiResponse.d.ts +26 -0
- package/nextApiResponse.js +135 -0
- package/nft.d.ts +31 -0
- package/nft.js +158 -0
- package/package.json +41 -0
- package/types.d.ts +125 -0
- package/types.js +1 -0
- package/utils.d.ts +23 -0
- package/utils.js +336 -0
package/index.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { resolve, relative, sep } from 'path';
|
|
4
|
+
import { scanNextApiPages, scanNextPages } from './utils.js';
|
|
5
|
+
import { createImportMap, rewriteImportsInWrapper } from './nft.js';
|
|
6
|
+
export const ZUBY_PAGES_DIR = './zuby-pages';
|
|
7
|
+
/**
|
|
8
|
+
* Zuby.js Next.js compatibility plugin
|
|
9
|
+
* Converts Next.js pages and API routes to Zuby.js handlers
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Automatically scans ./pages directory
|
|
13
|
+
* - Converts ./pages/api routes to Zuby handlers
|
|
14
|
+
* - Converts React page components to Zuby pages
|
|
15
|
+
* - Supports Next.js API route syntax (get, post, put, delete, etc.)
|
|
16
|
+
* - Provides NextApiRequest and NextApiResponse compatibility objects
|
|
17
|
+
* - Handles both default and method-specific exports
|
|
18
|
+
* - Supports getStaticProps (prerender = true) and getServerSideProps (prerender = false)
|
|
19
|
+
* - Passes page props through context
|
|
20
|
+
*
|
|
21
|
+
* @param options Plugin options
|
|
22
|
+
* @returns ZubyPlugin
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // pages/api/hello.ts
|
|
26
|
+
* export default function handler(req, res) {
|
|
27
|
+
* return new Response('Hello from Zuby!');
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* // pages/api/posts/[id].ts
|
|
31
|
+
* export async function get(req, res) {
|
|
32
|
+
* const post = await fetchPost(req.query.id);
|
|
33
|
+
* return new Response(JSON.stringify(post));
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* export async function post(req, res) {
|
|
37
|
+
* const newPost = await createPost(req.body);
|
|
38
|
+
* return new Response(JSON.stringify(newPost), { status: 201 });
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* // pages/index.tsx with static generation
|
|
42
|
+
* export default function HomePage(props) {
|
|
43
|
+
* return <h1>Posts: {props.count}</h1>;
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* export async function getStaticProps() {
|
|
47
|
+
* return { props: { count: 42 } };
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* // pages/about.tsx with server-side rendering
|
|
51
|
+
* export default function AboutPage(props) {
|
|
52
|
+
* return <h1>About - User: {props.user}</h1>;
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* export async function getServerSideProps(context) {
|
|
56
|
+
* return { props: { user: 'John' } };
|
|
57
|
+
* }
|
|
58
|
+
*/
|
|
59
|
+
export default ({ autoDetectApiRoutes = true, pagesDir = './pages', traceDependencies = true, } = {}) => ({
|
|
60
|
+
name: 'zuby-next-plugin',
|
|
61
|
+
description: 'Next.js compatibility plugin',
|
|
62
|
+
buildStep: true,
|
|
63
|
+
hooks: {
|
|
64
|
+
'zuby:config:setup': async ({ config, logger, addHandler }) => {
|
|
65
|
+
// Validate configuration
|
|
66
|
+
if (config.srcPagesDir && config.srcPagesDir !== './pages') {
|
|
67
|
+
logger?.error(`[zuby-next-plugin] The srcPagesDir zuby.config.mjs option cannot be used together with the next.js pages router. Please keep srcPagesDir to the default './pages'.`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Create zuby-pages directory
|
|
71
|
+
if (!existsSync(ZUBY_PAGES_DIR)) {
|
|
72
|
+
await mkdir(ZUBY_PAGES_DIR, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
config.srcPagesDir = ZUBY_PAGES_DIR;
|
|
75
|
+
const nextPagesDir = resolve(config.srcDir || '.', pagesDir);
|
|
76
|
+
// Scan and convert Next.js pages/api if enabled
|
|
77
|
+
if (autoDetectApiRoutes) {
|
|
78
|
+
try {
|
|
79
|
+
const apiHandlers = await scanNextApiPages(nextPagesDir);
|
|
80
|
+
for (const handler of apiHandlers) {
|
|
81
|
+
// Create wrapper file for each handler
|
|
82
|
+
// Convert /api/hello to api/hello.ts to maintain Next.js structure
|
|
83
|
+
const handlerPathNormalized = handler.path
|
|
84
|
+
.replace(/^\//, '') // Remove leading slash
|
|
85
|
+
.replace(/\//g, sep); // Convert / to platform separator
|
|
86
|
+
const wrapperPath = resolve(ZUBY_PAGES_DIR, handlerPathNormalized + '.ts');
|
|
87
|
+
// Calculate relative path from wrapper to original handler file
|
|
88
|
+
const relativePathToHandler = relative(resolve(wrapperPath, '..'), handler.filename);
|
|
89
|
+
logger?.info(`[zuby-next-plugin] Generating wrapper for ${handler.path}`);
|
|
90
|
+
let wrapperContent = generateHandlerWrapper(handler.path, relativePathToHandler);
|
|
91
|
+
// Trace and rewrite imports if enabled
|
|
92
|
+
if (traceDependencies) {
|
|
93
|
+
try {
|
|
94
|
+
const importMap = await createImportMap(handler.filename, wrapperPath);
|
|
95
|
+
wrapperContent = rewriteImportsInWrapper(wrapperContent, importMap);
|
|
96
|
+
if (Object.keys(importMap).length > 0) {
|
|
97
|
+
logger?.info(`[zuby-next-plugin] Rewritten imports for ${handler.path}: ${Object.keys(importMap).length} mappings`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
logger?.warn(`[zuby-next-plugin] Failed to trace dependencies for ${handler.path}: ${error}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Ensure directory exists
|
|
105
|
+
const wrapperDir = resolve(wrapperPath, '..');
|
|
106
|
+
if (!existsSync(wrapperDir)) {
|
|
107
|
+
await mkdir(wrapperDir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
await writeFile(wrapperPath, wrapperContent);
|
|
110
|
+
// Register handler with Zuby
|
|
111
|
+
addHandler(wrapperPath, handler.path);
|
|
112
|
+
logger?.info(`[zuby-next-plugin] Converted API route: ${handler.path}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error?.code !== 'ENOENT') {
|
|
117
|
+
logger?.warn(`[zuby-next-plugin] Failed to scan API routes: ${error}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Scan and convert Next.js pages
|
|
122
|
+
try {
|
|
123
|
+
const pages = await scanNextPages(nextPagesDir);
|
|
124
|
+
for (const page of pages) {
|
|
125
|
+
logger?.info(`[zuby-next-plugin] Generating wrapper for page ${page.path}`);
|
|
126
|
+
// Create wrapper file for each page
|
|
127
|
+
const pagePathNormalized = page.path
|
|
128
|
+
.replace(/^\//, '') // Remove leading slash
|
|
129
|
+
.replace(/\//g, sep); // Convert / to platform separator
|
|
130
|
+
const wrapperPath = resolve(ZUBY_PAGES_DIR, pagePathNormalized + '.ts');
|
|
131
|
+
// Calculate relative path from wrapper to original page file
|
|
132
|
+
const relativePathToPage = relative(resolve(wrapperPath, '..'), page.filename);
|
|
133
|
+
const hasStaticProps = page.hasGetStaticProps || false;
|
|
134
|
+
const hasServerSideProps = page.hasGetServerSideProps || false;
|
|
135
|
+
logger?.info(`[zuby-next-plugin] Generating wrapper for page ${page.path}`);
|
|
136
|
+
let wrapperContent = generatePageWrapper(page.path, relativePathToPage, hasStaticProps, hasServerSideProps);
|
|
137
|
+
// Trace and rewrite imports if enabled
|
|
138
|
+
if (traceDependencies) {
|
|
139
|
+
try {
|
|
140
|
+
const importMap = await createImportMap(page.filename, wrapperPath);
|
|
141
|
+
wrapperContent = rewriteImportsInWrapper(wrapperContent, importMap);
|
|
142
|
+
if (Object.keys(importMap).length > 0) {
|
|
143
|
+
logger?.info(`[zuby-next-plugin] Rewritten imports for ${page.path}: ${Object.keys(importMap).length} mappings`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
logger?.warn(`[zuby-next-plugin] Failed to trace dependencies for ${page.path}: ${error}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Ensure directory exists
|
|
151
|
+
const wrapperDir = resolve(wrapperPath, '..');
|
|
152
|
+
if (!existsSync(wrapperDir)) {
|
|
153
|
+
await mkdir(wrapperDir, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
await writeFile(wrapperPath, wrapperContent);
|
|
156
|
+
// Register page with Zuby
|
|
157
|
+
addHandler(wrapperPath, page.path);
|
|
158
|
+
logger?.info(`[zuby-next-plugin] Converted page: ${page.path}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (error?.code !== 'ENOENT') {
|
|
163
|
+
logger?.warn(`[zuby-next-plugin] Failed to scan pages: ${error}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
/**
|
|
170
|
+
* Generate a wrapper file for Next.js API routes
|
|
171
|
+
* This wrapper converts Next.js handlers to Zuby handler format
|
|
172
|
+
*/
|
|
173
|
+
function generateHandlerWrapper(nextApiPath, importPath) {
|
|
174
|
+
// Normalize path for cross-platform compatibility and ensure it's relative
|
|
175
|
+
const normalizedPath = importPath
|
|
176
|
+
.replace(/\\/g, '/') // Convert backslashes to forward slashes
|
|
177
|
+
.replace(/\.tsx?$/, '') // Remove .ts or .tsx extension
|
|
178
|
+
.replace(/^(?!\.\/|\.\.\/|\/)/, './'); // Add ./ prefix if not already present
|
|
179
|
+
return `import nextApiModule from '${normalizedPath}';
|
|
180
|
+
import { createNextApiRequest } from '@zubyjs/next/nextApiRequest.js';
|
|
181
|
+
import { createNextApiResponse, nextApiResponseToResponse } from '@zubyjs/next/nextApiResponse.js';
|
|
182
|
+
import type { PageContext } from 'zuby';
|
|
183
|
+
|
|
184
|
+
async function wrapHandler(handler: Function, context: any) {
|
|
185
|
+
const req = createNextApiRequest(context);
|
|
186
|
+
const res = createNextApiResponse();
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const result = await handler(req, res);
|
|
190
|
+
|
|
191
|
+
if (result instanceof Response) {
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return nextApiResponseToResponse(res);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return new Response(
|
|
198
|
+
JSON.stringify({
|
|
199
|
+
error: 'Internal Server Error',
|
|
200
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
201
|
+
}),
|
|
202
|
+
{
|
|
203
|
+
status: 500,
|
|
204
|
+
headers: { 'content-type': 'application/json' },
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export default async (context: PageContext) => {
|
|
211
|
+
const method = context.request?.method?.toLowerCase() || 'get';
|
|
212
|
+
|
|
213
|
+
// Try method-specific handler first
|
|
214
|
+
if (typeof nextApiModule[method] === 'function') {
|
|
215
|
+
return wrapHandler(nextApiModule[method], context);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Fall back to default handler
|
|
219
|
+
if (typeof nextApiModule.default === 'function') {
|
|
220
|
+
return wrapHandler(nextApiModule.default, context);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (typeof nextApiModule === 'function') {
|
|
224
|
+
return wrapHandler(nextApiModule, context);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return new Response('Method not allowed', { status: 405 });
|
|
228
|
+
};
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Generate a wrapper file for Next.js pages with getStaticProps or getServerSideProps
|
|
233
|
+
* This wrapper creates a handler that renders the React component
|
|
234
|
+
*/
|
|
235
|
+
function generatePageWrapper(pagePath, importPath, hasStaticProps, hasServerSideProps) {
|
|
236
|
+
// Normalize path for cross-platform compatibility and ensure it's relative
|
|
237
|
+
const normalizedPath = importPath
|
|
238
|
+
.replace(/\\/g, '/') // Convert backslashes to forward slashes
|
|
239
|
+
.replace(/\.tsx?$/, '') // Remove .ts or .tsx extension
|
|
240
|
+
.replace(/^(?!\.\/|\.\.\/|\/)/, './'); // Add ./ prefix if not already present
|
|
241
|
+
const prenderMode = hasStaticProps ? 'true' : 'false';
|
|
242
|
+
return `import pageModule from '${normalizedPath}';
|
|
243
|
+
import type { PageContext } from 'zuby';
|
|
244
|
+
|
|
245
|
+
// Re-export the React component as default
|
|
246
|
+
export const Component = pageModule.default;
|
|
247
|
+
|
|
248
|
+
// Export prerender setting based on getStaticProps/getServerSideProps
|
|
249
|
+
export const prerender = ${prenderMode};
|
|
250
|
+
|
|
251
|
+
// Page handler that calls getStaticProps or getServerSideProps if available
|
|
252
|
+
export default async (context: PageContext) => {
|
|
253
|
+
let props: Record<string, any> = {};
|
|
254
|
+
|
|
255
|
+
// Call getStaticProps if available
|
|
256
|
+
if (typeof pageModule.getStaticProps === 'function') {
|
|
257
|
+
try {
|
|
258
|
+
const result = await pageModule.getStaticProps({
|
|
259
|
+
params: context.params || {},
|
|
260
|
+
});
|
|
261
|
+
if (result && result.props) {
|
|
262
|
+
props = result.props;
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('[zuby-next-plugin] Error in getStaticProps:', error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Call getServerSideProps if available
|
|
270
|
+
if (typeof pageModule.getServerSideProps === 'function') {
|
|
271
|
+
try {
|
|
272
|
+
const result = await pageModule.getServerSideProps({
|
|
273
|
+
params: context.params || {},
|
|
274
|
+
req: {
|
|
275
|
+
headers: Object.fromEntries(
|
|
276
|
+
context.request?.headers?.entries?.() || []
|
|
277
|
+
),
|
|
278
|
+
method: context.request?.method || 'GET',
|
|
279
|
+
url: context.url || '/',
|
|
280
|
+
} as any,
|
|
281
|
+
res: {} as any,
|
|
282
|
+
});
|
|
283
|
+
if (result && result.props) {
|
|
284
|
+
props = result.props;
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error('[zuby-next-plugin] Error in getServerSideProps:', error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Store props in context for the page to access
|
|
292
|
+
context.props = props;
|
|
293
|
+
|
|
294
|
+
// Return empty response - the component will be rendered by Zuby
|
|
295
|
+
return new Response(null, { status: 200 });
|
|
296
|
+
};
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
export { createNextApiRequest } from './nextApiRequest.js';
|
|
300
|
+
export { createNextApiResponse, nextApiResponseToResponse, getResponseState } from './nextApiResponse.js';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a NextApiRequest object from Zuby PageContext
|
|
3
|
+
* Parses query parameters, cookies, headers, and body from the request
|
|
4
|
+
*/
|
|
5
|
+
export function createNextApiRequest(context) {
|
|
6
|
+
const url = new URL(context.url || '/', 'http://localhost');
|
|
7
|
+
const query = {};
|
|
8
|
+
// Parse query parameters from URL
|
|
9
|
+
url.searchParams.forEach((value, key) => {
|
|
10
|
+
if (query[key]) {
|
|
11
|
+
if (Array.isArray(query[key])) {
|
|
12
|
+
query[key].push(value);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
query[key] = [query[key], value];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
query[key] = value;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
// Add path parameters from context
|
|
23
|
+
if (context.params) {
|
|
24
|
+
Object.assign(query, context.params);
|
|
25
|
+
}
|
|
26
|
+
const req = {
|
|
27
|
+
url: context.url || '/',
|
|
28
|
+
method: context.request?.method || 'GET',
|
|
29
|
+
query,
|
|
30
|
+
cookies: parseCookies(context.request?.headers?.get('cookie')),
|
|
31
|
+
headers: headersToRecord(context.request?.headers),
|
|
32
|
+
baseUrl: `${url.protocol}//${url.host}`,
|
|
33
|
+
pathname: url.pathname,
|
|
34
|
+
body: context.body,
|
|
35
|
+
};
|
|
36
|
+
// Add socket info for client IP if available
|
|
37
|
+
if (context.clientAddress) {
|
|
38
|
+
req.socket = { remoteAddress: context.clientAddress };
|
|
39
|
+
}
|
|
40
|
+
return req;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse cookies from cookie header string
|
|
44
|
+
*/
|
|
45
|
+
function parseCookies(cookieHeader) {
|
|
46
|
+
const cookies = {};
|
|
47
|
+
if (!cookieHeader)
|
|
48
|
+
return cookies;
|
|
49
|
+
cookieHeader.split(';').forEach((cookie) => {
|
|
50
|
+
const [name, value] = cookie.trim().split('=');
|
|
51
|
+
if (name && value) {
|
|
52
|
+
cookies[name] = decodeURIComponent(value);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return cookies;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Convert Headers object to Record
|
|
59
|
+
*/
|
|
60
|
+
function headersToRecord(headers) {
|
|
61
|
+
const record = {};
|
|
62
|
+
if (!headers)
|
|
63
|
+
return record;
|
|
64
|
+
headers.forEach((value, key) => {
|
|
65
|
+
const lowerKey = key.toLowerCase();
|
|
66
|
+
if (record[lowerKey]) {
|
|
67
|
+
if (Array.isArray(record[lowerKey])) {
|
|
68
|
+
record[lowerKey].push(value);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
record[lowerKey] = [record[lowerKey], value];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
record[lowerKey] = value;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return record;
|
|
79
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NextApiResponse } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Internal response state for tracking what's been sent
|
|
4
|
+
*/
|
|
5
|
+
interface InternalResponseState {
|
|
6
|
+
statusCode: number;
|
|
7
|
+
headers: Record<string, string | string[] | number>;
|
|
8
|
+
body?: any;
|
|
9
|
+
sent: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create a NextApiResponse object with optional body type
|
|
13
|
+
* Tracks response state and provides methods compatible with Next.js API responses
|
|
14
|
+
* @template TBody The type of the response body
|
|
15
|
+
*/
|
|
16
|
+
export declare function createNextApiResponse<TBody = any>(): NextApiResponse<TBody>;
|
|
17
|
+
/**
|
|
18
|
+
* Get the internal state from a response object
|
|
19
|
+
* Used to convert the response into a Response object
|
|
20
|
+
*/
|
|
21
|
+
export declare function getResponseState(res: NextApiResponse): InternalResponseState;
|
|
22
|
+
/**
|
|
23
|
+
* Convert a NextApiResponse to a Response object for Zuby
|
|
24
|
+
*/
|
|
25
|
+
export declare function nextApiResponseToResponse(res: NextApiResponse): Response;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a NextApiResponse object with optional body type
|
|
3
|
+
* Tracks response state and provides methods compatible with Next.js API responses
|
|
4
|
+
* @template TBody The type of the response body
|
|
5
|
+
*/
|
|
6
|
+
export function createNextApiResponse() {
|
|
7
|
+
const state = {
|
|
8
|
+
statusCode: 200,
|
|
9
|
+
headers: {},
|
|
10
|
+
sent: false,
|
|
11
|
+
};
|
|
12
|
+
const res = {
|
|
13
|
+
statusCode: state.statusCode,
|
|
14
|
+
headers: state.headers,
|
|
15
|
+
headersSent: false,
|
|
16
|
+
status(code) {
|
|
17
|
+
state.statusCode = code;
|
|
18
|
+
this.statusCode = code;
|
|
19
|
+
return this;
|
|
20
|
+
},
|
|
21
|
+
setHeader(name, value) {
|
|
22
|
+
const lowerName = name.toLowerCase();
|
|
23
|
+
state.headers[lowerName] = value;
|
|
24
|
+
this.headers[lowerName] = value;
|
|
25
|
+
return this;
|
|
26
|
+
},
|
|
27
|
+
getHeader(name) {
|
|
28
|
+
const lowerName = name.toLowerCase();
|
|
29
|
+
return state.headers[lowerName];
|
|
30
|
+
},
|
|
31
|
+
json(body) {
|
|
32
|
+
this.setHeader('content-type', 'application/json');
|
|
33
|
+
state.body = JSON.stringify(body);
|
|
34
|
+
state.sent = true;
|
|
35
|
+
this.headersSent = true;
|
|
36
|
+
return this;
|
|
37
|
+
},
|
|
38
|
+
send(body) {
|
|
39
|
+
state.body = body;
|
|
40
|
+
state.sent = true;
|
|
41
|
+
this.headersSent = true;
|
|
42
|
+
if (typeof body === 'string') {
|
|
43
|
+
this.setHeader('content-type', 'text/plain');
|
|
44
|
+
}
|
|
45
|
+
else if (typeof body === 'object') {
|
|
46
|
+
this.setHeader('content-type', 'application/json');
|
|
47
|
+
state.body = JSON.stringify(body);
|
|
48
|
+
}
|
|
49
|
+
return this;
|
|
50
|
+
},
|
|
51
|
+
end(body) {
|
|
52
|
+
if (body !== undefined) {
|
|
53
|
+
this.send(body);
|
|
54
|
+
}
|
|
55
|
+
state.sent = true;
|
|
56
|
+
this.headersSent = true;
|
|
57
|
+
return this;
|
|
58
|
+
},
|
|
59
|
+
write(chunk) {
|
|
60
|
+
if (!state.body) {
|
|
61
|
+
state.body = '';
|
|
62
|
+
}
|
|
63
|
+
state.body += chunk;
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
redirect(statusOrUrl, url) {
|
|
67
|
+
if (typeof statusOrUrl === 'string') {
|
|
68
|
+
state.statusCode = 302;
|
|
69
|
+
state.body = statusOrUrl;
|
|
70
|
+
this.statusCode = 302;
|
|
71
|
+
this.setHeader('location', statusOrUrl);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
state.statusCode = statusOrUrl;
|
|
75
|
+
this.statusCode = statusOrUrl;
|
|
76
|
+
this.setHeader('location', url || '');
|
|
77
|
+
state.body = url;
|
|
78
|
+
}
|
|
79
|
+
state.sent = true;
|
|
80
|
+
this.headersSent = true;
|
|
81
|
+
return this;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
// Attach internal state to response object for later retrieval
|
|
85
|
+
res.__state__ = state;
|
|
86
|
+
return res;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get the internal state from a response object
|
|
90
|
+
* Used to convert the response into a Response object
|
|
91
|
+
*/
|
|
92
|
+
export function getResponseState(res) {
|
|
93
|
+
return res.__state__ || {
|
|
94
|
+
statusCode: res.statusCode || 200,
|
|
95
|
+
headers: res.headers || {},
|
|
96
|
+
sent: false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Convert a NextApiResponse to a Response object for Zuby
|
|
101
|
+
*/
|
|
102
|
+
export function nextApiResponseToResponse(res) {
|
|
103
|
+
const state = getResponseState(res);
|
|
104
|
+
let body = null;
|
|
105
|
+
if (state.body !== undefined) {
|
|
106
|
+
if (typeof state.body === 'string') {
|
|
107
|
+
body = state.body;
|
|
108
|
+
}
|
|
109
|
+
else if (Buffer.isBuffer(state.body)) {
|
|
110
|
+
body = Buffer.from(state.body);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
body = JSON.stringify(state.body);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return new Response(body, {
|
|
117
|
+
status: state.statusCode || 200,
|
|
118
|
+
headers: normalizeHeaders(state.headers),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Normalize headers to Response headers format
|
|
123
|
+
*/
|
|
124
|
+
function normalizeHeaders(headers) {
|
|
125
|
+
const normalized = {};
|
|
126
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
127
|
+
if (Array.isArray(value)) {
|
|
128
|
+
normalized[key] = value.join(', ');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
normalized[key] = String(value);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return normalized;
|
|
135
|
+
}
|
package/nft.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency tracing using @vercel/nft
|
|
3
|
+
* Finds all imports and dependencies of API route files
|
|
4
|
+
* and updates imports to point to original folders
|
|
5
|
+
*/
|
|
6
|
+
export interface ImportMap {
|
|
7
|
+
[importPath: string]: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Trace all imports in a file and create a map for rewriting them
|
|
11
|
+
* This analyzes the wrapper file location and creates relative paths
|
|
12
|
+
* back to the original import locations
|
|
13
|
+
*/
|
|
14
|
+
export declare function createImportMap(apiFilePath: string, wrapperFilePath: string): Promise<ImportMap>;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve import path relative to the importing file
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveImportPath(fromFile: string, importPath: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Rewrite imports in wrapper content to point to original locations
|
|
21
|
+
* Updates relative imports to reference the correct paths from zuby-pages
|
|
22
|
+
*/
|
|
23
|
+
export declare function rewriteImportsInWrapper(wrapperContent: string, importMap: ImportMap): string;
|
|
24
|
+
/**
|
|
25
|
+
* Get all relative imports from a file
|
|
26
|
+
*/
|
|
27
|
+
export declare function getRelativeImports(filePath: string): Promise<string[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Get all external dependencies (not relative imports)
|
|
30
|
+
*/
|
|
31
|
+
export declare function getExternalDependencies(filePath: string): Promise<string[]>;
|