@netlify/dev 2.2.2 → 2.3.1
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 +1 -1
- package/dist/main.cjs +190 -21
- package/dist/main.d.cts +11 -0
- package/dist/main.d.ts +11 -0
- package/dist/main.js +190 -21
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Functions, Blobs, Static files, and Redirects.
|
|
|
21
21
|
| Cache API | ✅ Yes |
|
|
22
22
|
| Redirects and Rewrites | ✅ Yes |
|
|
23
23
|
| Headers | ❌ No |
|
|
24
|
-
| Environment Variables |
|
|
24
|
+
| Environment Variables | ✅ Yes |
|
|
25
25
|
|
|
26
26
|
> Note: Missing features will be added incrementally. This module is **not** intended to be a full replacement for the
|
|
27
27
|
> Netlify CLI.
|
package/dist/main.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(main_exports, {
|
|
|
33
33
|
NetlifyDev: () => NetlifyDev
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(main_exports);
|
|
36
|
+
var import_node_fs2 = require("fs");
|
|
36
37
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
37
38
|
var import_node_process2 = __toESM(require("process"), 1);
|
|
38
39
|
var import_config = require("@netlify/config");
|
|
@@ -41,6 +42,139 @@ var import_dev = require("@netlify/functions/dev");
|
|
|
41
42
|
var import_redirects = require("@netlify/redirects");
|
|
42
43
|
var import_static = require("@netlify/static");
|
|
43
44
|
|
|
45
|
+
// src/lib/env.ts
|
|
46
|
+
var SUPPORTED_CONTEXTS = ["all", "production", "deploy-preview", "branch-deploy", "dev"];
|
|
47
|
+
var injectEnvVariables = async ({
|
|
48
|
+
accountSlug,
|
|
49
|
+
baseVariables = {},
|
|
50
|
+
envAPI,
|
|
51
|
+
netlifyAPI,
|
|
52
|
+
siteID
|
|
53
|
+
}) => {
|
|
54
|
+
const results = {};
|
|
55
|
+
let variables = baseVariables;
|
|
56
|
+
if (netlifyAPI && accountSlug) {
|
|
57
|
+
variables = await getEnvelopeEnv({
|
|
58
|
+
accountId: accountSlug,
|
|
59
|
+
api: netlifyAPI,
|
|
60
|
+
env: baseVariables,
|
|
61
|
+
siteId: siteID
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
for (const [key, variable] of Object.entries(variables)) {
|
|
65
|
+
const existsInProcess = envAPI.has(key);
|
|
66
|
+
const [usedSource, ...overriddenSources] = existsInProcess ? ["process", ...variable.sources] : variable.sources;
|
|
67
|
+
const isInternal = variable.sources.includes("internal");
|
|
68
|
+
const result = {
|
|
69
|
+
isInternal,
|
|
70
|
+
originalValue: envAPI.get(key),
|
|
71
|
+
overriddenSources,
|
|
72
|
+
usedSource
|
|
73
|
+
};
|
|
74
|
+
if (!existsInProcess || isInternal) {
|
|
75
|
+
envAPI.set(key, variable.value);
|
|
76
|
+
}
|
|
77
|
+
results[key] = result;
|
|
78
|
+
}
|
|
79
|
+
return results;
|
|
80
|
+
};
|
|
81
|
+
var getValueForContext = (values, contextOrBranch) => {
|
|
82
|
+
const isSupportedContext = SUPPORTED_CONTEXTS.includes(contextOrBranch);
|
|
83
|
+
if (!isSupportedContext) {
|
|
84
|
+
const valueMatchingAsBranch = values.find((val) => val.context_parameter === contextOrBranch);
|
|
85
|
+
if (valueMatchingAsBranch != null) {
|
|
86
|
+
return valueMatchingAsBranch;
|
|
87
|
+
}
|
|
88
|
+
const valueMatchingContext = values.find((val) => val.context === "all" || val.context === "branch-deploy");
|
|
89
|
+
return valueMatchingContext ?? void 0;
|
|
90
|
+
}
|
|
91
|
+
const valueMatchingAsContext = values.find((val) => val.context === "all" || val.context === contextOrBranch);
|
|
92
|
+
return valueMatchingAsContext ?? void 0;
|
|
93
|
+
};
|
|
94
|
+
var filterEnvBySource = (env, source) => Object.fromEntries(Object.entries(env).filter(([, variable]) => variable.sources[0] === source));
|
|
95
|
+
var fetchEnvelopeItems = async function({
|
|
96
|
+
accountId,
|
|
97
|
+
api,
|
|
98
|
+
key,
|
|
99
|
+
siteId
|
|
100
|
+
}) {
|
|
101
|
+
if (accountId === void 0) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
if (key) {
|
|
106
|
+
const envelopeItem = await api.getEnvVar({ accountId, key, siteId });
|
|
107
|
+
return [envelopeItem];
|
|
108
|
+
}
|
|
109
|
+
const envelopeItems = await api.getEnvVars({ accountId, siteId });
|
|
110
|
+
return envelopeItems;
|
|
111
|
+
} catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var formatEnvelopeData = ({
|
|
116
|
+
context = "dev",
|
|
117
|
+
envelopeItems = [],
|
|
118
|
+
scope = "any",
|
|
119
|
+
source
|
|
120
|
+
}) => envelopeItems.filter(({ values }) => Boolean(getValueForContext(values, context))).filter(({ scopes }) => scope === "any" ? true : scopes.includes(scope)).sort((left, right) => left.key.toLowerCase() < right.key.toLowerCase() ? -1 : 1).reduce((acc, cur) => {
|
|
121
|
+
const val = getValueForContext(cur.values, context);
|
|
122
|
+
if (val === void 0) {
|
|
123
|
+
throw new TypeError(`failed to locate environment variable value for ${context} context`);
|
|
124
|
+
}
|
|
125
|
+
const { context: itemContext, context_parameter: branch, value } = val;
|
|
126
|
+
return {
|
|
127
|
+
...acc,
|
|
128
|
+
[cur.key]: {
|
|
129
|
+
context: itemContext,
|
|
130
|
+
branch,
|
|
131
|
+
scopes: cur.scopes,
|
|
132
|
+
sources: [source],
|
|
133
|
+
value
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}, {});
|
|
137
|
+
var getEnvelopeEnv = async ({
|
|
138
|
+
accountId,
|
|
139
|
+
api,
|
|
140
|
+
context = "dev",
|
|
141
|
+
env,
|
|
142
|
+
key = "",
|
|
143
|
+
raw = false,
|
|
144
|
+
scope = "any",
|
|
145
|
+
siteId
|
|
146
|
+
}) => {
|
|
147
|
+
const [accountEnvelopeItems, siteEnvelopeItems] = await Promise.all([
|
|
148
|
+
fetchEnvelopeItems({ api, accountId, key }),
|
|
149
|
+
fetchEnvelopeItems({ api, accountId, key, siteId })
|
|
150
|
+
]);
|
|
151
|
+
const accountEnv = formatEnvelopeData({ context, envelopeItems: accountEnvelopeItems, scope, source: "account" });
|
|
152
|
+
const siteEnv = formatEnvelopeData({ context, envelopeItems: siteEnvelopeItems, scope, source: "ui" });
|
|
153
|
+
if (raw) {
|
|
154
|
+
const entries = Object.entries({ ...accountEnv, ...siteEnv });
|
|
155
|
+
return entries.reduce(
|
|
156
|
+
(obj, [envVarKey, metadata]) => ({
|
|
157
|
+
...obj,
|
|
158
|
+
[envVarKey]: metadata.value
|
|
159
|
+
}),
|
|
160
|
+
{}
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const generalEnv = filterEnvBySource(env, "general");
|
|
164
|
+
const internalEnv = filterEnvBySource(env, "internal");
|
|
165
|
+
const addonsEnv = filterEnvBySource(env, "addons");
|
|
166
|
+
const configFileEnv = filterEnvBySource(env, "configFile");
|
|
167
|
+
const includeConfigEnvVars = /any|builds|post[-_]processing/.test(scope);
|
|
168
|
+
return {
|
|
169
|
+
...generalEnv,
|
|
170
|
+
...accountEnv,
|
|
171
|
+
...includeConfigEnvVars ? addonsEnv : {},
|
|
172
|
+
...siteEnv,
|
|
173
|
+
...includeConfigEnvVars ? configFileEnv : {},
|
|
174
|
+
...internalEnv
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
44
178
|
// src/lib/fs.ts
|
|
45
179
|
var import_node_fs = require("fs");
|
|
46
180
|
var isDirectory = async (path3) => {
|
|
@@ -112,6 +246,7 @@ var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
|
|
|
112
246
|
siteID
|
|
113
247
|
});
|
|
114
248
|
return {
|
|
249
|
+
env,
|
|
115
250
|
stop: async () => {
|
|
116
251
|
restoreEnvironment(envSnapshot);
|
|
117
252
|
await blobsServer?.stop();
|
|
@@ -122,6 +257,8 @@ var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
|
|
|
122
257
|
// src/main.ts
|
|
123
258
|
var notFoundHandler = async () => new Response("Not found", { status: 404 });
|
|
124
259
|
var NetlifyDev = class {
|
|
260
|
+
#apiHost;
|
|
261
|
+
#apiScheme;
|
|
125
262
|
#apiToken;
|
|
126
263
|
#config;
|
|
127
264
|
#features;
|
|
@@ -130,8 +267,15 @@ var NetlifyDev = class {
|
|
|
130
267
|
#runtime;
|
|
131
268
|
#siteID;
|
|
132
269
|
constructor(options) {
|
|
270
|
+
if (options.apiURL) {
|
|
271
|
+
const apiURL = new URL(options.apiURL);
|
|
272
|
+
this.#apiHost = apiURL.host;
|
|
273
|
+
this.#apiScheme = apiURL.protocol.slice(0, -1);
|
|
274
|
+
}
|
|
275
|
+
this.#apiToken = options.apiToken;
|
|
133
276
|
this.#features = {
|
|
134
277
|
blobs: options.blobs?.enabled !== false,
|
|
278
|
+
environmentVariables: options.environmentVariables?.enabled !== false,
|
|
135
279
|
functions: options.functions?.enabled !== false,
|
|
136
280
|
redirects: options.redirects?.enabled !== false,
|
|
137
281
|
static: options.staticFiles?.enabled !== false
|
|
@@ -139,27 +283,12 @@ var NetlifyDev = class {
|
|
|
139
283
|
this.#logger = options.logger ?? globalThis.console;
|
|
140
284
|
this.#projectRoot = options.projectRoot ?? import_node_process2.default.cwd();
|
|
141
285
|
}
|
|
142
|
-
async
|
|
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) {
|
|
286
|
+
async handleInEphemeralDirectory(request, destPath) {
|
|
158
287
|
const userFunctionsPath = this.#config?.config.functionsDirectory ?? import_node_path2.default.join(this.#projectRoot, "netlify/functions");
|
|
159
288
|
const userFunctionsPathExists = await isDirectory(userFunctionsPath);
|
|
160
289
|
const functions = this.#features.functions ? new import_dev.FunctionsHandler({
|
|
161
290
|
config: this.#config,
|
|
162
|
-
destPath
|
|
291
|
+
destPath,
|
|
163
292
|
projectRoot: this.#projectRoot,
|
|
164
293
|
settings: {},
|
|
165
294
|
siteId: this.#siteID,
|
|
@@ -206,23 +335,63 @@ var NetlifyDev = class {
|
|
|
206
335
|
return staticMatch.handle();
|
|
207
336
|
}
|
|
208
337
|
}
|
|
338
|
+
async getConfig() {
|
|
339
|
+
const configFilePath = import_node_path2.default.resolve(this.#projectRoot, "netlify.toml");
|
|
340
|
+
const configFileExists = await isFile(configFilePath);
|
|
341
|
+
const config = await (0, import_config.resolveConfig)({
|
|
342
|
+
config: configFileExists ? configFilePath : void 0,
|
|
343
|
+
context: "dev",
|
|
344
|
+
cwd: import_node_process2.default.cwd(),
|
|
345
|
+
host: this.#apiHost,
|
|
346
|
+
offline: !this.#siteID,
|
|
347
|
+
mode: "cli",
|
|
348
|
+
repositoryRoot: this.#projectRoot,
|
|
349
|
+
scheme: this.#apiScheme,
|
|
350
|
+
siteId: this.#siteID,
|
|
351
|
+
token: this.#apiToken
|
|
352
|
+
});
|
|
353
|
+
return config;
|
|
354
|
+
}
|
|
355
|
+
async handle(request) {
|
|
356
|
+
const servePath = import_node_path2.default.join(this.#projectRoot, ".netlify", "functions-serve");
|
|
357
|
+
await import_node_fs2.promises.mkdir(servePath, { recursive: true });
|
|
358
|
+
const destPath = await import_node_fs2.promises.mkdtemp(import_node_path2.default.join(servePath, "_"));
|
|
359
|
+
try {
|
|
360
|
+
return await this.handleInEphemeralDirectory(request, destPath);
|
|
361
|
+
} finally {
|
|
362
|
+
try {
|
|
363
|
+
await import_node_fs2.promises.rm(destPath, { force: true, recursive: true });
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
209
368
|
get siteIsLinked() {
|
|
210
369
|
return Boolean(this.#siteID);
|
|
211
370
|
}
|
|
212
371
|
async start() {
|
|
213
372
|
await (0, import_dev_utils.ensureNetlifyIgnore)(this.#projectRoot, this.#logger);
|
|
214
|
-
|
|
215
|
-
this.#apiToken = apiToken;
|
|
373
|
+
this.#apiToken = this.#apiToken ?? await (0, import_dev_utils.getAPIToken)();
|
|
216
374
|
const state = new import_dev_utils.LocalState(this.#projectRoot);
|
|
217
375
|
const siteID = state.get("siteId");
|
|
218
376
|
this.#siteID = siteID;
|
|
219
|
-
|
|
220
|
-
this.#
|
|
377
|
+
const config = await this.getConfig();
|
|
378
|
+
this.#config = config;
|
|
379
|
+
const runtime = await getRuntime({
|
|
221
380
|
blobs: Boolean(this.#features.blobs),
|
|
222
381
|
deployID: "0",
|
|
223
382
|
projectRoot: this.#projectRoot,
|
|
224
383
|
siteID: siteID ?? "0"
|
|
225
384
|
});
|
|
385
|
+
this.#runtime = runtime;
|
|
386
|
+
if (this.#features.environmentVariables && siteID) {
|
|
387
|
+
await injectEnvVariables({
|
|
388
|
+
accountSlug: config?.siteInfo?.account_slug,
|
|
389
|
+
baseVariables: config?.env || {},
|
|
390
|
+
envAPI: runtime.env,
|
|
391
|
+
netlifyAPI: config?.api,
|
|
392
|
+
siteID
|
|
393
|
+
});
|
|
394
|
+
}
|
|
226
395
|
}
|
|
227
396
|
async stop() {
|
|
228
397
|
await this.#runtime?.stop();
|
package/dist/main.d.cts
CHANGED
|
@@ -9,6 +9,14 @@ interface Features {
|
|
|
9
9
|
blobs?: {
|
|
10
10
|
enabled: boolean;
|
|
11
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for environment variables.
|
|
14
|
+
*
|
|
15
|
+
* {@link} https://docs.netlify.com/environment-variables/overview/
|
|
16
|
+
*/
|
|
17
|
+
environmentVariables?: {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
};
|
|
12
20
|
/**
|
|
13
21
|
* Configuration options for Netlify Functions.
|
|
14
22
|
*
|
|
@@ -33,12 +41,15 @@ interface Features {
|
|
|
33
41
|
};
|
|
34
42
|
}
|
|
35
43
|
interface NetlifyDevOptions extends Features {
|
|
44
|
+
apiURL?: string;
|
|
45
|
+
apiToken?: string;
|
|
36
46
|
logger?: Logger;
|
|
37
47
|
projectRoot?: string;
|
|
38
48
|
}
|
|
39
49
|
declare class NetlifyDev {
|
|
40
50
|
#private;
|
|
41
51
|
constructor(options: NetlifyDevOptions);
|
|
52
|
+
private handleInEphemeralDirectory;
|
|
42
53
|
private getConfig;
|
|
43
54
|
handle(request: Request): Promise<Response | undefined>;
|
|
44
55
|
get siteIsLinked(): boolean;
|
package/dist/main.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ interface Features {
|
|
|
9
9
|
blobs?: {
|
|
10
10
|
enabled: boolean;
|
|
11
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for environment variables.
|
|
14
|
+
*
|
|
15
|
+
* {@link} https://docs.netlify.com/environment-variables/overview/
|
|
16
|
+
*/
|
|
17
|
+
environmentVariables?: {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
};
|
|
12
20
|
/**
|
|
13
21
|
* Configuration options for Netlify Functions.
|
|
14
22
|
*
|
|
@@ -33,12 +41,15 @@ interface Features {
|
|
|
33
41
|
};
|
|
34
42
|
}
|
|
35
43
|
interface NetlifyDevOptions extends Features {
|
|
44
|
+
apiURL?: string;
|
|
45
|
+
apiToken?: string;
|
|
36
46
|
logger?: Logger;
|
|
37
47
|
projectRoot?: string;
|
|
38
48
|
}
|
|
39
49
|
declare class NetlifyDev {
|
|
40
50
|
#private;
|
|
41
51
|
constructor(options: NetlifyDevOptions);
|
|
52
|
+
private handleInEphemeralDirectory;
|
|
42
53
|
private getConfig;
|
|
43
54
|
handle(request: Request): Promise<Response | undefined>;
|
|
44
55
|
get siteIsLinked(): boolean;
|
package/dist/main.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/main.ts
|
|
2
|
+
import { promises as fs2 } from "node:fs";
|
|
2
3
|
import path2 from "node:path";
|
|
3
4
|
import process2 from "node:process";
|
|
4
5
|
import { resolveConfig } from "@netlify/config";
|
|
@@ -7,6 +8,139 @@ import { FunctionsHandler } from "@netlify/functions/dev";
|
|
|
7
8
|
import { RedirectsHandler } from "@netlify/redirects";
|
|
8
9
|
import { StaticHandler } from "@netlify/static";
|
|
9
10
|
|
|
11
|
+
// src/lib/env.ts
|
|
12
|
+
var SUPPORTED_CONTEXTS = ["all", "production", "deploy-preview", "branch-deploy", "dev"];
|
|
13
|
+
var injectEnvVariables = async ({
|
|
14
|
+
accountSlug,
|
|
15
|
+
baseVariables = {},
|
|
16
|
+
envAPI,
|
|
17
|
+
netlifyAPI,
|
|
18
|
+
siteID
|
|
19
|
+
}) => {
|
|
20
|
+
const results = {};
|
|
21
|
+
let variables = baseVariables;
|
|
22
|
+
if (netlifyAPI && accountSlug) {
|
|
23
|
+
variables = await getEnvelopeEnv({
|
|
24
|
+
accountId: accountSlug,
|
|
25
|
+
api: netlifyAPI,
|
|
26
|
+
env: baseVariables,
|
|
27
|
+
siteId: siteID
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
for (const [key, variable] of Object.entries(variables)) {
|
|
31
|
+
const existsInProcess = envAPI.has(key);
|
|
32
|
+
const [usedSource, ...overriddenSources] = existsInProcess ? ["process", ...variable.sources] : variable.sources;
|
|
33
|
+
const isInternal = variable.sources.includes("internal");
|
|
34
|
+
const result = {
|
|
35
|
+
isInternal,
|
|
36
|
+
originalValue: envAPI.get(key),
|
|
37
|
+
overriddenSources,
|
|
38
|
+
usedSource
|
|
39
|
+
};
|
|
40
|
+
if (!existsInProcess || isInternal) {
|
|
41
|
+
envAPI.set(key, variable.value);
|
|
42
|
+
}
|
|
43
|
+
results[key] = result;
|
|
44
|
+
}
|
|
45
|
+
return results;
|
|
46
|
+
};
|
|
47
|
+
var getValueForContext = (values, contextOrBranch) => {
|
|
48
|
+
const isSupportedContext = SUPPORTED_CONTEXTS.includes(contextOrBranch);
|
|
49
|
+
if (!isSupportedContext) {
|
|
50
|
+
const valueMatchingAsBranch = values.find((val) => val.context_parameter === contextOrBranch);
|
|
51
|
+
if (valueMatchingAsBranch != null) {
|
|
52
|
+
return valueMatchingAsBranch;
|
|
53
|
+
}
|
|
54
|
+
const valueMatchingContext = values.find((val) => val.context === "all" || val.context === "branch-deploy");
|
|
55
|
+
return valueMatchingContext ?? void 0;
|
|
56
|
+
}
|
|
57
|
+
const valueMatchingAsContext = values.find((val) => val.context === "all" || val.context === contextOrBranch);
|
|
58
|
+
return valueMatchingAsContext ?? void 0;
|
|
59
|
+
};
|
|
60
|
+
var filterEnvBySource = (env, source) => Object.fromEntries(Object.entries(env).filter(([, variable]) => variable.sources[0] === source));
|
|
61
|
+
var fetchEnvelopeItems = async function({
|
|
62
|
+
accountId,
|
|
63
|
+
api,
|
|
64
|
+
key,
|
|
65
|
+
siteId
|
|
66
|
+
}) {
|
|
67
|
+
if (accountId === void 0) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
if (key) {
|
|
72
|
+
const envelopeItem = await api.getEnvVar({ accountId, key, siteId });
|
|
73
|
+
return [envelopeItem];
|
|
74
|
+
}
|
|
75
|
+
const envelopeItems = await api.getEnvVars({ accountId, siteId });
|
|
76
|
+
return envelopeItems;
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var formatEnvelopeData = ({
|
|
82
|
+
context = "dev",
|
|
83
|
+
envelopeItems = [],
|
|
84
|
+
scope = "any",
|
|
85
|
+
source
|
|
86
|
+
}) => envelopeItems.filter(({ values }) => Boolean(getValueForContext(values, context))).filter(({ scopes }) => scope === "any" ? true : scopes.includes(scope)).sort((left, right) => left.key.toLowerCase() < right.key.toLowerCase() ? -1 : 1).reduce((acc, cur) => {
|
|
87
|
+
const val = getValueForContext(cur.values, context);
|
|
88
|
+
if (val === void 0) {
|
|
89
|
+
throw new TypeError(`failed to locate environment variable value for ${context} context`);
|
|
90
|
+
}
|
|
91
|
+
const { context: itemContext, context_parameter: branch, value } = val;
|
|
92
|
+
return {
|
|
93
|
+
...acc,
|
|
94
|
+
[cur.key]: {
|
|
95
|
+
context: itemContext,
|
|
96
|
+
branch,
|
|
97
|
+
scopes: cur.scopes,
|
|
98
|
+
sources: [source],
|
|
99
|
+
value
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}, {});
|
|
103
|
+
var getEnvelopeEnv = async ({
|
|
104
|
+
accountId,
|
|
105
|
+
api,
|
|
106
|
+
context = "dev",
|
|
107
|
+
env,
|
|
108
|
+
key = "",
|
|
109
|
+
raw = false,
|
|
110
|
+
scope = "any",
|
|
111
|
+
siteId
|
|
112
|
+
}) => {
|
|
113
|
+
const [accountEnvelopeItems, siteEnvelopeItems] = await Promise.all([
|
|
114
|
+
fetchEnvelopeItems({ api, accountId, key }),
|
|
115
|
+
fetchEnvelopeItems({ api, accountId, key, siteId })
|
|
116
|
+
]);
|
|
117
|
+
const accountEnv = formatEnvelopeData({ context, envelopeItems: accountEnvelopeItems, scope, source: "account" });
|
|
118
|
+
const siteEnv = formatEnvelopeData({ context, envelopeItems: siteEnvelopeItems, scope, source: "ui" });
|
|
119
|
+
if (raw) {
|
|
120
|
+
const entries = Object.entries({ ...accountEnv, ...siteEnv });
|
|
121
|
+
return entries.reduce(
|
|
122
|
+
(obj, [envVarKey, metadata]) => ({
|
|
123
|
+
...obj,
|
|
124
|
+
[envVarKey]: metadata.value
|
|
125
|
+
}),
|
|
126
|
+
{}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const generalEnv = filterEnvBySource(env, "general");
|
|
130
|
+
const internalEnv = filterEnvBySource(env, "internal");
|
|
131
|
+
const addonsEnv = filterEnvBySource(env, "addons");
|
|
132
|
+
const configFileEnv = filterEnvBySource(env, "configFile");
|
|
133
|
+
const includeConfigEnvVars = /any|builds|post[-_]processing/.test(scope);
|
|
134
|
+
return {
|
|
135
|
+
...generalEnv,
|
|
136
|
+
...accountEnv,
|
|
137
|
+
...includeConfigEnvVars ? addonsEnv : {},
|
|
138
|
+
...siteEnv,
|
|
139
|
+
...includeConfigEnvVars ? configFileEnv : {},
|
|
140
|
+
...internalEnv
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
10
144
|
// src/lib/fs.ts
|
|
11
145
|
import { promises as fs } from "node:fs";
|
|
12
146
|
var isDirectory = async (path3) => {
|
|
@@ -78,6 +212,7 @@ var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
|
|
|
78
212
|
siteID
|
|
79
213
|
});
|
|
80
214
|
return {
|
|
215
|
+
env,
|
|
81
216
|
stop: async () => {
|
|
82
217
|
restoreEnvironment(envSnapshot);
|
|
83
218
|
await blobsServer?.stop();
|
|
@@ -88,6 +223,8 @@ var getRuntime = async ({ blobs, deployID, projectRoot, siteID }) => {
|
|
|
88
223
|
// src/main.ts
|
|
89
224
|
var notFoundHandler = async () => new Response("Not found", { status: 404 });
|
|
90
225
|
var NetlifyDev = class {
|
|
226
|
+
#apiHost;
|
|
227
|
+
#apiScheme;
|
|
91
228
|
#apiToken;
|
|
92
229
|
#config;
|
|
93
230
|
#features;
|
|
@@ -96,8 +233,15 @@ var NetlifyDev = class {
|
|
|
96
233
|
#runtime;
|
|
97
234
|
#siteID;
|
|
98
235
|
constructor(options) {
|
|
236
|
+
if (options.apiURL) {
|
|
237
|
+
const apiURL = new URL(options.apiURL);
|
|
238
|
+
this.#apiHost = apiURL.host;
|
|
239
|
+
this.#apiScheme = apiURL.protocol.slice(0, -1);
|
|
240
|
+
}
|
|
241
|
+
this.#apiToken = options.apiToken;
|
|
99
242
|
this.#features = {
|
|
100
243
|
blobs: options.blobs?.enabled !== false,
|
|
244
|
+
environmentVariables: options.environmentVariables?.enabled !== false,
|
|
101
245
|
functions: options.functions?.enabled !== false,
|
|
102
246
|
redirects: options.redirects?.enabled !== false,
|
|
103
247
|
static: options.staticFiles?.enabled !== false
|
|
@@ -105,27 +249,12 @@ var NetlifyDev = class {
|
|
|
105
249
|
this.#logger = options.logger ?? globalThis.console;
|
|
106
250
|
this.#projectRoot = options.projectRoot ?? process2.cwd();
|
|
107
251
|
}
|
|
108
|
-
async
|
|
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) {
|
|
252
|
+
async handleInEphemeralDirectory(request, destPath) {
|
|
124
253
|
const userFunctionsPath = this.#config?.config.functionsDirectory ?? path2.join(this.#projectRoot, "netlify/functions");
|
|
125
254
|
const userFunctionsPathExists = await isDirectory(userFunctionsPath);
|
|
126
255
|
const functions = this.#features.functions ? new FunctionsHandler({
|
|
127
256
|
config: this.#config,
|
|
128
|
-
destPath
|
|
257
|
+
destPath,
|
|
129
258
|
projectRoot: this.#projectRoot,
|
|
130
259
|
settings: {},
|
|
131
260
|
siteId: this.#siteID,
|
|
@@ -172,23 +301,63 @@ var NetlifyDev = class {
|
|
|
172
301
|
return staticMatch.handle();
|
|
173
302
|
}
|
|
174
303
|
}
|
|
304
|
+
async getConfig() {
|
|
305
|
+
const configFilePath = path2.resolve(this.#projectRoot, "netlify.toml");
|
|
306
|
+
const configFileExists = await isFile(configFilePath);
|
|
307
|
+
const config = await resolveConfig({
|
|
308
|
+
config: configFileExists ? configFilePath : void 0,
|
|
309
|
+
context: "dev",
|
|
310
|
+
cwd: process2.cwd(),
|
|
311
|
+
host: this.#apiHost,
|
|
312
|
+
offline: !this.#siteID,
|
|
313
|
+
mode: "cli",
|
|
314
|
+
repositoryRoot: this.#projectRoot,
|
|
315
|
+
scheme: this.#apiScheme,
|
|
316
|
+
siteId: this.#siteID,
|
|
317
|
+
token: this.#apiToken
|
|
318
|
+
});
|
|
319
|
+
return config;
|
|
320
|
+
}
|
|
321
|
+
async handle(request) {
|
|
322
|
+
const servePath = path2.join(this.#projectRoot, ".netlify", "functions-serve");
|
|
323
|
+
await fs2.mkdir(servePath, { recursive: true });
|
|
324
|
+
const destPath = await fs2.mkdtemp(path2.join(servePath, "_"));
|
|
325
|
+
try {
|
|
326
|
+
return await this.handleInEphemeralDirectory(request, destPath);
|
|
327
|
+
} finally {
|
|
328
|
+
try {
|
|
329
|
+
await fs2.rm(destPath, { force: true, recursive: true });
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
175
334
|
get siteIsLinked() {
|
|
176
335
|
return Boolean(this.#siteID);
|
|
177
336
|
}
|
|
178
337
|
async start() {
|
|
179
338
|
await ensureNetlifyIgnore(this.#projectRoot, this.#logger);
|
|
180
|
-
|
|
181
|
-
this.#apiToken = apiToken;
|
|
339
|
+
this.#apiToken = this.#apiToken ?? await getAPIToken();
|
|
182
340
|
const state = new LocalState(this.#projectRoot);
|
|
183
341
|
const siteID = state.get("siteId");
|
|
184
342
|
this.#siteID = siteID;
|
|
185
|
-
|
|
186
|
-
this.#
|
|
343
|
+
const config = await this.getConfig();
|
|
344
|
+
this.#config = config;
|
|
345
|
+
const runtime = await getRuntime({
|
|
187
346
|
blobs: Boolean(this.#features.blobs),
|
|
188
347
|
deployID: "0",
|
|
189
348
|
projectRoot: this.#projectRoot,
|
|
190
349
|
siteID: siteID ?? "0"
|
|
191
350
|
});
|
|
351
|
+
this.#runtime = runtime;
|
|
352
|
+
if (this.#features.environmentVariables && siteID) {
|
|
353
|
+
await injectEnvVariables({
|
|
354
|
+
accountSlug: config?.siteInfo?.account_slug,
|
|
355
|
+
baseVariables: config?.env || {},
|
|
356
|
+
envAPI: runtime.env,
|
|
357
|
+
netlifyAPI: config?.api,
|
|
358
|
+
siteID
|
|
359
|
+
});
|
|
360
|
+
}
|
|
192
361
|
}
|
|
193
362
|
async stop() {
|
|
194
363
|
await this.#runtime?.stop();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/dev",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Emulation of the Netlify environment for local development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -46,18 +46,19 @@
|
|
|
46
46
|
},
|
|
47
47
|
"author": "Netlify Inc.",
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@netlify/
|
|
49
|
+
"@netlify/api": "^14.0.2",
|
|
50
|
+
"@netlify/types": "1.2.0",
|
|
50
51
|
"tmp-promise": "^3.0.3",
|
|
51
52
|
"tsup": "^8.0.0",
|
|
52
53
|
"vitest": "^3.0.0"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
|
-
"@netlify/blobs": "9.1.
|
|
56
|
-
"@netlify/config": "^
|
|
57
|
-
"@netlify/dev-utils": "2.
|
|
58
|
-
"@netlify/functions": "3.1.
|
|
59
|
-
"@netlify/redirects": "1.1.
|
|
60
|
-
"@netlify/runtime": "2.2.
|
|
61
|
-
"@netlify/static": "1.1.
|
|
56
|
+
"@netlify/blobs": "9.1.2",
|
|
57
|
+
"@netlify/config": "^23.0.7",
|
|
58
|
+
"@netlify/dev-utils": "2.2.0",
|
|
59
|
+
"@netlify/functions": "3.1.10",
|
|
60
|
+
"@netlify/redirects": "1.1.4",
|
|
61
|
+
"@netlify/runtime": "2.2.2",
|
|
62
|
+
"@netlify/static": "1.1.4"
|
|
62
63
|
}
|
|
63
64
|
}
|