@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 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
+ });