@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 +66 -0
- package/dist/main.cjs +160 -74
- package/dist/main.d.cts +46 -3
- package/dist/main.d.ts +46 -3
- package/dist/main.js +159 -73
- package/package.json +9 -7
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
|
-
|
|
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/
|
|
68
|
-
var
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 {
|
|
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
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
133
|
-
|
|
134
|
-
return
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
233
|
+
NetlifyDev
|
|
148
234
|
});
|
package/dist/main.d.cts
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
|
-
|
|
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
|
|
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 {
|
|
49
|
+
export { type Features, NetlifyDev };
|
package/dist/main.d.ts
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
|
-
|
|
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
|
|
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 {
|
|
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/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 {
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
99
|
-
|
|
100
|
-
return
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
198
|
+
NetlifyDev
|
|
113
199
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/dev",
|
|
3
|
-
"version": "
|
|
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": "^
|
|
50
|
-
"vitest": "^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": "
|
|
55
|
-
"@netlify/functions": "3.1.
|
|
56
|
-
"@netlify/redirects": "1.1.
|
|
57
|
-
"@netlify/
|
|
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
|
}
|