@vercel/fs-detectors 7.1.7 → 7.2.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/index.d.ts +7 -0
- package/dist/index.js +39 -0
- package/dist/services/auto-detect.d.ts +46 -0
- package/dist/services/auto-detect.js +269 -0
- package/dist/services/detect-procfile.d.ts +22 -0
- package/dist/services/detect-procfile.js +250 -0
- package/dist/services/detect-railway.d.ts +22 -0
- package/dist/services/detect-railway.js +242 -0
- package/dist/services/detect-render.d.ts +14 -0
- package/dist/services/detect-render.js +231 -0
- package/dist/services/detect-services.d.ts +48 -0
- package/dist/services/detect-services.js +424 -0
- package/dist/services/resolve-v2.d.ts +11 -0
- package/dist/services/resolve-v2.js +383 -0
- package/dist/services/resolve.d.ts +65 -0
- package/dist/services/resolve.js +901 -0
- package/dist/services/runtimes/constants.d.ts +19 -0
- package/dist/services/runtimes/constants.js +85 -0
- package/dist/services/runtimes/entrypoint.d.ts +20 -0
- package/dist/services/runtimes/entrypoint.js +58 -0
- package/dist/services/runtimes/framework.d.ts +25 -0
- package/dist/services/runtimes/framework.js +70 -0
- package/dist/services/runtimes/runtime.d.ts +18 -0
- package/dist/services/runtimes/runtime.js +61 -0
- package/dist/services/types.d.ts +113 -0
- package/dist/services/types.js +16 -0
- package/dist/services/utils.d.ts +48 -0
- package/dist/services/utils.js +157 -0
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export { builderToFrameworks, detectBuilders, detectOutputDirectory, detectApiDirectory, detectApiExtensions, getProxyBuilder, validateProxyConfig, validateProxy, type Options as DetectBuildersOptions, type ProxyConfig, } from './detect-builders';
|
|
2
|
+
export { detectServices, generateServiceRewrites, generateServicesRoutes, } from './services/detect-services';
|
|
3
|
+
export { resolveAllConfiguredServicesV2, resolveConfiguredServiceV2, validateServiceConfigV2, } from './services/resolve-v2';
|
|
4
|
+
export { isExperimentalService, isExperimentalServiceV2, } from '@vercel/build-utils';
|
|
5
|
+
export { autoDetectServices } from './services/auto-detect';
|
|
6
|
+
export type { AutoDetectOptions, AutoDetectResult, } from './services/auto-detect';
|
|
7
|
+
export { isStaticBuild, isRouteOwningBuilder, INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPath, getInternalServiceCronPathPrefix, getInternalServiceWorkerPath, getInternalServiceWorkerPathPrefix, } from './services/utils';
|
|
8
|
+
export type { DetectServicesOptions, DetectServicesResult, DetectServicesSource, InferredServiceConfig, InferredServicesConfig, ResolvedServicesResult, InferredServicesResult, ResolvedService, Service, ExperimentalService, ExperimentalServiceV2, ExperimentalServiceV2Config, ExperimentalServicesV2, ExperimentalServiceV2Binding, ServiceBinding, ServiceConfig, Services, ServicesRoutes, ServiceDetectionError, ConfiguredServices, ConfiguredServicesType, } from './services/types';
|
|
2
9
|
export { detectFileSystemAPI } from './detect-file-system-api';
|
|
3
10
|
export { detectFramework, detectFrameworks, detectFrameworkRecord, detectFrameworkVersion, } from './detect-framework';
|
|
4
11
|
export { getProjectPaths } from './get-project-paths';
|
package/dist/index.js
CHANGED
|
@@ -22,10 +22,12 @@ __export(src_exports, {
|
|
|
22
22
|
DetectorFilesystem: () => import_filesystem.DetectorFilesystem,
|
|
23
23
|
GetWorkspaceOptions: () => import_get_workspaces.GetWorkspaceOptions,
|
|
24
24
|
GetWorkspacePackagePathsOptions: () => import_get_workspace_package_paths.GetWorkspacePackagePathsOptions,
|
|
25
|
+
INTERNAL_SERVICE_PREFIX: () => import_utils.INTERNAL_SERVICE_PREFIX,
|
|
25
26
|
LocalFileSystemDetector: () => import_local_file_system_detector.LocalFileSystemDetector,
|
|
26
27
|
REGEX_NON_VERCEL_PLATFORM_FILES: () => import_detect_builders2.REGEX_NON_VERCEL_PLATFORM_FILES,
|
|
27
28
|
Workspace: () => import_get_workspaces.Workspace,
|
|
28
29
|
WorkspaceType: () => import_get_workspaces.WorkspaceType,
|
|
30
|
+
autoDetectServices: () => import_auto_detect.autoDetectServices,
|
|
29
31
|
builderToFrameworks: () => import_detect_builders.builderToFrameworks,
|
|
30
32
|
detectApiDirectory: () => import_detect_builders.detectApiDirectory,
|
|
31
33
|
detectApiExtensions: () => import_detect_builders.detectApiExtensions,
|
|
@@ -37,20 +39,40 @@ __export(src_exports, {
|
|
|
37
39
|
detectFrameworks: () => import_detect_framework.detectFrameworks,
|
|
38
40
|
detectInstrumentation: () => import_detect_instrumentation.detectInstrumentation,
|
|
39
41
|
detectOutputDirectory: () => import_detect_builders.detectOutputDirectory,
|
|
42
|
+
detectServices: () => import_detect_services.detectServices,
|
|
43
|
+
generateServiceRewrites: () => import_detect_services.generateServiceRewrites,
|
|
44
|
+
generateServicesRoutes: () => import_detect_services.generateServicesRoutes,
|
|
45
|
+
getInternalServiceCronPath: () => import_utils.getInternalServiceCronPath,
|
|
46
|
+
getInternalServiceCronPathPrefix: () => import_utils.getInternalServiceCronPathPrefix,
|
|
47
|
+
getInternalServiceFunctionPath: () => import_utils.getInternalServiceFunctionPath,
|
|
48
|
+
getInternalServiceWorkerPath: () => import_utils.getInternalServiceWorkerPath,
|
|
49
|
+
getInternalServiceWorkerPathPrefix: () => import_utils.getInternalServiceWorkerPathPrefix,
|
|
40
50
|
getProjectPaths: () => import_get_project_paths.getProjectPaths,
|
|
41
51
|
getProxyBuilder: () => import_detect_builders.getProxyBuilder,
|
|
42
52
|
getWorkspacePackagePaths: () => import_get_workspace_package_paths.getWorkspacePackagePaths,
|
|
43
53
|
getWorkspaces: () => import_get_workspaces.getWorkspaces,
|
|
54
|
+
isExperimentalService: () => import_build_utils.isExperimentalService,
|
|
55
|
+
isExperimentalServiceV2: () => import_build_utils.isExperimentalServiceV2,
|
|
44
56
|
isOfficialRuntime: () => import_is_official_runtime.isOfficialRuntime,
|
|
57
|
+
isRouteOwningBuilder: () => import_utils.isRouteOwningBuilder,
|
|
58
|
+
isStaticBuild: () => import_utils.isStaticBuild,
|
|
45
59
|
isStaticRuntime: () => import_is_official_runtime.isStaticRuntime,
|
|
46
60
|
monorepoManagers: () => import_monorepo_managers.monorepoManagers,
|
|
47
61
|
packageManagers: () => import_package_managers.packageManagers,
|
|
62
|
+
resolveAllConfiguredServicesV2: () => import_resolve_v2.resolveAllConfiguredServicesV2,
|
|
63
|
+
resolveConfiguredServiceV2: () => import_resolve_v2.resolveConfiguredServiceV2,
|
|
48
64
|
validateProxy: () => import_detect_builders.validateProxy,
|
|
49
65
|
validateProxyConfig: () => import_detect_builders.validateProxyConfig,
|
|
66
|
+
validateServiceConfigV2: () => import_resolve_v2.validateServiceConfigV2,
|
|
50
67
|
workspaceManagers: () => import_workspace_managers.workspaceManagers
|
|
51
68
|
});
|
|
52
69
|
module.exports = __toCommonJS(src_exports);
|
|
53
70
|
var import_detect_builders = require("./detect-builders");
|
|
71
|
+
var import_detect_services = require("./services/detect-services");
|
|
72
|
+
var import_resolve_v2 = require("./services/resolve-v2");
|
|
73
|
+
var import_build_utils = require("@vercel/build-utils");
|
|
74
|
+
var import_auto_detect = require("./services/auto-detect");
|
|
75
|
+
var import_utils = require("./services/utils");
|
|
54
76
|
var import_detect_file_system_api = require("./detect-file-system-api");
|
|
55
77
|
var import_detect_framework = require("./detect-framework");
|
|
56
78
|
var import_get_project_paths = require("./get-project-paths");
|
|
@@ -70,10 +92,12 @@ var import_detect_instrumentation = require("./detect-instrumentation");
|
|
|
70
92
|
DetectorFilesystem,
|
|
71
93
|
GetWorkspaceOptions,
|
|
72
94
|
GetWorkspacePackagePathsOptions,
|
|
95
|
+
INTERNAL_SERVICE_PREFIX,
|
|
73
96
|
LocalFileSystemDetector,
|
|
74
97
|
REGEX_NON_VERCEL_PLATFORM_FILES,
|
|
75
98
|
Workspace,
|
|
76
99
|
WorkspaceType,
|
|
100
|
+
autoDetectServices,
|
|
77
101
|
builderToFrameworks,
|
|
78
102
|
detectApiDirectory,
|
|
79
103
|
detectApiExtensions,
|
|
@@ -85,16 +109,31 @@ var import_detect_instrumentation = require("./detect-instrumentation");
|
|
|
85
109
|
detectFrameworks,
|
|
86
110
|
detectInstrumentation,
|
|
87
111
|
detectOutputDirectory,
|
|
112
|
+
detectServices,
|
|
113
|
+
generateServiceRewrites,
|
|
114
|
+
generateServicesRoutes,
|
|
115
|
+
getInternalServiceCronPath,
|
|
116
|
+
getInternalServiceCronPathPrefix,
|
|
117
|
+
getInternalServiceFunctionPath,
|
|
118
|
+
getInternalServiceWorkerPath,
|
|
119
|
+
getInternalServiceWorkerPathPrefix,
|
|
88
120
|
getProjectPaths,
|
|
89
121
|
getProxyBuilder,
|
|
90
122
|
getWorkspacePackagePaths,
|
|
91
123
|
getWorkspaces,
|
|
124
|
+
isExperimentalService,
|
|
125
|
+
isExperimentalServiceV2,
|
|
92
126
|
isOfficialRuntime,
|
|
127
|
+
isRouteOwningBuilder,
|
|
128
|
+
isStaticBuild,
|
|
93
129
|
isStaticRuntime,
|
|
94
130
|
monorepoManagers,
|
|
95
131
|
packageManagers,
|
|
132
|
+
resolveAllConfiguredServicesV2,
|
|
133
|
+
resolveConfiguredServiceV2,
|
|
96
134
|
validateProxy,
|
|
97
135
|
validateProxyConfig,
|
|
136
|
+
validateServiceConfigV2,
|
|
98
137
|
workspaceManagers,
|
|
99
138
|
...require("./monorepos/get-monorepo-default-settings")
|
|
100
139
|
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { DetectEntrypointFn } from '@vercel/build-utils';
|
|
2
|
+
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
3
|
+
import type { InferredServicesConfig, ServiceDetectionError, ServiceDetectionWarning } from './types';
|
|
4
|
+
export interface AutoDetectOptions {
|
|
5
|
+
fs: DetectorFilesystem;
|
|
6
|
+
/**
|
|
7
|
+
* Optional callback used to enrich runtime services with a normalized
|
|
8
|
+
* entrypoint (file path or `module:attr` reference).
|
|
9
|
+
*/
|
|
10
|
+
detectEntrypoint?: DetectEntrypointFn;
|
|
11
|
+
}
|
|
12
|
+
export interface AutoDetectResult {
|
|
13
|
+
services: InferredServicesConfig | null;
|
|
14
|
+
errors: ServiceDetectionError[];
|
|
15
|
+
warnings: ServiceDetectionWarning[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Auto-detect services when services are not configured.
|
|
19
|
+
*
|
|
20
|
+
* Scans the project for frameworks, supporting multiple layouts:
|
|
21
|
+
*
|
|
22
|
+
* Frontend at root, backend in backend/:
|
|
23
|
+
* project/
|
|
24
|
+
* ├── package.json
|
|
25
|
+
* └── backend/
|
|
26
|
+
*
|
|
27
|
+
* Frontend in frontend/, backend in backend/:
|
|
28
|
+
* project/
|
|
29
|
+
* ├── frontend/
|
|
30
|
+
* └── backend/
|
|
31
|
+
*
|
|
32
|
+
* Frontend in frontend/, backend in services/{service-name}/:
|
|
33
|
+
* project/
|
|
34
|
+
* ├── frontend/
|
|
35
|
+
* └── services/
|
|
36
|
+
* ├── service-a/
|
|
37
|
+
* └── service-b/
|
|
38
|
+
*
|
|
39
|
+
* Frontend in apps/web/ monorepo, backend in services/{service-name}/:
|
|
40
|
+
* project/
|
|
41
|
+
* ├── apps/web/
|
|
42
|
+
* └── services/
|
|
43
|
+
* ├── service-a/
|
|
44
|
+
* └── service-b/
|
|
45
|
+
*/
|
|
46
|
+
export declare function autoDetectServices(options: AutoDetectOptions): Promise<AutoDetectResult>;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var auto_detect_exports = {};
|
|
20
|
+
__export(auto_detect_exports, {
|
|
21
|
+
autoDetectServices: () => autoDetectServices
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(auto_detect_exports);
|
|
24
|
+
var import_detect_framework = require("../detect-framework");
|
|
25
|
+
var import_frameworks = require("@vercel/frameworks");
|
|
26
|
+
var import_framework = require("./runtimes/framework");
|
|
27
|
+
var import_utils = require("./utils");
|
|
28
|
+
const FRONTEND_DIR = "frontend";
|
|
29
|
+
const APPS_WEB_DIR = "apps/web";
|
|
30
|
+
const BACKEND_DIR = "backend";
|
|
31
|
+
const SERVICES_DIR = "services";
|
|
32
|
+
const FRONTEND_LOCATIONS = [FRONTEND_DIR, APPS_WEB_DIR];
|
|
33
|
+
async function autoDetectServices(options) {
|
|
34
|
+
const { fs, detectEntrypoint } = options;
|
|
35
|
+
const rootFrameworks = await (0, import_detect_framework.detectFrameworks)({
|
|
36
|
+
fs,
|
|
37
|
+
frameworkList: import_frameworks.frameworkList
|
|
38
|
+
});
|
|
39
|
+
if (rootFrameworks.length > 1) {
|
|
40
|
+
const frameworkNames = rootFrameworks.map((f) => f.name).join(", ");
|
|
41
|
+
return {
|
|
42
|
+
services: null,
|
|
43
|
+
warnings: [],
|
|
44
|
+
errors: [
|
|
45
|
+
{
|
|
46
|
+
code: "MULTIPLE_FRAMEWORKS_ROOT",
|
|
47
|
+
message: `Multiple frameworks detected at root: ${frameworkNames}. Use explicit services config.`
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (rootFrameworks.length === 1) {
|
|
53
|
+
return detectServicesAtRoot(fs, rootFrameworks[0], detectEntrypoint);
|
|
54
|
+
}
|
|
55
|
+
for (const frontendLocation of FRONTEND_LOCATIONS) {
|
|
56
|
+
const hasFrontendDir = await fs.hasPath(frontendLocation);
|
|
57
|
+
if (!hasFrontendDir) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const frontendFs = fs.chdir(frontendLocation);
|
|
61
|
+
const frontendFrameworks = await (0, import_detect_framework.detectFrameworks)({
|
|
62
|
+
fs: frontendFs,
|
|
63
|
+
frameworkList: import_frameworks.frameworkList
|
|
64
|
+
});
|
|
65
|
+
if (frontendFrameworks.length > 1) {
|
|
66
|
+
const frameworkNames = frontendFrameworks.map((f) => f.name).join(", ");
|
|
67
|
+
return {
|
|
68
|
+
services: null,
|
|
69
|
+
warnings: [],
|
|
70
|
+
errors: [
|
|
71
|
+
{
|
|
72
|
+
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
73
|
+
message: `Multiple frameworks detected in ${frontendLocation}/: ${frameworkNames}. Use explicit services config.`
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (frontendFrameworks.length === 1) {
|
|
79
|
+
return detectServicesFrontendSubdir(
|
|
80
|
+
fs,
|
|
81
|
+
frontendFrameworks[0],
|
|
82
|
+
frontendLocation,
|
|
83
|
+
detectEntrypoint
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
services: null,
|
|
89
|
+
warnings: [],
|
|
90
|
+
errors: [
|
|
91
|
+
{
|
|
92
|
+
code: "NO_SERVICES_CONFIGURED",
|
|
93
|
+
message: "No services detected. Configure `services` in vercel.json or ensure a framework exists at project root, frontend/, or apps/web/."
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function detectServicesAtRoot(fs, rootFramework, detectEntrypoint) {
|
|
99
|
+
const services = {};
|
|
100
|
+
services.frontend = {
|
|
101
|
+
root: ".",
|
|
102
|
+
framework: rootFramework.slug ?? void 0,
|
|
103
|
+
mountPath: "/"
|
|
104
|
+
};
|
|
105
|
+
const backendResult = await detectBackendServices(fs, detectEntrypoint);
|
|
106
|
+
if (backendResult.error) {
|
|
107
|
+
return {
|
|
108
|
+
services: null,
|
|
109
|
+
warnings: [],
|
|
110
|
+
errors: [backendResult.error]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (Object.keys(backendResult.services).length === 0) {
|
|
114
|
+
return {
|
|
115
|
+
services: null,
|
|
116
|
+
warnings: [],
|
|
117
|
+
errors: []
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
Object.assign(services, backendResult.services);
|
|
121
|
+
const mountWarnings = (0, import_utils.assignMountPaths)(services);
|
|
122
|
+
return {
|
|
123
|
+
services,
|
|
124
|
+
warnings: mountWarnings,
|
|
125
|
+
errors: []
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async function detectServicesFrontendSubdir(fs, frontendFramework, frontendLocation, detectEntrypoint) {
|
|
129
|
+
const services = {};
|
|
130
|
+
const serviceName = frontendLocation.split("/").pop() || "frontend";
|
|
131
|
+
services[serviceName] = {
|
|
132
|
+
framework: frontendFramework.slug ?? void 0,
|
|
133
|
+
root: frontendLocation,
|
|
134
|
+
mountPath: "/"
|
|
135
|
+
};
|
|
136
|
+
const backendResult = await detectBackendServices(fs, detectEntrypoint);
|
|
137
|
+
if (backendResult.error) {
|
|
138
|
+
return {
|
|
139
|
+
services: null,
|
|
140
|
+
warnings: [],
|
|
141
|
+
errors: [backendResult.error]
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (Object.keys(backendResult.services).length === 0) {
|
|
145
|
+
return {
|
|
146
|
+
services: null,
|
|
147
|
+
warnings: [],
|
|
148
|
+
errors: [
|
|
149
|
+
{
|
|
150
|
+
code: "NO_BACKEND_SERVICES",
|
|
151
|
+
message: `Frontend detected in ${frontendLocation}/ but no backend services found. Add a backend/ or services/ directory with a supported framework.`
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
Object.assign(services, backendResult.services);
|
|
157
|
+
const mountWarnings = (0, import_utils.assignMountPaths)(services);
|
|
158
|
+
return {
|
|
159
|
+
services,
|
|
160
|
+
warnings: mountWarnings,
|
|
161
|
+
errors: []
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function detectBackendServices(fs, detectEntrypoint) {
|
|
165
|
+
const services = {};
|
|
166
|
+
const backendResult = await detectServiceInDir(
|
|
167
|
+
fs,
|
|
168
|
+
BACKEND_DIR,
|
|
169
|
+
"backend",
|
|
170
|
+
detectEntrypoint
|
|
171
|
+
);
|
|
172
|
+
if (backendResult.error) {
|
|
173
|
+
return { services: {}, error: backendResult.error };
|
|
174
|
+
}
|
|
175
|
+
if (backendResult.service) {
|
|
176
|
+
services.backend = backendResult.service;
|
|
177
|
+
}
|
|
178
|
+
const multiServicesResult = await detectServicesDirectory(
|
|
179
|
+
fs,
|
|
180
|
+
detectEntrypoint
|
|
181
|
+
);
|
|
182
|
+
if (multiServicesResult.error) {
|
|
183
|
+
return { services: {}, error: multiServicesResult.error };
|
|
184
|
+
}
|
|
185
|
+
for (const serviceName of Object.keys(multiServicesResult.services)) {
|
|
186
|
+
if (services[serviceName]) {
|
|
187
|
+
return {
|
|
188
|
+
services: {},
|
|
189
|
+
error: {
|
|
190
|
+
code: "SERVICE_NAME_CONFLICT",
|
|
191
|
+
message: `Service name conflict: "${serviceName}" exists in both ${BACKEND_DIR}/ and ${SERVICES_DIR}/${serviceName}/. Rename one of the directories or use explicit services config.`,
|
|
192
|
+
serviceName
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
Object.assign(services, multiServicesResult.services);
|
|
198
|
+
return { services };
|
|
199
|
+
}
|
|
200
|
+
async function detectServicesDirectory(fs, detectEntrypoint) {
|
|
201
|
+
const services = {};
|
|
202
|
+
const hasServicesDir = await fs.hasPath(SERVICES_DIR);
|
|
203
|
+
if (!hasServicesDir) {
|
|
204
|
+
return { services };
|
|
205
|
+
}
|
|
206
|
+
const servicesFs = fs.chdir(SERVICES_DIR);
|
|
207
|
+
const entries = await servicesFs.readdir("/");
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (entry.type !== "dir") {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const serviceName = entry.name;
|
|
213
|
+
const serviceDir = `${SERVICES_DIR}/${serviceName}`;
|
|
214
|
+
const result = await detectServiceInDir(
|
|
215
|
+
fs,
|
|
216
|
+
serviceDir,
|
|
217
|
+
serviceName,
|
|
218
|
+
detectEntrypoint
|
|
219
|
+
);
|
|
220
|
+
if (result.error) {
|
|
221
|
+
return { services: {}, error: result.error };
|
|
222
|
+
}
|
|
223
|
+
if (result.service) {
|
|
224
|
+
services[serviceName] = result.service;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { services };
|
|
228
|
+
}
|
|
229
|
+
async function detectServiceInDir(fs, dirPath, serviceName, detectEntrypoint) {
|
|
230
|
+
const hasDirPath = await fs.hasPath(dirPath);
|
|
231
|
+
if (!hasDirPath) {
|
|
232
|
+
return {};
|
|
233
|
+
}
|
|
234
|
+
const serviceFs = fs.chdir(dirPath);
|
|
235
|
+
const frameworks = await (0, import_detect_framework.detectFrameworks)({
|
|
236
|
+
fs: serviceFs,
|
|
237
|
+
frameworkList: import_framework.DETECTION_FRAMEWORKS,
|
|
238
|
+
useExperimentalFrameworks: true
|
|
239
|
+
});
|
|
240
|
+
if (frameworks.length > 1) {
|
|
241
|
+
const frameworkNames = frameworks.map((f) => f.name).join(", ");
|
|
242
|
+
return {
|
|
243
|
+
error: {
|
|
244
|
+
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
245
|
+
message: `Multiple frameworks detected in ${dirPath}/: ${frameworkNames}. Use explicit services config.`,
|
|
246
|
+
serviceName
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (frameworks.length !== 1) {
|
|
251
|
+
return {};
|
|
252
|
+
}
|
|
253
|
+
const framework = frameworks[0];
|
|
254
|
+
const slug = framework.slug ?? void 0;
|
|
255
|
+
const mountPath = `/${serviceName}`;
|
|
256
|
+
const detected = detectEntrypoint && !(0, import_framework.isFrontendFramework)(slug) ? await detectEntrypoint({ workPath: dirPath, framework: slug }) : null;
|
|
257
|
+
return {
|
|
258
|
+
service: {
|
|
259
|
+
framework: slug,
|
|
260
|
+
root: dirPath,
|
|
261
|
+
...detected ? { entrypoint: detected.entrypoint } : {},
|
|
262
|
+
mountPath
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
267
|
+
0 && (module.exports = {
|
|
268
|
+
autoDetectServices
|
|
269
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
2
|
+
import type { InferredServicesConfig, ServiceDetectionError, ServiceDetectionWarning } from './types';
|
|
3
|
+
export interface ProcfileDetectResult {
|
|
4
|
+
services: InferredServicesConfig | null;
|
|
5
|
+
errors: ServiceDetectionError[];
|
|
6
|
+
warnings: ServiceDetectionWarning[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Detect service configurations from a Procfile.
|
|
10
|
+
*
|
|
11
|
+
* We infer runtime and entrypoint from the start command where possible,
|
|
12
|
+
* then run framework detection from the project root to fill in the framework.
|
|
13
|
+
*
|
|
14
|
+
* Process type mapping:
|
|
15
|
+
* - `web`: web service
|
|
16
|
+
* - `release`: embeded into one of web service's buildCommand
|
|
17
|
+
* - `worker`-like process name: try to infer entrypoint for support Python worker or produce a hint
|
|
18
|
+
* - everything else is tried to be inferred as `web` or produces a hint
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectProcfileServices(options: {
|
|
21
|
+
fs: DetectorFilesystem;
|
|
22
|
+
}): Promise<ProcfileDetectResult>;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var detect_procfile_exports = {};
|
|
20
|
+
__export(detect_procfile_exports, {
|
|
21
|
+
detectProcfileServices: () => detectProcfileServices
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(detect_procfile_exports);
|
|
24
|
+
var import_detect_framework = require("../detect-framework");
|
|
25
|
+
var import_framework = require("./runtimes/framework");
|
|
26
|
+
var import_utils = require("./utils");
|
|
27
|
+
const PROCFILE = "Procfile";
|
|
28
|
+
const PROCFILE_LINE_RE = /^\s*([A-Za-z_][A-Za-z0-9_-]*):\s*(.+)/;
|
|
29
|
+
const PY_IDENT = "[A-Za-z_][A-Za-z0-9_]*";
|
|
30
|
+
const PY_MODULE_RE = new RegExp(
|
|
31
|
+
`^${PY_IDENT}(?:\\.${PY_IDENT})*(?::${PY_IDENT})?$`
|
|
32
|
+
);
|
|
33
|
+
const SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
34
|
+
".js",
|
|
35
|
+
".cjs",
|
|
36
|
+
".mjs",
|
|
37
|
+
".ts",
|
|
38
|
+
".cts",
|
|
39
|
+
".mts",
|
|
40
|
+
".py",
|
|
41
|
+
".go",
|
|
42
|
+
".rs",
|
|
43
|
+
".rb",
|
|
44
|
+
".ru"
|
|
45
|
+
]);
|
|
46
|
+
const SUPPORTED_WORKER_COMMANDS = /* @__PURE__ */ new Set(["celery", "dramatiq"]);
|
|
47
|
+
async function detectProcfileServices(options) {
|
|
48
|
+
const { fs } = options;
|
|
49
|
+
const raw = await readProcfile(fs);
|
|
50
|
+
if (raw.warning) {
|
|
51
|
+
return { services: null, errors: [], warnings: [raw.warning] };
|
|
52
|
+
}
|
|
53
|
+
if (!raw.content) {
|
|
54
|
+
return { services: null, errors: [], warnings: [] };
|
|
55
|
+
}
|
|
56
|
+
const entries = parseProcfile(raw.content);
|
|
57
|
+
if (entries.length === 0) {
|
|
58
|
+
return { services: null, errors: [], warnings: [] };
|
|
59
|
+
}
|
|
60
|
+
const services = {};
|
|
61
|
+
const errors = [];
|
|
62
|
+
const warnings = [];
|
|
63
|
+
let releaseCommand;
|
|
64
|
+
const serviceEntries = [];
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
if (entry.processType === "release") {
|
|
67
|
+
releaseCommand = entry.command;
|
|
68
|
+
} else {
|
|
69
|
+
serviceEntries.push(entry);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (serviceEntries.length === 0 && releaseCommand) {
|
|
73
|
+
warnings.push({
|
|
74
|
+
code: "PROCFILE_RELEASE_ONLY",
|
|
75
|
+
message: `Found only a release process in Procfile (command: "${releaseCommand}"). The release command can be used as a build step. You can add it as part of a web service if you add it to "buildCommand".`
|
|
76
|
+
});
|
|
77
|
+
return { services: null, errors: [], warnings };
|
|
78
|
+
}
|
|
79
|
+
if (serviceEntries.length === 0) {
|
|
80
|
+
return { services: null, errors: [], warnings: [] };
|
|
81
|
+
}
|
|
82
|
+
const frameworks = await (0, import_detect_framework.detectFrameworks)({
|
|
83
|
+
fs,
|
|
84
|
+
frameworkList: import_framework.DETECTION_FRAMEWORKS,
|
|
85
|
+
useExperimentalFrameworks: true
|
|
86
|
+
});
|
|
87
|
+
if (frameworks.length > 1) {
|
|
88
|
+
const names = frameworks.map((f) => f.name).join(", ");
|
|
89
|
+
return {
|
|
90
|
+
services: null,
|
|
91
|
+
errors: [
|
|
92
|
+
{
|
|
93
|
+
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
94
|
+
message: `Multiple frameworks detected: ${names}. Use explicit services config.`
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
warnings
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const detectedFramework = frameworks.length === 1 ? frameworks[0] : null;
|
|
101
|
+
const serviceNames = /* @__PURE__ */ new Set();
|
|
102
|
+
for (const entry of serviceEntries) {
|
|
103
|
+
const { processType, command } = entry;
|
|
104
|
+
const tokens = command.split(/\s+/).filter(Boolean);
|
|
105
|
+
const entrypoint = await extractEntrypoint(tokens, fs);
|
|
106
|
+
const isWorkerLikeProcess = processType.includes("worker") || hasSupportedWorkerCommand(tokens);
|
|
107
|
+
if (serviceNames.has(processType)) {
|
|
108
|
+
errors.push({
|
|
109
|
+
code: "DUPLICATE_SERVICE",
|
|
110
|
+
message: `Duplicate process type "${processType}" in Procfile.`,
|
|
111
|
+
serviceName: processType
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
serviceNames.add(processType);
|
|
116
|
+
if (isWorkerLikeProcess) {
|
|
117
|
+
if (hasSupportedWorkerCommand(tokens) && entrypoint?.endsWith(".py")) {
|
|
118
|
+
services[processType] = {
|
|
119
|
+
root: ".",
|
|
120
|
+
type: "worker",
|
|
121
|
+
entrypoint,
|
|
122
|
+
runtime: "python"
|
|
123
|
+
};
|
|
124
|
+
} else {
|
|
125
|
+
emitWorkerHint(processType, command, entrypoint, warnings);
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (!detectedFramework && !entrypoint) {
|
|
130
|
+
warnings.push({
|
|
131
|
+
code: "SERVICE_SKIPPED",
|
|
132
|
+
message: `Skipped Procfile process "${processType}": no framework detected and could not infer entrypoint from command "${command}". Configure it manually in services.`
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
services[processType] = {
|
|
137
|
+
root: ".",
|
|
138
|
+
type: "web",
|
|
139
|
+
...detectedFramework?.slug ? { framework: detectedFramework.slug } : {},
|
|
140
|
+
entrypoint: entrypoint ?? "."
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (errors.length > 0) {
|
|
144
|
+
return { services: null, errors, warnings };
|
|
145
|
+
}
|
|
146
|
+
if (Object.keys(services).length === 0) {
|
|
147
|
+
return { services: null, errors: [], warnings };
|
|
148
|
+
}
|
|
149
|
+
if (releaseCommand && services.web) {
|
|
150
|
+
services.web.buildCommand = releaseCommand;
|
|
151
|
+
} else if (releaseCommand) {
|
|
152
|
+
const firstService = Object.values(services)[0];
|
|
153
|
+
if (firstService) {
|
|
154
|
+
firstService.buildCommand = releaseCommand;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
warnings.push(...(0, import_utils.assignMountPaths)(services));
|
|
158
|
+
return { services, errors: [], warnings };
|
|
159
|
+
}
|
|
160
|
+
function parseProcfile(content) {
|
|
161
|
+
const entries = [];
|
|
162
|
+
for (const rawLine of content.split("\n")) {
|
|
163
|
+
const match = rawLine.match(PROCFILE_LINE_RE);
|
|
164
|
+
if (match) {
|
|
165
|
+
entries.push({ processType: match[1], command: match[2].trim() });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return entries;
|
|
169
|
+
}
|
|
170
|
+
async function extractEntrypoint(tokens, fs) {
|
|
171
|
+
let firstModulePath;
|
|
172
|
+
let lastFilePath;
|
|
173
|
+
for (const token of tokens) {
|
|
174
|
+
if (!firstModulePath && PY_MODULE_RE.test(token)) {
|
|
175
|
+
const resolved = await resolvePythonModule(token, fs);
|
|
176
|
+
if (resolved)
|
|
177
|
+
firstModulePath = resolved;
|
|
178
|
+
}
|
|
179
|
+
if (hasSupportedExtension(token)) {
|
|
180
|
+
lastFilePath = token;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return firstModulePath ?? lastFilePath;
|
|
184
|
+
}
|
|
185
|
+
async function resolvePythonModule(spec, fs) {
|
|
186
|
+
const [modulePart] = spec.split(":");
|
|
187
|
+
const filePath = `${modulePart.replace(/\./g, "/")}.py`;
|
|
188
|
+
const initPath = `${modulePart.replace(/\./g, "/")}/__init__.py`;
|
|
189
|
+
try {
|
|
190
|
+
if (await fs.isFile(filePath))
|
|
191
|
+
return filePath;
|
|
192
|
+
if (await fs.isFile(initPath))
|
|
193
|
+
return initPath;
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
function hasSupportedExtension(token) {
|
|
199
|
+
const dot = token.lastIndexOf(".");
|
|
200
|
+
return dot > 0 && SUPPORTED_EXTENSIONS.has(token.slice(dot));
|
|
201
|
+
}
|
|
202
|
+
function hasSupportedWorkerCommand(tokens) {
|
|
203
|
+
return tokens.some((t) => SUPPORTED_WORKER_COMMANDS.has(baseCommand(t)));
|
|
204
|
+
}
|
|
205
|
+
function emitWorkerHint(processType, command, entrypoint, warnings) {
|
|
206
|
+
const hint = {
|
|
207
|
+
type: "worker",
|
|
208
|
+
entrypoint: entrypoint ?? "<path-to-handler>",
|
|
209
|
+
runtime: "python"
|
|
210
|
+
};
|
|
211
|
+
if (entrypoint?.endsWith(".py")) {
|
|
212
|
+
warnings.push({
|
|
213
|
+
code: "PROCFILE_WORKER_HINT",
|
|
214
|
+
message: `Found Procfile worker process "${processType}". Python workers that use Celery, Dramatiq and Django tasks are supported. You can add the following to define this worker:
|
|
215
|
+
"${processType}": ${JSON.stringify(hint, null, 2)}`
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
warnings.push({
|
|
220
|
+
code: "PROCFILE_WORKER_HINT",
|
|
221
|
+
message: `Found Procfile worker process "${processType}" (command: "${command}"). Could not determine runtime. Only Python workers are currently supported.`
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function baseCommand(token) {
|
|
225
|
+
if (!token)
|
|
226
|
+
return "";
|
|
227
|
+
const parts = token.split("/");
|
|
228
|
+
return parts[parts.length - 1];
|
|
229
|
+
}
|
|
230
|
+
async function readProcfile(fs) {
|
|
231
|
+
try {
|
|
232
|
+
const exists = await fs.isFile(PROCFILE);
|
|
233
|
+
if (!exists)
|
|
234
|
+
return { content: null };
|
|
235
|
+
const buf = await fs.readFile(PROCFILE);
|
|
236
|
+
return { content: buf.toString("utf-8") };
|
|
237
|
+
} catch (err) {
|
|
238
|
+
return {
|
|
239
|
+
content: null,
|
|
240
|
+
warning: {
|
|
241
|
+
code: "PROCFILE_READ_ERROR",
|
|
242
|
+
message: `Failed to read ${PROCFILE}: ${err instanceof Error ? err.message : String(err)}`
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
248
|
+
0 && (module.exports = {
|
|
249
|
+
detectProcfileServices
|
|
250
|
+
});
|