@vercel/fs-detectors 6.2.2 → 6.3.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/dist/detect-builders.d.ts +1 -0
- package/dist/services/detect-services.js +18 -1
- package/dist/services/get-services-builders.d.ts +1 -0
- package/dist/services/get-services-builders.js +2 -1
- package/dist/services/resolve.d.ts +7 -1
- package/dist/services/resolve.js +79 -6
- package/dist/services/types.d.ts +3 -2
- package/dist/services/utils.d.ts +2 -1
- package/package.json +3 -3
|
@@ -42,10 +42,17 @@ function emptyRoutes() {
|
|
|
42
42
|
workers: []
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
function isEnvVars(env) {
|
|
46
|
+
if (!env)
|
|
47
|
+
return false;
|
|
48
|
+
const first = Object.values(env)[0];
|
|
49
|
+
return typeof first === "object" && first !== null;
|
|
50
|
+
}
|
|
45
51
|
function withResolvedResult(resolved, inferred = null) {
|
|
46
52
|
return {
|
|
47
53
|
services: resolved.services,
|
|
48
54
|
source: resolved.source,
|
|
55
|
+
useImplicitEnvInjection: resolved.useImplicitEnvInjection,
|
|
49
56
|
routes: resolved.routes,
|
|
50
57
|
errors: resolved.errors,
|
|
51
58
|
warnings: resolved.warnings,
|
|
@@ -81,6 +88,7 @@ async function detectServices(options) {
|
|
|
81
88
|
return withResolvedResult({
|
|
82
89
|
services: [],
|
|
83
90
|
source: "configured",
|
|
91
|
+
useImplicitEnvInjection: true,
|
|
84
92
|
routes: emptyRoutes(),
|
|
85
93
|
errors: [configError],
|
|
86
94
|
warnings: []
|
|
@@ -95,6 +103,7 @@ async function detectServices(options) {
|
|
|
95
103
|
return withResolvedResult({
|
|
96
104
|
services: [],
|
|
97
105
|
source: "auto-detected",
|
|
106
|
+
useImplicitEnvInjection: true,
|
|
98
107
|
routes: emptyRoutes(),
|
|
99
108
|
errors: railwayResult.errors,
|
|
100
109
|
warnings: railwayResult.warnings
|
|
@@ -116,6 +125,7 @@ async function detectServices(options) {
|
|
|
116
125
|
{
|
|
117
126
|
services: [],
|
|
118
127
|
source: "auto-detected",
|
|
128
|
+
useImplicitEnvInjection: true,
|
|
119
129
|
routes: emptyRoutes(),
|
|
120
130
|
errors: result2.errors,
|
|
121
131
|
warnings: railwayResult.warnings
|
|
@@ -134,6 +144,7 @@ async function detectServices(options) {
|
|
|
134
144
|
const resolved = {
|
|
135
145
|
services: result2.services,
|
|
136
146
|
source: "auto-detected",
|
|
147
|
+
useImplicitEnvInjection: true,
|
|
137
148
|
routes: routes2,
|
|
138
149
|
errors: result2.errors,
|
|
139
150
|
warnings: []
|
|
@@ -152,6 +163,7 @@ async function detectServices(options) {
|
|
|
152
163
|
return withResolvedResult({
|
|
153
164
|
services: [],
|
|
154
165
|
source: "auto-detected",
|
|
166
|
+
useImplicitEnvInjection: true,
|
|
155
167
|
routes: emptyRoutes(),
|
|
156
168
|
errors: autoResult.errors,
|
|
157
169
|
warnings: []
|
|
@@ -160,6 +172,7 @@ async function detectServices(options) {
|
|
|
160
172
|
return withResolvedResult({
|
|
161
173
|
services: [],
|
|
162
174
|
source: "auto-detected",
|
|
175
|
+
useImplicitEnvInjection: true,
|
|
163
176
|
routes: emptyRoutes(),
|
|
164
177
|
errors: [
|
|
165
178
|
{
|
|
@@ -177,13 +190,17 @@ async function detectServices(options) {
|
|
|
177
190
|
{
|
|
178
191
|
requireFileEntrypointForBackendRuntimes: Boolean(
|
|
179
192
|
hasNonEmptyPublicServicesConfig
|
|
180
|
-
)
|
|
193
|
+
),
|
|
194
|
+
rootEnv: isEnvVars(vercelConfig?.env) ? vercelConfig?.env : void 0
|
|
181
195
|
}
|
|
182
196
|
);
|
|
183
197
|
const routes = generateServicesRoutes(result.services);
|
|
184
198
|
return withResolvedResult({
|
|
185
199
|
services: result.services,
|
|
186
200
|
source: "configured",
|
|
201
|
+
// GA `services` opts into explicit `env`; experimentalServices keeps
|
|
202
|
+
// the legacy `{NAME}_URL` injection.
|
|
203
|
+
useImplicitEnvInjection: !hasNonEmptyPublicServicesConfig,
|
|
187
204
|
routes,
|
|
188
205
|
errors: result.errors,
|
|
189
206
|
warnings: []
|
|
@@ -121,7 +121,8 @@ async function getServicesBuilders(options) {
|
|
|
121
121
|
...result.routes.crons
|
|
122
122
|
] : null,
|
|
123
123
|
errorRoutes: [],
|
|
124
|
-
services: result.services
|
|
124
|
+
services: result.services,
|
|
125
|
+
useImplicitEnvInjection: result.useImplicitEnvInjection
|
|
125
126
|
};
|
|
126
127
|
}
|
|
127
128
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Service, ConfiguredServices, ExperimentalServiceConfig, ServiceConfig, ServiceDetectionError } from './types';
|
|
1
|
+
import type { EnvVars, Service, ConfiguredServices, ExperimentalServiceConfig, ServiceConfig, ServiceDetectionError } from './types';
|
|
2
2
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
3
3
|
type ConfiguredServiceConfig = (ServiceConfig | ExperimentalServiceConfig) & Partial<ExperimentalServiceConfig>;
|
|
4
4
|
interface ResolvedEntrypointPath {
|
|
@@ -18,6 +18,12 @@ interface ResolveConfiguredServiceOptions {
|
|
|
18
18
|
}
|
|
19
19
|
interface ResolveAllConfiguredServicesOptions {
|
|
20
20
|
requireFileEntrypointForBackendRuntimes?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Optional top-level `env` (from vercel.json `env`). Per-service `env`
|
|
23
|
+
* values take precedence; entries here are folded into every service that
|
|
24
|
+
* doesn't already define the same name.
|
|
25
|
+
*/
|
|
26
|
+
rootEnv?: EnvVars;
|
|
21
27
|
}
|
|
22
28
|
/**
|
|
23
29
|
* Validate a service configuration from vercel.json services.
|
package/dist/services/resolve.js
CHANGED
|
@@ -45,7 +45,7 @@ function parsePyModuleAttrEntrypoint(entrypoint) {
|
|
|
45
45
|
}
|
|
46
46
|
const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
|
|
47
47
|
const DNS_LABEL_RE = /^(?!-)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
48
|
-
const
|
|
48
|
+
const ENV_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
49
49
|
const ENTRYPOINT_REQUIRED_RUNTIMES = /* @__PURE__ */ new Set([
|
|
50
50
|
"node",
|
|
51
51
|
"python",
|
|
@@ -424,14 +424,46 @@ function validateServiceConfig(name, config, options = {}) {
|
|
|
424
424
|
};
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
|
-
if (config.
|
|
428
|
-
if (
|
|
427
|
+
if (config.env !== void 0) {
|
|
428
|
+
if (typeof config.env !== "object" || Array.isArray(config.env)) {
|
|
429
429
|
return {
|
|
430
|
-
code: "
|
|
431
|
-
message: `Service "${name}" has invalid
|
|
430
|
+
code: "INVALID_ENV_VARS",
|
|
431
|
+
message: `Service "${name}" has invalid "env". Must be an object keyed by environment variable name.`,
|
|
432
432
|
serviceName: name
|
|
433
433
|
};
|
|
434
434
|
}
|
|
435
|
+
for (const [envVarName, envVar] of Object.entries(config.env)) {
|
|
436
|
+
if (!ENV_VAR_NAME_RE.test(envVarName)) {
|
|
437
|
+
return {
|
|
438
|
+
code: "INVALID_ENV_VAR_NAME",
|
|
439
|
+
message: `Service "${name}" has invalid env key "${envVarName}". Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`,
|
|
440
|
+
serviceName: name
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
if (!envVar || typeof envVar !== "object" || Array.isArray(envVar)) {
|
|
444
|
+
return {
|
|
445
|
+
code: "INVALID_ENV_VAR",
|
|
446
|
+
message: `Service "${name}" has invalid env["${envVarName}"]. Must be an object with a "type" discriminator.`,
|
|
447
|
+
serviceName: name
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
const envVarType = envVar.type;
|
|
451
|
+
if (envVarType !== "service-ref") {
|
|
452
|
+
return {
|
|
453
|
+
code: "INVALID_ENV_VAR_TYPE",
|
|
454
|
+
message: `Service "${name}" env["${envVarName}"] has unknown type "${envVarType}".`,
|
|
455
|
+
serviceName: name
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const refService = envVar.service;
|
|
459
|
+
if (typeof refService !== "string" || refService.length === 0) {
|
|
460
|
+
return {
|
|
461
|
+
code: "INVALID_ENV_VAR_REF",
|
|
462
|
+
message: `Service "${name}" env["${envVarName}"] must specify "service" as a non-empty string.`,
|
|
463
|
+
serviceName: name
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
435
467
|
}
|
|
436
468
|
if (config.runtime && !(config.runtime in import_types.RUNTIME_BUILDERS)) {
|
|
437
469
|
return {
|
|
@@ -655,7 +687,7 @@ async function resolveConfiguredService(options) {
|
|
|
655
687
|
schedule: config.schedule,
|
|
656
688
|
handlerFunction: moduleAttrParsed?.attrName,
|
|
657
689
|
topics,
|
|
658
|
-
|
|
690
|
+
env: config.env
|
|
659
691
|
};
|
|
660
692
|
}
|
|
661
693
|
async function resolveAllConfiguredServices(services, fs, routePrefixSource = "configured", options = {}) {
|
|
@@ -803,8 +835,49 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
|
|
|
803
835
|
}
|
|
804
836
|
resolved.push(service);
|
|
805
837
|
}
|
|
838
|
+
const servicesByName = new Map(resolved.map((s) => [s.name, s]));
|
|
839
|
+
for (const service of resolved) {
|
|
840
|
+
if (!service.env)
|
|
841
|
+
continue;
|
|
842
|
+
validateEnvRefs(
|
|
843
|
+
service.env,
|
|
844
|
+
`Service "${service.name}" env`,
|
|
845
|
+
servicesByName,
|
|
846
|
+
errors,
|
|
847
|
+
service.name
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
if (options.rootEnv) {
|
|
851
|
+
validateEnvRefs(options.rootEnv, "env", servicesByName, errors);
|
|
852
|
+
for (const service of resolved) {
|
|
853
|
+
service.env = { ...options.rootEnv, ...service.env ?? {} };
|
|
854
|
+
}
|
|
855
|
+
}
|
|
806
856
|
return { services: resolved, errors };
|
|
807
857
|
}
|
|
858
|
+
function validateEnvRefs(env, pathPrefix, servicesByName, errors, serviceName) {
|
|
859
|
+
for (const [envVarName, envVar] of Object.entries(env)) {
|
|
860
|
+
if (envVar.type !== "service-ref")
|
|
861
|
+
continue;
|
|
862
|
+
const refName = envVar.service;
|
|
863
|
+
const target = servicesByName.get(refName);
|
|
864
|
+
if (!target) {
|
|
865
|
+
errors.push({
|
|
866
|
+
code: "UNKNOWN_SERVICE_REF",
|
|
867
|
+
message: `${pathPrefix}["${envVarName}"] references unknown service "${refName}".`,
|
|
868
|
+
...serviceName ? { serviceName } : {}
|
|
869
|
+
});
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (target.type !== "web") {
|
|
873
|
+
errors.push({
|
|
874
|
+
code: "INVALID_SERVICE_REF_TYPE",
|
|
875
|
+
message: `${pathPrefix}["${envVarName}"] references service "${refName}" which is a ${target.type} service and has no URL. Only web services can be referenced.`,
|
|
876
|
+
...serviceName ? { serviceName } : {}
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
808
881
|
// Annotate the CommonJS export names for ESM import in node:
|
|
809
882
|
0 && (module.exports = {
|
|
810
883
|
resolveAllConfiguredServices,
|
package/dist/services/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Route } from '@vercel/routing-utils';
|
|
2
|
-
import type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceConfig, Services, ServiceRuntime, ServiceType, Service, Builder } from '@vercel/build-utils';
|
|
2
|
+
import type { EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceConfig, Services, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder } from '@vercel/build-utils';
|
|
3
3
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
4
|
-
export type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceConfig, Services, ServiceRuntime, ServiceType, Service, Builder, };
|
|
4
|
+
export type { EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceConfig, Services, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder, };
|
|
5
5
|
/**
|
|
6
6
|
* @deprecated Use `Service` instead
|
|
7
7
|
*/
|
|
@@ -39,6 +39,7 @@ export type InferredServicesConfig = ExperimentalServices;
|
|
|
39
39
|
export interface ResolvedServicesResult {
|
|
40
40
|
services: Service[];
|
|
41
41
|
source: DetectServicesSource;
|
|
42
|
+
useImplicitEnvInjection: boolean;
|
|
42
43
|
routes: ServicesRoutes;
|
|
43
44
|
errors: ServiceDetectionError[];
|
|
44
45
|
warnings: ServiceDetectionWarning[];
|
package/dist/services/utils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPathPrefix, getInternalServiceCronPath } from '@vercel/build-utils';
|
|
2
2
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
3
|
-
import type { ServiceRuntime, ExperimentalServices, Services, ServiceDetectionError, ResolvedService } from './types';
|
|
3
|
+
import type { EnvVars, ServiceRuntime, ExperimentalServices, Services, ServiceDetectionError, ResolvedService } from './types';
|
|
4
4
|
export { INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPathPrefix, getInternalServiceCronPath, };
|
|
5
5
|
export declare function hasFile(fs: DetectorFilesystem, filePath: string): Promise<boolean>;
|
|
6
6
|
export declare function isPublicServicesEnabled(): boolean;
|
|
@@ -59,6 +59,7 @@ export interface ReadVercelConfigResult {
|
|
|
59
59
|
config: {
|
|
60
60
|
services?: Services;
|
|
61
61
|
experimentalServices?: ExperimentalServices;
|
|
62
|
+
env?: Record<string, string> | EnvVars;
|
|
62
63
|
} | null;
|
|
63
64
|
error: ServiceDetectionError | null;
|
|
64
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/fs-detectors",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Vercel filesystem detectors",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"semver": "6.3.1",
|
|
22
22
|
"smol-toml": "1.5.2",
|
|
23
23
|
"@vercel/routing-utils": "6.2.0",
|
|
24
|
-
"@vercel/
|
|
24
|
+
"@vercel/frameworks": "3.26.0",
|
|
25
25
|
"@vercel/error-utils": "2.1.0",
|
|
26
|
-
"@vercel/
|
|
26
|
+
"@vercel/build-utils": "13.24.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/glob": "7.2.0",
|