({
workPath,
pythonPackage,
pythonVersion,
uvLockPath,
framework,
serviceType,
}: {
workPath: string;
pythonPackage: PythonPackage;
pythonVersion: PythonVersion;
uvLockPath: string;
framework?: string | null;
serviceType?: string | null;
})
| 78 | * Called during build() so data is collected while it's already available. |
| 79 | */ |
| 80 | export async function generateProjectManifest({ |
| 81 | workPath, |
| 82 | pythonPackage, |
| 83 | pythonVersion, |
| 84 | uvLockPath, |
| 85 | framework, |
| 86 | serviceType, |
| 87 | }: { |
| 88 | workPath: string; |
| 89 | pythonPackage: PythonPackage; |
| 90 | pythonVersion: PythonVersion; |
| 91 | uvLockPath: string; |
| 92 | framework?: string | null; |
| 93 | serviceType?: string | null; |
| 94 | }): Promise<void> { |
| 95 | const resolved = pythonVersionString(pythonVersion) ?? ''; |
| 96 | const constraint = pythonPackage.requiresPython?.[0]; |
| 97 | const requested = constraint?.specifier; |
| 98 | |
| 99 | const project = pythonPackage.manifest?.data?.project; |
| 100 | const pyprojectData = pythonPackage.manifest?.data; |
| 101 | |
| 102 | // Track direct dependency names and their scopes. |
| 103 | // A package can appear in multiple groups, so we collect all scopes. |
| 104 | const directScopesMap = new Map<string, Set<string>>(); |
| 105 | const directRequested = new Map<string, string>(); |
| 106 | |
| 107 | // Collect all raw dependency strings paired with their scope first, |
| 108 | // then parse them in a single batch to avoid repeated WASM calls. |
| 109 | const allRawDeps: { dep: string; scope: string }[] = []; |
| 110 | |
| 111 | // 1. project.dependencies → scope "main" |
| 112 | if (project?.dependencies && Array.isArray(project.dependencies)) { |
| 113 | for (const dep of project.dependencies) { |
| 114 | allRawDeps.push({ dep, scope: 'main' }); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | // 2. project.optional-dependencies → scope = group key |
| 119 | const optDeps = project?.['optional-dependencies']; |
| 120 | if (optDeps) { |
| 121 | for (const [group, deps] of Object.entries(optDeps)) { |
| 122 | if (Array.isArray(deps)) { |
| 123 | for (const dep of deps) { |
| 124 | allRawDeps.push({ dep, scope: group }); |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | // 3. dependency-groups → scope = group key |
| 131 | // Supports PEP 735 include-group references via recursive resolution. |
| 132 | const depGroups = pyprojectData?.['dependency-groups']; |
| 133 | if (depGroups) { |
| 134 | for (const group of Object.keys(depGroups)) { |
| 135 | const groupDeps = resolveDependencyGroup(group, depGroups); |
| 136 | for (const dep of groupDeps) { |
| 137 | allRawDeps.push({ dep, scope: group }); |
no test coverage detected