@netlify/dev 3.0.0 → 4.0.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/main.cjs +155 -62
- package/dist/main.d.cts +44 -9
- package/dist/main.d.ts +44 -9
- package/dist/main.js +155 -62
- package/package.json +14 -12
package/dist/main.cjs
CHANGED
|
@@ -38,7 +38,8 @@ var import_node_path2 = __toESM(require("path"), 1);
|
|
|
38
38
|
var import_node_process2 = __toESM(require("process"), 1);
|
|
39
39
|
var import_config = require("@netlify/config");
|
|
40
40
|
var import_dev_utils = require("@netlify/dev-utils");
|
|
41
|
-
var import_dev = require("@netlify/functions/dev");
|
|
41
|
+
var import_dev = require("@netlify/edge-functions/dev");
|
|
42
|
+
var import_dev2 = require("@netlify/functions/dev");
|
|
42
43
|
var import_headers = require("@netlify/headers");
|
|
43
44
|
var import_redirects = require("@netlify/redirects");
|
|
44
45
|
var import_static = require("@netlify/static");
|
|
@@ -54,7 +55,7 @@ var injectEnvVariables = async ({
|
|
|
54
55
|
}) => {
|
|
55
56
|
const results = {};
|
|
56
57
|
let variables = baseVariables;
|
|
57
|
-
if (netlifyAPI && accountSlug) {
|
|
58
|
+
if (netlifyAPI && siteID && accountSlug) {
|
|
58
59
|
variables = await getEnvelopeEnv({
|
|
59
60
|
accountId: accountSlug,
|
|
60
61
|
api: netlifyAPI,
|
|
@@ -70,7 +71,8 @@ var injectEnvVariables = async ({
|
|
|
70
71
|
isInternal,
|
|
71
72
|
originalValue: envAPI.get(key),
|
|
72
73
|
overriddenSources,
|
|
73
|
-
usedSource
|
|
74
|
+
usedSource,
|
|
75
|
+
value: variable.value
|
|
74
76
|
};
|
|
75
77
|
if (!existsInProcess || isInternal) {
|
|
76
78
|
envAPI.set(key, variable.value);
|
|
@@ -195,6 +197,10 @@ var isFile = async (path3) => {
|
|
|
195
197
|
return false;
|
|
196
198
|
};
|
|
197
199
|
|
|
200
|
+
// src/lib/request_id.ts
|
|
201
|
+
var import_ulid = require("ulid");
|
|
202
|
+
var generateRequestID = () => (0, import_ulid.ulid)();
|
|
203
|
+
|
|
198
204
|
// src/lib/runtime.ts
|
|
199
205
|
var import_node_path = __toESM(require("path"), 1);
|
|
200
206
|
var import_node_process = __toESM(require("process"), 1);
|
|
@@ -261,93 +267,98 @@ var NetlifyDev = class {
|
|
|
261
267
|
#apiHost;
|
|
262
268
|
#apiScheme;
|
|
263
269
|
#apiToken;
|
|
270
|
+
#cleanupJobs;
|
|
271
|
+
#edgeFunctionsHandler;
|
|
272
|
+
#functionsHandler;
|
|
273
|
+
#functionsServePath;
|
|
264
274
|
#config;
|
|
265
275
|
#features;
|
|
276
|
+
#headersHandler;
|
|
266
277
|
#logger;
|
|
267
278
|
#projectRoot;
|
|
268
|
-
#
|
|
279
|
+
#redirectsHandler;
|
|
280
|
+
#server;
|
|
269
281
|
#siteID;
|
|
282
|
+
#staticHandler;
|
|
283
|
+
#staticHandlerAdditionalDirectories;
|
|
270
284
|
constructor(options) {
|
|
271
285
|
if (options.apiURL) {
|
|
272
286
|
const apiURL = new URL(options.apiURL);
|
|
273
287
|
this.#apiHost = apiURL.host;
|
|
274
288
|
this.#apiScheme = apiURL.protocol.slice(0, -1);
|
|
275
289
|
}
|
|
290
|
+
const projectRoot = options.projectRoot ?? import_node_process2.default.cwd();
|
|
276
291
|
this.#apiToken = options.apiToken;
|
|
292
|
+
this.#cleanupJobs = [];
|
|
277
293
|
this.#features = {
|
|
278
294
|
blobs: options.blobs?.enabled !== false,
|
|
295
|
+
edgeFunctions: options.edgeFunctions?.enabled !== false,
|
|
279
296
|
environmentVariables: options.environmentVariables?.enabled !== false,
|
|
280
297
|
functions: options.functions?.enabled !== false,
|
|
281
298
|
headers: options.headers?.enabled !== false,
|
|
282
299
|
redirects: options.redirects?.enabled !== false,
|
|
283
300
|
static: options.staticFiles?.enabled !== false
|
|
284
301
|
};
|
|
302
|
+
this.#functionsServePath = import_node_path2.default.join(projectRoot, ".netlify", "functions-serve");
|
|
285
303
|
this.#logger = options.logger ?? globalThis.console;
|
|
286
|
-
this.#
|
|
304
|
+
this.#server = options.serverAddress;
|
|
305
|
+
this.#projectRoot = projectRoot;
|
|
306
|
+
this.#staticHandlerAdditionalDirectories = options.staticFiles?.directories ?? [];
|
|
287
307
|
}
|
|
288
|
-
async handleInEphemeralDirectory(request, destPath) {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
projectRoot: this.#projectRoot,
|
|
295
|
-
settings: {},
|
|
296
|
-
siteId: this.#siteID,
|
|
297
|
-
timeouts: {},
|
|
298
|
-
userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
|
|
299
|
-
}) : null;
|
|
300
|
-
const headers = this.#features.headers ? new import_headers.HeadersHandler({
|
|
301
|
-
configPath: this.#config?.configPath,
|
|
302
|
-
configHeaders: this.#config?.config.headers,
|
|
303
|
-
projectDir: this.#projectRoot,
|
|
304
|
-
publishDir: this.#config?.config.build.publish ?? void 0,
|
|
305
|
-
logger: this.#logger
|
|
306
|
-
}) : { handle: async (_request, response) => response };
|
|
307
|
-
const redirects = this.#features.redirects ? new import_redirects.RedirectsHandler({
|
|
308
|
-
configPath: this.#config?.configPath,
|
|
309
|
-
configRedirects: this.#config?.config.redirects,
|
|
310
|
-
jwtRoleClaim: "",
|
|
311
|
-
jwtSecret: "",
|
|
312
|
-
notFoundHandler,
|
|
313
|
-
projectDir: this.#projectRoot
|
|
314
|
-
}) : null;
|
|
315
|
-
const staticFiles = this.#features.static ? new import_static.StaticHandler({
|
|
316
|
-
directory: this.#config?.config.build.publish ?? this.#projectRoot
|
|
317
|
-
}) : null;
|
|
318
|
-
const functionMatch = await functions?.match(request);
|
|
308
|
+
async handleInEphemeralDirectory(request, destPath, options = {}) {
|
|
309
|
+
const edgeFunctionResponse = await this.#edgeFunctionsHandler?.handle(request.clone());
|
|
310
|
+
if (edgeFunctionResponse) {
|
|
311
|
+
return { response: edgeFunctionResponse, type: "edge-function" };
|
|
312
|
+
}
|
|
313
|
+
const functionMatch = await this.#functionsHandler?.match(request, destPath);
|
|
319
314
|
if (functionMatch) {
|
|
320
315
|
if (functionMatch.preferStatic) {
|
|
321
|
-
const staticMatch2 = await
|
|
316
|
+
const staticMatch2 = await this.#staticHandler?.match(request);
|
|
322
317
|
if (staticMatch2) {
|
|
323
318
|
const response = await staticMatch2.handle();
|
|
324
|
-
|
|
319
|
+
await this.#headersHandler?.apply(request, response, options.headersCollector);
|
|
320
|
+
return { response, type: "static" };
|
|
325
321
|
}
|
|
326
322
|
}
|
|
327
|
-
return functionMatch.handle(request);
|
|
323
|
+
return { response: await functionMatch.handle(request), type: "function" };
|
|
328
324
|
}
|
|
329
|
-
const redirectMatch = await
|
|
325
|
+
const redirectMatch = await this.#redirectsHandler?.match(request);
|
|
330
326
|
if (redirectMatch) {
|
|
331
|
-
const functionMatch2 = await
|
|
327
|
+
const functionMatch2 = await this.#functionsHandler?.match(new Request(redirectMatch.target), destPath);
|
|
332
328
|
if (functionMatch2 && !functionMatch2.preferStatic) {
|
|
333
|
-
return functionMatch2.handle(request);
|
|
329
|
+
return { response: await functionMatch2.handle(request), type: "function" };
|
|
334
330
|
}
|
|
335
|
-
const response = await
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
331
|
+
const response = await this.#redirectsHandler?.handle(
|
|
332
|
+
request,
|
|
333
|
+
redirectMatch,
|
|
334
|
+
async (maybeStaticFile) => {
|
|
335
|
+
const staticMatch2 = await this.#staticHandler?.match(maybeStaticFile);
|
|
336
|
+
if (!staticMatch2) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
return async () => {
|
|
340
|
+
const response2 = await staticMatch2.handle();
|
|
341
|
+
await this.#headersHandler?.apply(new Request(redirectMatch.target), response2, options.headersCollector);
|
|
342
|
+
return response2;
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
);
|
|
343
346
|
if (response) {
|
|
344
|
-
return response;
|
|
347
|
+
return { response, type: "redirect" };
|
|
345
348
|
}
|
|
346
349
|
}
|
|
347
|
-
const
|
|
350
|
+
const { pathname } = new URL(request.url);
|
|
351
|
+
if (pathname.startsWith("/.netlify/images")) {
|
|
352
|
+
this.#logger.error(
|
|
353
|
+
"The Netlify Image CDN is currently only supported in the Netlify CLI. Run `npx netlify dev` to get started."
|
|
354
|
+
);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const staticMatch = await this.#staticHandler?.match(request);
|
|
348
358
|
if (staticMatch) {
|
|
349
359
|
const response = await staticMatch.handle();
|
|
350
|
-
|
|
360
|
+
await this.#headersHandler?.apply(request, response, options.headersCollector);
|
|
361
|
+
return { response, type: "static" };
|
|
351
362
|
}
|
|
352
363
|
}
|
|
353
364
|
async getConfig() {
|
|
@@ -367,12 +378,17 @@ var NetlifyDev = class {
|
|
|
367
378
|
});
|
|
368
379
|
return config;
|
|
369
380
|
}
|
|
370
|
-
async handle(request) {
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
381
|
+
async handle(request, options = {}) {
|
|
382
|
+
const result = await this.handleAndIntrospect(request, options);
|
|
383
|
+
return result?.response;
|
|
384
|
+
}
|
|
385
|
+
async handleAndIntrospect(request, options = {}) {
|
|
386
|
+
const requestID = generateRequestID();
|
|
387
|
+
request.headers.set("x-nf-request-id", requestID);
|
|
388
|
+
await import_node_fs2.promises.mkdir(this.#functionsServePath, { recursive: true });
|
|
389
|
+
const destPath = await import_node_fs2.promises.mkdtemp(import_node_path2.default.join(this.#functionsServePath, `${requestID}_`));
|
|
374
390
|
try {
|
|
375
|
-
return await this.handleInEphemeralDirectory(request, destPath);
|
|
391
|
+
return await this.handleInEphemeralDirectory(request, destPath, options);
|
|
376
392
|
} finally {
|
|
377
393
|
try {
|
|
378
394
|
await import_node_fs2.promises.rm(destPath, { force: true, recursive: true });
|
|
@@ -397,9 +413,21 @@ var NetlifyDev = class {
|
|
|
397
413
|
projectRoot: this.#projectRoot,
|
|
398
414
|
siteID: siteID ?? "0"
|
|
399
415
|
});
|
|
400
|
-
this.#
|
|
401
|
-
|
|
402
|
-
|
|
416
|
+
this.#cleanupJobs.push(() => runtime.stop());
|
|
417
|
+
let serverAddress;
|
|
418
|
+
if (typeof this.#server === "string") {
|
|
419
|
+
serverAddress = this.#server;
|
|
420
|
+
} else if (this.#features.edgeFunctions) {
|
|
421
|
+
const passthroughServer = new import_dev_utils.HTTPServer(async (req) => {
|
|
422
|
+
const res = await this.handle(req);
|
|
423
|
+
return res ?? new Response(null, { status: 404 });
|
|
424
|
+
});
|
|
425
|
+
this.#cleanupJobs.push(() => passthroughServer.stop());
|
|
426
|
+
serverAddress = await passthroughServer.start();
|
|
427
|
+
}
|
|
428
|
+
let envVariables = {};
|
|
429
|
+
if (this.#features.environmentVariables) {
|
|
430
|
+
envVariables = await injectEnvVariables({
|
|
403
431
|
accountSlug: config?.siteInfo?.account_slug,
|
|
404
432
|
baseVariables: config?.env || {},
|
|
405
433
|
envAPI: runtime.env,
|
|
@@ -407,9 +435,74 @@ var NetlifyDev = class {
|
|
|
407
435
|
siteID
|
|
408
436
|
});
|
|
409
437
|
}
|
|
438
|
+
if (this.#features.edgeFunctions && serverAddress !== void 0) {
|
|
439
|
+
const env = Object.entries(envVariables).reduce((acc, [key, variable]) => {
|
|
440
|
+
if (variable.usedSource === "account" || variable.usedSource === "addons" || variable.usedSource === "internal" || variable.usedSource === "ui" || variable.usedSource.startsWith(".env")) {
|
|
441
|
+
return {
|
|
442
|
+
...acc,
|
|
443
|
+
[key]: variable.value
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return acc;
|
|
447
|
+
}, {});
|
|
448
|
+
this.#edgeFunctionsHandler = new import_dev.EdgeFunctionsHandler({
|
|
449
|
+
configDeclarations: this.#config?.config.edge_functions ?? [],
|
|
450
|
+
directories: [this.#config?.config.build.edge_functions].filter(Boolean),
|
|
451
|
+
env,
|
|
452
|
+
geolocation: import_dev_utils.mockLocation,
|
|
453
|
+
logger: this.#logger,
|
|
454
|
+
originServerAddress: serverAddress,
|
|
455
|
+
siteID,
|
|
456
|
+
siteName: config?.siteInfo.name
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (this.#features.functions) {
|
|
460
|
+
const userFunctionsPath = this.#config?.config.functionsDirectory ?? import_node_path2.default.join(this.#projectRoot, "netlify/functions");
|
|
461
|
+
const userFunctionsPathExists = await isDirectory(userFunctionsPath);
|
|
462
|
+
this.#functionsHandler = new import_dev2.FunctionsHandler({
|
|
463
|
+
config: this.#config,
|
|
464
|
+
destPath: this.#functionsServePath,
|
|
465
|
+
geolocation: import_dev_utils.mockLocation,
|
|
466
|
+
projectRoot: this.#projectRoot,
|
|
467
|
+
settings: {},
|
|
468
|
+
siteId: this.#siteID,
|
|
469
|
+
timeouts: {},
|
|
470
|
+
userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
if (this.#features.headers) {
|
|
474
|
+
this.#headersHandler = new import_headers.HeadersHandler({
|
|
475
|
+
configPath: this.#config?.configPath,
|
|
476
|
+
configHeaders: this.#config?.config.headers,
|
|
477
|
+
projectDir: this.#projectRoot,
|
|
478
|
+
publishDir: this.#config?.config.build.publish ?? void 0,
|
|
479
|
+
logger: this.#logger
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
if (this.#features.redirects) {
|
|
483
|
+
this.#redirectsHandler = new import_redirects.RedirectsHandler({
|
|
484
|
+
configPath: this.#config?.configPath,
|
|
485
|
+
configRedirects: this.#config?.config.redirects,
|
|
486
|
+
jwtRoleClaim: "",
|
|
487
|
+
jwtSecret: "",
|
|
488
|
+
notFoundHandler,
|
|
489
|
+
projectDir: this.#projectRoot
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (this.#features.static) {
|
|
493
|
+
this.#staticHandler = new import_static.StaticHandler({
|
|
494
|
+
directory: [
|
|
495
|
+
this.#config?.config.build.publish ?? this.#projectRoot,
|
|
496
|
+
...this.#staticHandlerAdditionalDirectories
|
|
497
|
+
]
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
serverAddress
|
|
502
|
+
};
|
|
410
503
|
}
|
|
411
504
|
async stop() {
|
|
412
|
-
await this.#
|
|
505
|
+
await Promise.allSettled(this.#cleanupJobs.map((task) => task()));
|
|
413
506
|
}
|
|
414
507
|
};
|
|
415
508
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/main.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Logger } from '@netlify/dev-utils';
|
|
2
|
+
import { HeadersCollector } from '@netlify/headers';
|
|
2
3
|
|
|
3
4
|
interface Features {
|
|
4
5
|
/**
|
|
@@ -7,7 +8,15 @@ interface Features {
|
|
|
7
8
|
* {@link} https://docs.netlify.com/blobs/overview/
|
|
8
9
|
*/
|
|
9
10
|
blobs?: {
|
|
10
|
-
enabled
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Configuration options for environment variables.
|
|
15
|
+
*
|
|
16
|
+
* {@link} https://docs.netlify.com/edge-functions/overview/
|
|
17
|
+
*/
|
|
18
|
+
edgeFunctions?: {
|
|
19
|
+
enabled?: boolean;
|
|
11
20
|
};
|
|
12
21
|
/**
|
|
13
22
|
* Configuration options for environment variables.
|
|
@@ -15,7 +24,7 @@ interface Features {
|
|
|
15
24
|
* {@link} https://docs.netlify.com/environment-variables/overview/
|
|
16
25
|
*/
|
|
17
26
|
environmentVariables?: {
|
|
18
|
-
enabled
|
|
27
|
+
enabled?: boolean;
|
|
19
28
|
};
|
|
20
29
|
/**
|
|
21
30
|
* Configuration options for Netlify Functions.
|
|
@@ -23,7 +32,7 @@ interface Features {
|
|
|
23
32
|
* {@link} https://docs.netlify.com/functions/overview/
|
|
24
33
|
*/
|
|
25
34
|
functions?: {
|
|
26
|
-
enabled
|
|
35
|
+
enabled?: boolean;
|
|
27
36
|
};
|
|
28
37
|
/**
|
|
29
38
|
* Configuration options for Netlify response headers.
|
|
@@ -31,7 +40,7 @@ interface Features {
|
|
|
31
40
|
* {@link} https://docs.netlify.com/routing/headers/
|
|
32
41
|
*/
|
|
33
42
|
headers?: {
|
|
34
|
-
enabled
|
|
43
|
+
enabled?: boolean;
|
|
35
44
|
};
|
|
36
45
|
/**
|
|
37
46
|
* Configuration options for Netlify redirects and rewrites.
|
|
@@ -39,13 +48,23 @@ interface Features {
|
|
|
39
48
|
* {@link} https://docs.netlify.com/routing/redirects/
|
|
40
49
|
*/
|
|
41
50
|
redirects?: {
|
|
42
|
-
enabled
|
|
51
|
+
enabled?: boolean;
|
|
43
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* If your local development setup has its own HTTP server (e.g. Vite), set
|
|
55
|
+
* its address here.
|
|
56
|
+
*/
|
|
57
|
+
serverAddress?: string;
|
|
44
58
|
/**
|
|
45
59
|
* Configuration options for serving static files.
|
|
46
60
|
*/
|
|
47
61
|
staticFiles?: {
|
|
48
|
-
enabled
|
|
62
|
+
enabled?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Additional list of directories where static files can be found. The
|
|
65
|
+
* `publish` directory configured on your site will be used automatically.
|
|
66
|
+
*/
|
|
67
|
+
directories?: string[];
|
|
49
68
|
};
|
|
50
69
|
}
|
|
51
70
|
interface NetlifyDevOptions extends Features {
|
|
@@ -54,15 +73,31 @@ interface NetlifyDevOptions extends Features {
|
|
|
54
73
|
logger?: Logger;
|
|
55
74
|
projectRoot?: string;
|
|
56
75
|
}
|
|
76
|
+
interface HandleOptions {
|
|
77
|
+
/**
|
|
78
|
+
* An optional callback that will be called with every header (key and value)
|
|
79
|
+
* coming from header rules.
|
|
80
|
+
*
|
|
81
|
+
* {@link} https://docs.netlify.com/routing/headers/
|
|
82
|
+
*/
|
|
83
|
+
headersCollector?: HeadersCollector;
|
|
84
|
+
}
|
|
85
|
+
type ResponseType = 'edge-function' | 'function' | 'redirect' | 'static';
|
|
57
86
|
declare class NetlifyDev {
|
|
58
87
|
#private;
|
|
59
88
|
constructor(options: NetlifyDevOptions);
|
|
60
89
|
private handleInEphemeralDirectory;
|
|
61
90
|
private getConfig;
|
|
62
|
-
handle(request: Request): Promise<Response | undefined>;
|
|
91
|
+
handle(request: Request, options?: HandleOptions): Promise<Response | undefined>;
|
|
92
|
+
handleAndIntrospect(request: Request, options?: HandleOptions): Promise<{
|
|
93
|
+
response: Response;
|
|
94
|
+
type: ResponseType;
|
|
95
|
+
} | undefined>;
|
|
63
96
|
get siteIsLinked(): boolean;
|
|
64
|
-
start(): Promise<
|
|
97
|
+
start(): Promise<{
|
|
98
|
+
serverAddress: string | undefined;
|
|
99
|
+
}>;
|
|
65
100
|
stop(): Promise<void>;
|
|
66
101
|
}
|
|
67
102
|
|
|
68
|
-
export { type Features, NetlifyDev };
|
|
103
|
+
export { type Features, NetlifyDev, type ResponseType };
|
package/dist/main.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Logger } from '@netlify/dev-utils';
|
|
2
|
+
import { HeadersCollector } from '@netlify/headers';
|
|
2
3
|
|
|
3
4
|
interface Features {
|
|
4
5
|
/**
|
|
@@ -7,7 +8,15 @@ interface Features {
|
|
|
7
8
|
* {@link} https://docs.netlify.com/blobs/overview/
|
|
8
9
|
*/
|
|
9
10
|
blobs?: {
|
|
10
|
-
enabled
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Configuration options for environment variables.
|
|
15
|
+
*
|
|
16
|
+
* {@link} https://docs.netlify.com/edge-functions/overview/
|
|
17
|
+
*/
|
|
18
|
+
edgeFunctions?: {
|
|
19
|
+
enabled?: boolean;
|
|
11
20
|
};
|
|
12
21
|
/**
|
|
13
22
|
* Configuration options for environment variables.
|
|
@@ -15,7 +24,7 @@ interface Features {
|
|
|
15
24
|
* {@link} https://docs.netlify.com/environment-variables/overview/
|
|
16
25
|
*/
|
|
17
26
|
environmentVariables?: {
|
|
18
|
-
enabled
|
|
27
|
+
enabled?: boolean;
|
|
19
28
|
};
|
|
20
29
|
/**
|
|
21
30
|
* Configuration options for Netlify Functions.
|
|
@@ -23,7 +32,7 @@ interface Features {
|
|
|
23
32
|
* {@link} https://docs.netlify.com/functions/overview/
|
|
24
33
|
*/
|
|
25
34
|
functions?: {
|
|
26
|
-
enabled
|
|
35
|
+
enabled?: boolean;
|
|
27
36
|
};
|
|
28
37
|
/**
|
|
29
38
|
* Configuration options for Netlify response headers.
|
|
@@ -31,7 +40,7 @@ interface Features {
|
|
|
31
40
|
* {@link} https://docs.netlify.com/routing/headers/
|
|
32
41
|
*/
|
|
33
42
|
headers?: {
|
|
34
|
-
enabled
|
|
43
|
+
enabled?: boolean;
|
|
35
44
|
};
|
|
36
45
|
/**
|
|
37
46
|
* Configuration options for Netlify redirects and rewrites.
|
|
@@ -39,13 +48,23 @@ interface Features {
|
|
|
39
48
|
* {@link} https://docs.netlify.com/routing/redirects/
|
|
40
49
|
*/
|
|
41
50
|
redirects?: {
|
|
42
|
-
enabled
|
|
51
|
+
enabled?: boolean;
|
|
43
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* If your local development setup has its own HTTP server (e.g. Vite), set
|
|
55
|
+
* its address here.
|
|
56
|
+
*/
|
|
57
|
+
serverAddress?: string;
|
|
44
58
|
/**
|
|
45
59
|
* Configuration options for serving static files.
|
|
46
60
|
*/
|
|
47
61
|
staticFiles?: {
|
|
48
|
-
enabled
|
|
62
|
+
enabled?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Additional list of directories where static files can be found. The
|
|
65
|
+
* `publish` directory configured on your site will be used automatically.
|
|
66
|
+
*/
|
|
67
|
+
directories?: string[];
|
|
49
68
|
};
|
|
50
69
|
}
|
|
51
70
|
interface NetlifyDevOptions extends Features {
|
|
@@ -54,15 +73,31 @@ interface NetlifyDevOptions extends Features {
|
|
|
54
73
|
logger?: Logger;
|
|
55
74
|
projectRoot?: string;
|
|
56
75
|
}
|
|
76
|
+
interface HandleOptions {
|
|
77
|
+
/**
|
|
78
|
+
* An optional callback that will be called with every header (key and value)
|
|
79
|
+
* coming from header rules.
|
|
80
|
+
*
|
|
81
|
+
* {@link} https://docs.netlify.com/routing/headers/
|
|
82
|
+
*/
|
|
83
|
+
headersCollector?: HeadersCollector;
|
|
84
|
+
}
|
|
85
|
+
type ResponseType = 'edge-function' | 'function' | 'redirect' | 'static';
|
|
57
86
|
declare class NetlifyDev {
|
|
58
87
|
#private;
|
|
59
88
|
constructor(options: NetlifyDevOptions);
|
|
60
89
|
private handleInEphemeralDirectory;
|
|
61
90
|
private getConfig;
|
|
62
|
-
handle(request: Request): Promise<Response | undefined>;
|
|
91
|
+
handle(request: Request, options?: HandleOptions): Promise<Response | undefined>;
|
|
92
|
+
handleAndIntrospect(request: Request, options?: HandleOptions): Promise<{
|
|
93
|
+
response: Response;
|
|
94
|
+
type: ResponseType;
|
|
95
|
+
} | undefined>;
|
|
63
96
|
get siteIsLinked(): boolean;
|
|
64
|
-
start(): Promise<
|
|
97
|
+
start(): Promise<{
|
|
98
|
+
serverAddress: string | undefined;
|
|
99
|
+
}>;
|
|
65
100
|
stop(): Promise<void>;
|
|
66
101
|
}
|
|
67
102
|
|
|
68
|
-
export { type Features, NetlifyDev };
|
|
103
|
+
export { type Features, NetlifyDev, type ResponseType };
|
package/dist/main.js
CHANGED
|
@@ -3,7 +3,8 @@ import { promises as fs2 } from "fs";
|
|
|
3
3
|
import path2 from "path";
|
|
4
4
|
import process2 from "process";
|
|
5
5
|
import { resolveConfig } from "@netlify/config";
|
|
6
|
-
import { ensureNetlifyIgnore, getAPIToken, LocalState } from "@netlify/dev-utils";
|
|
6
|
+
import { ensureNetlifyIgnore, getAPIToken, mockLocation, LocalState, HTTPServer } from "@netlify/dev-utils";
|
|
7
|
+
import { EdgeFunctionsHandler } from "@netlify/edge-functions/dev";
|
|
7
8
|
import { FunctionsHandler } from "@netlify/functions/dev";
|
|
8
9
|
import { HeadersHandler } from "@netlify/headers";
|
|
9
10
|
import { RedirectsHandler } from "@netlify/redirects";
|
|
@@ -20,7 +21,7 @@ var injectEnvVariables = async ({
|
|
|
20
21
|
}) => {
|
|
21
22
|
const results = {};
|
|
22
23
|
let variables = baseVariables;
|
|
23
|
-
if (netlifyAPI && accountSlug) {
|
|
24
|
+
if (netlifyAPI && siteID && accountSlug) {
|
|
24
25
|
variables = await getEnvelopeEnv({
|
|
25
26
|
accountId: accountSlug,
|
|
26
27
|
api: netlifyAPI,
|
|
@@ -36,7 +37,8 @@ var injectEnvVariables = async ({
|
|
|
36
37
|
isInternal,
|
|
37
38
|
originalValue: envAPI.get(key),
|
|
38
39
|
overriddenSources,
|
|
39
|
-
usedSource
|
|
40
|
+
usedSource,
|
|
41
|
+
value: variable.value
|
|
40
42
|
};
|
|
41
43
|
if (!existsInProcess || isInternal) {
|
|
42
44
|
envAPI.set(key, variable.value);
|
|
@@ -161,6 +163,10 @@ var isFile = async (path3) => {
|
|
|
161
163
|
return false;
|
|
162
164
|
};
|
|
163
165
|
|
|
166
|
+
// src/lib/request_id.ts
|
|
167
|
+
import { ulid } from "ulid";
|
|
168
|
+
var generateRequestID = () => ulid();
|
|
169
|
+
|
|
164
170
|
// src/lib/runtime.ts
|
|
165
171
|
import path from "path";
|
|
166
172
|
import process from "process";
|
|
@@ -227,93 +233,98 @@ var NetlifyDev = class {
|
|
|
227
233
|
#apiHost;
|
|
228
234
|
#apiScheme;
|
|
229
235
|
#apiToken;
|
|
236
|
+
#cleanupJobs;
|
|
237
|
+
#edgeFunctionsHandler;
|
|
238
|
+
#functionsHandler;
|
|
239
|
+
#functionsServePath;
|
|
230
240
|
#config;
|
|
231
241
|
#features;
|
|
242
|
+
#headersHandler;
|
|
232
243
|
#logger;
|
|
233
244
|
#projectRoot;
|
|
234
|
-
#
|
|
245
|
+
#redirectsHandler;
|
|
246
|
+
#server;
|
|
235
247
|
#siteID;
|
|
248
|
+
#staticHandler;
|
|
249
|
+
#staticHandlerAdditionalDirectories;
|
|
236
250
|
constructor(options) {
|
|
237
251
|
if (options.apiURL) {
|
|
238
252
|
const apiURL = new URL(options.apiURL);
|
|
239
253
|
this.#apiHost = apiURL.host;
|
|
240
254
|
this.#apiScheme = apiURL.protocol.slice(0, -1);
|
|
241
255
|
}
|
|
256
|
+
const projectRoot = options.projectRoot ?? process2.cwd();
|
|
242
257
|
this.#apiToken = options.apiToken;
|
|
258
|
+
this.#cleanupJobs = [];
|
|
243
259
|
this.#features = {
|
|
244
260
|
blobs: options.blobs?.enabled !== false,
|
|
261
|
+
edgeFunctions: options.edgeFunctions?.enabled !== false,
|
|
245
262
|
environmentVariables: options.environmentVariables?.enabled !== false,
|
|
246
263
|
functions: options.functions?.enabled !== false,
|
|
247
264
|
headers: options.headers?.enabled !== false,
|
|
248
265
|
redirects: options.redirects?.enabled !== false,
|
|
249
266
|
static: options.staticFiles?.enabled !== false
|
|
250
267
|
};
|
|
268
|
+
this.#functionsServePath = path2.join(projectRoot, ".netlify", "functions-serve");
|
|
251
269
|
this.#logger = options.logger ?? globalThis.console;
|
|
252
|
-
this.#
|
|
270
|
+
this.#server = options.serverAddress;
|
|
271
|
+
this.#projectRoot = projectRoot;
|
|
272
|
+
this.#staticHandlerAdditionalDirectories = options.staticFiles?.directories ?? [];
|
|
253
273
|
}
|
|
254
|
-
async handleInEphemeralDirectory(request, destPath) {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
projectRoot: this.#projectRoot,
|
|
261
|
-
settings: {},
|
|
262
|
-
siteId: this.#siteID,
|
|
263
|
-
timeouts: {},
|
|
264
|
-
userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
|
|
265
|
-
}) : null;
|
|
266
|
-
const headers = this.#features.headers ? new HeadersHandler({
|
|
267
|
-
configPath: this.#config?.configPath,
|
|
268
|
-
configHeaders: this.#config?.config.headers,
|
|
269
|
-
projectDir: this.#projectRoot,
|
|
270
|
-
publishDir: this.#config?.config.build.publish ?? void 0,
|
|
271
|
-
logger: this.#logger
|
|
272
|
-
}) : { handle: async (_request, response) => response };
|
|
273
|
-
const redirects = this.#features.redirects ? new RedirectsHandler({
|
|
274
|
-
configPath: this.#config?.configPath,
|
|
275
|
-
configRedirects: this.#config?.config.redirects,
|
|
276
|
-
jwtRoleClaim: "",
|
|
277
|
-
jwtSecret: "",
|
|
278
|
-
notFoundHandler,
|
|
279
|
-
projectDir: this.#projectRoot
|
|
280
|
-
}) : null;
|
|
281
|
-
const staticFiles = this.#features.static ? new StaticHandler({
|
|
282
|
-
directory: this.#config?.config.build.publish ?? this.#projectRoot
|
|
283
|
-
}) : null;
|
|
284
|
-
const functionMatch = await functions?.match(request);
|
|
274
|
+
async handleInEphemeralDirectory(request, destPath, options = {}) {
|
|
275
|
+
const edgeFunctionResponse = await this.#edgeFunctionsHandler?.handle(request.clone());
|
|
276
|
+
if (edgeFunctionResponse) {
|
|
277
|
+
return { response: edgeFunctionResponse, type: "edge-function" };
|
|
278
|
+
}
|
|
279
|
+
const functionMatch = await this.#functionsHandler?.match(request, destPath);
|
|
285
280
|
if (functionMatch) {
|
|
286
281
|
if (functionMatch.preferStatic) {
|
|
287
|
-
const staticMatch2 = await
|
|
282
|
+
const staticMatch2 = await this.#staticHandler?.match(request);
|
|
288
283
|
if (staticMatch2) {
|
|
289
284
|
const response = await staticMatch2.handle();
|
|
290
|
-
|
|
285
|
+
await this.#headersHandler?.apply(request, response, options.headersCollector);
|
|
286
|
+
return { response, type: "static" };
|
|
291
287
|
}
|
|
292
288
|
}
|
|
293
|
-
return functionMatch.handle(request);
|
|
289
|
+
return { response: await functionMatch.handle(request), type: "function" };
|
|
294
290
|
}
|
|
295
|
-
const redirectMatch = await
|
|
291
|
+
const redirectMatch = await this.#redirectsHandler?.match(request);
|
|
296
292
|
if (redirectMatch) {
|
|
297
|
-
const functionMatch2 = await
|
|
293
|
+
const functionMatch2 = await this.#functionsHandler?.match(new Request(redirectMatch.target), destPath);
|
|
298
294
|
if (functionMatch2 && !functionMatch2.preferStatic) {
|
|
299
|
-
return functionMatch2.handle(request);
|
|
295
|
+
return { response: await functionMatch2.handle(request), type: "function" };
|
|
300
296
|
}
|
|
301
|
-
const response = await
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
297
|
+
const response = await this.#redirectsHandler?.handle(
|
|
298
|
+
request,
|
|
299
|
+
redirectMatch,
|
|
300
|
+
async (maybeStaticFile) => {
|
|
301
|
+
const staticMatch2 = await this.#staticHandler?.match(maybeStaticFile);
|
|
302
|
+
if (!staticMatch2) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
return async () => {
|
|
306
|
+
const response2 = await staticMatch2.handle();
|
|
307
|
+
await this.#headersHandler?.apply(new Request(redirectMatch.target), response2, options.headersCollector);
|
|
308
|
+
return response2;
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
);
|
|
309
312
|
if (response) {
|
|
310
|
-
return response;
|
|
313
|
+
return { response, type: "redirect" };
|
|
311
314
|
}
|
|
312
315
|
}
|
|
313
|
-
const
|
|
316
|
+
const { pathname } = new URL(request.url);
|
|
317
|
+
if (pathname.startsWith("/.netlify/images")) {
|
|
318
|
+
this.#logger.error(
|
|
319
|
+
"The Netlify Image CDN is currently only supported in the Netlify CLI. Run `npx netlify dev` to get started."
|
|
320
|
+
);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const staticMatch = await this.#staticHandler?.match(request);
|
|
314
324
|
if (staticMatch) {
|
|
315
325
|
const response = await staticMatch.handle();
|
|
316
|
-
|
|
326
|
+
await this.#headersHandler?.apply(request, response, options.headersCollector);
|
|
327
|
+
return { response, type: "static" };
|
|
317
328
|
}
|
|
318
329
|
}
|
|
319
330
|
async getConfig() {
|
|
@@ -333,12 +344,17 @@ var NetlifyDev = class {
|
|
|
333
344
|
});
|
|
334
345
|
return config;
|
|
335
346
|
}
|
|
336
|
-
async handle(request) {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
347
|
+
async handle(request, options = {}) {
|
|
348
|
+
const result = await this.handleAndIntrospect(request, options);
|
|
349
|
+
return result?.response;
|
|
350
|
+
}
|
|
351
|
+
async handleAndIntrospect(request, options = {}) {
|
|
352
|
+
const requestID = generateRequestID();
|
|
353
|
+
request.headers.set("x-nf-request-id", requestID);
|
|
354
|
+
await fs2.mkdir(this.#functionsServePath, { recursive: true });
|
|
355
|
+
const destPath = await fs2.mkdtemp(path2.join(this.#functionsServePath, `${requestID}_`));
|
|
340
356
|
try {
|
|
341
|
-
return await this.handleInEphemeralDirectory(request, destPath);
|
|
357
|
+
return await this.handleInEphemeralDirectory(request, destPath, options);
|
|
342
358
|
} finally {
|
|
343
359
|
try {
|
|
344
360
|
await fs2.rm(destPath, { force: true, recursive: true });
|
|
@@ -363,9 +379,21 @@ var NetlifyDev = class {
|
|
|
363
379
|
projectRoot: this.#projectRoot,
|
|
364
380
|
siteID: siteID ?? "0"
|
|
365
381
|
});
|
|
366
|
-
this.#
|
|
367
|
-
|
|
368
|
-
|
|
382
|
+
this.#cleanupJobs.push(() => runtime.stop());
|
|
383
|
+
let serverAddress;
|
|
384
|
+
if (typeof this.#server === "string") {
|
|
385
|
+
serverAddress = this.#server;
|
|
386
|
+
} else if (this.#features.edgeFunctions) {
|
|
387
|
+
const passthroughServer = new HTTPServer(async (req) => {
|
|
388
|
+
const res = await this.handle(req);
|
|
389
|
+
return res ?? new Response(null, { status: 404 });
|
|
390
|
+
});
|
|
391
|
+
this.#cleanupJobs.push(() => passthroughServer.stop());
|
|
392
|
+
serverAddress = await passthroughServer.start();
|
|
393
|
+
}
|
|
394
|
+
let envVariables = {};
|
|
395
|
+
if (this.#features.environmentVariables) {
|
|
396
|
+
envVariables = await injectEnvVariables({
|
|
369
397
|
accountSlug: config?.siteInfo?.account_slug,
|
|
370
398
|
baseVariables: config?.env || {},
|
|
371
399
|
envAPI: runtime.env,
|
|
@@ -373,9 +401,74 @@ var NetlifyDev = class {
|
|
|
373
401
|
siteID
|
|
374
402
|
});
|
|
375
403
|
}
|
|
404
|
+
if (this.#features.edgeFunctions && serverAddress !== void 0) {
|
|
405
|
+
const env = Object.entries(envVariables).reduce((acc, [key, variable]) => {
|
|
406
|
+
if (variable.usedSource === "account" || variable.usedSource === "addons" || variable.usedSource === "internal" || variable.usedSource === "ui" || variable.usedSource.startsWith(".env")) {
|
|
407
|
+
return {
|
|
408
|
+
...acc,
|
|
409
|
+
[key]: variable.value
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return acc;
|
|
413
|
+
}, {});
|
|
414
|
+
this.#edgeFunctionsHandler = new EdgeFunctionsHandler({
|
|
415
|
+
configDeclarations: this.#config?.config.edge_functions ?? [],
|
|
416
|
+
directories: [this.#config?.config.build.edge_functions].filter(Boolean),
|
|
417
|
+
env,
|
|
418
|
+
geolocation: mockLocation,
|
|
419
|
+
logger: this.#logger,
|
|
420
|
+
originServerAddress: serverAddress,
|
|
421
|
+
siteID,
|
|
422
|
+
siteName: config?.siteInfo.name
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
if (this.#features.functions) {
|
|
426
|
+
const userFunctionsPath = this.#config?.config.functionsDirectory ?? path2.join(this.#projectRoot, "netlify/functions");
|
|
427
|
+
const userFunctionsPathExists = await isDirectory(userFunctionsPath);
|
|
428
|
+
this.#functionsHandler = new FunctionsHandler({
|
|
429
|
+
config: this.#config,
|
|
430
|
+
destPath: this.#functionsServePath,
|
|
431
|
+
geolocation: mockLocation,
|
|
432
|
+
projectRoot: this.#projectRoot,
|
|
433
|
+
settings: {},
|
|
434
|
+
siteId: this.#siteID,
|
|
435
|
+
timeouts: {},
|
|
436
|
+
userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (this.#features.headers) {
|
|
440
|
+
this.#headersHandler = new HeadersHandler({
|
|
441
|
+
configPath: this.#config?.configPath,
|
|
442
|
+
configHeaders: this.#config?.config.headers,
|
|
443
|
+
projectDir: this.#projectRoot,
|
|
444
|
+
publishDir: this.#config?.config.build.publish ?? void 0,
|
|
445
|
+
logger: this.#logger
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (this.#features.redirects) {
|
|
449
|
+
this.#redirectsHandler = new RedirectsHandler({
|
|
450
|
+
configPath: this.#config?.configPath,
|
|
451
|
+
configRedirects: this.#config?.config.redirects,
|
|
452
|
+
jwtRoleClaim: "",
|
|
453
|
+
jwtSecret: "",
|
|
454
|
+
notFoundHandler,
|
|
455
|
+
projectDir: this.#projectRoot
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (this.#features.static) {
|
|
459
|
+
this.#staticHandler = new StaticHandler({
|
|
460
|
+
directory: [
|
|
461
|
+
this.#config?.config.build.publish ?? this.#projectRoot,
|
|
462
|
+
...this.#staticHandlerAdditionalDirectories
|
|
463
|
+
]
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
serverAddress
|
|
468
|
+
};
|
|
376
469
|
}
|
|
377
470
|
async stop() {
|
|
378
|
-
await this.#
|
|
471
|
+
await Promise.allSettled(this.#cleanupJobs.map((task) => task()));
|
|
379
472
|
}
|
|
380
473
|
};
|
|
381
474
|
export {
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/dev",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Emulation of the Netlify environment for local development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": "
|
|
7
|
+
"node": ">=20.6.1"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/main.cjs",
|
|
10
10
|
"module": "./dist/main.js",
|
|
@@ -46,19 +46,21 @@
|
|
|
46
46
|
},
|
|
47
47
|
"author": "Netlify Inc.",
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@netlify/api": "^14.0.
|
|
50
|
-
"@netlify/types": "2.0.
|
|
49
|
+
"@netlify/api": "^14.0.3",
|
|
50
|
+
"@netlify/types": "2.0.1",
|
|
51
51
|
"tsup": "^8.0.0",
|
|
52
52
|
"vitest": "^3.0.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@netlify/blobs": "9.1.
|
|
56
|
-
"@netlify/config": "^23.0.
|
|
57
|
-
"@netlify/dev-utils": "3.
|
|
58
|
-
"@netlify/functions": "
|
|
59
|
-
"@netlify/
|
|
60
|
-
"@netlify/
|
|
61
|
-
"@netlify/
|
|
62
|
-
"@netlify/
|
|
55
|
+
"@netlify/blobs": "9.1.4",
|
|
56
|
+
"@netlify/config": "^23.0.8",
|
|
57
|
+
"@netlify/dev-utils": "3.1.0",
|
|
58
|
+
"@netlify/edge-functions": "2.13.0",
|
|
59
|
+
"@netlify/functions": "4.1.0",
|
|
60
|
+
"@netlify/headers": "2.0.0",
|
|
61
|
+
"@netlify/redirects": "3.0.0",
|
|
62
|
+
"@netlify/runtime": "4.0.0",
|
|
63
|
+
"@netlify/static": "3.0.0",
|
|
64
|
+
"ulid": "^3.0.0"
|
|
63
65
|
}
|
|
64
66
|
}
|