@vercel/fs-detectors 7.1.6 → 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 +6 -5
|
@@ -0,0 +1,22 @@
|
|
|
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 RailwayDetectResult {
|
|
5
|
+
services: InferredServicesConfig | null;
|
|
6
|
+
errors: ServiceDetectionError[];
|
|
7
|
+
warnings: ServiceDetectionWarning[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Detect Railway service configurations in the project.
|
|
11
|
+
*
|
|
12
|
+
* Scans for railway.{json,toml} files, parses them,
|
|
13
|
+
* tries to detect frameworks in each service directory, and maps to
|
|
14
|
+
* services format.
|
|
15
|
+
*
|
|
16
|
+
* When a Railway config has `deploy.preDeployCommand`, it's added to
|
|
17
|
+
* `buildCommand` since we don't have a separate pre-deploy yet.
|
|
18
|
+
*/
|
|
19
|
+
export declare function detectRailwayServices(options: {
|
|
20
|
+
fs: DetectorFilesystem;
|
|
21
|
+
detectEntrypoint?: DetectEntrypointFn;
|
|
22
|
+
}): Promise<RailwayDetectResult>;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var detect_railway_exports = {};
|
|
30
|
+
__export(detect_railway_exports, {
|
|
31
|
+
detectRailwayServices: () => detectRailwayServices
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(detect_railway_exports);
|
|
34
|
+
var import_path = require("path");
|
|
35
|
+
var import_smol_toml = __toESM(require("smol-toml"));
|
|
36
|
+
var import_detect_framework = require("../detect-framework");
|
|
37
|
+
var import_framework = require("./runtimes/framework");
|
|
38
|
+
var import_utils = require("./utils");
|
|
39
|
+
const RAILWAY_JSON = "railway.json";
|
|
40
|
+
const RAILWAY_TOML = "railway.toml";
|
|
41
|
+
const MAX_SCAN_DEPTH = 5;
|
|
42
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
43
|
+
".hg",
|
|
44
|
+
".git",
|
|
45
|
+
".svn",
|
|
46
|
+
".cache",
|
|
47
|
+
".next",
|
|
48
|
+
".now",
|
|
49
|
+
".vercel",
|
|
50
|
+
".venv",
|
|
51
|
+
".yarn",
|
|
52
|
+
".turbo",
|
|
53
|
+
".output",
|
|
54
|
+
"node_modules",
|
|
55
|
+
"__pycache__",
|
|
56
|
+
"venv",
|
|
57
|
+
"CVS"
|
|
58
|
+
]);
|
|
59
|
+
async function detectRailwayServices(options) {
|
|
60
|
+
const { fs, detectEntrypoint } = options;
|
|
61
|
+
const { configs, warnings } = await findRailwayConfigs(fs);
|
|
62
|
+
if (configs.length === 0) {
|
|
63
|
+
return { services: null, errors: [], warnings };
|
|
64
|
+
}
|
|
65
|
+
const services = {};
|
|
66
|
+
const serviceDirs = /* @__PURE__ */ new Map();
|
|
67
|
+
const errors = [];
|
|
68
|
+
for (const cf of configs) {
|
|
69
|
+
const serviceFs = cf.dirPath === "." ? fs : fs.chdir(cf.dirPath);
|
|
70
|
+
const dirLabel = cf.dirPath === "." ? "root" : cf.dirPath;
|
|
71
|
+
const frameworks = await (0, import_detect_framework.detectFrameworks)({
|
|
72
|
+
fs: serviceFs,
|
|
73
|
+
frameworkList: import_framework.DETECTION_FRAMEWORKS,
|
|
74
|
+
useExperimentalFrameworks: true
|
|
75
|
+
});
|
|
76
|
+
if (cf.config.deploy?.cronSchedule) {
|
|
77
|
+
const schedule = cf.config.deploy.cronSchedule;
|
|
78
|
+
const runtime = frameworks.length === 1 ? (0, import_framework.inferRuntimeFromFramework)(frameworks[0].slug) : void 0;
|
|
79
|
+
const hint = {
|
|
80
|
+
type: "job",
|
|
81
|
+
trigger: "schedule",
|
|
82
|
+
schedule,
|
|
83
|
+
entrypoint: "<path-to-handler>"
|
|
84
|
+
};
|
|
85
|
+
if (runtime) {
|
|
86
|
+
hint.runtime = runtime;
|
|
87
|
+
}
|
|
88
|
+
warnings.push({
|
|
89
|
+
code: "RAILWAY_CRON_HINT",
|
|
90
|
+
message: `Found Railway cron in ${dirLabel}/ (schedule: "${schedule}"). Vercel crons work with a file entrypoint. You can add the following to define this scheduled job service:
|
|
91
|
+
"${deriveServiceName(cf.dirPath)}": ${JSON.stringify(hint, null, 2)}`
|
|
92
|
+
});
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const serviceName = deriveServiceName(cf.dirPath);
|
|
96
|
+
const existingDir = serviceDirs.get(serviceName);
|
|
97
|
+
if (existingDir) {
|
|
98
|
+
errors.push({
|
|
99
|
+
code: "DUPLICATE_SERVICE",
|
|
100
|
+
message: `Duplicate service name "${serviceName}" derived from ${existingDir}/ and ${dirLabel}/. Rename one of the directories to avoid conflicts.`,
|
|
101
|
+
serviceName
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
serviceDirs.set(serviceName, dirLabel);
|
|
106
|
+
if (frameworks.length === 0) {
|
|
107
|
+
warnings.push({
|
|
108
|
+
code: "SERVICE_SKIPPED",
|
|
109
|
+
message: `Skipped service in ${dirLabel}/: no framework detected. Configure it manually in services.`
|
|
110
|
+
});
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (frameworks.length > 1) {
|
|
114
|
+
const names = frameworks.map((f) => f.name).join(", ");
|
|
115
|
+
errors.push({
|
|
116
|
+
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
117
|
+
message: `Multiple frameworks detected in ${dirLabel}/: ${names}. Use explicit services config.`,
|
|
118
|
+
serviceName
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const framework = frameworks[0];
|
|
123
|
+
const slug = framework.slug ?? void 0;
|
|
124
|
+
const serviceConfig = {
|
|
125
|
+
root: cf.dirPath,
|
|
126
|
+
framework: slug
|
|
127
|
+
};
|
|
128
|
+
if (cf.dirPath !== "." && detectEntrypoint && !(0, import_framework.isFrontendFramework)(slug)) {
|
|
129
|
+
const detected = await detectEntrypoint({
|
|
130
|
+
workPath: cf.dirPath,
|
|
131
|
+
framework: slug
|
|
132
|
+
});
|
|
133
|
+
if (detected) {
|
|
134
|
+
serviceConfig.entrypoint = detected.entrypoint;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (cf.config.build?.buildCommand) {
|
|
138
|
+
serviceConfig.buildCommand = cf.config.build.buildCommand;
|
|
139
|
+
}
|
|
140
|
+
const railwayPreDeploy = cf.config.deploy?.preDeployCommand;
|
|
141
|
+
if (railwayPreDeploy) {
|
|
142
|
+
serviceConfig.preDeployCommand = Array.isArray(railwayPreDeploy) ? railwayPreDeploy.join(" && ") : railwayPreDeploy;
|
|
143
|
+
}
|
|
144
|
+
services[serviceName] = serviceConfig;
|
|
145
|
+
}
|
|
146
|
+
if (errors.length > 0) {
|
|
147
|
+
return { services: null, errors, warnings };
|
|
148
|
+
}
|
|
149
|
+
const serviceNames = Object.keys(services);
|
|
150
|
+
if (serviceNames.length === 0) {
|
|
151
|
+
return { services: null, errors: [], warnings };
|
|
152
|
+
}
|
|
153
|
+
warnings.push(...(0, import_utils.assignMountPaths)(services));
|
|
154
|
+
return { services, errors: [], warnings };
|
|
155
|
+
}
|
|
156
|
+
async function findRailwayConfigs(fs, dirPath = ".", depth = 0) {
|
|
157
|
+
const configs = [];
|
|
158
|
+
const warnings = [];
|
|
159
|
+
const readResult = await readRailwayConfigRaw(fs, dirPath);
|
|
160
|
+
warnings.push(...readResult.warnings);
|
|
161
|
+
const { config, warning } = tryParseRailwayConfig(readResult.raw);
|
|
162
|
+
if (warning) {
|
|
163
|
+
warnings.push(warning);
|
|
164
|
+
}
|
|
165
|
+
if (config) {
|
|
166
|
+
configs.push({ dirPath, config });
|
|
167
|
+
}
|
|
168
|
+
if (depth >= MAX_SCAN_DEPTH) {
|
|
169
|
+
return { configs, warnings };
|
|
170
|
+
}
|
|
171
|
+
const readPath = dirPath === "." ? "/" : dirPath;
|
|
172
|
+
let entries;
|
|
173
|
+
try {
|
|
174
|
+
entries = await fs.readdir(readPath);
|
|
175
|
+
} catch {
|
|
176
|
+
return { configs, warnings };
|
|
177
|
+
}
|
|
178
|
+
for (const entry of entries) {
|
|
179
|
+
if (entry.type !== "dir" || SKIP_DIRS.has(entry.name)) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const childPath = dirPath === "." ? entry.name : import_path.posix.join(dirPath, entry.name);
|
|
183
|
+
const child = await findRailwayConfigs(fs, childPath, depth + 1);
|
|
184
|
+
configs.push(...child.configs);
|
|
185
|
+
warnings.push(...child.warnings);
|
|
186
|
+
}
|
|
187
|
+
return { configs, warnings };
|
|
188
|
+
}
|
|
189
|
+
async function readRailwayConfigRaw(fs, dirPath) {
|
|
190
|
+
const warnings = [];
|
|
191
|
+
for (const filename of [RAILWAY_JSON, RAILWAY_TOML]) {
|
|
192
|
+
const filePath = dirPath === "." ? filename : import_path.posix.join(dirPath, filename);
|
|
193
|
+
try {
|
|
194
|
+
const exists = await fs.isFile(filePath);
|
|
195
|
+
if (!exists)
|
|
196
|
+
continue;
|
|
197
|
+
} catch {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const buf = await fs.readFile(filePath);
|
|
202
|
+
return {
|
|
203
|
+
raw: { path: filePath, content: buf.toString("utf-8") },
|
|
204
|
+
warnings
|
|
205
|
+
};
|
|
206
|
+
} catch (err) {
|
|
207
|
+
warnings.push({
|
|
208
|
+
code: "RAILWAY_CONFIG_ERROR",
|
|
209
|
+
message: `Failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { raw: null, warnings };
|
|
214
|
+
}
|
|
215
|
+
function tryParseRailwayConfig(raw) {
|
|
216
|
+
if (!raw) {
|
|
217
|
+
return { config: null };
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const config = raw.path.endsWith(".toml") ? import_smol_toml.default.parse(raw.content) : JSON.parse(raw.content);
|
|
221
|
+
return { config };
|
|
222
|
+
} catch (err) {
|
|
223
|
+
return {
|
|
224
|
+
config: null,
|
|
225
|
+
warning: {
|
|
226
|
+
code: "RAILWAY_PARSE_ERROR",
|
|
227
|
+
message: `Failed to parse ${raw.path}: ${err instanceof Error ? err.message : String(err)}`
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function deriveServiceName(dirPath) {
|
|
233
|
+
if (dirPath === ".") {
|
|
234
|
+
return "web";
|
|
235
|
+
}
|
|
236
|
+
const segments = dirPath.split("/");
|
|
237
|
+
return segments[segments.length - 1];
|
|
238
|
+
}
|
|
239
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
240
|
+
0 && (module.exports = {
|
|
241
|
+
detectRailwayServices
|
|
242
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
2
|
+
import type { DetectEntrypointFn, InferredServicesConfig, ServiceDetectionError, ServiceDetectionWarning } from './types';
|
|
3
|
+
export interface RenderDetectResult {
|
|
4
|
+
services: InferredServicesConfig | null;
|
|
5
|
+
errors: ServiceDetectionError[];
|
|
6
|
+
warnings: ServiceDetectionWarning[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Detect Render service configurations from render.yaml.
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectRenderServices(options: {
|
|
12
|
+
fs: DetectorFilesystem;
|
|
13
|
+
detectEntrypoint?: DetectEntrypointFn;
|
|
14
|
+
}): Promise<RenderDetectResult>;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var detect_render_exports = {};
|
|
30
|
+
__export(detect_render_exports, {
|
|
31
|
+
detectRenderServices: () => detectRenderServices
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(detect_render_exports);
|
|
34
|
+
var import_js_yaml = __toESM(require("js-yaml"));
|
|
35
|
+
var import_detect_framework = require("../detect-framework");
|
|
36
|
+
var import_constants = require("./runtimes/constants");
|
|
37
|
+
var import_framework = require("./runtimes/framework");
|
|
38
|
+
var import_utils = require("./utils");
|
|
39
|
+
const RENDER_YAML = "render.yaml";
|
|
40
|
+
const SERVICE_TYPE_MAP = {
|
|
41
|
+
web: "web",
|
|
42
|
+
static: "web"
|
|
43
|
+
};
|
|
44
|
+
async function detectRenderServices(options) {
|
|
45
|
+
const { fs, detectEntrypoint } = options;
|
|
46
|
+
const raw = await readRenderYaml(fs);
|
|
47
|
+
if (raw.warning) {
|
|
48
|
+
return { services: null, errors: [], warnings: [raw.warning] };
|
|
49
|
+
} else if (!raw.content) {
|
|
50
|
+
return { services: null, errors: [], warnings: [] };
|
|
51
|
+
}
|
|
52
|
+
const parsed = tryParseRenderConfig(raw.content);
|
|
53
|
+
if (parsed.warning) {
|
|
54
|
+
return { services: null, errors: [], warnings: [parsed.warning] };
|
|
55
|
+
} else if (!parsed.config) {
|
|
56
|
+
return { services: null, errors: [], warnings: [] };
|
|
57
|
+
}
|
|
58
|
+
const renderServices = parsed.config.services;
|
|
59
|
+
if (!Array.isArray(renderServices) || renderServices.length === 0) {
|
|
60
|
+
return { services: null, errors: [], warnings: [] };
|
|
61
|
+
}
|
|
62
|
+
const services = {};
|
|
63
|
+
const serviceNames = /* @__PURE__ */ new Set();
|
|
64
|
+
const errors = [];
|
|
65
|
+
const warnings = [];
|
|
66
|
+
for (const rs of renderServices) {
|
|
67
|
+
const serviceType = rs.type;
|
|
68
|
+
if (serviceType === "cron") {
|
|
69
|
+
const name = rs.name ?? "unnamed";
|
|
70
|
+
const schedule = rs.schedule;
|
|
71
|
+
const runtime = rs.runtime && rs.runtime in import_constants.RUNTIME_BUILDERS ? rs.runtime : void 0;
|
|
72
|
+
const hint = {
|
|
73
|
+
type: "cron",
|
|
74
|
+
...schedule ? { schedule } : {},
|
|
75
|
+
entrypoint: "<path-to-handler>",
|
|
76
|
+
...runtime ? { runtime } : {}
|
|
77
|
+
};
|
|
78
|
+
warnings.push({
|
|
79
|
+
code: "RENDER_CRON_HINT",
|
|
80
|
+
message: `Found Render cron service "${name}"` + (schedule ? ` (schedule: "${schedule}")` : "") + `. Vercel crons work with a file entrypoint. You can add the following to define this cron service:
|
|
81
|
+
"${name}": ${JSON.stringify(hint, null, 2)}`
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (serviceType === "worker") {
|
|
86
|
+
const name = rs.name ?? "unnamed";
|
|
87
|
+
const runtime = rs.runtime ?? "unknown";
|
|
88
|
+
if (runtime === "python") {
|
|
89
|
+
const hint = {
|
|
90
|
+
type: "worker",
|
|
91
|
+
entrypoint: "<path-to-celery-app>",
|
|
92
|
+
runtime: "python"
|
|
93
|
+
};
|
|
94
|
+
warnings.push({
|
|
95
|
+
code: "RENDER_WORKER_HINT",
|
|
96
|
+
message: `Found Render worker service "${name}". Python workers using Celery are supported. You can add the following to define this worker:
|
|
97
|
+
"${name}": ${JSON.stringify(hint, null, 2)}`
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
warnings.push({
|
|
101
|
+
code: "RENDER_WORKER_HINT",
|
|
102
|
+
message: `Found Render worker service "${name}" with runtime "${runtime}". Only Python workers are currently supported.`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (serviceType === "pserv") {
|
|
108
|
+
const name = rs.name ?? "unnamed";
|
|
109
|
+
const hint = {
|
|
110
|
+
entrypoint: rs.rootDir ?? "<path-to-entrypoint>",
|
|
111
|
+
mountPath: `/api/${name}`
|
|
112
|
+
};
|
|
113
|
+
warnings.push({
|
|
114
|
+
code: "RENDER_PSERV_HINT",
|
|
115
|
+
message: `Found Render private service "${name}". Private services are not yet supported. If you'd like to deploy it as a regular web service, you can add the following:
|
|
116
|
+
"${name}": ${JSON.stringify(hint, null, 2)}`
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!serviceType || !(serviceType in SERVICE_TYPE_MAP)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const serviceName = rs.name;
|
|
124
|
+
if (!serviceName) {
|
|
125
|
+
warnings.push({
|
|
126
|
+
code: "RENDER_CONFIG_ERROR",
|
|
127
|
+
message: "Skipped a Render service with no name. Each service in render.yaml must have a name."
|
|
128
|
+
});
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (serviceNames.has(serviceName)) {
|
|
132
|
+
errors.push({
|
|
133
|
+
code: "DUPLICATE_SERVICE",
|
|
134
|
+
message: `Duplicate service name "${serviceName}" in render.yaml.`,
|
|
135
|
+
serviceName
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
serviceNames.add(serviceName);
|
|
140
|
+
const rootDir = rs.rootDir || ".";
|
|
141
|
+
const serviceFs = rootDir === "." ? fs : fs.chdir(rootDir);
|
|
142
|
+
const frameworks = await (0, import_detect_framework.detectFrameworks)({
|
|
143
|
+
fs: serviceFs,
|
|
144
|
+
frameworkList: import_framework.DETECTION_FRAMEWORKS,
|
|
145
|
+
useExperimentalFrameworks: true
|
|
146
|
+
});
|
|
147
|
+
if (frameworks.length === 0) {
|
|
148
|
+
warnings.push({
|
|
149
|
+
code: "SERVICE_SKIPPED",
|
|
150
|
+
message: `Skipped Render service "${serviceName}": no framework detected. Configure it manually in services.`
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (frameworks.length > 1) {
|
|
155
|
+
const names = frameworks.map((f) => f.name).join(", ");
|
|
156
|
+
errors.push({
|
|
157
|
+
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
158
|
+
message: `Multiple frameworks detected for Render service "${serviceName}": ${names}. Use explicit services config.`,
|
|
159
|
+
serviceName
|
|
160
|
+
});
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const framework = frameworks[0];
|
|
164
|
+
const vercelType = SERVICE_TYPE_MAP[serviceType];
|
|
165
|
+
const serviceConfig = {
|
|
166
|
+
root: rootDir,
|
|
167
|
+
type: vercelType,
|
|
168
|
+
framework: framework.slug ?? void 0
|
|
169
|
+
};
|
|
170
|
+
if (rootDir !== "." && detectEntrypoint && !(0, import_framework.isFrontendFramework)(serviceConfig.framework)) {
|
|
171
|
+
const detected = await detectEntrypoint({
|
|
172
|
+
workPath: rootDir,
|
|
173
|
+
framework: serviceConfig.framework
|
|
174
|
+
});
|
|
175
|
+
if (detected) {
|
|
176
|
+
serviceConfig.entrypoint = detected.entrypoint;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const buildCommand = (0, import_utils.combineBuildCommand)(
|
|
180
|
+
rs.buildCommand,
|
|
181
|
+
rs.preDeployCommand
|
|
182
|
+
);
|
|
183
|
+
if (buildCommand) {
|
|
184
|
+
serviceConfig.buildCommand = buildCommand;
|
|
185
|
+
}
|
|
186
|
+
services[serviceName] = serviceConfig;
|
|
187
|
+
}
|
|
188
|
+
if (errors.length > 0) {
|
|
189
|
+
return { services: null, errors, warnings };
|
|
190
|
+
}
|
|
191
|
+
if (Object.keys(services).length === 0) {
|
|
192
|
+
return { services: null, errors: [], warnings };
|
|
193
|
+
}
|
|
194
|
+
warnings.push(...(0, import_utils.assignMountPaths)(services));
|
|
195
|
+
return { services, errors: [], warnings };
|
|
196
|
+
}
|
|
197
|
+
async function readRenderYaml(fs) {
|
|
198
|
+
try {
|
|
199
|
+
const exists = await fs.isFile(RENDER_YAML);
|
|
200
|
+
if (!exists)
|
|
201
|
+
return { content: null };
|
|
202
|
+
const buf = await fs.readFile(RENDER_YAML);
|
|
203
|
+
return { content: buf.toString("utf-8") };
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return {
|
|
206
|
+
content: null,
|
|
207
|
+
warning: {
|
|
208
|
+
code: "RENDER_CONFIG_ERROR",
|
|
209
|
+
message: `Failed to read ${RENDER_YAML}: ${err instanceof Error ? err.message : String(err)}`
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function tryParseRenderConfig(content) {
|
|
215
|
+
try {
|
|
216
|
+
const config = import_js_yaml.default.load(content);
|
|
217
|
+
return { config };
|
|
218
|
+
} catch (err) {
|
|
219
|
+
return {
|
|
220
|
+
config: null,
|
|
221
|
+
warning: {
|
|
222
|
+
code: "RENDER_PARSE_ERROR",
|
|
223
|
+
message: `Failed to parse ${RENDER_YAML}: ${err instanceof Error ? err.message : String(err)}`
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
229
|
+
0 && (module.exports = {
|
|
230
|
+
detectRenderServices
|
|
231
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Rewrite } from '@vercel/routing-utils';
|
|
2
|
+
import type { DetectServicesOptions, DetectServicesResult, InferredServicesConfig, Service, ServicesRoutes } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Detect and resolve services within a project.
|
|
5
|
+
*
|
|
6
|
+
* Reads vercel.json and resolves configured services into Service objects.
|
|
7
|
+
* Returns an error if no services are configured.
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectServices(options: DetectServicesOptions): Promise<DetectServicesResult>;
|
|
10
|
+
/**
|
|
11
|
+
* Generate top-level service-targeted rewrites from inferred mount paths.
|
|
12
|
+
*
|
|
13
|
+
* Produces `Rewrite` objects (same format as vercel.json `rewrites`) that
|
|
14
|
+
* delegate public traffic into services based on their `mountPath`.
|
|
15
|
+
*
|
|
16
|
+
* Rewrites are ordered by mount path length (longest first) so more
|
|
17
|
+
* specific paths match before broader ones. The root service (`/`) is
|
|
18
|
+
* always last as a catch-all.
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateServiceRewrites(services: InferredServicesConfig): Rewrite[];
|
|
21
|
+
/**
|
|
22
|
+
* Generate routing rules for services.
|
|
23
|
+
*
|
|
24
|
+
* Routes are ordered by prefix length (longest first) to ensure more specific
|
|
25
|
+
* routes match before broader ones. For example, `/api/users` must be checked
|
|
26
|
+
* before `/api`, which must be checked before the catch-all `/`.
|
|
27
|
+
*
|
|
28
|
+
* Services routing only generates *synthetic* routes for builders that do not
|
|
29
|
+
* provide their own route tables:
|
|
30
|
+
*
|
|
31
|
+
* - **Static/SPA services** (`@vercel/static-build`, `@vercel/static`):
|
|
32
|
+
* SPA fallback routes to index.html under the service prefix.
|
|
33
|
+
*
|
|
34
|
+
* - **Runtime services** (`@vercel/python`, `@vercel/go`, `@vercel/ruby`, etc.):
|
|
35
|
+
* Prefix rewrites to an internal runtime destination (`/_svc/{name}/index`)
|
|
36
|
+
* with `check: true`.
|
|
37
|
+
*
|
|
38
|
+
* Builders that provide their own routing (`@vercel/next`, `@vercel/backends`,
|
|
39
|
+
* Build Output API builders, etc.) are not given synthetic routes here.
|
|
40
|
+
*
|
|
41
|
+
* - Worker and queue-triggered job services:
|
|
42
|
+
* Use private path routing. The generated function is not publicly accessible.
|
|
43
|
+
*
|
|
44
|
+
* - Schedule-triggered job services:
|
|
45
|
+
* Internal cron callback routes under `/_svc/{serviceName}/crons/{entry}/{handler}`
|
|
46
|
+
* that rewrite to `/_svc/{serviceName}/index`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function generateServicesRoutes(allServices: Service[]): ServicesRoutes;
|