@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 +66 -0
- package/dist/main.cjs +122 -125
- package/dist/main.d.cts +43 -13
- package/dist/main.d.ts +43 -13
- package/dist/main.js +121 -123
- package/package.json +7 -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,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
|
-
|
|
34
|
-
start: () => start
|
|
33
|
+
NetlifyDev: () => NetlifyDev
|
|
35
34
|
});
|
|
36
35
|
module.exports = __toCommonJS(main_exports);
|
|
37
|
-
var
|
|
38
|
-
var
|
|
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 (
|
|
46
|
+
var isDirectory = async (path3) => {
|
|
52
47
|
try {
|
|
53
|
-
const stat = await import_node_fs.promises.stat(
|
|
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 (
|
|
54
|
+
var isFile = async (path3) => {
|
|
60
55
|
try {
|
|
61
|
-
const stat = await import_node_fs.promises.stat(
|
|
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
|
|
92
|
-
var
|
|
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
|
|
71
|
+
delete import_node_process.default.env[key];
|
|
99
72
|
} else {
|
|
100
|
-
|
|
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:
|
|
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] ||
|
|
115
|
-
delete
|
|
87
|
+
envSnapshot[key] = envSnapshot[key] || import_node_process.default.env[key];
|
|
88
|
+
delete import_node_process.default.env[key];
|
|
116
89
|
},
|
|
117
|
-
get: (key) =>
|
|
118
|
-
has: (key) => Boolean(
|
|
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] ||
|
|
121
|
-
|
|
93
|
+
envSnapshot[key] = envSnapshot[key] || import_node_process.default.env[key];
|
|
94
|
+
import_node_process.default.env[key] = value;
|
|
122
95
|
},
|
|
123
|
-
toObject: () =>
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
205
|
-
|
|
206
|
-
return
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
8
|
-
|
|
35
|
+
interface NetlifyDevOptions extends Features {
|
|
36
|
+
logger?: Logger;
|
|
9
37
|
projectRoot?: string;
|
|
10
38
|
}
|
|
11
|
-
declare
|
|
12
|
-
|
|
13
|
-
|
|
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 {
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
8
|
-
|
|
35
|
+
interface NetlifyDevOptions extends Features {
|
|
36
|
+
logger?: Logger;
|
|
9
37
|
projectRoot?: string;
|
|
10
38
|
}
|
|
11
|
-
declare
|
|
12
|
-
|
|
13
|
-
|
|
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 {
|
|
49
|
+
export { type Features, NetlifyDev };
|
package/dist/main.js
CHANGED
|
@@ -1,91 +1,65 @@
|
|
|
1
1
|
// src/main.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
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 (
|
|
12
|
+
var isDirectory = async (path3) => {
|
|
17
13
|
try {
|
|
18
|
-
const stat = await fs.stat(
|
|
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 (
|
|
20
|
+
var isFile = async (path3) => {
|
|
25
21
|
try {
|
|
26
|
-
const stat = await fs.stat(
|
|
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
|
|
57
|
-
import
|
|
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
|
|
37
|
+
delete process.env[key];
|
|
64
38
|
} else {
|
|
65
|
-
|
|
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:
|
|
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] ||
|
|
80
|
-
delete
|
|
53
|
+
envSnapshot[key] = envSnapshot[key] || process.env[key];
|
|
54
|
+
delete process.env[key];
|
|
81
55
|
},
|
|
82
|
-
get: (key) =>
|
|
83
|
-
has: (key) => Boolean(
|
|
56
|
+
get: (key) => process.env[key],
|
|
57
|
+
has: (key) => Boolean(process.env[key]),
|
|
84
58
|
set: (key, value) => {
|
|
85
|
-
envSnapshot[key] = envSnapshot[key] ||
|
|
86
|
-
|
|
59
|
+
envSnapshot[key] = envSnapshot[key] || process.env[key];
|
|
60
|
+
process.env[key] = value;
|
|
87
61
|
},
|
|
88
|
-
toObject: () =>
|
|
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
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
170
|
-
|
|
171
|
-
return
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
200
|
-
start
|
|
198
|
+
NetlifyDev
|
|
201
199
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/dev",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
53
|
+
"@netlify/blobs": "9.0.1",
|
|
54
54
|
"@netlify/config": "^22.0.0",
|
|
55
|
-
"@netlify/dev-utils": "2.
|
|
56
|
-
"@netlify/functions": "3.1.
|
|
57
|
-
"@netlify/redirects": "1.1.
|
|
58
|
-
"@netlify/runtime": "2.
|
|
59
|
-
"@netlify/static": "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
|
}
|