@foro-sh/foro 0.10.0 → 0.12.1

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.
@@ -1,219 +1,713 @@
1
1
  export declare const rawManifestCases: readonly [{
2
- readonly name: "minimal-valid";
3
- readonly yaml: "name: my-server\nentrypoint: server.py\n";
2
+ readonly name: "python-minimal";
3
+ readonly files: {
4
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n";
5
+ readonly "server.py": "";
6
+ };
7
+ readonly expect: {
8
+ readonly ok: true;
9
+ };
10
+ }, {
11
+ readonly name: "python-poetry-only-name";
12
+ readonly files: {
13
+ readonly "pyproject.toml": "[tool.poetry]\nname = \"my-server\"\n";
14
+ readonly "main.py": "";
15
+ };
4
16
  readonly expect: {
5
17
  readonly ok: true;
6
18
  };
7
19
  }, {
8
- readonly name: "nested-entrypoint";
9
- readonly yaml: "name: my-server\nentrypoint: src/server.py\n";
20
+ readonly name: "python-explicit-entrypoint";
21
+ readonly files: {
22
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"src/server.py\"\n";
23
+ };
10
24
  readonly expect: {
11
25
  readonly ok: true;
12
26
  };
13
27
  }, {
14
- readonly name: "all-fields";
15
- readonly yaml: "name: my-server\nentrypoint: server.py\nbuild_path: app\nruntime: python\nruntime_version: \"3.11\"\nport: 9000\ndependency_manager: poetry\n";
28
+ readonly name: "python-all-fields";
29
+ readonly files: {
30
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nbuild_path = \"app\"\nentrypoint = \"server.py\"\nruntime = \"python\"\nruntime_version = \"3.11\"\nport = 9000\ndependency_manager = \"poetry\"\n";
31
+ };
16
32
  readonly expect: {
17
33
  readonly ok: true;
18
34
  };
19
35
  }, {
20
- readonly name: "unquoted-runtime-version";
21
- readonly yaml: "name: my-server\nentrypoint: server.py\nruntime_version: 3.12\n";
36
+ readonly name: "python-requires-python-range";
37
+ readonly files: {
38
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\nrequires-python = \">=3.11,<3.13\"\n";
39
+ readonly "server.py": "";
40
+ };
22
41
  readonly expect: {
23
42
  readonly ok: true;
24
43
  };
25
44
  }, {
26
- readonly name: "bad-name-uppercase";
27
- readonly yaml: "name: My-Server\nentrypoint: server.py\n";
45
+ readonly name: "python-requires-python-exclusion";
46
+ readonly files: {
47
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\nrequires-python = \">=3.11,!=3.13\"\n";
48
+ readonly "server.py": "";
49
+ };
50
+ readonly expect: {
51
+ readonly ok: true;
52
+ };
53
+ }, {
54
+ readonly name: "python-requires-python-excludes-every-supported-version-one-by-one";
55
+ readonly files: {
56
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\nrequires-python = \">=3.11,!=3.11,!=3.12,!=3.13\"\n";
57
+ readonly "server.py": "";
58
+ };
28
59
  readonly expect: {
29
60
  readonly ok: false;
30
- readonly reason: "invalid_name";
61
+ readonly reason: "invalid_runtime_version";
31
62
  };
32
63
  }, {
33
- readonly name: "bad-name-too-short";
34
- readonly yaml: "name: ab\nentrypoint: server.py\n";
64
+ readonly name: "python-hyphen-inside-a-path-segment-is-fine";
65
+ readonly files: {
66
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"my-tools/mcp-server.py\"\n";
67
+ };
68
+ readonly expect: {
69
+ readonly ok: true;
70
+ };
71
+ }, {
72
+ readonly name: "python-scoped-unspellable-name-is-normalised";
73
+ readonly files: {
74
+ readonly "pyproject.toml": "[project]\nname = \"My_Server\"\n";
75
+ readonly "server.py": "";
76
+ };
77
+ readonly expect: {
78
+ readonly ok: true;
79
+ };
80
+ }, {
81
+ readonly name: "python-no-entry-file";
82
+ readonly files: {
83
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n";
84
+ };
35
85
  readonly expect: {
36
86
  readonly ok: false;
37
- readonly reason: "invalid_name";
87
+ readonly reason: "invalid_entrypoint";
38
88
  };
39
89
  }, {
40
- readonly name: "missing-name";
41
- readonly yaml: "entrypoint: server.py\n";
90
+ readonly name: "python-entrypoint-traversal";
91
+ readonly files: {
92
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"../server.py\"\n";
93
+ };
42
94
  readonly expect: {
43
95
  readonly ok: false;
44
- readonly reason: "invalid_name";
96
+ readonly reason: "invalid_entrypoint";
45
97
  };
46
98
  }, {
47
- readonly name: "bad-entrypoint-traversal";
48
- readonly yaml: "name: my-server\nentrypoint: ../server.py\n";
99
+ readonly name: "python-entrypoint-absolute";
100
+ readonly files: {
101
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"/server.py\"\n";
102
+ };
49
103
  readonly expect: {
50
104
  readonly ok: false;
51
105
  readonly reason: "invalid_entrypoint";
52
106
  };
53
107
  }, {
54
- readonly name: "bad-entrypoint-absolute";
55
- readonly yaml: "name: my-server\nentrypoint: /server.py\n";
108
+ readonly name: "python-entrypoint-shell-metachar";
109
+ readonly files: {
110
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py; rm -rf x\"\n";
111
+ };
56
112
  readonly expect: {
57
113
  readonly ok: false;
58
114
  readonly reason: "invalid_entrypoint";
59
115
  };
60
116
  }, {
61
- readonly name: "bad-entrypoint-shell-metachar";
62
- readonly yaml: "name: my-server\nentrypoint: \"server.py; rm -rf x\"\n";
117
+ readonly name: "python-entrypoint-leading-dash";
118
+ readonly files: {
119
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"--isolated\"\n";
120
+ };
63
121
  readonly expect: {
64
122
  readonly ok: false;
65
123
  readonly reason: "invalid_entrypoint";
66
124
  };
67
125
  }, {
68
- readonly name: "missing-entrypoint";
69
- readonly yaml: "name: my-server\n";
126
+ readonly name: "python-entrypoint-leading-dash-nested";
127
+ readonly files: {
128
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"src/-rf.py\"\n";
129
+ };
70
130
  readonly expect: {
71
131
  readonly ok: false;
72
132
  readonly reason: "invalid_entrypoint";
73
133
  };
74
134
  }, {
75
- readonly name: "bad-build-path-traversal";
76
- readonly yaml: "name: my-server\nentrypoint: server.py\nbuild_path: ../x\n";
135
+ readonly name: "python-build-path-traversal";
136
+ readonly files: {
137
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nbuild_path = \"../x\"\n";
138
+ };
77
139
  readonly expect: {
78
140
  readonly ok: false;
79
141
  readonly reason: "invalid_build_path";
80
142
  };
81
143
  }, {
82
- readonly name: "bad-build-path-shell-metachar";
83
- readonly yaml: "name: my-server\nentrypoint: server.py\nbuild_path: \". && whoami\"\n";
144
+ readonly name: "python-build-path-shell-metachar";
145
+ readonly files: {
146
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nbuild_path = \". && whoami\"\n";
147
+ };
84
148
  readonly expect: {
85
149
  readonly ok: false;
86
150
  readonly reason: "invalid_build_path";
87
151
  };
88
152
  }, {
89
- readonly name: "bad-build-path-space";
90
- readonly yaml: "name: my-server\nentrypoint: server.py\nbuild_path: \"my services\"\n";
153
+ readonly name: "python-build-path-space";
154
+ readonly files: {
155
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nbuild_path = \"my services\"\n";
156
+ };
91
157
  readonly expect: {
92
158
  readonly ok: false;
93
159
  readonly reason: "invalid_build_path";
94
160
  };
95
161
  }, {
96
- readonly name: "bad-build-path-newline";
97
- readonly yaml: "name: my-server\nentrypoint: server.py\nbuild_path: \"app\\nUSER root\"\n";
162
+ readonly name: "python-build-path-newline";
163
+ readonly files: {
164
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nbuild_path = \"app\\nUSER root\"\n";
165
+ };
98
166
  readonly expect: {
99
167
  readonly ok: false;
100
168
  readonly reason: "invalid_build_path";
101
169
  };
102
170
  }, {
103
- readonly name: "bad-runtime-version";
104
- readonly yaml: "name: my-server\nentrypoint: server.py\nruntime_version: \"3.9\"\n";
171
+ readonly name: "python-build-path-leading-dash";
172
+ readonly files: {
173
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nbuild_path = \"-rf\"\n";
174
+ };
175
+ readonly expect: {
176
+ readonly ok: false;
177
+ readonly reason: "invalid_build_path";
178
+ };
179
+ }, {
180
+ readonly name: "python-runtime-version-outside-allowlist";
181
+ readonly files: {
182
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nruntime_version = \"3.9\"\n";
183
+ };
105
184
  readonly expect: {
106
185
  readonly ok: false;
107
186
  readonly reason: "invalid_runtime_version";
108
187
  };
109
188
  }, {
110
- readonly name: "bad-runtime";
111
- readonly yaml: "name: my-server\nentrypoint: server.py\nruntime: node\n";
189
+ readonly name: "python-requires-python-excludes-every-supported-version";
190
+ readonly files: {
191
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\nrequires-python = \"==3.9\"\n";
192
+ readonly "server.py": "";
193
+ };
112
194
  readonly expect: {
113
195
  readonly ok: false;
114
- readonly reason: "invalid_runtime";
196
+ readonly reason: "invalid_runtime_version";
115
197
  };
116
198
  }, {
117
- readonly name: "runtime-version-from-another-runtime";
118
- readonly yaml: "name: my-server\nentrypoint: server.py\nruntime: python\nruntime_version: \"22\"\n";
199
+ readonly name: "python-runtime-version-from-another-runtime";
200
+ readonly files: {
201
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nruntime_version = \"22\"\n";
202
+ };
119
203
  readonly expect: {
120
204
  readonly ok: false;
121
205
  readonly reason: "invalid_runtime_version";
122
206
  };
207
+ }, {
208
+ readonly name: "runtime-outside-allowlist";
209
+ readonly files: {
210
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nruntime = \"ruby\"\n";
211
+ };
212
+ readonly expect: {
213
+ readonly ok: false;
214
+ readonly reason: "invalid_runtime";
215
+ };
216
+ }, {
217
+ readonly name: "runtime-disagrees-with-the-file-it-was-read-from";
218
+ readonly files: {
219
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nruntime = \"node\"\n";
220
+ };
221
+ readonly expect: {
222
+ readonly ok: false;
223
+ readonly reason: "invalid_runtime";
224
+ };
225
+ }, {
226
+ readonly name: "both-config-files-and-neither-claims-the-runtime";
227
+ readonly files: {
228
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n";
229
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\"}";
230
+ readonly "server.py": "";
231
+ };
232
+ readonly expect: {
233
+ readonly ok: false;
234
+ readonly reason: "invalid_runtime";
235
+ };
236
+ }, {
237
+ readonly name: "both-config-files-and-both-claim-the-runtime";
238
+ readonly files: {
239
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nruntime = \"python\"\n";
240
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\", \"foro\": {\"runtime\": \"node\"}}";
241
+ readonly "server.py": "";
242
+ };
243
+ readonly expect: {
244
+ readonly ok: false;
245
+ readonly reason: "invalid_runtime";
246
+ };
247
+ }, {
248
+ readonly name: "both-config-files-python-claims-the-runtime";
249
+ readonly files: {
250
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nruntime = \"python\"\n";
251
+ readonly "package.json": "{\"name\": \"tooling-only\"}";
252
+ readonly "server.py": "";
253
+ };
254
+ readonly expect: {
255
+ readonly ok: true;
256
+ };
257
+ }, {
258
+ readonly name: "both-config-files-node-claims-the-runtime";
259
+ readonly files: {
260
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n";
261
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\", \"foro\": {\"runtime\": \"node\"}}";
262
+ readonly "server.py": "";
263
+ };
264
+ readonly expect: {
265
+ readonly ok: true;
266
+ };
267
+ }, {
268
+ readonly name: "no-config-file-only-a-legacy-manifest";
269
+ readonly files: {
270
+ readonly "foro.yaml": "name: my-server\nentrypoint: server.py\n";
271
+ readonly "server.py": "";
272
+ };
273
+ readonly expect: {
274
+ readonly ok: false;
275
+ readonly reason: "missing_manifest";
276
+ };
123
277
  }, {
124
278
  readonly name: "unknown-field-stale-python-version";
125
- readonly yaml: "name: my-server\nentrypoint: server.py\npython_version: \"3.13\"\n";
279
+ readonly files: {
280
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\npython_version = \"3.13\"\n";
281
+ };
126
282
  readonly expect: {
127
283
  readonly ok: false;
128
284
  readonly reason: "unknown_field";
129
285
  };
130
286
  }, {
131
287
  readonly name: "unknown-field-typo";
132
- readonly yaml: "name: my-server\nentrypoint: server.py\nentrypoints: server.py\n";
288
+ readonly files: {
289
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nentrypoints = \"server.py\"\n";
290
+ };
133
291
  readonly expect: {
134
292
  readonly ok: false;
135
293
  readonly reason: "unknown_field";
136
294
  };
137
295
  }, {
138
- readonly name: "bad-port-too-low";
139
- readonly yaml: "name: my-server\nentrypoint: server.py\nport: 80\n";
296
+ readonly name: "unknown-field-name-belongs-to-the-package";
297
+ readonly files: {
298
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nname = \"other\"\n";
299
+ };
300
+ readonly expect: {
301
+ readonly ok: false;
302
+ readonly reason: "unknown_field";
303
+ };
304
+ }, {
305
+ readonly name: "port-too-low";
306
+ readonly files: {
307
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nport = 80\n";
308
+ };
309
+ readonly expect: {
310
+ readonly ok: false;
311
+ readonly reason: "invalid_port";
312
+ };
313
+ }, {
314
+ readonly name: "port-too-high";
315
+ readonly files: {
316
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nport = 70000\n";
317
+ };
140
318
  readonly expect: {
141
319
  readonly ok: false;
142
320
  readonly reason: "invalid_port";
143
321
  };
144
322
  }, {
145
- readonly name: "bad-port-too-high";
146
- readonly yaml: "name: my-server\nentrypoint: server.py\nport: 70000\n";
323
+ readonly name: "port-sidecar-reserved";
324
+ readonly files: {
325
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nport = 8001\n";
326
+ };
147
327
  readonly expect: {
148
328
  readonly ok: false;
149
329
  readonly reason: "invalid_port";
150
330
  };
151
331
  }, {
152
- readonly name: "bad-port-sidecar-reserved";
153
- readonly yaml: "name: my-server\nentrypoint: server.py\nport: 8001\n";
332
+ readonly name: "port-gate-reserved";
333
+ readonly files: {
334
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nport = 8002\n";
335
+ };
154
336
  readonly expect: {
155
337
  readonly ok: false;
156
338
  readonly reason: "invalid_port";
157
339
  };
158
340
  }, {
159
- readonly name: "bad-port-non-integer";
160
- readonly yaml: "name: my-server\nentrypoint: server.py\nport: \"abc\"\n";
341
+ readonly name: "port-non-integer";
342
+ readonly files: {
343
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\nport = \"abc\"\n";
344
+ };
161
345
  readonly expect: {
162
346
  readonly ok: false;
163
347
  readonly reason: "invalid_port";
164
348
  };
165
349
  }, {
166
- readonly name: "bad-dependency-manager";
167
- readonly yaml: "name: my-server\nentrypoint: server.py\ndependency_manager: pipx\n";
350
+ readonly name: "dependency-manager-outside-allowlist";
351
+ readonly files: {
352
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\ndependency_manager = \"pipx\"\n";
353
+ };
168
354
  readonly expect: {
169
355
  readonly ok: false;
170
356
  readonly reason: "invalid_dependency_manager";
171
357
  };
172
358
  }, {
173
- readonly name: "invalid-yaml";
174
- readonly yaml: "name: [unclosed\n";
359
+ readonly name: "node-dependency-manager-on-python";
360
+ readonly files: {
361
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\ndependency_manager = \"npm\"\n";
362
+ };
175
363
  readonly expect: {
176
364
  readonly ok: false;
177
- readonly reason: "invalid_yaml";
365
+ readonly reason: "invalid_dependency_manager";
178
366
  };
179
367
  }, {
180
- readonly name: "invalid-shape-scalar";
181
- readonly yaml: "just a string\n";
368
+ readonly name: "unparseable-pyproject";
369
+ readonly files: {
370
+ readonly "pyproject.toml": "[project\nname = \"my-server\"\n";
371
+ };
182
372
  readonly expect: {
183
373
  readonly ok: false;
184
374
  readonly reason: "invalid_shape";
185
375
  };
186
376
  }, {
187
- readonly name: "invalid-shape-list";
188
- readonly yaml: "- one\n- two\n";
377
+ readonly name: "node-minimal";
378
+ readonly files: {
379
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"dist/index.js\"}";
380
+ };
381
+ readonly expect: {
382
+ readonly ok: true;
383
+ };
384
+ }, {
385
+ readonly name: "node-scoped-name";
386
+ readonly files: {
387
+ readonly "package.json": "{\"name\": \"@acme/my-server\", \"main\": \"dist/index.js\"}";
388
+ };
389
+ readonly expect: {
390
+ readonly ok: true;
391
+ };
392
+ }, {
393
+ readonly name: "node-bin-map";
394
+ readonly files: {
395
+ readonly "package.json": "{\"name\": \"my-server\", \"bin\": {\"my-server\": \"dist/cli.js\"}}";
396
+ };
397
+ readonly expect: {
398
+ readonly ok: true;
399
+ };
400
+ }, {
401
+ readonly name: "node-implicit-index";
402
+ readonly files: {
403
+ readonly "package.json": "{\"name\": \"my-server\"}";
404
+ readonly "index.js": "";
405
+ };
406
+ readonly expect: {
407
+ readonly ok: true;
408
+ };
409
+ }, {
410
+ readonly name: "node-engines-range";
411
+ readonly files: {
412
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\", \"engines\": {\"node\": \">=22\"}}";
413
+ };
414
+ readonly expect: {
415
+ readonly ok: true;
416
+ };
417
+ }, {
418
+ readonly name: "node-engines-union-of-every-supported-major";
419
+ readonly files: {
420
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\", \"engines\": {\"node\": \"^22 || ^24\"}}";
421
+ };
422
+ readonly expect: {
423
+ readonly ok: true;
424
+ };
425
+ }, {
426
+ readonly name: "node-engines-union-with-one-unsupported-branch";
427
+ readonly files: {
428
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\", \"engines\": {\"node\": \"^20 || ^22\"}}";
429
+ };
430
+ readonly expect: {
431
+ readonly ok: true;
432
+ };
433
+ }, {
434
+ readonly name: "node-all-fields";
435
+ readonly files: {
436
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"dist/index.js\", \"foro\": {\"runtime\": \"node\", \"runtime_version\": \"22\", \"port\": 9000, \"dependency_manager\": \"pnpm\"}}";
437
+ };
438
+ readonly expect: {
439
+ readonly ok: true;
440
+ };
441
+ }, {
442
+ readonly name: "node-no-entry-file";
443
+ readonly files: {
444
+ readonly "package.json": "{\"name\": \"my-server\"}";
445
+ };
446
+ readonly expect: {
447
+ readonly ok: false;
448
+ readonly reason: "invalid_entrypoint";
449
+ };
450
+ }, {
451
+ readonly name: "node-engines-excludes-every-supported-version";
452
+ readonly files: {
453
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\", \"engines\": {\"node\": \"^20\"}}";
454
+ };
455
+ readonly expect: {
456
+ readonly ok: false;
457
+ readonly reason: "invalid_runtime_version";
458
+ };
459
+ }, {
460
+ readonly name: "node-runtime-version-from-another-runtime";
461
+ readonly files: {
462
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\", \"foro\": {\"runtime_version\": \"3.12\"}}";
463
+ };
464
+ readonly expect: {
465
+ readonly ok: false;
466
+ readonly reason: "invalid_runtime_version";
467
+ };
468
+ }, {
469
+ readonly name: "python-dependency-manager-on-node";
470
+ readonly files: {
471
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\", \"foro\": {\"dependency_manager\": \"uv\"}}";
472
+ };
473
+ readonly expect: {
474
+ readonly ok: false;
475
+ readonly reason: "invalid_dependency_manager";
476
+ };
477
+ }, {
478
+ readonly name: "unparseable-package-json";
479
+ readonly files: {
480
+ readonly "package.json": "{\"name\": ";
481
+ };
189
482
  readonly expect: {
190
483
  readonly ok: false;
191
484
  readonly reason: "invalid_shape";
192
485
  };
193
486
  }, {
194
- readonly name: "bad-entrypoint-leading-dash";
195
- readonly yaml: "name: my-server\nentrypoint: --isolated\n";
487
+ readonly name: "python-egress-ip-entry";
488
+ readonly files: {
489
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"10.20.30.40:5432\"]\n";
490
+ };
491
+ readonly expect: {
492
+ readonly ok: true;
493
+ };
494
+ }, {
495
+ readonly name: "python-egress-cidr-entry";
496
+ readonly files: {
497
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"10.20.0.0/16:443\"]\n";
498
+ };
499
+ readonly expect: {
500
+ readonly ok: true;
501
+ };
502
+ }, {
503
+ readonly name: "python-egress-hostname-entry";
504
+ readonly files: {
505
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"sql.database.windows.net:1433\"]\n";
506
+ };
507
+ readonly expect: {
508
+ readonly ok: true;
509
+ };
510
+ }, {
511
+ readonly name: "python-egress-multiple-entries";
512
+ readonly files: {
513
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"sql.database.windows.net:1433\", \"10.20.30.40:5432\", \"10.20.0.0/16:443\"]\n";
514
+ };
515
+ readonly expect: {
516
+ readonly ok: true;
517
+ };
518
+ }, {
519
+ readonly name: "python-egress-empty-list-denies-everything";
520
+ readonly files: {
521
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = []\n";
522
+ };
523
+ readonly expect: {
524
+ readonly ok: true;
525
+ };
526
+ }, {
527
+ readonly name: "python-egress-missing-port";
528
+ readonly files: {
529
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"example.com\"]\n";
530
+ };
196
531
  readonly expect: {
197
532
  readonly ok: false;
198
- readonly reason: "invalid_entrypoint";
533
+ readonly reason: "invalid_egress";
199
534
  };
200
535
  }, {
201
- readonly name: "bad-entrypoint-leading-dash-nested";
202
- readonly yaml: "name: my-server\nentrypoint: src/-rf.py\n";
536
+ readonly name: "python-egress-port-zero";
537
+ readonly files: {
538
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"example.com:0\"]\n";
539
+ };
203
540
  readonly expect: {
204
541
  readonly ok: false;
205
- readonly reason: "invalid_entrypoint";
542
+ readonly reason: "invalid_egress";
206
543
  };
207
544
  }, {
208
- readonly name: "bad-build-path-leading-dash";
209
- readonly yaml: "name: my-server\nentrypoint: server.py\nbuild_path: -rf\n";
545
+ readonly name: "python-egress-port-too-high";
546
+ readonly files: {
547
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"example.com:65536\"]\n";
548
+ };
210
549
  readonly expect: {
211
550
  readonly ok: false;
212
- readonly reason: "invalid_build_path";
551
+ readonly reason: "invalid_egress";
552
+ };
553
+ }, {
554
+ readonly name: "python-egress-blocked-mail-port";
555
+ readonly files: {
556
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"smtp.example.com:25\"]\n";
557
+ };
558
+ readonly expect: {
559
+ readonly ok: false;
560
+ readonly reason: "invalid_egress";
561
+ };
562
+ }, {
563
+ readonly name: "python-egress-metadata-endpoint";
564
+ readonly files: {
565
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"169.254.169.254:80\"]\n";
566
+ };
567
+ readonly expect: {
568
+ readonly ok: false;
569
+ readonly reason: "invalid_egress";
570
+ };
571
+ }, {
572
+ readonly name: "python-egress-whole-internet";
573
+ readonly files: {
574
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"0.0.0.0/0:443\"]\n";
575
+ };
576
+ readonly expect: {
577
+ readonly ok: false;
578
+ readonly reason: "invalid_egress";
579
+ };
580
+ }, {
581
+ readonly name: "python-egress-wide-range-swallows-a-reserved-one";
582
+ readonly files: {
583
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"172.16.0.0/12:443\"]\n";
584
+ };
585
+ readonly expect: {
586
+ readonly ok: false;
587
+ readonly reason: "invalid_egress";
588
+ };
589
+ }, {
590
+ readonly name: "python-egress-hostname-over-253-chars";
591
+ readonly files: {
592
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa.aaaa:443\"]\n";
593
+ };
594
+ readonly expect: {
595
+ readonly ok: false;
596
+ readonly reason: "invalid_egress";
597
+ };
598
+ }, {
599
+ readonly name: "python-egress-string-instead-of-list";
600
+ readonly files: {
601
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = \"example.com:443\"\n";
602
+ };
603
+ readonly expect: {
604
+ readonly ok: false;
605
+ readonly reason: "invalid_egress";
606
+ };
607
+ }, {
608
+ readonly name: "python-egress-over-entry-cap";
609
+ readonly files: {
610
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"h0.example.com:443\", \"h1.example.com:443\", \"h2.example.com:443\", \"h3.example.com:443\", \"h4.example.com:443\", \"h5.example.com:443\", \"h6.example.com:443\", \"h7.example.com:443\", \"h8.example.com:443\", \"h9.example.com:443\", \"h10.example.com:443\", \"h11.example.com:443\", \"h12.example.com:443\", \"h13.example.com:443\", \"h14.example.com:443\", \"h15.example.com:443\", \"h16.example.com:443\", \"h17.example.com:443\", \"h18.example.com:443\", \"h19.example.com:443\", \"h20.example.com:443\"]\n";
611
+ };
612
+ readonly expect: {
613
+ readonly ok: false;
614
+ readonly reason: "invalid_egress";
615
+ };
616
+ }, {
617
+ readonly name: "node-egress-non-string-entry";
618
+ readonly files: {
619
+ readonly "package.json": "{\"name\": \"my-server\", \"main\": \"index.js\", \"foro\": {\"egress\": [443]}}";
620
+ };
621
+ readonly expect: {
622
+ readonly ok: false;
623
+ readonly reason: "invalid_egress";
624
+ };
625
+ }, {
626
+ readonly name: "python-egress-shell-metacharacter";
627
+ readonly files: {
628
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"evil.com:443; rm -rf /\"]\n";
629
+ };
630
+ readonly expect: {
631
+ readonly ok: false;
632
+ readonly reason: "invalid_egress";
633
+ };
634
+ }, {
635
+ readonly name: "python-egress-embedded-whitespace";
636
+ readonly files: {
637
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"evil .com:443\"]\n";
638
+ };
639
+ readonly expect: {
640
+ readonly ok: false;
641
+ readonly reason: "invalid_egress";
213
642
  };
214
643
  }, {
215
- readonly name: "hyphen-inside-a-path-segment-is-fine";
216
- readonly yaml: "name: my-server\nentrypoint: my-tools/mcp-server.py\n";
644
+ readonly name: "python-egress-embedded-newline";
645
+ readonly files: {
646
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"evil.com:443\\nother:80\"]\n";
647
+ };
648
+ readonly expect: {
649
+ readonly ok: false;
650
+ readonly reason: "invalid_egress";
651
+ };
652
+ }, {
653
+ readonly name: "python-egress-ipv6-literal";
654
+ readonly files: {
655
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"::1:443\"]\n";
656
+ };
657
+ readonly expect: {
658
+ readonly ok: false;
659
+ readonly reason: "invalid_egress";
660
+ };
661
+ }, {
662
+ readonly name: "python-egress-cidr-prefix-out-of-range";
663
+ readonly files: {
664
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"10.0.0.0/33:443\"]\n";
665
+ };
666
+ readonly expect: {
667
+ readonly ok: false;
668
+ readonly reason: "invalid_egress";
669
+ };
670
+ }, {
671
+ readonly name: "python-egress-link-local-range";
672
+ readonly files: {
673
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"169.254.0.0/16:443\"]\n";
674
+ };
675
+ readonly expect: {
676
+ readonly ok: false;
677
+ readonly reason: "invalid_egress";
678
+ };
679
+ }, {
680
+ readonly name: "python-egress-loopback";
681
+ readonly files: {
682
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"127.0.0.1:5432\"]\n";
683
+ };
684
+ readonly expect: {
685
+ readonly ok: false;
686
+ readonly reason: "invalid_egress";
687
+ };
688
+ }, {
689
+ readonly name: "python-egress-platform-bridge-network";
690
+ readonly files: {
691
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"172.20.0.5:5432\"]\n";
692
+ };
693
+ readonly expect: {
694
+ readonly ok: false;
695
+ readonly reason: "invalid_egress";
696
+ };
697
+ }, {
698
+ readonly name: "python-egress-submission-port";
699
+ readonly files: {
700
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"smtp.example.com:587\"]\n";
701
+ };
702
+ readonly expect: {
703
+ readonly ok: false;
704
+ readonly reason: "invalid_egress";
705
+ };
706
+ }, {
707
+ readonly name: "python-egress-private-ranges-stay-allowlistable";
708
+ readonly files: {
709
+ readonly "pyproject.toml": "[project]\nname = \"my-server\"\n\n[tool.foro]\nentrypoint = \"server.py\"\negress = [\"10.20.30.40:5432\", \"192.168.1.0/24:443\"]\n";
710
+ };
217
711
  readonly expect: {
218
712
  readonly ok: true;
219
713
  };