git-goose 0.0.1__py3-none-any.whl
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.
- git_goose-0.0.1.dist-info/LICENSE +29 -0
- git_goose-0.0.1.dist-info/METADATA +235 -0
- git_goose-0.0.1.dist-info/RECORD +30 -0
- git_goose-0.0.1.dist-info/WHEEL +5 -0
- git_goose-0.0.1.dist-info/top_level.txt +1 -0
- goose/__init__.py +0 -0
- goose/__main__.py +251 -0
- goose/_utils/__init__.py +0 -0
- goose/_utils/pydantic.py +10 -0
- goose/asyncio.py +19 -0
- goose/backend/__init__.py +0 -0
- goose/backend/base.py +76 -0
- goose/backend/index.py +20 -0
- goose/backend/node.py +190 -0
- goose/backend/python.py +185 -0
- goose/config.py +74 -0
- goose/context.py +34 -0
- goose/environment.py +259 -0
- goose/executable_unit.py +11 -0
- goose/filter.py +10 -0
- goose/git/__init__.py +0 -0
- goose/git/shared.py +48 -0
- goose/git/status.py +155 -0
- goose/manifest.py +160 -0
- goose/orphan_environments.py +20 -0
- goose/parallel.py +40 -0
- goose/paths.py +22 -0
- goose/process.py +26 -0
- goose/scheduler.py +154 -0
- goose/targets.py +103 -0
goose/backend/node.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import asyncio.subprocess
|
|
3
|
+
import contextlib
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import Iterator
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Final
|
|
12
|
+
|
|
13
|
+
from pydantic import Field
|
|
14
|
+
|
|
15
|
+
from goose._utils.pydantic import BaseModel
|
|
16
|
+
from goose.config import EnvironmentConfig
|
|
17
|
+
from goose.executable_unit import ExecutableUnit
|
|
18
|
+
from goose.manifest import build_manifest
|
|
19
|
+
from goose.manifest import write_manifest
|
|
20
|
+
from goose.process import stream_both
|
|
21
|
+
from goose.process import system_python
|
|
22
|
+
|
|
23
|
+
from .base import Backend
|
|
24
|
+
from .base import RunResult
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PackageJson(BaseModel):
|
|
28
|
+
lockfile_version: int = Field(default=3, serialization_alias="lockfileVersion")
|
|
29
|
+
dependencies: Mapping[str, str]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _bootstrap_env() -> dict[str, str]:
|
|
33
|
+
return os.environ | {
|
|
34
|
+
"PYTHONUNBUFFERED": "1",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _create_node_env(env_path: Path, version: str) -> None:
|
|
39
|
+
process = await asyncio.create_subprocess_exec(
|
|
40
|
+
system_python(),
|
|
41
|
+
*(
|
|
42
|
+
"-m",
|
|
43
|
+
"nodeenv",
|
|
44
|
+
f"--node={version}",
|
|
45
|
+
str(env_path),
|
|
46
|
+
),
|
|
47
|
+
env=_bootstrap_env(),
|
|
48
|
+
stdout=asyncio.subprocess.PIPE,
|
|
49
|
+
stderr=asyncio.subprocess.PIPE,
|
|
50
|
+
)
|
|
51
|
+
await stream_both(process)
|
|
52
|
+
await process.wait()
|
|
53
|
+
if process.returncode != 0:
|
|
54
|
+
raise RuntimeError("Failed creating virtualenv {process.returncode=}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def bootstrap(
|
|
58
|
+
env_path: Path,
|
|
59
|
+
config: EnvironmentConfig,
|
|
60
|
+
) -> None:
|
|
61
|
+
print(f"Creating node env at {env_path.name}", file=sys.stderr)
|
|
62
|
+
await _create_node_env(env_path, config.ecosystem.version)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _write_package_json(
|
|
66
|
+
config: EnvironmentConfig,
|
|
67
|
+
lock_files_path: Path,
|
|
68
|
+
) -> Path:
|
|
69
|
+
package_json_path = lock_files_path / "package.json"
|
|
70
|
+
package_json = PackageJson(
|
|
71
|
+
dependencies={
|
|
72
|
+
# todo: support version specs
|
|
73
|
+
dependency: "*"
|
|
74
|
+
for dependency in config.dependencies
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
package_json_path.write_text(package_json.model_dump_json(by_alias=True))
|
|
78
|
+
return package_json_path
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@contextlib.contextmanager
|
|
82
|
+
def cd_to(path: Path) -> Iterator[None]:
|
|
83
|
+
cd = os.getcwd()
|
|
84
|
+
try:
|
|
85
|
+
os.chdir(path)
|
|
86
|
+
yield
|
|
87
|
+
finally:
|
|
88
|
+
os.chdir(cd)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def freeze(
|
|
92
|
+
env_path: Path,
|
|
93
|
+
config: EnvironmentConfig,
|
|
94
|
+
lock_files_path: Path,
|
|
95
|
+
) -> None:
|
|
96
|
+
lock_files_path.mkdir(exist_ok=True)
|
|
97
|
+
|
|
98
|
+
package_json_path = _write_package_json(config, lock_files_path)
|
|
99
|
+
|
|
100
|
+
with cd_to(lock_files_path):
|
|
101
|
+
process = await asyncio.create_subprocess_exec(
|
|
102
|
+
env_path / "bin" / "npm",
|
|
103
|
+
*(
|
|
104
|
+
"install",
|
|
105
|
+
"--package-lock-only",
|
|
106
|
+
),
|
|
107
|
+
env=os.environ | {"PATH": f"{os.environ['PATH']}:{env_path / 'bin'}"},
|
|
108
|
+
stdout=asyncio.subprocess.PIPE,
|
|
109
|
+
stderr=asyncio.subprocess.PIPE,
|
|
110
|
+
)
|
|
111
|
+
await stream_both(process)
|
|
112
|
+
await process.wait()
|
|
113
|
+
|
|
114
|
+
if process.returncode != 0:
|
|
115
|
+
raise RuntimeError("Failed freezing dependencies {process.returncode=}")
|
|
116
|
+
|
|
117
|
+
package_lock_json_path = lock_files_path / "package-lock.json"
|
|
118
|
+
manifest = build_manifest(
|
|
119
|
+
source_dependencies=config.dependencies,
|
|
120
|
+
lock_files=(
|
|
121
|
+
package_json_path,
|
|
122
|
+
package_lock_json_path,
|
|
123
|
+
),
|
|
124
|
+
lock_files_path=lock_files_path,
|
|
125
|
+
)
|
|
126
|
+
write_manifest(lock_files_path, manifest)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def sync(
|
|
130
|
+
env_path: Path,
|
|
131
|
+
config: EnvironmentConfig,
|
|
132
|
+
lock_files_path: Path,
|
|
133
|
+
) -> None:
|
|
134
|
+
package_lock_path = lock_files_path / "package-lock.json"
|
|
135
|
+
package_json_path = lock_files_path / "package.json"
|
|
136
|
+
|
|
137
|
+
shutil.copy(package_lock_path, env_path / package_lock_path.name)
|
|
138
|
+
shutil.copy(package_json_path, env_path / package_json_path.name)
|
|
139
|
+
|
|
140
|
+
with cd_to(env_path):
|
|
141
|
+
process = await asyncio.create_subprocess_exec(
|
|
142
|
+
env_path / "bin" / "npm",
|
|
143
|
+
*(
|
|
144
|
+
"install",
|
|
145
|
+
"--no-save",
|
|
146
|
+
),
|
|
147
|
+
env=os.environ | {"PATH": f"{os.environ['PATH']}:{env_path / 'bin'}"},
|
|
148
|
+
stdout=asyncio.subprocess.PIPE,
|
|
149
|
+
stderr=asyncio.subprocess.PIPE,
|
|
150
|
+
)
|
|
151
|
+
await stream_both(process)
|
|
152
|
+
await process.wait()
|
|
153
|
+
|
|
154
|
+
if process.returncode != 0:
|
|
155
|
+
raise RuntimeError("Failed syncing dependencies {process.returncode=}")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def run(
|
|
159
|
+
env_path: Path,
|
|
160
|
+
config: EnvironmentConfig,
|
|
161
|
+
unit: ExecutableUnit,
|
|
162
|
+
) -> RunResult:
|
|
163
|
+
args: Sequence[str | Path] = (
|
|
164
|
+
"exec",
|
|
165
|
+
f"--prefix={env_path}",
|
|
166
|
+
unit.hook.command,
|
|
167
|
+
"--",
|
|
168
|
+
*unit.hook.args,
|
|
169
|
+
*unit.targets,
|
|
170
|
+
)
|
|
171
|
+
process = await asyncio.create_subprocess_exec(
|
|
172
|
+
env_path / "bin" / "npm",
|
|
173
|
+
*args,
|
|
174
|
+
env=os.environ | {"PATH": f"{os.environ['PATH']}:{env_path / 'bin'}"},
|
|
175
|
+
stdout=asyncio.subprocess.PIPE,
|
|
176
|
+
stderr=asyncio.subprocess.PIPE,
|
|
177
|
+
)
|
|
178
|
+
await stream_both(process)
|
|
179
|
+
await process.wait()
|
|
180
|
+
|
|
181
|
+
return RunResult.ok if process.returncode == 0 else RunResult.error
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
backend: Final = Backend(
|
|
185
|
+
ecosystem="node",
|
|
186
|
+
bootstrap=bootstrap,
|
|
187
|
+
freeze=freeze,
|
|
188
|
+
sync=sync,
|
|
189
|
+
run=run,
|
|
190
|
+
)
|
goose/backend/python.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import asyncio.subprocess
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from contextlib import ExitStack
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Final
|
|
9
|
+
|
|
10
|
+
from goose.config import EnvironmentConfig
|
|
11
|
+
from goose.executable_unit import ExecutableUnit
|
|
12
|
+
from goose.manifest import build_manifest
|
|
13
|
+
from goose.manifest import write_manifest
|
|
14
|
+
from goose.process import stream_both
|
|
15
|
+
from goose.process import system_python
|
|
16
|
+
|
|
17
|
+
from .base import Backend
|
|
18
|
+
from .base import RunResult
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _venv_python(env_path: Path) -> Path:
|
|
22
|
+
return env_path / "bin" / "python"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _bootstrap_env() -> dict[str, str]:
|
|
26
|
+
return os.environ | {
|
|
27
|
+
"PYTHONUNBUFFERED": "1",
|
|
28
|
+
"PIP_REQUIRE_VIRTUALENV": "true",
|
|
29
|
+
"PIP_DISABLE_PIP_VERSION_CHECK": "true",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _run_env(env_path: Path) -> dict[str, str]:
|
|
34
|
+
bin_path = env_path / "bin"
|
|
35
|
+
return os.environ | {
|
|
36
|
+
"PATH": f"{bin_path}:{os.environ['PATH']}",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def _create_virtualenv(env_path: Path) -> None:
|
|
41
|
+
process = await asyncio.create_subprocess_exec(
|
|
42
|
+
system_python(),
|
|
43
|
+
*("-m", "venv", str(env_path)),
|
|
44
|
+
env=_bootstrap_env(),
|
|
45
|
+
stdout=asyncio.subprocess.PIPE,
|
|
46
|
+
stderr=asyncio.subprocess.PIPE,
|
|
47
|
+
)
|
|
48
|
+
await stream_both(process)
|
|
49
|
+
await process.wait()
|
|
50
|
+
if process.returncode != 0:
|
|
51
|
+
raise RuntimeError("Failed creating virtualenv {process.returncode=}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def _pip_install(
|
|
55
|
+
env_path: Path,
|
|
56
|
+
dependencies: Iterable[str],
|
|
57
|
+
) -> None:
|
|
58
|
+
process = await asyncio.create_subprocess_exec(
|
|
59
|
+
_venv_python(env_path),
|
|
60
|
+
*("-m", "pip", "install", *dependencies),
|
|
61
|
+
env=_bootstrap_env(),
|
|
62
|
+
stdout=asyncio.subprocess.PIPE,
|
|
63
|
+
stderr=asyncio.subprocess.PIPE,
|
|
64
|
+
)
|
|
65
|
+
await stream_both(process)
|
|
66
|
+
await process.wait()
|
|
67
|
+
if process.returncode != 0:
|
|
68
|
+
raise RuntimeError("Failed installing packages {process.returncode=}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def _pip_sync(
|
|
72
|
+
env_path: Path,
|
|
73
|
+
requirements_txt: Path,
|
|
74
|
+
) -> None:
|
|
75
|
+
process = await asyncio.create_subprocess_exec(
|
|
76
|
+
env_path / "bin" / "pip-sync",
|
|
77
|
+
str(requirements_txt),
|
|
78
|
+
env=_bootstrap_env(),
|
|
79
|
+
stdout=asyncio.subprocess.PIPE,
|
|
80
|
+
stderr=asyncio.subprocess.PIPE,
|
|
81
|
+
)
|
|
82
|
+
await stream_both(process)
|
|
83
|
+
await process.wait()
|
|
84
|
+
if process.returncode != 0:
|
|
85
|
+
raise RuntimeError("Failed syncing dependencies {process.returncode=}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def bootstrap(
|
|
89
|
+
env_path: Path,
|
|
90
|
+
config: EnvironmentConfig,
|
|
91
|
+
) -> None:
|
|
92
|
+
print(f"Creating virtualenv {env_path.name}", file=sys.stderr)
|
|
93
|
+
await _create_virtualenv(env_path)
|
|
94
|
+
|
|
95
|
+
print("Installing pip-tools ...", file=sys.stderr)
|
|
96
|
+
await _pip_install(env_path, ("pip-tools",))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def freeze(
|
|
100
|
+
env_path: Path,
|
|
101
|
+
config: EnvironmentConfig,
|
|
102
|
+
lock_files_path: Path,
|
|
103
|
+
) -> None:
|
|
104
|
+
lock_files_path.mkdir(exist_ok=True)
|
|
105
|
+
|
|
106
|
+
tmp_requirements_in = lock_files_path / "requirements.in"
|
|
107
|
+
requirements_txt = lock_files_path / "requirements.txt"
|
|
108
|
+
|
|
109
|
+
with ExitStack() as stack:
|
|
110
|
+
stack.callback(tmp_requirements_in.unlink, missing_ok=True)
|
|
111
|
+
|
|
112
|
+
# Write equivalent of a requirements.in.
|
|
113
|
+
with tmp_requirements_in.open("w") as fd:
|
|
114
|
+
for dependency in config.dependencies:
|
|
115
|
+
print(dependency, file=fd)
|
|
116
|
+
|
|
117
|
+
process = await asyncio.create_subprocess_exec(
|
|
118
|
+
env_path / "bin" / "pip-compile",
|
|
119
|
+
*(
|
|
120
|
+
"--upgrade",
|
|
121
|
+
"--strip-extras",
|
|
122
|
+
"--generate-hashes",
|
|
123
|
+
"--resolver=backtracking",
|
|
124
|
+
"--no-annotate",
|
|
125
|
+
"--no-header",
|
|
126
|
+
f"--output-file={requirements_txt}",
|
|
127
|
+
f"{tmp_requirements_in}",
|
|
128
|
+
),
|
|
129
|
+
env=_bootstrap_env(),
|
|
130
|
+
stdout=asyncio.subprocess.PIPE,
|
|
131
|
+
stderr=asyncio.subprocess.PIPE,
|
|
132
|
+
)
|
|
133
|
+
await stream_both(process)
|
|
134
|
+
await process.wait()
|
|
135
|
+
|
|
136
|
+
if process.returncode != 0:
|
|
137
|
+
raise RuntimeError("Failed freezing dependencies {process.returncode=}")
|
|
138
|
+
|
|
139
|
+
manifest = build_manifest(
|
|
140
|
+
source_dependencies=config.dependencies,
|
|
141
|
+
lock_files=(requirements_txt,),
|
|
142
|
+
lock_files_path=lock_files_path,
|
|
143
|
+
)
|
|
144
|
+
write_manifest(lock_files_path, manifest)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
async def sync(
|
|
148
|
+
env_path: Path,
|
|
149
|
+
config: EnvironmentConfig,
|
|
150
|
+
lock_files_path: Path,
|
|
151
|
+
) -> None:
|
|
152
|
+
requirements_txt = lock_files_path / "requirements.txt"
|
|
153
|
+
|
|
154
|
+
await _pip_sync(
|
|
155
|
+
env_path=env_path,
|
|
156
|
+
requirements_txt=requirements_txt,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def run(
|
|
161
|
+
env_path: Path,
|
|
162
|
+
config: EnvironmentConfig,
|
|
163
|
+
unit: ExecutableUnit,
|
|
164
|
+
) -> RunResult:
|
|
165
|
+
process = await asyncio.create_subprocess_exec(
|
|
166
|
+
unit.hook.command,
|
|
167
|
+
*unit.hook.args,
|
|
168
|
+
*unit.targets,
|
|
169
|
+
env=_run_env(env_path),
|
|
170
|
+
stdout=asyncio.subprocess.PIPE,
|
|
171
|
+
stderr=asyncio.subprocess.PIPE,
|
|
172
|
+
)
|
|
173
|
+
await stream_both(process)
|
|
174
|
+
await process.wait()
|
|
175
|
+
|
|
176
|
+
return RunResult.ok if process.returncode == 0 else RunResult.error
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
backend: Final = Backend(
|
|
180
|
+
ecosystem="python",
|
|
181
|
+
bootstrap=bootstrap,
|
|
182
|
+
freeze=freeze,
|
|
183
|
+
sync=sync,
|
|
184
|
+
run=run,
|
|
185
|
+
)
|
goose/config.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from re import Pattern
|
|
5
|
+
from typing import Literal
|
|
6
|
+
from typing import NewType
|
|
7
|
+
from typing import Self
|
|
8
|
+
from typing import final
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
from pydantic import model_validator
|
|
12
|
+
|
|
13
|
+
from ._utils.pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@final
|
|
17
|
+
class EcosystemConfig(BaseModel):
|
|
18
|
+
language: Literal["python", "node"]
|
|
19
|
+
version: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
EnvironmentId = NewType("EnvironmentId", str)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@final
|
|
26
|
+
class EnvironmentConfig(BaseModel):
|
|
27
|
+
id: EnvironmentId
|
|
28
|
+
ecosystem: EcosystemConfig
|
|
29
|
+
dependencies: tuple[str, ...]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@final
|
|
33
|
+
class HookConfig(BaseModel):
|
|
34
|
+
id: str
|
|
35
|
+
environment: EnvironmentId
|
|
36
|
+
command: str
|
|
37
|
+
args: tuple[str, ...] = ()
|
|
38
|
+
parameterize: bool = True
|
|
39
|
+
types: frozenset[str] = frozenset()
|
|
40
|
+
exclude: tuple[Pattern, ...] = ()
|
|
41
|
+
read_only: bool = False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@final
|
|
45
|
+
class Config(BaseModel):
|
|
46
|
+
environments: tuple[EnvironmentConfig, ...]
|
|
47
|
+
hooks: tuple[HookConfig, ...]
|
|
48
|
+
exclude: tuple[Pattern, ...] = ()
|
|
49
|
+
|
|
50
|
+
@model_validator(mode="after") # type: ignore[misc]
|
|
51
|
+
def validate_hook_environments_configured(self) -> Self:
|
|
52
|
+
environments = {env.id for env in self.environments}
|
|
53
|
+
for hook in self.hooks:
|
|
54
|
+
if hook.environment not in environments:
|
|
55
|
+
raise ValueError(f"Unknown hook environment: {hook.environment!r}")
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
@model_validator(mode="after") # type: ignore[misc]
|
|
59
|
+
def validate_hook_ids_unique(self) -> Self:
|
|
60
|
+
if len({hook.id for hook in self.hooks}) != len(self.hooks):
|
|
61
|
+
raise ValueError("Hook ids must be unique")
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
@model_validator(mode="after") # type: ignore[misc]
|
|
65
|
+
def validate_environment_ids_unique(self) -> Self:
|
|
66
|
+
if len({env.id for env in self.environments}) != len(self.environments):
|
|
67
|
+
raise ValueError("Environment ids must be unique")
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def load_config(path: Path) -> Config:
|
|
72
|
+
with path.open("rb") as fd:
|
|
73
|
+
loaded = yaml.safe_load(fd)
|
|
74
|
+
return Config.model_validate(loaded)
|
goose/context.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .config import Config
|
|
6
|
+
from .config import EnvironmentId
|
|
7
|
+
from .config import load_config
|
|
8
|
+
from .environment import Environment
|
|
9
|
+
from .environment import build_environments
|
|
10
|
+
from .paths import get_env_path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
14
|
+
class Context:
|
|
15
|
+
config: Config
|
|
16
|
+
lock_files_path: Path
|
|
17
|
+
environments_path: Path
|
|
18
|
+
environments: Mapping[EnvironmentId, Environment]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def gather_context(
|
|
22
|
+
config_path: Path,
|
|
23
|
+
) -> Context:
|
|
24
|
+
lock_files_path = Path("./.goose").resolve()
|
|
25
|
+
lock_files_path.mkdir(exist_ok=True)
|
|
26
|
+
config = load_config(config_path)
|
|
27
|
+
environments_path = get_env_path()
|
|
28
|
+
return Context(
|
|
29
|
+
config=config,
|
|
30
|
+
# fixme: should be configurable.
|
|
31
|
+
lock_files_path=lock_files_path,
|
|
32
|
+
environments_path=environments_path,
|
|
33
|
+
environments=build_environments(config, environments_path, lock_files_path),
|
|
34
|
+
)
|