@vercel/fs-detectors 6.2.2 → 6.4.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-railway.js +6 -16
- 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 +80 -6
- package/dist/services/types.d.ts +3 -2
- package/dist/services/utils.d.ts +2 -1
- package/package.json +4 -4
|
@@ -128,12 +128,12 @@ async function detectRailwayServices(options) {
|
|
|
128
128
|
if (cf.dirPath !== ".") {
|
|
129
129
|
serviceConfig.entrypoint = cf.dirPath;
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
cf.config.build
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
serviceConfig.
|
|
131
|
+
if (cf.config.build?.buildCommand) {
|
|
132
|
+
serviceConfig.buildCommand = cf.config.build.buildCommand;
|
|
133
|
+
}
|
|
134
|
+
const railwayPreDeploy = cf.config.deploy?.preDeployCommand;
|
|
135
|
+
if (railwayPreDeploy) {
|
|
136
|
+
serviceConfig.preDeployCommand = Array.isArray(railwayPreDeploy) ? railwayPreDeploy.join(" && ") : railwayPreDeploy;
|
|
137
137
|
}
|
|
138
138
|
services[serviceName] = serviceConfig;
|
|
139
139
|
}
|
|
@@ -230,16 +230,6 @@ function deriveServiceName(dirPath) {
|
|
|
230
230
|
const segments = dirPath.split("/");
|
|
231
231
|
return segments[segments.length - 1];
|
|
232
232
|
}
|
|
233
|
-
function combineBuildCommand(buildCommand, preDeployCommand) {
|
|
234
|
-
const preDeploy = Array.isArray(preDeployCommand) ? preDeployCommand.join(" && ") : preDeployCommand;
|
|
235
|
-
if (preDeploy && buildCommand) {
|
|
236
|
-
return `${buildCommand} && ${preDeploy}`;
|
|
237
|
-
} else if (preDeploy) {
|
|
238
|
-
return preDeploy;
|
|
239
|
-
} else {
|
|
240
|
-
return buildCommand;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
233
|
function assignRoutePrefixes(services) {
|
|
244
234
|
const warnings = [];
|
|
245
235
|
const names = Object.keys(services);
|
|
@@ -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 {
|
|
@@ -652,10 +684,11 @@ async function resolveConfiguredService(options) {
|
|
|
652
684
|
runtime,
|
|
653
685
|
buildCommand: config.buildCommand,
|
|
654
686
|
installCommand: config.installCommand,
|
|
687
|
+
preDeployCommand: config.preDeployCommand,
|
|
655
688
|
schedule: config.schedule,
|
|
656
689
|
handlerFunction: moduleAttrParsed?.attrName,
|
|
657
690
|
topics,
|
|
658
|
-
|
|
691
|
+
env: config.env
|
|
659
692
|
};
|
|
660
693
|
}
|
|
661
694
|
async function resolveAllConfiguredServices(services, fs, routePrefixSource = "configured", options = {}) {
|
|
@@ -803,8 +836,49 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
|
|
|
803
836
|
}
|
|
804
837
|
resolved.push(service);
|
|
805
838
|
}
|
|
839
|
+
const servicesByName = new Map(resolved.map((s) => [s.name, s]));
|
|
840
|
+
for (const service of resolved) {
|
|
841
|
+
if (!service.env)
|
|
842
|
+
continue;
|
|
843
|
+
validateEnvRefs(
|
|
844
|
+
service.env,
|
|
845
|
+
`Service "${service.name}" env`,
|
|
846
|
+
servicesByName,
|
|
847
|
+
errors,
|
|
848
|
+
service.name
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
if (options.rootEnv) {
|
|
852
|
+
validateEnvRefs(options.rootEnv, "env", servicesByName, errors);
|
|
853
|
+
for (const service of resolved) {
|
|
854
|
+
service.env = { ...options.rootEnv, ...service.env ?? {} };
|
|
855
|
+
}
|
|
856
|
+
}
|
|
806
857
|
return { services: resolved, errors };
|
|
807
858
|
}
|
|
859
|
+
function validateEnvRefs(env, pathPrefix, servicesByName, errors, serviceName) {
|
|
860
|
+
for (const [envVarName, envVar] of Object.entries(env)) {
|
|
861
|
+
if (envVar.type !== "service-ref")
|
|
862
|
+
continue;
|
|
863
|
+
const refName = envVar.service;
|
|
864
|
+
const target = servicesByName.get(refName);
|
|
865
|
+
if (!target) {
|
|
866
|
+
errors.push({
|
|
867
|
+
code: "UNKNOWN_SERVICE_REF",
|
|
868
|
+
message: `${pathPrefix}["${envVarName}"] references unknown service "${refName}".`,
|
|
869
|
+
...serviceName ? { serviceName } : {}
|
|
870
|
+
});
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (target.type !== "web") {
|
|
874
|
+
errors.push({
|
|
875
|
+
code: "INVALID_SERVICE_REF_TYPE",
|
|
876
|
+
message: `${pathPrefix}["${envVarName}"] references service "${refName}" which is a ${target.type} service and has no URL. Only web services can be referenced.`,
|
|
877
|
+
...serviceName ? { serviceName } : {}
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
808
882
|
// Annotate the CommonJS export names for ESM import in node:
|
|
809
883
|
0 && (module.exports = {
|
|
810
884
|
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.4.0",
|
|
4
4
|
"description": "Vercel filesystem detectors",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"minimatch": "3.1.2",
|
|
21
21
|
"semver": "6.3.1",
|
|
22
22
|
"smol-toml": "1.5.2",
|
|
23
|
-
"@vercel/
|
|
24
|
-
"@vercel/
|
|
23
|
+
"@vercel/build-utils": "13.25.0",
|
|
24
|
+
"@vercel/frameworks": "3.26.1",
|
|
25
25
|
"@vercel/error-utils": "2.1.0",
|
|
26
|
-
"@vercel/
|
|
26
|
+
"@vercel/routing-utils": "6.2.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/glob": "7.2.0",
|