@netlify/dev 1.1.2 β†’ 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @netlify/dev
2
+
3
+ > [!WARNING] This module is under active development and does **not** yet support all Netlify platform features.
4
+
5
+ `@netlify/dev` is a local emulator for the Netlify production environment. While it can be used directly by advanced
6
+ users, it is primarily designed as a foundational library for higher-level tools like the
7
+ [Netlify CLI](https://docs.netlify.com/cli/get-started/) and the
8
+ [Netlify Vite Plugin](https://docs.netlify.com/integrations/vite/overview/).
9
+
10
+ It provides a local request pipeline that mimics the Netlify platform’s request handling, including support for
11
+ Functions, Blobs, Static files, and Redirects.
12
+
13
+ ## 🚧 Feature Support
14
+
15
+ | Feature | Supported |
16
+ | ---------------------- | --------- |
17
+ | Functions | βœ… Yes |
18
+ | Edge Functions | ❌ No |
19
+ | Blobs | βœ… Yes |
20
+ | Cache API | βœ… Yes |
21
+ | Redirects and Rewrites | βœ… Yes |
22
+ | Headers | ❌ No |
23
+ | Environment Variables | ❌ No |
24
+
25
+ > Note: Missing features will be added incrementally. This module is **not** intended to be a full replacement for the
26
+ > Netlify CLI.
27
+
28
+ ## πŸ“¦ Installation
29
+
30
+ ```bash
31
+ npm install @netlify/dev
32
+ ```
33
+
34
+ or
35
+
36
+ ```bash
37
+ yarn add @netlify/dev
38
+ ```
39
+
40
+ ## πŸš€ Usage
41
+
42
+ You can use `@netlify/dev` to emulate the Netlify runtime in your own development tooling or custom integrations:
43
+
44
+ ```ts
45
+ import { NetlifyDev } from '@netlify/dev'
46
+
47
+ const devServer = new NetlifyDev({
48
+ functions: { enabled: true },
49
+ blobs: { enabled: true },
50
+ redirects: { enabled: true },
51
+ staticFiles: { enabled: true },
52
+ })
53
+
54
+ await devServer.start()
55
+
56
+ const response = await devServer.handle(new Request('http://localhost:8888/path'))
57
+
58
+ console.log(await response.text())
59
+
60
+ await devServer.stop()
61
+ ```
62
+
63
+ ## πŸ§ͺ Contributing and feedback
64
+
65
+ This module is **experimental**, and we welcome feedback and contributions. Feel free to open issues or pull requests if
66
+ you encounter bugs or have suggestions.
package/dist/main.cjs CHANGED
@@ -30,21 +30,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/main.ts
31
31
  var main_exports = {};
32
32
  __export(main_exports, {
33
- handle: () => handle
33
+ NetlifyDev: () => NetlifyDev
34
34
  });
35
35
  module.exports = __toCommonJS(main_exports);
36
36
  var import_node_path2 = __toESM(require("path"), 1);
37
37
  var import_node_process2 = __toESM(require("process"), 1);
38
+ var import_config = require("@netlify/config");
39
+ var import_dev_utils = require("@netlify/dev-utils");
38
40
  var import_dev = require("@netlify/functions/dev");
39
41
  var import_redirects = require("@netlify/redirects");
40
42
  var import_static = require("@netlify/static");
41
43
 
42
- // src/lib/config.ts
43
- var import_node_path = __toESM(require("path"), 1);
44
- var import_node_process = __toESM(require("process"), 1);
45
- var import_config = require("@netlify/config");
46
- var import_dev_utils = require("@netlify/dev-utils");
47
-
48
44
  // src/lib/fs.ts
49
45
  var import_node_fs = require("fs");
50
46
  var isDirectory = async (path3) => {
@@ -64,85 +60,175 @@ var isFile = async (path3) => {
64
60
  return false;
65
61
  };
66
62
 
67
- // src/lib/config.ts
68
- var getConfig = async ({
69
- projectRoot
70
- }) => {
71
- const token = await (0, import_dev_utils.getAPIToken)();
72
- const state = new import_dev_utils.LocalState(projectRoot);
73
- const siteID = state.get("siteId");
74
- const configFilePath = import_node_path.default.resolve(projectRoot, "netlify.toml");
75
- const configFileExists = await isFile(configFilePath);
76
- const config = await (0, import_config.resolveConfig)({
77
- config: configFileExists ? configFilePath : void 0,
78
- repositoryRoot: projectRoot,
79
- cwd: import_node_process.default.cwd(),
80
- context: "dev",
81
- siteId: siteID,
82
- token,
83
- mode: "cli",
84
- offline: !siteID
63
+ // src/lib/runtime.ts
64
+ var import_node_path = __toESM(require("path"), 1);
65
+ var import_node_process = __toESM(require("process"), 1);
66
+ var import_server = require("@netlify/blobs/server");
67
+ var import_runtime = require("@netlify/runtime");
68
+ var restoreEnvironment = (snapshot) => {
69
+ for (const key in snapshot) {
70
+ if (snapshot[key] === void 0) {
71
+ delete import_node_process.default.env[key];
72
+ } else {
73
+ import_node_process.default.env[key] = snapshot[key];
74
+ }
75
+ }
76
+ };
77
+ var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
78
+ const blobsToken = Math.random().toString().slice(2);
79
+ const blobsServer = blobs ? new import_server.BlobsServer({
80
+ directory: import_node_path.default.join(projectRoot, ".netlify", "blobs-serve"),
81
+ token: blobsToken
82
+ }) : null;
83
+ const blobsServerDetails = await blobsServer?.start();
84
+ const envSnapshot = {};
85
+ const env = {
86
+ delete: (key) => {
87
+ envSnapshot[key] = envSnapshot[key] || import_node_process.default.env[key];
88
+ delete import_node_process.default.env[key];
89
+ },
90
+ get: (key) => import_node_process.default.env[key],
91
+ has: (key) => Boolean(import_node_process.default.env[key]),
92
+ set: (key, value) => {
93
+ envSnapshot[key] = envSnapshot[key] || import_node_process.default.env[key];
94
+ import_node_process.default.env[key] = value;
95
+ },
96
+ toObject: () => import_node_process.default.env
97
+ };
98
+ (0, import_runtime.startRuntime)({
99
+ blobs: blobsServerDetails ? {
100
+ edgeURL: `http://localhost:${blobsServerDetails.port}`,
101
+ uncachedEdgeURL: `http://localhost:${blobsServerDetails.port}`,
102
+ primaryRegion: "us-east-2",
103
+ token: blobsToken
104
+ } : void 0,
105
+ cache: {
106
+ getCacheAPIContext: () => null,
107
+ purgeToken: ""
108
+ },
109
+ deployID,
110
+ env,
111
+ getRequestContext: () => null,
112
+ siteID
85
113
  });
86
- return { config, siteID };
114
+ return {
115
+ stop: async () => {
116
+ restoreEnvironment(envSnapshot);
117
+ await blobsServer?.stop();
118
+ }
119
+ };
87
120
  };
88
121
 
89
122
  // src/main.ts
90
123
  var notFoundHandler = async () => new Response("Not found", { status: 404 });
91
- var handle = async (request, options) => {
92
- const { projectRoot = import_node_process2.default.cwd() } = options ?? {};
93
- const { config, siteID } = await getConfig({ projectRoot }) ?? {};
94
- const userFunctionsPath = config?.config.functionsDirectory ?? import_node_path2.default.join(projectRoot, "netlify/functions");
95
- const userFunctionsPathExists = await isDirectory(userFunctionsPath);
96
- const functions = new import_dev.FunctionsHandler({
97
- config,
98
- destPath: import_node_path2.default.join(projectRoot, ".netlify", "functions-serve"),
99
- projectRoot,
100
- settings: {},
101
- siteId: siteID,
102
- timeouts: {},
103
- userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
104
- });
105
- const redirects = new import_redirects.RedirectsHandler({
106
- configPath: config?.configPath,
107
- configRedirects: config?.config.redirects,
108
- jwtRoleClaim: "",
109
- jwtSecret: "",
110
- notFoundHandler,
111
- projectDir: projectRoot
112
- });
113
- const staticFiles = new import_static.StaticHandler({
114
- directory: config?.config.build.publish ?? projectRoot
115
- });
116
- const functionMatch = await functions.match(request);
117
- if (functionMatch) {
118
- if (functionMatch.preferStatic) {
119
- const staticMatch2 = await staticFiles.match(request);
120
- if (staticMatch2) {
121
- return staticMatch2.handle();
124
+ var NetlifyDev = class {
125
+ #apiToken;
126
+ #config;
127
+ #features;
128
+ #logger;
129
+ #projectRoot;
130
+ #runtime;
131
+ #siteID;
132
+ constructor(options) {
133
+ this.#features = {
134
+ blobs: options.blobs?.enabled !== false,
135
+ functions: options.functions?.enabled !== false,
136
+ redirects: options.redirects?.enabled !== false,
137
+ static: options.staticFiles?.enabled !== false
138
+ };
139
+ this.#logger = options.logger ?? globalThis.console;
140
+ this.#projectRoot = options.projectRoot ?? import_node_process2.default.cwd();
141
+ }
142
+ async getConfig() {
143
+ const configFilePath = import_node_path2.default.resolve(this.#projectRoot, "netlify.toml");
144
+ const configFileExists = await isFile(configFilePath);
145
+ const config = await (0, import_config.resolveConfig)({
146
+ config: configFileExists ? configFilePath : void 0,
147
+ repositoryRoot: this.#projectRoot,
148
+ cwd: import_node_process2.default.cwd(),
149
+ context: "dev",
150
+ siteId: this.#siteID,
151
+ token: this.#apiToken,
152
+ mode: "cli",
153
+ offline: !this.#siteID
154
+ });
155
+ return config;
156
+ }
157
+ async handle(request) {
158
+ const userFunctionsPath = this.#config?.config.functionsDirectory ?? import_node_path2.default.join(this.#projectRoot, "netlify/functions");
159
+ const userFunctionsPathExists = await isDirectory(userFunctionsPath);
160
+ const functions = this.#features.functions ? new import_dev.FunctionsHandler({
161
+ config: this.#config,
162
+ destPath: import_node_path2.default.join(this.#projectRoot, ".netlify", "functions-serve"),
163
+ projectRoot: this.#projectRoot,
164
+ settings: {},
165
+ siteId: this.#siteID,
166
+ timeouts: {},
167
+ userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
168
+ }) : null;
169
+ const redirects = this.#features.redirects ? new import_redirects.RedirectsHandler({
170
+ configPath: this.#config?.configPath,
171
+ configRedirects: this.#config?.config.redirects,
172
+ jwtRoleClaim: "",
173
+ jwtSecret: "",
174
+ notFoundHandler,
175
+ projectDir: this.#projectRoot
176
+ }) : null;
177
+ const staticFiles = this.#features.static ? new import_static.StaticHandler({
178
+ directory: this.#config?.config.build.publish ?? this.#projectRoot
179
+ }) : null;
180
+ const functionMatch = await functions?.match(request);
181
+ if (functionMatch) {
182
+ if (functionMatch.preferStatic) {
183
+ const staticMatch2 = await staticFiles?.match(request);
184
+ if (staticMatch2) {
185
+ return staticMatch2.handle();
186
+ }
122
187
  }
188
+ return functionMatch.handle(request);
123
189
  }
124
- return functionMatch.handle(request);
125
- }
126
- const redirectMatch = await redirects.match(request);
127
- if (redirectMatch) {
128
- const functionMatch2 = await functions.match(new Request(redirectMatch.target));
129
- if (functionMatch2 && !functionMatch2.preferStatic) {
130
- return functionMatch2.handle(request);
190
+ const redirectMatch = await redirects?.match(request);
191
+ if (redirectMatch) {
192
+ const functionMatch2 = await functions?.match(new Request(redirectMatch.target));
193
+ if (functionMatch2 && !functionMatch2.preferStatic) {
194
+ return functionMatch2.handle(request);
195
+ }
196
+ const response = await redirects?.handle(request, redirectMatch, async (maybeStaticFile) => {
197
+ const staticMatch2 = await staticFiles?.match(maybeStaticFile);
198
+ return staticMatch2?.handle;
199
+ });
200
+ if (response) {
201
+ return response;
202
+ }
131
203
  }
132
- const response = await redirects.handle(request, redirectMatch, async (maybeStaticFile) => {
133
- const staticMatch2 = await staticFiles.match(maybeStaticFile);
134
- return staticMatch2?.handle;
135
- });
136
- if (response) {
137
- return response;
204
+ const staticMatch = await staticFiles?.match(request);
205
+ if (staticMatch) {
206
+ return staticMatch.handle();
138
207
  }
139
208
  }
140
- const staticMatch = await staticFiles.match(request);
141
- if (staticMatch) {
142
- return staticMatch.handle();
209
+ get siteIsLinked() {
210
+ return Boolean(this.#siteID);
211
+ }
212
+ async start() {
213
+ await (0, import_dev_utils.ensureNetlifyIgnore)(this.#projectRoot, this.#logger);
214
+ const apiToken = await (0, import_dev_utils.getAPIToken)();
215
+ this.#apiToken = apiToken;
216
+ const state = new import_dev_utils.LocalState(this.#projectRoot);
217
+ const siteID = state.get("siteId");
218
+ this.#siteID = siteID;
219
+ this.#config = await this.getConfig();
220
+ this.#runtime = await getRuntime({
221
+ blobs: Boolean(this.#features.blobs),
222
+ deployID: "0",
223
+ projectRoot: this.#projectRoot,
224
+ siteID: siteID ?? "0"
225
+ });
226
+ }
227
+ async stop() {
228
+ await this.#runtime?.stop();
143
229
  }
144
230
  };
145
231
  // Annotate the CommonJS export names for ESM import in node:
146
232
  0 && (module.exports = {
147
- handle
233
+ NetlifyDev
148
234
  });
package/dist/main.d.cts CHANGED
@@ -1,6 +1,49 @@
1
- interface HandleOptions {
1
+ import { Logger } from '@netlify/dev-utils';
2
+
3
+ interface Features {
4
+ /**
5
+ * Configuration options for Netlify Blobs.
6
+ *
7
+ * {@link} https://docs.netlify.com/blobs/overview/
8
+ */
9
+ blobs?: {
10
+ enabled: boolean;
11
+ };
12
+ /**
13
+ * Configuration options for Netlify Functions.
14
+ *
15
+ * {@link} https://docs.netlify.com/functions/overview/
16
+ */
17
+ functions?: {
18
+ enabled: boolean;
19
+ };
20
+ /**
21
+ * Configuration options for Netlify redirects and rewrites.
22
+ *
23
+ * {@link} https://docs.netlify.com/routing/redirects/
24
+ */
25
+ redirects?: {
26
+ enabled: boolean;
27
+ };
28
+ /**
29
+ * Configuration options for serving static files.
30
+ */
31
+ staticFiles?: {
32
+ enabled: boolean;
33
+ };
34
+ }
35
+ interface NetlifyDevOptions extends Features {
36
+ logger?: Logger;
2
37
  projectRoot?: string;
3
38
  }
4
- declare const handle: (request: Request, options?: HandleOptions) => Promise<Response | undefined>;
39
+ declare class NetlifyDev {
40
+ #private;
41
+ constructor(options: NetlifyDevOptions);
42
+ private getConfig;
43
+ handle(request: Request): Promise<Response | undefined>;
44
+ get siteIsLinked(): boolean;
45
+ start(): Promise<void>;
46
+ stop(): Promise<void>;
47
+ }
5
48
 
6
- export { handle };
49
+ export { type Features, NetlifyDev };
package/dist/main.d.ts CHANGED
@@ -1,6 +1,49 @@
1
- interface HandleOptions {
1
+ import { Logger } from '@netlify/dev-utils';
2
+
3
+ interface Features {
4
+ /**
5
+ * Configuration options for Netlify Blobs.
6
+ *
7
+ * {@link} https://docs.netlify.com/blobs/overview/
8
+ */
9
+ blobs?: {
10
+ enabled: boolean;
11
+ };
12
+ /**
13
+ * Configuration options for Netlify Functions.
14
+ *
15
+ * {@link} https://docs.netlify.com/functions/overview/
16
+ */
17
+ functions?: {
18
+ enabled: boolean;
19
+ };
20
+ /**
21
+ * Configuration options for Netlify redirects and rewrites.
22
+ *
23
+ * {@link} https://docs.netlify.com/routing/redirects/
24
+ */
25
+ redirects?: {
26
+ enabled: boolean;
27
+ };
28
+ /**
29
+ * Configuration options for serving static files.
30
+ */
31
+ staticFiles?: {
32
+ enabled: boolean;
33
+ };
34
+ }
35
+ interface NetlifyDevOptions extends Features {
36
+ logger?: Logger;
2
37
  projectRoot?: string;
3
38
  }
4
- declare const handle: (request: Request, options?: HandleOptions) => Promise<Response | undefined>;
39
+ declare class NetlifyDev {
40
+ #private;
41
+ constructor(options: NetlifyDevOptions);
42
+ private getConfig;
43
+ handle(request: Request): Promise<Response | undefined>;
44
+ get siteIsLinked(): boolean;
45
+ start(): Promise<void>;
46
+ stop(): Promise<void>;
47
+ }
5
48
 
6
- export { handle };
49
+ export { type Features, NetlifyDev };
package/dist/main.js CHANGED
@@ -1,16 +1,12 @@
1
1
  // src/main.ts
2
2
  import path2 from "node:path";
3
3
  import process2 from "node:process";
4
+ import { resolveConfig } from "@netlify/config";
5
+ import { ensureNetlifyIgnore, getAPIToken, LocalState } from "@netlify/dev-utils";
4
6
  import { FunctionsHandler } from "@netlify/functions/dev";
5
7
  import { RedirectsHandler } from "@netlify/redirects";
6
8
  import { StaticHandler } from "@netlify/static";
7
9
 
8
- // src/lib/config.ts
9
- import path from "node:path";
10
- import process from "node:process";
11
- import { resolveConfig } from "@netlify/config";
12
- import { getAPIToken, LocalState } from "@netlify/dev-utils";
13
-
14
10
  // src/lib/fs.ts
15
11
  import { promises as fs } from "node:fs";
16
12
  var isDirectory = async (path3) => {
@@ -30,84 +26,174 @@ var isFile = async (path3) => {
30
26
  return false;
31
27
  };
32
28
 
33
- // src/lib/config.ts
34
- var getConfig = async ({
35
- projectRoot
36
- }) => {
37
- const token = await getAPIToken();
38
- const state = new LocalState(projectRoot);
39
- const siteID = state.get("siteId");
40
- const configFilePath = path.resolve(projectRoot, "netlify.toml");
41
- const configFileExists = await isFile(configFilePath);
42
- const config = await resolveConfig({
43
- config: configFileExists ? configFilePath : void 0,
44
- repositoryRoot: projectRoot,
45
- cwd: process.cwd(),
46
- context: "dev",
47
- siteId: siteID,
48
- token,
49
- mode: "cli",
50
- offline: !siteID
29
+ // src/lib/runtime.ts
30
+ import path from "node:path";
31
+ import process from "node:process";
32
+ import { BlobsServer } from "@netlify/blobs/server";
33
+ import { startRuntime } from "@netlify/runtime";
34
+ var restoreEnvironment = (snapshot) => {
35
+ for (const key in snapshot) {
36
+ if (snapshot[key] === void 0) {
37
+ delete process.env[key];
38
+ } else {
39
+ process.env[key] = snapshot[key];
40
+ }
41
+ }
42
+ };
43
+ var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
44
+ const blobsToken = Math.random().toString().slice(2);
45
+ const blobsServer = blobs ? new BlobsServer({
46
+ directory: path.join(projectRoot, ".netlify", "blobs-serve"),
47
+ token: blobsToken
48
+ }) : null;
49
+ const blobsServerDetails = await blobsServer?.start();
50
+ const envSnapshot = {};
51
+ const env = {
52
+ delete: (key) => {
53
+ envSnapshot[key] = envSnapshot[key] || process.env[key];
54
+ delete process.env[key];
55
+ },
56
+ get: (key) => process.env[key],
57
+ has: (key) => Boolean(process.env[key]),
58
+ set: (key, value) => {
59
+ envSnapshot[key] = envSnapshot[key] || process.env[key];
60
+ process.env[key] = value;
61
+ },
62
+ toObject: () => process.env
63
+ };
64
+ startRuntime({
65
+ blobs: blobsServerDetails ? {
66
+ edgeURL: `http://localhost:${blobsServerDetails.port}`,
67
+ uncachedEdgeURL: `http://localhost:${blobsServerDetails.port}`,
68
+ primaryRegion: "us-east-2",
69
+ token: blobsToken
70
+ } : void 0,
71
+ cache: {
72
+ getCacheAPIContext: () => null,
73
+ purgeToken: ""
74
+ },
75
+ deployID,
76
+ env,
77
+ getRequestContext: () => null,
78
+ siteID
51
79
  });
52
- return { config, siteID };
80
+ return {
81
+ stop: async () => {
82
+ restoreEnvironment(envSnapshot);
83
+ await blobsServer?.stop();
84
+ }
85
+ };
53
86
  };
54
87
 
55
88
  // src/main.ts
56
89
  var notFoundHandler = async () => new Response("Not found", { status: 404 });
57
- var handle = async (request, options) => {
58
- const { projectRoot = process2.cwd() } = options ?? {};
59
- const { config, siteID } = await getConfig({ projectRoot }) ?? {};
60
- const userFunctionsPath = config?.config.functionsDirectory ?? path2.join(projectRoot, "netlify/functions");
61
- const userFunctionsPathExists = await isDirectory(userFunctionsPath);
62
- const functions = new FunctionsHandler({
63
- config,
64
- destPath: path2.join(projectRoot, ".netlify", "functions-serve"),
65
- projectRoot,
66
- settings: {},
67
- siteId: siteID,
68
- timeouts: {},
69
- userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
70
- });
71
- const redirects = new RedirectsHandler({
72
- configPath: config?.configPath,
73
- configRedirects: config?.config.redirects,
74
- jwtRoleClaim: "",
75
- jwtSecret: "",
76
- notFoundHandler,
77
- projectDir: projectRoot
78
- });
79
- const staticFiles = new StaticHandler({
80
- directory: config?.config.build.publish ?? projectRoot
81
- });
82
- const functionMatch = await functions.match(request);
83
- if (functionMatch) {
84
- if (functionMatch.preferStatic) {
85
- const staticMatch2 = await staticFiles.match(request);
86
- if (staticMatch2) {
87
- return staticMatch2.handle();
90
+ var NetlifyDev = class {
91
+ #apiToken;
92
+ #config;
93
+ #features;
94
+ #logger;
95
+ #projectRoot;
96
+ #runtime;
97
+ #siteID;
98
+ constructor(options) {
99
+ this.#features = {
100
+ blobs: options.blobs?.enabled !== false,
101
+ functions: options.functions?.enabled !== false,
102
+ redirects: options.redirects?.enabled !== false,
103
+ static: options.staticFiles?.enabled !== false
104
+ };
105
+ this.#logger = options.logger ?? globalThis.console;
106
+ this.#projectRoot = options.projectRoot ?? process2.cwd();
107
+ }
108
+ async getConfig() {
109
+ const configFilePath = path2.resolve(this.#projectRoot, "netlify.toml");
110
+ const configFileExists = await isFile(configFilePath);
111
+ const config = await resolveConfig({
112
+ config: configFileExists ? configFilePath : void 0,
113
+ repositoryRoot: this.#projectRoot,
114
+ cwd: process2.cwd(),
115
+ context: "dev",
116
+ siteId: this.#siteID,
117
+ token: this.#apiToken,
118
+ mode: "cli",
119
+ offline: !this.#siteID
120
+ });
121
+ return config;
122
+ }
123
+ async handle(request) {
124
+ const userFunctionsPath = this.#config?.config.functionsDirectory ?? path2.join(this.#projectRoot, "netlify/functions");
125
+ const userFunctionsPathExists = await isDirectory(userFunctionsPath);
126
+ const functions = this.#features.functions ? new FunctionsHandler({
127
+ config: this.#config,
128
+ destPath: path2.join(this.#projectRoot, ".netlify", "functions-serve"),
129
+ projectRoot: this.#projectRoot,
130
+ settings: {},
131
+ siteId: this.#siteID,
132
+ timeouts: {},
133
+ userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
134
+ }) : null;
135
+ const redirects = this.#features.redirects ? new RedirectsHandler({
136
+ configPath: this.#config?.configPath,
137
+ configRedirects: this.#config?.config.redirects,
138
+ jwtRoleClaim: "",
139
+ jwtSecret: "",
140
+ notFoundHandler,
141
+ projectDir: this.#projectRoot
142
+ }) : null;
143
+ const staticFiles = this.#features.static ? new StaticHandler({
144
+ directory: this.#config?.config.build.publish ?? this.#projectRoot
145
+ }) : null;
146
+ const functionMatch = await functions?.match(request);
147
+ if (functionMatch) {
148
+ if (functionMatch.preferStatic) {
149
+ const staticMatch2 = await staticFiles?.match(request);
150
+ if (staticMatch2) {
151
+ return staticMatch2.handle();
152
+ }
88
153
  }
154
+ return functionMatch.handle(request);
89
155
  }
90
- return functionMatch.handle(request);
91
- }
92
- const redirectMatch = await redirects.match(request);
93
- if (redirectMatch) {
94
- const functionMatch2 = await functions.match(new Request(redirectMatch.target));
95
- if (functionMatch2 && !functionMatch2.preferStatic) {
96
- return functionMatch2.handle(request);
156
+ const redirectMatch = await redirects?.match(request);
157
+ if (redirectMatch) {
158
+ const functionMatch2 = await functions?.match(new Request(redirectMatch.target));
159
+ if (functionMatch2 && !functionMatch2.preferStatic) {
160
+ return functionMatch2.handle(request);
161
+ }
162
+ const response = await redirects?.handle(request, redirectMatch, async (maybeStaticFile) => {
163
+ const staticMatch2 = await staticFiles?.match(maybeStaticFile);
164
+ return staticMatch2?.handle;
165
+ });
166
+ if (response) {
167
+ return response;
168
+ }
97
169
  }
98
- const response = await redirects.handle(request, redirectMatch, async (maybeStaticFile) => {
99
- const staticMatch2 = await staticFiles.match(maybeStaticFile);
100
- return staticMatch2?.handle;
101
- });
102
- if (response) {
103
- return response;
170
+ const staticMatch = await staticFiles?.match(request);
171
+ if (staticMatch) {
172
+ return staticMatch.handle();
104
173
  }
105
174
  }
106
- const staticMatch = await staticFiles.match(request);
107
- if (staticMatch) {
108
- return staticMatch.handle();
175
+ get siteIsLinked() {
176
+ return Boolean(this.#siteID);
177
+ }
178
+ async start() {
179
+ await ensureNetlifyIgnore(this.#projectRoot, this.#logger);
180
+ const apiToken = await getAPIToken();
181
+ this.#apiToken = apiToken;
182
+ const state = new LocalState(this.#projectRoot);
183
+ const siteID = state.get("siteId");
184
+ this.#siteID = siteID;
185
+ this.#config = await this.getConfig();
186
+ this.#runtime = await getRuntime({
187
+ blobs: Boolean(this.#features.blobs),
188
+ deployID: "0",
189
+ projectRoot: this.#projectRoot,
190
+ siteID: siteID ?? "0"
191
+ });
192
+ }
193
+ async stop() {
194
+ await this.#runtime?.stop();
109
195
  }
110
196
  };
111
197
  export {
112
- handle
198
+ NetlifyDev
113
199
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/dev",
3
- "version": "1.1.2",
3
+ "version": "2.1.0",
4
4
  "description": "Local development emulation for Netlify",
5
5
  "type": "module",
6
6
  "engines": {
@@ -46,14 +46,16 @@
46
46
  "author": "Netlify Inc.",
47
47
  "devDependencies": {
48
48
  "tmp-promise": "^3.0.3",
49
- "tsup": "^7.2.0",
50
- "vitest": "^0.34.0"
49
+ "tsup": "^8.0.0",
50
+ "vitest": "^3.0.0"
51
51
  },
52
52
  "dependencies": {
53
+ "@netlify/blobs": "9.0.1",
53
54
  "@netlify/config": "^22.0.0",
54
- "@netlify/dev-utils": "1.1.0",
55
- "@netlify/functions": "3.1.2",
56
- "@netlify/redirects": "1.1.0",
57
- "@netlify/static": "1.1.0"
55
+ "@netlify/dev-utils": "2.1.0",
56
+ "@netlify/functions": "3.1.4",
57
+ "@netlify/redirects": "1.1.2",
58
+ "@netlify/runtime": "2.1.0",
59
+ "@netlify/static": "1.1.2"
58
60
  }
59
61
  }