@netlify/dev 2.0.0 β†’ 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,97 +30,70 @@ 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,
34
- start: () => start
33
+ NetlifyDev: () => NetlifyDev
35
34
  });
36
35
  module.exports = __toCommonJS(main_exports);
37
- var import_node_path3 = __toESM(require("path"), 1);
38
- var import_node_process3 = __toESM(require("process"), 1);
36
+ var import_node_path2 = __toESM(require("path"), 1);
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");
39
40
  var import_dev = require("@netlify/functions/dev");
40
41
  var import_redirects = require("@netlify/redirects");
41
42
  var import_static = require("@netlify/static");
42
43
 
43
- // src/lib/config.ts
44
- var import_node_path = __toESM(require("path"), 1);
45
- var import_node_process = __toESM(require("process"), 1);
46
- var import_config = require("@netlify/config");
47
- var import_dev_utils = require("@netlify/dev-utils");
48
-
49
44
  // src/lib/fs.ts
50
45
  var import_node_fs = require("fs");
51
- var isDirectory = async (path4) => {
46
+ var isDirectory = async (path3) => {
52
47
  try {
53
- const stat = await import_node_fs.promises.stat(path4);
48
+ const stat = await import_node_fs.promises.stat(path3);
54
49
  return stat.isDirectory();
55
50
  } catch {
56
51
  }
57
52
  return false;
58
53
  };
59
- var isFile = async (path4) => {
54
+ var isFile = async (path3) => {
60
55
  try {
61
- const stat = await import_node_fs.promises.stat(path4);
56
+ const stat = await import_node_fs.promises.stat(path3);
62
57
  return stat.isFile();
63
58
  } catch {
64
59
  }
65
60
  return false;
66
61
  };
67
62
 
68
- // src/lib/config.ts
69
- var getConfig = async ({
70
- projectRoot
71
- }) => {
72
- const token = await (0, import_dev_utils.getAPIToken)();
73
- const state = new import_dev_utils.LocalState(projectRoot);
74
- const siteID = state.get("siteId");
75
- const configFilePath = import_node_path.default.resolve(projectRoot, "netlify.toml");
76
- const configFileExists = await isFile(configFilePath);
77
- const config = await (0, import_config.resolveConfig)({
78
- config: configFileExists ? configFilePath : void 0,
79
- repositoryRoot: projectRoot,
80
- cwd: import_node_process.default.cwd(),
81
- context: "dev",
82
- siteId: siteID,
83
- token,
84
- mode: "cli",
85
- offline: !siteID
86
- });
87
- return { config, siteID };
88
- };
89
-
90
63
  // src/lib/runtime.ts
91
- var import_node_path2 = __toESM(require("path"), 1);
92
- var import_node_process2 = __toESM(require("process"), 1);
64
+ var import_node_path = __toESM(require("path"), 1);
65
+ var import_node_process = __toESM(require("process"), 1);
93
66
  var import_server = require("@netlify/blobs/server");
94
67
  var import_runtime = require("@netlify/runtime");
95
68
  var restoreEnvironment = (snapshot) => {
96
69
  for (const key in snapshot) {
97
70
  if (snapshot[key] === void 0) {
98
- delete import_node_process2.default.env[key];
71
+ delete import_node_process.default.env[key];
99
72
  } else {
100
- import_node_process2.default.env[key] = snapshot[key];
73
+ import_node_process.default.env[key] = snapshot[key];
101
74
  }
102
75
  }
103
76
  };
104
77
  var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
105
78
  const blobsToken = Math.random().toString().slice(2);
106
79
  const blobsServer = blobs ? new import_server.BlobsServer({
107
- directory: import_node_path2.default.join(projectRoot, ".netlify", "blobs-serve"),
80
+ directory: import_node_path.default.join(projectRoot, ".netlify", "blobs-serve"),
108
81
  token: blobsToken
109
82
  }) : null;
110
83
  const blobsServerDetails = await blobsServer?.start();
111
84
  const envSnapshot = {};
112
85
  const env = {
113
86
  delete: (key) => {
114
- envSnapshot[key] = envSnapshot[key] || import_node_process2.default.env[key];
115
- delete import_node_process2.default.env[key];
87
+ envSnapshot[key] = envSnapshot[key] || import_node_process.default.env[key];
88
+ delete import_node_process.default.env[key];
116
89
  },
117
- get: (key) => import_node_process2.default.env[key],
118
- has: (key) => Boolean(import_node_process2.default.env[key]),
90
+ get: (key) => import_node_process.default.env[key],
91
+ has: (key) => Boolean(import_node_process.default.env[key]),
119
92
  set: (key, value) => {
120
- envSnapshot[key] = envSnapshot[key] || import_node_process2.default.env[key];
121
- import_node_process2.default.env[key] = value;
93
+ envSnapshot[key] = envSnapshot[key] || import_node_process.default.env[key];
94
+ import_node_process.default.env[key] = value;
122
95
  },
123
- toObject: () => import_node_process2.default.env
96
+ toObject: () => import_node_process.default.env
124
97
  };
125
98
  (0, import_runtime.startRuntime)({
126
99
  blobs: blobsServerDetails ? {
@@ -147,91 +120,115 @@ var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
147
120
  };
148
121
 
149
122
  // src/main.ts
150
- var defaultFeatures = {
151
- blobs: true,
152
- functions: true,
153
- redirects: true,
154
- static: true
155
- };
156
123
  var notFoundHandler = async () => new Response("Not found", { status: 404 });
157
- var handle = async (request, options) => {
158
- const { features = defaultFeatures, projectRoot = import_node_process3.default.cwd() } = options ?? {};
159
- const { config, siteID } = await getConfig({ projectRoot }) ?? {};
160
- const userFunctionsPath = config?.config.functionsDirectory ?? import_node_path3.default.join(projectRoot, "netlify/functions");
161
- const userFunctionsPathExists = await isDirectory(userFunctionsPath);
162
- const functions = features.functions ? new import_dev.FunctionsHandler({
163
- config,
164
- destPath: import_node_path3.default.join(projectRoot, ".netlify", "functions-serve"),
165
- projectRoot,
166
- settings: {},
167
- siteId: siteID,
168
- timeouts: {},
169
- userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
170
- }) : null;
171
- const redirects = features.redirects ? new import_redirects.RedirectsHandler({
172
- configPath: config?.configPath,
173
- configRedirects: config?.config.redirects,
174
- jwtRoleClaim: "",
175
- jwtSecret: "",
176
- notFoundHandler,
177
- projectDir: projectRoot
178
- }) : null;
179
- const staticFiles = features.static ? new import_static.StaticHandler({
180
- directory: config?.config.build.publish ?? projectRoot
181
- }) : null;
182
- const runtime = await getRuntime({
183
- blobs: Boolean(features.blobs),
184
- deployID: "0",
185
- projectRoot,
186
- siteID: siteID ?? "0"
187
- });
188
- const functionMatch = await functions?.match(request);
189
- if (functionMatch) {
190
- if (functionMatch.preferStatic) {
191
- const staticMatch2 = await staticFiles?.match(request);
192
- if (staticMatch2) {
193
- 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
+ }
194
187
  }
188
+ return functionMatch.handle(request);
195
189
  }
196
- return functionMatch.handle(request);
197
- }
198
- const redirectMatch = await redirects?.match(request);
199
- if (redirectMatch) {
200
- const functionMatch2 = await functions?.match(new Request(redirectMatch.target));
201
- if (functionMatch2 && !functionMatch2.preferStatic) {
202
- 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
+ }
203
203
  }
204
- const response = await redirects?.handle(request, redirectMatch, async (maybeStaticFile) => {
205
- const staticMatch2 = await staticFiles?.match(maybeStaticFile);
206
- return staticMatch2?.handle;
207
- });
208
- if (response) {
209
- return response;
204
+ const staticMatch = await staticFiles?.match(request);
205
+ if (staticMatch) {
206
+ return staticMatch.handle();
210
207
  }
211
208
  }
212
- const staticMatch = await staticFiles?.match(request);
213
- if (staticMatch) {
214
- 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();
215
229
  }
216
- await runtime.stop();
217
- };
218
- var start = async (options) => {
219
- const { projectRoot = import_node_process3.default.cwd() } = options ?? {};
220
- const { siteID } = await getConfig({ projectRoot }) ?? {};
221
- const runtime = await getRuntime({
222
- blobs: true,
223
- deployID: "0",
224
- projectRoot,
225
- siteID: siteID ?? "0"
226
- });
227
- return {
228
- stop: async () => {
229
- await runtime.stop();
230
- }
231
- };
232
230
  };
233
231
  // Annotate the CommonJS export names for ESM import in node:
234
232
  0 && (module.exports = {
235
- handle,
236
- start
233
+ NetlifyDev
237
234
  });
package/dist/main.d.cts CHANGED
@@ -1,19 +1,49 @@
1
+ import { Logger } from '@netlify/dev-utils';
2
+
1
3
  interface Features {
2
- blobs?: boolean;
3
- functions?: boolean;
4
- redirects?: boolean;
5
- static?: boolean;
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
+ };
6
34
  }
7
- interface HandleOptions {
8
- features?: Features;
35
+ interface NetlifyDevOptions extends Features {
36
+ logger?: Logger;
9
37
  projectRoot?: string;
10
38
  }
11
- declare const handle: (request: Request, options?: HandleOptions) => Promise<Response | undefined>;
12
- interface StartOptions {
13
- projectRoot?: string;
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>;
14
47
  }
15
- declare const start: (options?: StartOptions) => Promise<{
16
- stop: () => Promise<void>;
17
- }>;
18
48
 
19
- export { handle, start };
49
+ export { type Features, NetlifyDev };
package/dist/main.d.ts CHANGED
@@ -1,19 +1,49 @@
1
+ import { Logger } from '@netlify/dev-utils';
2
+
1
3
  interface Features {
2
- blobs?: boolean;
3
- functions?: boolean;
4
- redirects?: boolean;
5
- static?: boolean;
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
+ };
6
34
  }
7
- interface HandleOptions {
8
- features?: Features;
35
+ interface NetlifyDevOptions extends Features {
36
+ logger?: Logger;
9
37
  projectRoot?: string;
10
38
  }
11
- declare const handle: (request: Request, options?: HandleOptions) => Promise<Response | undefined>;
12
- interface StartOptions {
13
- projectRoot?: string;
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>;
14
47
  }
15
- declare const start: (options?: StartOptions) => Promise<{
16
- stop: () => Promise<void>;
17
- }>;
18
48
 
19
- export { handle, start };
49
+ export { type Features, NetlifyDev };
package/dist/main.js CHANGED
@@ -1,91 +1,65 @@
1
1
  // src/main.ts
2
- import path3 from "node:path";
3
- import process3 from "node:process";
2
+ import path2 from "node:path";
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
- var isDirectory = async (path4) => {
12
+ var isDirectory = async (path3) => {
17
13
  try {
18
- const stat = await fs.stat(path4);
14
+ const stat = await fs.stat(path3);
19
15
  return stat.isDirectory();
20
16
  } catch {
21
17
  }
22
18
  return false;
23
19
  };
24
- var isFile = async (path4) => {
20
+ var isFile = async (path3) => {
25
21
  try {
26
- const stat = await fs.stat(path4);
22
+ const stat = await fs.stat(path3);
27
23
  return stat.isFile();
28
24
  } catch {
29
25
  }
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
51
- });
52
- return { config, siteID };
53
- };
54
-
55
29
  // src/lib/runtime.ts
56
- import path2 from "node:path";
57
- import process2 from "node:process";
30
+ import path from "node:path";
31
+ import process from "node:process";
58
32
  import { BlobsServer } from "@netlify/blobs/server";
59
33
  import { startRuntime } from "@netlify/runtime";
60
34
  var restoreEnvironment = (snapshot) => {
61
35
  for (const key in snapshot) {
62
36
  if (snapshot[key] === void 0) {
63
- delete process2.env[key];
37
+ delete process.env[key];
64
38
  } else {
65
- process2.env[key] = snapshot[key];
39
+ process.env[key] = snapshot[key];
66
40
  }
67
41
  }
68
42
  };
69
43
  var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
70
44
  const blobsToken = Math.random().toString().slice(2);
71
45
  const blobsServer = blobs ? new BlobsServer({
72
- directory: path2.join(projectRoot, ".netlify", "blobs-serve"),
46
+ directory: path.join(projectRoot, ".netlify", "blobs-serve"),
73
47
  token: blobsToken
74
48
  }) : null;
75
49
  const blobsServerDetails = await blobsServer?.start();
76
50
  const envSnapshot = {};
77
51
  const env = {
78
52
  delete: (key) => {
79
- envSnapshot[key] = envSnapshot[key] || process2.env[key];
80
- delete process2.env[key];
53
+ envSnapshot[key] = envSnapshot[key] || process.env[key];
54
+ delete process.env[key];
81
55
  },
82
- get: (key) => process2.env[key],
83
- has: (key) => Boolean(process2.env[key]),
56
+ get: (key) => process.env[key],
57
+ has: (key) => Boolean(process.env[key]),
84
58
  set: (key, value) => {
85
- envSnapshot[key] = envSnapshot[key] || process2.env[key];
86
- process2.env[key] = value;
59
+ envSnapshot[key] = envSnapshot[key] || process.env[key];
60
+ process.env[key] = value;
87
61
  },
88
- toObject: () => process2.env
62
+ toObject: () => process.env
89
63
  };
90
64
  startRuntime({
91
65
  blobs: blobsServerDetails ? {
@@ -112,90 +86,114 @@ var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
112
86
  };
113
87
 
114
88
  // src/main.ts
115
- var defaultFeatures = {
116
- blobs: true,
117
- functions: true,
118
- redirects: true,
119
- static: true
120
- };
121
89
  var notFoundHandler = async () => new Response("Not found", { status: 404 });
122
- var handle = async (request, options) => {
123
- const { features = defaultFeatures, projectRoot = process3.cwd() } = options ?? {};
124
- const { config, siteID } = await getConfig({ projectRoot }) ?? {};
125
- const userFunctionsPath = config?.config.functionsDirectory ?? path3.join(projectRoot, "netlify/functions");
126
- const userFunctionsPathExists = await isDirectory(userFunctionsPath);
127
- const functions = features.functions ? new FunctionsHandler({
128
- config,
129
- destPath: path3.join(projectRoot, ".netlify", "functions-serve"),
130
- projectRoot,
131
- settings: {},
132
- siteId: siteID,
133
- timeouts: {},
134
- userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : void 0
135
- }) : null;
136
- const redirects = features.redirects ? new RedirectsHandler({
137
- configPath: config?.configPath,
138
- configRedirects: config?.config.redirects,
139
- jwtRoleClaim: "",
140
- jwtSecret: "",
141
- notFoundHandler,
142
- projectDir: projectRoot
143
- }) : null;
144
- const staticFiles = features.static ? new StaticHandler({
145
- directory: config?.config.build.publish ?? projectRoot
146
- }) : null;
147
- const runtime = await getRuntime({
148
- blobs: Boolean(features.blobs),
149
- deployID: "0",
150
- projectRoot,
151
- siteID: siteID ?? "0"
152
- });
153
- const functionMatch = await functions?.match(request);
154
- if (functionMatch) {
155
- if (functionMatch.preferStatic) {
156
- const staticMatch2 = await staticFiles?.match(request);
157
- if (staticMatch2) {
158
- 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
+ }
159
153
  }
154
+ return functionMatch.handle(request);
160
155
  }
161
- return functionMatch.handle(request);
162
- }
163
- const redirectMatch = await redirects?.match(request);
164
- if (redirectMatch) {
165
- const functionMatch2 = await functions?.match(new Request(redirectMatch.target));
166
- if (functionMatch2 && !functionMatch2.preferStatic) {
167
- 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
+ }
168
169
  }
169
- const response = await redirects?.handle(request, redirectMatch, async (maybeStaticFile) => {
170
- const staticMatch2 = await staticFiles?.match(maybeStaticFile);
171
- return staticMatch2?.handle;
172
- });
173
- if (response) {
174
- return response;
170
+ const staticMatch = await staticFiles?.match(request);
171
+ if (staticMatch) {
172
+ return staticMatch.handle();
175
173
  }
176
174
  }
177
- const staticMatch = await staticFiles?.match(request);
178
- if (staticMatch) {
179
- 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();
180
195
  }
181
- await runtime.stop();
182
- };
183
- var start = async (options) => {
184
- const { projectRoot = process3.cwd() } = options ?? {};
185
- const { siteID } = await getConfig({ projectRoot }) ?? {};
186
- const runtime = await getRuntime({
187
- blobs: true,
188
- deployID: "0",
189
- projectRoot,
190
- siteID: siteID ?? "0"
191
- });
192
- return {
193
- stop: async () => {
194
- await runtime.stop();
195
- }
196
- };
197
196
  };
198
197
  export {
199
- handle,
200
- start
198
+ NetlifyDev
201
199
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/dev",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Local development emulation for Netlify",
5
5
  "type": "module",
6
6
  "engines": {
@@ -50,12 +50,12 @@
50
50
  "vitest": "^3.0.0"
51
51
  },
52
52
  "dependencies": {
53
- "@netlify/blobs": "9.0.0",
53
+ "@netlify/blobs": "9.0.1",
54
54
  "@netlify/config": "^22.0.0",
55
- "@netlify/dev-utils": "2.0.0",
56
- "@netlify/functions": "3.1.3",
57
- "@netlify/redirects": "1.1.1",
58
- "@netlify/runtime": "2.0.0",
59
- "@netlify/static": "1.1.1"
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"
60
60
  }
61
61
  }