shipit-cli 0.13.4__py3-none-any.whl → 0.15.0__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.
- shipit/builders/__init__.py +9 -0
- shipit/builders/base.py +14 -0
- shipit/builders/docker.py +250 -0
- shipit/builders/local.py +161 -0
- shipit/cli.py +291 -1323
- shipit/generator.py +56 -36
- shipit/procfile.py +4 -72
- shipit/providers/base.py +50 -11
- shipit/providers/hugo.py +64 -14
- shipit/providers/jekyll.py +123 -0
- shipit/providers/laravel.py +40 -31
- shipit/providers/mkdocs.py +34 -19
- shipit/providers/node_static.py +219 -136
- shipit/providers/php.py +42 -38
- shipit/providers/python.py +284 -228
- shipit/providers/registry.py +2 -2
- shipit/providers/staticfile.py +45 -26
- shipit/providers/wordpress.py +26 -27
- shipit/runners/__init__.py +9 -0
- shipit/runners/base.py +17 -0
- shipit/runners/local.py +105 -0
- shipit/runners/wasmer.py +470 -0
- shipit/shipit_types.py +103 -0
- shipit/ui.py +14 -0
- shipit/utils.py +10 -0
- shipit/version.py +2 -2
- {shipit_cli-0.13.4.dist-info → shipit_cli-0.15.0.dist-info}/METADATA +6 -3
- shipit_cli-0.15.0.dist-info/RECORD +34 -0
- {shipit_cli-0.13.4.dist-info → shipit_cli-0.15.0.dist-info}/WHEEL +1 -1
- shipit_cli-0.13.4.dist-info/RECORD +0 -22
- {shipit_cli-0.13.4.dist-info → shipit_cli-0.15.0.dist-info}/entry_points.txt +0 -0
shipit/cli.py
CHANGED
|
@@ -1,47 +1,40 @@
|
|
|
1
1
|
import tempfile
|
|
2
|
-
import hashlib
|
|
3
|
-
import requests
|
|
4
2
|
import os
|
|
5
|
-
import shlex
|
|
6
|
-
import shutil
|
|
7
3
|
import sys
|
|
8
4
|
import json
|
|
9
|
-
import yaml
|
|
10
|
-
import base64
|
|
11
|
-
from dataclasses import dataclass
|
|
12
5
|
from pathlib import Path
|
|
13
|
-
from
|
|
14
|
-
|
|
15
|
-
Dict,
|
|
16
|
-
List,
|
|
17
|
-
Optional,
|
|
18
|
-
Protocol,
|
|
19
|
-
Set,
|
|
20
|
-
Tuple,
|
|
21
|
-
TypedDict,
|
|
22
|
-
Union,
|
|
23
|
-
Literal,
|
|
24
|
-
cast,
|
|
25
|
-
)
|
|
26
|
-
from shutil import copy, copytree, ignore_patterns
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Dict, List, Optional, Tuple, cast, Literal
|
|
27
8
|
|
|
28
|
-
import
|
|
29
|
-
import starlark as sl
|
|
9
|
+
import xingque as sl
|
|
30
10
|
import typer
|
|
31
11
|
from rich import box
|
|
32
|
-
from rich.console import Console
|
|
33
12
|
from rich.panel import Panel
|
|
34
|
-
from rich.rule import Rule
|
|
35
13
|
from rich.syntax import Syntax
|
|
36
14
|
|
|
37
|
-
from shipit.
|
|
38
|
-
from shipit.
|
|
39
|
-
from shipit.providers.base import CustomCommands
|
|
40
|
-
from shipit.procfile import Procfile
|
|
15
|
+
from shipit.generator import generate_shipit, load_provider, load_provider_config
|
|
16
|
+
from shipit.providers.base import Config
|
|
41
17
|
from dotenv import dotenv_values
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
18
|
+
from shipit.builders import BuildBackend, DockerBuildBackend, LocalBuildBackend
|
|
19
|
+
from shipit.runners import Runner, LocalRunner, WasmerRunner
|
|
20
|
+
from shipit.shipit_types import (
|
|
21
|
+
Build,
|
|
22
|
+
CopyStep,
|
|
23
|
+
EnvStep,
|
|
24
|
+
Mount,
|
|
25
|
+
Package,
|
|
26
|
+
PathStep,
|
|
27
|
+
PrepareStep,
|
|
28
|
+
RunStep,
|
|
29
|
+
Serve,
|
|
30
|
+
Service,
|
|
31
|
+
Step,
|
|
32
|
+
UseStep,
|
|
33
|
+
Volume,
|
|
34
|
+
WorkdirStep,
|
|
35
|
+
)
|
|
36
|
+
from shipit.ui import console
|
|
37
|
+
from shipit.version import version as shipit_version
|
|
45
38
|
|
|
46
39
|
app = typer.Typer(invoke_without_command=True)
|
|
47
40
|
|
|
@@ -50,1157 +43,16 @@ ASSETS_PATH = DIR_PATH / "assets"
|
|
|
50
43
|
|
|
51
44
|
|
|
52
45
|
@dataclass
|
|
53
|
-
class
|
|
54
|
-
|
|
55
|
-
build_path: Path
|
|
56
|
-
serve_path: Path
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@dataclass
|
|
60
|
-
class Volume:
|
|
61
|
-
name: str
|
|
62
|
-
serve_path: Path
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@dataclass
|
|
66
|
-
class Service:
|
|
67
|
-
name: str
|
|
68
|
-
provider: Literal[
|
|
69
|
-
"postgres", "mysql", "redis"
|
|
70
|
-
] # Right now we only support postgres and mysql
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@dataclass
|
|
74
|
-
class Serve:
|
|
75
|
-
name: str
|
|
76
|
-
provider: str
|
|
77
|
-
build: List["Step"]
|
|
78
|
-
deps: List["Package"]
|
|
79
|
-
commands: Dict[str, str]
|
|
80
|
-
cwd: Optional[str] = None
|
|
81
|
-
prepare: Optional[List["PrepareStep"]] = None
|
|
82
|
-
workers: Optional[List[str]] = None
|
|
83
|
-
mounts: Optional[List[Mount]] = None
|
|
84
|
-
volumes: Optional[List[Volume]] = None
|
|
85
|
-
env: Optional[Dict[str, str]] = None
|
|
86
|
-
services: Optional[List[Service]] = None
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@dataclass
|
|
90
|
-
class Package:
|
|
91
|
-
name: str
|
|
92
|
-
version: Optional[str] = None
|
|
93
|
-
architecture: Optional[Literal["64-bit", "32-bit"]] = None
|
|
94
|
-
|
|
95
|
-
def __str__(self) -> str: # pragma: no cover - simple representation
|
|
96
|
-
name = f"{self.name}({self.architecture})" if self.architecture else self.name
|
|
97
|
-
if self.version is None:
|
|
98
|
-
return name
|
|
99
|
-
return f"{name}@{self.version}"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@dataclass
|
|
103
|
-
class RunStep:
|
|
104
|
-
command: str
|
|
105
|
-
inputs: Optional[List[str]] = None
|
|
106
|
-
outputs: Optional[List[str]] = None
|
|
107
|
-
group: Optional[str] = None
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@dataclass
|
|
111
|
-
class WorkdirStep:
|
|
112
|
-
path: Path
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
@dataclass
|
|
116
|
-
class CopyStep:
|
|
117
|
-
source: str
|
|
118
|
-
target: str
|
|
119
|
-
ignore: Optional[List[str]] = None
|
|
120
|
-
# We can copy from the app source or from the shipit assets folder
|
|
121
|
-
base: Literal["source", "assets"] = "source"
|
|
122
|
-
|
|
123
|
-
def is_download(self) -> bool:
|
|
124
|
-
return self.source.startswith("http://") or self.source.startswith("https://")
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
@dataclass
|
|
128
|
-
class EnvStep:
|
|
129
|
-
variables: Dict[str, str]
|
|
130
|
-
|
|
131
|
-
def __str__(self) -> str: # pragma: no cover - simple representation
|
|
132
|
-
return " ".join([f"{key}={value}" for key, value in self.variables.items()])
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
@dataclass
|
|
136
|
-
class UseStep:
|
|
137
|
-
dependencies: List[Package]
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
@dataclass
|
|
141
|
-
class PathStep:
|
|
46
|
+
class CtxMount:
|
|
47
|
+
ref: str
|
|
142
48
|
path: str
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
Step = Union[RunStep, CopyStep, EnvStep, PathStep, UseStep, WorkdirStep]
|
|
146
|
-
PrepareStep = Union[RunStep]
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
@dataclass
|
|
150
|
-
class Build:
|
|
151
|
-
deps: List[Package]
|
|
152
|
-
steps: List[Step]
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def write_stdout(line: str) -> None:
|
|
156
|
-
sys.stdout.write(line) # print to console
|
|
157
|
-
sys.stdout.flush()
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def write_stderr(line: str) -> None:
|
|
161
|
-
sys.stderr.write(line) # print to console
|
|
162
|
-
sys.stderr.flush()
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
class MapperItem(TypedDict):
|
|
166
|
-
dependencies: Dict[str, str]
|
|
167
|
-
scripts: Set[str]
|
|
168
|
-
env: Dict[str, str]
|
|
169
|
-
aliases: Dict[str, str]
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class Builder(Protocol):
|
|
173
|
-
def build(
|
|
174
|
-
self, env: Dict[str, str], mounts: List[Mount], steps: List[Step]
|
|
175
|
-
) -> None: ...
|
|
176
|
-
def build_prepare(self, serve: Serve) -> None: ...
|
|
177
|
-
def build_serve(self, serve: Serve) -> None: ...
|
|
178
|
-
def finalize_build(self, serve: Serve) -> None: ...
|
|
179
|
-
def prepare(self, env: Dict[str, str], prepare: List[PrepareStep]) -> None: ...
|
|
180
|
-
def getenv(self, name: str) -> Optional[str]: ...
|
|
181
|
-
def run_serve_command(self, command: str) -> None: ...
|
|
182
|
-
def run_command(
|
|
183
|
-
self, command: str, extra_args: Optional[List[str]] | None = None
|
|
184
|
-
) -> Any: ...
|
|
185
|
-
def get_build_mount_path(self, name: str) -> Path: ...
|
|
186
|
-
def get_serve_mount_path(self, name: str) -> Path: ...
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
class DockerBuilder:
|
|
190
|
-
mise_mapper = {
|
|
191
|
-
"php": {
|
|
192
|
-
"source": "ubi:adwinying/php",
|
|
193
|
-
},
|
|
194
|
-
"composer": {
|
|
195
|
-
"source": "ubi:composer/composer",
|
|
196
|
-
"postinstall": """composer_dir=$(mise where ubi:composer/composer); ln -s "$composer_dir/composer.phar" /usr/local/bin/composer""",
|
|
197
|
-
},
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
def __init__(self, src_dir: Path, docker_client: Optional[str] = None) -> None:
|
|
201
|
-
self.src_dir = src_dir
|
|
202
|
-
self.docker_file_contents = ""
|
|
203
|
-
self.docker_path = self.src_dir / ".shipit" / "docker"
|
|
204
|
-
self.docker_out_path = self.docker_path / "out"
|
|
205
|
-
self.depot_metadata = self.docker_path / "depot-build.json"
|
|
206
|
-
self.docker_file_path = self.docker_path / "Dockerfile"
|
|
207
|
-
self.docker_name_path = self.docker_path / "name"
|
|
208
|
-
self.docker_ignore_path = self.docker_path / "Dockerfile.dockerignore"
|
|
209
|
-
self.shipit_docker_path = Path("/shipit")
|
|
210
|
-
self.docker_client = docker_client or "docker"
|
|
211
|
-
self.env = {
|
|
212
|
-
"HOME": "/root",
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
def get_mount_path(self, name: str) -> Path:
|
|
216
|
-
if name == "app":
|
|
217
|
-
return Path("app")
|
|
218
|
-
else:
|
|
219
|
-
return Path("opt") / name
|
|
220
|
-
|
|
221
|
-
def get_build_mount_path(self, name: str) -> Path:
|
|
222
|
-
path = Path("/") / self.get_mount_path(name)
|
|
223
|
-
return path
|
|
224
|
-
|
|
225
|
-
def get_serve_mount_path(self, name: str) -> Path:
|
|
226
|
-
return self.docker_out_path / self.get_mount_path(name)
|
|
227
|
-
|
|
228
|
-
@property
|
|
229
|
-
def is_depot(self) -> bool:
|
|
230
|
-
return self.docker_client == "depot"
|
|
231
|
-
|
|
232
|
-
def getenv(self, name: str) -> Optional[str]:
|
|
233
|
-
return self.env.get(name) or os.environ.get(name)
|
|
234
|
-
|
|
235
|
-
def mkdir(self, path: Path) -> Path:
|
|
236
|
-
path = self.shipit_docker_path / path
|
|
237
|
-
self.docker_file_contents += f"RUN mkdir -p {str(path.absolute())}\n"
|
|
238
|
-
return path.absolute()
|
|
239
|
-
|
|
240
|
-
def build_dockerfile(self, image_name: str) -> None:
|
|
241
|
-
self.docker_file_path.write_text(self.docker_file_contents)
|
|
242
|
-
self.docker_name_path.write_text(image_name)
|
|
243
|
-
self.print_dockerfile()
|
|
244
|
-
extra_args = []
|
|
245
|
-
# if self.is_depot:
|
|
246
|
-
# # We load the docker image back into the local docker daemon
|
|
247
|
-
# # extra_args += ["--load"]
|
|
248
|
-
# extra_args += ["--save", f"--metadata-file={self.depot_metadata.absolute()}"]
|
|
249
|
-
sh.Command(self.docker_client)(
|
|
250
|
-
"build",
|
|
251
|
-
"-f",
|
|
252
|
-
(self.docker_path / "Dockerfile").absolute(),
|
|
253
|
-
"-t",
|
|
254
|
-
image_name,
|
|
255
|
-
"--platform",
|
|
256
|
-
"linux/amd64",
|
|
257
|
-
"--output",
|
|
258
|
-
self.docker_out_path.absolute(),
|
|
259
|
-
".",
|
|
260
|
-
*extra_args,
|
|
261
|
-
_cwd=self.src_dir.absolute(),
|
|
262
|
-
_env=os.environ, # Pass the current environment variables to the Docker client
|
|
263
|
-
_out=write_stdout,
|
|
264
|
-
_err=write_stderr,
|
|
265
|
-
)
|
|
266
|
-
# if self.is_depot:
|
|
267
|
-
# json_text = self.depot_metadata.read_text()
|
|
268
|
-
# json_data = json.loads(json_text)
|
|
269
|
-
# build_data = json_data["depot.build"]
|
|
270
|
-
# image_id = build_data["buildID"]
|
|
271
|
-
# project = build_data["projectID"]
|
|
272
|
-
# sh.Command("depot")(
|
|
273
|
-
# "pull",
|
|
274
|
-
# "--platform",
|
|
275
|
-
# "linux/amd64",
|
|
276
|
-
# "--project",
|
|
277
|
-
# project,
|
|
278
|
-
# image_id,
|
|
279
|
-
# _cwd=self.src_dir.absolute(),
|
|
280
|
-
# _env=os.environ, # Pass the current environment variables to the Docker client
|
|
281
|
-
# _out=write_stdout,
|
|
282
|
-
# _err=write_stderr,
|
|
283
|
-
# )
|
|
284
|
-
# # console.print(f"[bold]Image ID:[/bold] {image_id}")
|
|
285
|
-
|
|
286
|
-
def finalize_build(self, serve: Serve) -> None:
|
|
287
|
-
console.print(f"\n[bold]Building Docker file[/bold]")
|
|
288
|
-
self.build_dockerfile(serve.name)
|
|
289
|
-
console.print(Rule(characters="-", style="bright_black"))
|
|
290
|
-
console.print(f"[bold]Build complete ✅[/bold]")
|
|
291
|
-
|
|
292
|
-
def run_command(self, command: str, extra_args: Optional[List[str]] = None) -> Any:
|
|
293
|
-
image_name = self.docker_name_path.read_text()
|
|
294
|
-
docker_args: List[str] = [
|
|
295
|
-
"run",
|
|
296
|
-
"-p",
|
|
297
|
-
"80:80",
|
|
298
|
-
"--rm",
|
|
299
|
-
]
|
|
300
|
-
# Attach volumes if present
|
|
301
|
-
# if serve.volumes:
|
|
302
|
-
# for vol in serve.volumes:
|
|
303
|
-
# docker_args += [
|
|
304
|
-
# "--mount",
|
|
305
|
-
# f"type=volume,source={vol.name},target={str(vol.serve_path)}",
|
|
306
|
-
# ]
|
|
307
|
-
return sh.Command("docker")(
|
|
308
|
-
*docker_args,
|
|
309
|
-
image_name,
|
|
310
|
-
command,
|
|
311
|
-
*(extra_args or []),
|
|
312
|
-
_env={
|
|
313
|
-
"DOCKER_BUILDKIT": "1",
|
|
314
|
-
**os.environ,
|
|
315
|
-
}, # Pass the current environment variables to the Docker client
|
|
316
|
-
_out=write_stdout,
|
|
317
|
-
_err=write_stderr,
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
def create_file(self, path: Path, content: str, mode: int = 0o755) -> Path:
|
|
321
|
-
# docker_files = self.docker_path / "files" / path.name
|
|
322
|
-
# docker_files.write_text(content)
|
|
323
|
-
# docker_files.chmod(mode)
|
|
324
|
-
self.docker_file_contents += f"""
|
|
325
|
-
RUN cat > {path.absolute()} <<'EOF'
|
|
326
|
-
{content}
|
|
327
|
-
EOF
|
|
328
|
-
|
|
329
|
-
RUN chmod {oct(mode)[2:]} {path.absolute()}
|
|
330
|
-
"""
|
|
331
|
-
|
|
332
|
-
return path.absolute()
|
|
333
|
-
|
|
334
|
-
def print_dockerfile(self) -> None:
|
|
335
|
-
docker_file = self.docker_path / "Dockerfile"
|
|
336
|
-
manifest_panel = Panel(
|
|
337
|
-
Syntax(
|
|
338
|
-
docker_file.read_text(),
|
|
339
|
-
"dockerfile",
|
|
340
|
-
theme="monokai",
|
|
341
|
-
background_color="default",
|
|
342
|
-
line_numbers=True,
|
|
343
|
-
),
|
|
344
|
-
box=box.SQUARE,
|
|
345
|
-
border_style="bright_black",
|
|
346
|
-
expand=False,
|
|
347
|
-
)
|
|
348
|
-
console.print(manifest_panel, markup=False, highlight=True)
|
|
349
|
-
|
|
350
|
-
def add_dependency(self, dependency: Package):
|
|
351
|
-
if dependency.name == "pie":
|
|
352
|
-
self.docker_file_contents += f"RUN apt-get update && apt-get -y --no-install-recommends install gcc make autoconf libtool bison re2c pkg-config libpq-dev\n"
|
|
353
|
-
self.docker_file_contents += f"RUN curl -L --output /usr/bin/pie https://github.com/php/pie/releases/download/1.2.0/pie.phar && chmod +x /usr/bin/pie\n"
|
|
354
|
-
return
|
|
355
|
-
elif dependency.name == "static-web-server":
|
|
356
|
-
if dependency.version:
|
|
357
|
-
self.docker_file_contents += (
|
|
358
|
-
f"ENV SWS_INSTALL_VERSION={dependency.version}\n"
|
|
359
|
-
)
|
|
360
|
-
self.docker_file_contents += f"RUN curl --proto '=https' --tlsv1.2 -sSfL https://get.static-web-server.net | sh\n"
|
|
361
|
-
return
|
|
362
|
-
|
|
363
|
-
mapped_dependency = self.mise_mapper.get(dependency.name, {})
|
|
364
|
-
package_name = mapped_dependency.get("source", dependency.name)
|
|
365
|
-
if dependency.version:
|
|
366
|
-
self.docker_file_contents += (
|
|
367
|
-
f"RUN mise use --global {package_name}@{dependency.version}\n"
|
|
368
|
-
)
|
|
369
|
-
else:
|
|
370
|
-
self.docker_file_contents += f"RUN mise use --global {package_name}\n"
|
|
371
|
-
if mapped_dependency.get("postinstall"):
|
|
372
|
-
self.docker_file_contents += f"RUN {mapped_dependency.get('postinstall')}\n"
|
|
373
|
-
|
|
374
|
-
def build(
|
|
375
|
-
self, env: Dict[str, str], mounts: List[Mount], steps: List[Step]
|
|
376
|
-
) -> None:
|
|
377
|
-
base_path = self.docker_path
|
|
378
|
-
shutil.rmtree(base_path, ignore_errors=True)
|
|
379
|
-
base_path.mkdir(parents=True, exist_ok=True)
|
|
380
|
-
self.docker_file_contents = "# syntax=docker/dockerfile:1.7-labs\n"
|
|
381
|
-
self.docker_file_contents += "FROM debian:trixie-slim AS build\n"
|
|
382
|
-
|
|
383
|
-
self.docker_file_contents += """
|
|
384
|
-
RUN apt-get update \\
|
|
385
|
-
&& apt-get -y --no-install-recommends install \\
|
|
386
|
-
build-essential gcc make autoconf libtool bison \\
|
|
387
|
-
dpkg-dev pkg-config re2c locate \\
|
|
388
|
-
libmariadb-dev libmariadb-dev-compat libpq-dev \\
|
|
389
|
-
libvips-dev default-libmysqlclient-dev libmagickwand-dev \\
|
|
390
|
-
libicu-dev libxml2-dev libxslt-dev \\
|
|
391
|
-
sudo curl ca-certificates \\
|
|
392
|
-
&& rm -rf /var/lib/apt/lists/*
|
|
393
|
-
|
|
394
|
-
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
395
|
-
ENV MISE_DATA_DIR="/mise"
|
|
396
|
-
ENV MISE_CONFIG_DIR="/mise"
|
|
397
|
-
ENV MISE_CACHE_DIR="/mise/cache"
|
|
398
|
-
ENV MISE_INSTALL_PATH="/usr/local/bin/mise"
|
|
399
|
-
ENV PATH="/mise/shims:$PATH"
|
|
400
|
-
# ENV MISE_VERSION="..."
|
|
401
|
-
|
|
402
|
-
RUN curl https://mise.run | sh
|
|
403
|
-
"""
|
|
404
|
-
# docker_file_contents += "RUN curl https://mise.run | sh\n"
|
|
405
|
-
# self.docker_file_contents += """
|
|
406
|
-
# RUN curl https://get.wasmer.io -sSfL | sh -s "v6.1.0-rc.3"
|
|
407
|
-
# ENV PATH="/root/.wasmer/bin:${PATH}"
|
|
408
|
-
# """
|
|
409
|
-
for mount in mounts:
|
|
410
|
-
self.docker_file_contents += f"RUN mkdir -p {mount.build_path.absolute()}\n"
|
|
411
|
-
|
|
412
|
-
for step in steps:
|
|
413
|
-
if isinstance(step, WorkdirStep):
|
|
414
|
-
self.docker_file_contents += f"WORKDIR {step.path.absolute()}\n"
|
|
415
|
-
elif isinstance(step, RunStep):
|
|
416
|
-
if step.inputs:
|
|
417
|
-
pre = "\\\n " + "".join(
|
|
418
|
-
[
|
|
419
|
-
f"--mount=type=bind,source={input},target={input} \\\n "
|
|
420
|
-
for input in step.inputs
|
|
421
|
-
]
|
|
422
|
-
)
|
|
423
|
-
else:
|
|
424
|
-
pre = ""
|
|
425
|
-
self.docker_file_contents += f"RUN {pre}{step.command}\n"
|
|
426
|
-
elif isinstance(step, CopyStep):
|
|
427
|
-
if step.is_download():
|
|
428
|
-
self.docker_file_contents += (
|
|
429
|
-
"ADD " + step.source + " " + step.target + "\n"
|
|
430
|
-
)
|
|
431
|
-
elif step.base == "assets":
|
|
432
|
-
# Detect if the asset exists and is a file
|
|
433
|
-
if (ASSETS_PATH / step.source).is_file():
|
|
434
|
-
# Read the file content and write it to the target file
|
|
435
|
-
content_base64 = base64.b64encode(
|
|
436
|
-
(ASSETS_PATH / step.source).read_bytes()
|
|
437
|
-
).decode("utf-8")
|
|
438
|
-
self.docker_file_contents += (
|
|
439
|
-
f"RUN echo '{content_base64}' | base64 -d > {step.target}\n"
|
|
440
|
-
)
|
|
441
|
-
elif (ASSETS_PATH / step.source).is_dir():
|
|
442
|
-
raise Exception(
|
|
443
|
-
f"Asset {step.source} is a directory, shipit doesn't currently support coppying assets directories inside Docker"
|
|
444
|
-
)
|
|
445
|
-
else:
|
|
446
|
-
raise Exception(f"Asset {step.source} does not exist")
|
|
447
|
-
else:
|
|
448
|
-
if step.ignore:
|
|
449
|
-
exclude = (
|
|
450
|
-
" \\\n"
|
|
451
|
-
+ " \\\n".join(
|
|
452
|
-
[f" --exclude={ignore}" for ignore in step.ignore]
|
|
453
|
-
)
|
|
454
|
-
+ " \\\n "
|
|
455
|
-
)
|
|
456
|
-
else:
|
|
457
|
-
exclude = ""
|
|
458
|
-
self.docker_file_contents += (
|
|
459
|
-
f"COPY{exclude} {step.source} {step.target}\n"
|
|
460
|
-
)
|
|
461
|
-
elif isinstance(step, EnvStep):
|
|
462
|
-
env_vars = " ".join(
|
|
463
|
-
[f"{key}={value}" for key, value in step.variables.items()]
|
|
464
|
-
)
|
|
465
|
-
self.docker_file_contents += f"ENV {env_vars}\n"
|
|
466
|
-
elif isinstance(step, PathStep):
|
|
467
|
-
self.docker_file_contents += f"ENV PATH={step.path}:$PATH\n"
|
|
468
|
-
elif isinstance(step, UseStep):
|
|
469
|
-
for dependency in step.dependencies:
|
|
470
|
-
self.add_dependency(dependency)
|
|
471
|
-
|
|
472
|
-
self.docker_file_contents += """
|
|
473
|
-
FROM scratch
|
|
474
|
-
"""
|
|
475
|
-
for mount in mounts:
|
|
476
|
-
self.docker_file_contents += (
|
|
477
|
-
f"COPY --from=build {mount.build_path} {mount.build_path}\n"
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
self.docker_ignore_path.write_text("""
|
|
481
|
-
.shipit
|
|
482
|
-
Shipit
|
|
483
|
-
""")
|
|
484
|
-
|
|
485
|
-
def get_path(self) -> Path:
|
|
486
|
-
return Path("/")
|
|
487
|
-
|
|
488
|
-
def prepare(self, env: Dict[str, str], prepare: List[PrepareStep]) -> None:
|
|
489
|
-
raise NotImplementedError
|
|
490
|
-
|
|
491
|
-
def build_serve(self, serve: Serve) -> None:
|
|
492
|
-
serve_command_path = self.mkdir(Path("serve") / "bin")
|
|
493
|
-
console.print(f"[bold]Serve Commands:[/bold]")
|
|
494
|
-
for dep in serve.deps:
|
|
495
|
-
self.add_dependency(dep)
|
|
496
|
-
|
|
497
|
-
for command in serve.commands:
|
|
498
|
-
console.print(f"* {command}")
|
|
499
|
-
command_path = serve_command_path / command
|
|
500
|
-
self.create_file(
|
|
501
|
-
command_path,
|
|
502
|
-
f"#!/bin/bash\ncd {serve.cwd}\n{serve.commands[command]}",
|
|
503
|
-
mode=0o755,
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
def run_serve_command(self, command: str) -> None:
|
|
507
|
-
path = self.shipit_docker_path / "serve" / "bin" / command
|
|
508
|
-
self.run_command(str(path))
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
class LocalBuilder:
|
|
512
|
-
def __init__(self, src_dir: Path) -> None:
|
|
513
|
-
self.src_dir = src_dir
|
|
514
|
-
self.local_path = self.src_dir / ".shipit" / "local"
|
|
515
|
-
self.serve_bin_path = self.local_path / "serve" / "bin"
|
|
516
|
-
self.prepare_bash_script = self.local_path / "prepare" / "prepare.sh"
|
|
517
|
-
self.build_path = self.local_path / "build"
|
|
518
|
-
self.workdir = self.build_path
|
|
519
|
-
|
|
520
|
-
def get_mount_path(self, name: str) -> Path:
|
|
521
|
-
if name == "app":
|
|
522
|
-
return self.build_path / "app"
|
|
523
|
-
else:
|
|
524
|
-
return self.build_path / "opt" / name
|
|
525
|
-
|
|
526
|
-
def get_build_mount_path(self, name: str) -> Path:
|
|
527
|
-
return self.get_mount_path(name)
|
|
528
|
-
|
|
529
|
-
def get_serve_mount_path(self, name: str) -> Path:
|
|
530
|
-
return self.get_mount_path(name)
|
|
531
|
-
|
|
532
|
-
def execute_step(self, step: Step, env: Dict[str, str]) -> None:
|
|
533
|
-
build_path = self.workdir
|
|
534
|
-
if isinstance(step, UseStep):
|
|
535
|
-
console.print(
|
|
536
|
-
f"[bold]Using dependencies:[/bold] {', '.join([str(dep) for dep in step.dependencies])}"
|
|
537
|
-
)
|
|
538
|
-
elif isinstance(step, WorkdirStep):
|
|
539
|
-
console.print(f"[bold]Working in {step.path}[/bold]")
|
|
540
|
-
self.workdir = step.path
|
|
541
|
-
# We make sure the dir exists
|
|
542
|
-
step.path.mkdir(parents=True, exist_ok=True)
|
|
543
|
-
elif isinstance(step, RunStep):
|
|
544
|
-
extra = ""
|
|
545
|
-
if step.inputs:
|
|
546
|
-
for input in step.inputs:
|
|
547
|
-
print(f"Copying {input} to {build_path / input}")
|
|
548
|
-
copy((self.src_dir / input), (build_path / input))
|
|
549
|
-
all_inputs = ", ".join(step.inputs)
|
|
550
|
-
extra = f" [bright_black]# using {all_inputs}[/bright_black]"
|
|
551
|
-
console.print(
|
|
552
|
-
f"[bright_black]$[/bright_black] [bold]{step.command}[/bold]{extra}"
|
|
553
|
-
)
|
|
554
|
-
command_line = step.command
|
|
555
|
-
parts = shlex.split(command_line)
|
|
556
|
-
program = parts[0]
|
|
557
|
-
extended_paths = [
|
|
558
|
-
str(build_path / path) for path in env["PATH"].split(os.pathsep)
|
|
559
|
-
]
|
|
560
|
-
extended_paths.append(os.environ["PATH"])
|
|
561
|
-
PATH = os.pathsep.join(extended_paths) # type: ignore
|
|
562
|
-
exe = shutil.which(program, path=PATH)
|
|
563
|
-
if not exe:
|
|
564
|
-
raise Exception(f"Program is not installed: {program}")
|
|
565
|
-
cmd = sh.Command("bash") # "grep"
|
|
566
|
-
result = cmd(
|
|
567
|
-
"-c",
|
|
568
|
-
command_line,
|
|
569
|
-
_env={**env, "PATH": PATH},
|
|
570
|
-
_cwd=build_path,
|
|
571
|
-
_out=write_stdout,
|
|
572
|
-
_err=write_stderr,
|
|
573
|
-
)
|
|
574
|
-
elif isinstance(step, CopyStep):
|
|
575
|
-
ignore_extra = ""
|
|
576
|
-
if step.ignore:
|
|
577
|
-
ignore_extra = (
|
|
578
|
-
f" [bright_black]# ignoring {', '.join(step.ignore)}[/bright_black]"
|
|
579
|
-
)
|
|
580
|
-
ignore_matches = step.ignore if step.ignore else []
|
|
581
|
-
ignore_matches.append(".shipit")
|
|
582
|
-
ignore_matches.append("Shipit")
|
|
583
|
-
|
|
584
|
-
if step.is_download():
|
|
585
|
-
console.print(
|
|
586
|
-
f"[bold]Download from {step.source} to {step.target}[/bold]"
|
|
587
|
-
)
|
|
588
|
-
download_file(step.source, (build_path / step.target))
|
|
589
|
-
else:
|
|
590
|
-
if step.base == "source":
|
|
591
|
-
base = self.src_dir
|
|
592
|
-
elif step.base == "assets":
|
|
593
|
-
base = ASSETS_PATH
|
|
594
|
-
else:
|
|
595
|
-
raise Exception(f"Unknown base: {step.base}")
|
|
596
|
-
|
|
597
|
-
console.print(
|
|
598
|
-
f"[bold]Copy to {step.target} from {step.source}[/bold]{ignore_extra}"
|
|
599
|
-
)
|
|
600
|
-
|
|
601
|
-
if (base / step.source).is_dir():
|
|
602
|
-
copytree(
|
|
603
|
-
(base / step.source),
|
|
604
|
-
(build_path / step.target),
|
|
605
|
-
dirs_exist_ok=True,
|
|
606
|
-
ignore=ignore_patterns(*ignore_matches),
|
|
607
|
-
)
|
|
608
|
-
elif (base / step.source).is_file():
|
|
609
|
-
copy(
|
|
610
|
-
(base / step.source),
|
|
611
|
-
(build_path / step.target),
|
|
612
|
-
)
|
|
613
|
-
else:
|
|
614
|
-
raise Exception(f"Source {step.source} is not a file or directory")
|
|
615
|
-
elif isinstance(step, EnvStep):
|
|
616
|
-
print(f"Setting environment variables: {step}")
|
|
617
|
-
env.update(step.variables)
|
|
618
|
-
elif isinstance(step, PathStep):
|
|
619
|
-
console.print(f"[bold]Add {step.path}[/bold] to PATH")
|
|
620
|
-
fullpath = step.path
|
|
621
|
-
env["PATH"] = f"{fullpath}{os.pathsep}{env['PATH']}"
|
|
622
|
-
else:
|
|
623
|
-
raise Exception(f"Unknown step type: {type(step)}")
|
|
624
|
-
|
|
625
|
-
def build(
|
|
626
|
-
self, env: Dict[str, str], mounts: List[Mount], steps: List[Step]
|
|
627
|
-
) -> None:
|
|
628
|
-
console.print(f"\n[bold]Building... 🚀[/bold]")
|
|
629
|
-
base_path = self.local_path
|
|
630
|
-
shutil.rmtree(base_path, ignore_errors=True)
|
|
631
|
-
base_path.mkdir(parents=True, exist_ok=True)
|
|
632
|
-
self.build_path.mkdir(exist_ok=True)
|
|
633
|
-
for mount in mounts:
|
|
634
|
-
mount.build_path.mkdir(parents=True, exist_ok=True)
|
|
635
|
-
for step in steps:
|
|
636
|
-
console.print(Rule(characters="-", style="bright_black"))
|
|
637
|
-
self.execute_step(step, env)
|
|
638
|
-
|
|
639
|
-
if "PATH" in env:
|
|
640
|
-
path = base_path / ".path"
|
|
641
|
-
path.write_text(env["PATH"]) # type: ignore
|
|
642
|
-
|
|
643
|
-
console.print(Rule(characters="-", style="bright_black"))
|
|
644
|
-
console.print(f"[bold]Build complete ✅[/bold]")
|
|
645
|
-
|
|
646
|
-
def mkdir(self, path: Path) -> Path:
|
|
647
|
-
path = self.get_path() / path
|
|
648
|
-
path.mkdir(parents=True, exist_ok=True)
|
|
649
|
-
return path.absolute()
|
|
650
|
-
|
|
651
|
-
def create_file(self, path: Path, content: str, mode: int = 0o755) -> Path:
|
|
652
|
-
path.write_text(content)
|
|
653
|
-
path.chmod(mode)
|
|
654
|
-
return path.absolute()
|
|
655
|
-
|
|
656
|
-
def run_command(self, command: str, extra_args: Optional[List[str]] = None) -> Any:
|
|
657
|
-
return sh.Command(command)(
|
|
658
|
-
*(extra_args or []),
|
|
659
|
-
_out=write_stdout,
|
|
660
|
-
_err=write_stderr,
|
|
661
|
-
_env=os.environ,
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
-
def getenv(self, name: str) -> Optional[str]:
|
|
665
|
-
return os.environ.get(name)
|
|
666
|
-
|
|
667
|
-
def get_path(self) -> Path:
|
|
668
|
-
return self.local_path
|
|
669
|
-
|
|
670
|
-
def build_prepare(self, serve: Serve) -> None:
|
|
671
|
-
self.prepare_bash_script.parent.mkdir(parents=True, exist_ok=True)
|
|
672
|
-
commands: List[str] = []
|
|
673
|
-
if serve.cwd:
|
|
674
|
-
commands.append(f"cd {serve.cwd}")
|
|
675
|
-
if serve.prepare:
|
|
676
|
-
for step in serve.prepare:
|
|
677
|
-
if isinstance(step, RunStep):
|
|
678
|
-
commands.append(step.command)
|
|
679
|
-
elif isinstance(step, WorkdirStep):
|
|
680
|
-
commands.append(f"cd {step.path}")
|
|
681
|
-
content = "#!/bin/bash\n{body}".format(body="\n".join(commands))
|
|
682
|
-
console.print(
|
|
683
|
-
f"\n[bold]Created prepare.sh script to run before packaging ✅[/bold]"
|
|
684
|
-
)
|
|
685
|
-
manifest_panel = Panel(
|
|
686
|
-
Syntax(
|
|
687
|
-
content,
|
|
688
|
-
"bash",
|
|
689
|
-
theme="monokai",
|
|
690
|
-
background_color="default",
|
|
691
|
-
line_numbers=True,
|
|
692
|
-
),
|
|
693
|
-
box=box.SQUARE,
|
|
694
|
-
border_style="bright_black",
|
|
695
|
-
expand=False,
|
|
696
|
-
)
|
|
697
|
-
console.print(manifest_panel, markup=False, highlight=True)
|
|
698
|
-
self.prepare_bash_script.write_text(content)
|
|
699
|
-
self.prepare_bash_script.chmod(0o755)
|
|
700
|
-
|
|
701
|
-
def finalize_build(self, serve: Serve) -> None:
|
|
702
|
-
pass
|
|
703
|
-
|
|
704
|
-
def prepare(self, env: Dict[str, str], prepare: List[PrepareStep]) -> None:
|
|
705
|
-
sh.Command(f"{self.prepare_bash_script.absolute()}")(
|
|
706
|
-
_out=write_stdout, _err=write_stderr
|
|
707
|
-
)
|
|
708
|
-
|
|
709
|
-
def build_serve(self, serve: Serve) -> None:
|
|
710
|
-
# Remember serve configuration for run-time
|
|
711
|
-
console.print("\n[bold]Building serve[/bold]")
|
|
712
|
-
self.serve_bin_path.mkdir(parents=True, exist_ok=False)
|
|
713
|
-
path = self.get_path() / ".path"
|
|
714
|
-
path_text = path.read_text()
|
|
715
|
-
console.print(f"[bold]Serve Commands:[/bold]")
|
|
716
|
-
for command in serve.commands:
|
|
717
|
-
console.print(f"* {command}")
|
|
718
|
-
command_path = self.serve_bin_path / command
|
|
719
|
-
env_vars = ""
|
|
720
|
-
if serve.env:
|
|
721
|
-
env_vars = " ".join([f"{k}={v}" for k, v in serve.env.items()])
|
|
722
|
-
|
|
723
|
-
content = f"#!/bin/bash\ncd {serve.cwd}\nPATH={path_text}:$PATH {env_vars} {serve.commands[command]}"
|
|
724
|
-
command_path.write_text(content)
|
|
725
|
-
manifest_panel = Panel(
|
|
726
|
-
Syntax(
|
|
727
|
-
content.strip(),
|
|
728
|
-
"bash",
|
|
729
|
-
theme="monokai",
|
|
730
|
-
background_color="default",
|
|
731
|
-
line_numbers=True,
|
|
732
|
-
),
|
|
733
|
-
box=box.SQUARE,
|
|
734
|
-
border_style="bright_black",
|
|
735
|
-
expand=False,
|
|
736
|
-
)
|
|
737
|
-
console.print(manifest_panel, markup=False, highlight=True)
|
|
738
|
-
command_path.chmod(0o755)
|
|
739
|
-
|
|
740
|
-
def run_serve_command(self, command: str) -> None:
|
|
741
|
-
console.print(f"\n[bold]Running {command} command[/bold]")
|
|
742
|
-
command_path = self.serve_bin_path / command
|
|
743
|
-
sh.Command(str(command_path))(_out=write_stdout, _err=write_stderr)
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
class WasmerBuilder:
|
|
747
|
-
def get_build_mount_path(self, name: str) -> Path:
|
|
748
|
-
return self.inner_builder.get_build_mount_path(name)
|
|
749
|
-
|
|
750
|
-
def get_serve_mount_path(self, name: str) -> Path:
|
|
751
|
-
if name == "app":
|
|
752
|
-
return Path("/app")
|
|
753
|
-
else:
|
|
754
|
-
return Path("/opt") / name
|
|
755
|
-
|
|
756
|
-
mapper: Dict[str, MapperItem] = {
|
|
757
|
-
"python": {
|
|
758
|
-
"dependencies": {
|
|
759
|
-
"latest": "python/python@=3.13.1",
|
|
760
|
-
"3.13": "python/python@=3.13.1",
|
|
761
|
-
},
|
|
762
|
-
"scripts": {"python"},
|
|
763
|
-
"aliases": {},
|
|
764
|
-
"env": {
|
|
765
|
-
"PYTHONEXECUTABLE": "/bin/python",
|
|
766
|
-
"PYTHONHOME": "/cpython",
|
|
767
|
-
},
|
|
768
|
-
},
|
|
769
|
-
"pandoc": {
|
|
770
|
-
"dependencies": {
|
|
771
|
-
"latest": "wasmer/pandoc@=0.0.1",
|
|
772
|
-
"3.5": "wasmer/pandoc@=0.0.1",
|
|
773
|
-
},
|
|
774
|
-
"scripts": {"pandoc"},
|
|
775
|
-
},
|
|
776
|
-
"ffmpeg": {
|
|
777
|
-
"dependencies": {
|
|
778
|
-
"latest": "wasmer/ffmpeg@=1.0.5",
|
|
779
|
-
"N-111519": "wasmer/ffmpeg@=1.0.5",
|
|
780
|
-
},
|
|
781
|
-
"scripts": {"ffmpeg"},
|
|
782
|
-
},
|
|
783
|
-
"php": {
|
|
784
|
-
"dependencies": {
|
|
785
|
-
"latest": "php/php-32@=8.3.2102",
|
|
786
|
-
"8.3": "php/php-32@=8.3.2102",
|
|
787
|
-
"8.2": "php/php-32@=8.2.2801",
|
|
788
|
-
"8.1": "php/php-32@=8.1.3201",
|
|
789
|
-
"7.4": "php/php-32@=7.4.3301",
|
|
790
|
-
},
|
|
791
|
-
"architecture_dependencies": {
|
|
792
|
-
"64-bit": {
|
|
793
|
-
"latest": "php/php-64@=8.3.2102",
|
|
794
|
-
"8.3": "php/php-64@=8.3.2102",
|
|
795
|
-
"8.2": "php/php-64@=8.2.2801",
|
|
796
|
-
"8.1": "php/php-64@=8.1.3201",
|
|
797
|
-
"7.4": "php/php-64@=7.4.3301",
|
|
798
|
-
},
|
|
799
|
-
"32-bit": {
|
|
800
|
-
"latest": "php/php-32@=8.3.2102",
|
|
801
|
-
"8.3": "php/php-32@=8.3.2102",
|
|
802
|
-
"8.2": "php/php-32@=8.2.2801",
|
|
803
|
-
"8.1": "php/php-32@=8.1.3201",
|
|
804
|
-
"7.4": "php/php-32@=7.4.3301",
|
|
805
|
-
},
|
|
806
|
-
},
|
|
807
|
-
"scripts": {"php"},
|
|
808
|
-
"aliases": {},
|
|
809
|
-
"env": {},
|
|
810
|
-
},
|
|
811
|
-
"bash": {
|
|
812
|
-
"dependencies": {
|
|
813
|
-
"latest": "wasmer/bash@=1.0.24",
|
|
814
|
-
"8.3": "wasmer/bash@=1.0.24",
|
|
815
|
-
},
|
|
816
|
-
"scripts": {"bash", "sh"},
|
|
817
|
-
"aliases": {},
|
|
818
|
-
"env": {},
|
|
819
|
-
},
|
|
820
|
-
"static-web-server": {
|
|
821
|
-
"dependencies": {
|
|
822
|
-
"latest": "wasmer/static-web-server@=1.1.0",
|
|
823
|
-
"2.38.0": "wasmer/static-web-server@=1.1.0",
|
|
824
|
-
"0.1": "wasmer/static-web-server@=1.1.0",
|
|
825
|
-
},
|
|
826
|
-
"scripts": {"webserver"},
|
|
827
|
-
"aliases": {"static-web-server": "webserver"},
|
|
828
|
-
"env": {},
|
|
829
|
-
},
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
def __init__(
|
|
833
|
-
self,
|
|
834
|
-
inner_builder: Builder,
|
|
835
|
-
src_dir: Path,
|
|
836
|
-
registry: Optional[str] = None,
|
|
837
|
-
token: Optional[str] = None,
|
|
838
|
-
bin: Optional[str] = None,
|
|
839
|
-
) -> None:
|
|
840
|
-
self.src_dir = src_dir
|
|
841
|
-
self.inner_builder = inner_builder
|
|
842
|
-
# The path where we store the directory of the wasmer app in the inner builder
|
|
843
|
-
self.wasmer_dir_path = self.src_dir / ".shipit" / "wasmer"
|
|
844
|
-
self.wasmer_registry = registry
|
|
845
|
-
self.wasmer_token = token
|
|
846
|
-
self.bin = bin or "wasmer"
|
|
847
|
-
self.default_env = {
|
|
848
|
-
"SHIPIT_PYTHON_EXTRA_INDEX_URL": "https://pythonindex.wasix.org/simple",
|
|
849
|
-
"SHIPIT_PYTHON_CROSS_PLATFORM": "wasix_wasm32",
|
|
850
|
-
"SHIPIT_PYTHON_PRECOMPILE": "true",
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
def getenv(self, name: str) -> Optional[str]:
|
|
854
|
-
return self.inner_builder.getenv(name) or self.default_env.get(name)
|
|
855
|
-
|
|
856
|
-
def build(
|
|
857
|
-
self, env: Dict[str, str], mounts: List[Mount], build: List[Step]
|
|
858
|
-
) -> None:
|
|
859
|
-
return self.inner_builder.build(env, mounts, build)
|
|
860
|
-
|
|
861
|
-
def build_prepare(self, serve: Serve) -> None:
|
|
862
|
-
print("Building prepare")
|
|
863
|
-
prepare_dir = self.wasmer_dir_path / "prepare"
|
|
864
|
-
prepare_dir.mkdir(parents=True, exist_ok=True)
|
|
865
|
-
env = serve.env or {}
|
|
866
|
-
for dep in serve.deps:
|
|
867
|
-
if dep.name in self.mapper:
|
|
868
|
-
dep_env = self.mapper[dep.name].get("env")
|
|
869
|
-
if dep_env is not None:
|
|
870
|
-
env.update(dep_env)
|
|
871
|
-
if env:
|
|
872
|
-
env_lines = [f"export {k}={v}" for k, v in env.items()]
|
|
873
|
-
env_lines = "\n".join(env_lines)
|
|
874
|
-
else:
|
|
875
|
-
env_lines = ""
|
|
876
|
-
|
|
877
|
-
commands: List[str] = []
|
|
878
|
-
if serve.cwd:
|
|
879
|
-
commands.append(f"cd {serve.cwd}")
|
|
880
|
-
|
|
881
|
-
if serve.prepare:
|
|
882
|
-
for step in serve.prepare:
|
|
883
|
-
if isinstance(step, RunStep):
|
|
884
|
-
commands.append(step.command)
|
|
885
|
-
elif isinstance(step, WorkdirStep):
|
|
886
|
-
commands.append(f"cd {step.path}")
|
|
887
|
-
|
|
888
|
-
body = "\n".join(filter(None, [env_lines, *commands]))
|
|
889
|
-
content = f"#!/bin/bash\n\n{body}"
|
|
890
|
-
console.print(
|
|
891
|
-
f"\n[bold]Created prepare.sh script to run before packaging ✅[/bold]"
|
|
892
|
-
)
|
|
893
|
-
manifest_panel = Panel(
|
|
894
|
-
Syntax(
|
|
895
|
-
content,
|
|
896
|
-
"bash",
|
|
897
|
-
theme="monokai",
|
|
898
|
-
background_color="default",
|
|
899
|
-
line_numbers=True,
|
|
900
|
-
),
|
|
901
|
-
box=box.SQUARE,
|
|
902
|
-
border_style="bright_black",
|
|
903
|
-
expand=False,
|
|
904
|
-
)
|
|
905
|
-
console.print(manifest_panel, markup=False, highlight=True)
|
|
906
|
-
|
|
907
|
-
(prepare_dir / "prepare.sh").write_text(
|
|
908
|
-
content,
|
|
909
|
-
)
|
|
910
|
-
(prepare_dir / "prepare.sh").chmod(0o755)
|
|
911
|
-
|
|
912
|
-
def finalize_build(self, serve: Serve) -> None:
|
|
913
|
-
inner = cast(Any, self.inner_builder)
|
|
914
|
-
inner.finalize_build(serve)
|
|
915
|
-
|
|
916
|
-
def prepare(self, env: Dict[str, str], prepare: List[PrepareStep]) -> None:
|
|
917
|
-
prepare_dir = self.wasmer_dir_path / "prepare"
|
|
918
|
-
self.run_serve_command(
|
|
919
|
-
"bash",
|
|
920
|
-
extra_args=[
|
|
921
|
-
f"--mapdir=/prepare:{prepare_dir}",
|
|
922
|
-
"--",
|
|
923
|
-
"/prepare/prepare.sh",
|
|
924
|
-
],
|
|
925
|
-
)
|
|
926
|
-
|
|
927
|
-
def build_serve(self, serve: Serve) -> None:
|
|
928
|
-
from tomlkit import comment, document, nl, table, aot, string, array
|
|
929
|
-
|
|
930
|
-
doc = document()
|
|
931
|
-
doc.add(comment(f"Wasmer manifest generated with Shipit v{shipit_version}"))
|
|
932
|
-
package = table()
|
|
933
|
-
doc.add("package", package)
|
|
934
|
-
package.add("entrypoint", "start")
|
|
935
|
-
dependencies = table()
|
|
936
|
-
doc.add("dependencies", dependencies)
|
|
937
|
-
|
|
938
|
-
binaries = {}
|
|
939
|
-
|
|
940
|
-
deps = serve.deps or []
|
|
941
|
-
# We add bash if it's not present, as the prepare command is run in bash
|
|
942
|
-
if serve.prepare:
|
|
943
|
-
if not any(dep.name == "bash" for dep in deps):
|
|
944
|
-
deps.append(Package("bash"))
|
|
945
|
-
|
|
946
|
-
if deps:
|
|
947
|
-
console.print(f"[bold]Mapping dependencies to Wasmer packages:[/bold]")
|
|
948
|
-
for dep in deps:
|
|
949
|
-
if dep.name in self.mapper:
|
|
950
|
-
version = dep.version or "latest"
|
|
951
|
-
mapped_dependencies = self.mapper[dep.name]["dependencies"]
|
|
952
|
-
if dep.architecture:
|
|
953
|
-
architecture_dependencies = (
|
|
954
|
-
self.mapper[dep.name]
|
|
955
|
-
.get("architecture_dependencies", {})
|
|
956
|
-
.get(dep.architecture, {})
|
|
957
|
-
)
|
|
958
|
-
if architecture_dependencies:
|
|
959
|
-
mapped_dependencies = architecture_dependencies
|
|
960
|
-
if version in mapped_dependencies:
|
|
961
|
-
console.print(
|
|
962
|
-
f"* {dep.name}@{version} mapped to {self.mapper[dep.name]['dependencies'][version]}"
|
|
963
|
-
)
|
|
964
|
-
package_name, version = mapped_dependencies[version].split("@")
|
|
965
|
-
dependencies.add(package_name, version)
|
|
966
|
-
scripts = self.mapper[dep.name].get("scripts") or []
|
|
967
|
-
for script in scripts:
|
|
968
|
-
binaries[script] = {
|
|
969
|
-
"script": f"{package_name}:{script}",
|
|
970
|
-
"env": self.mapper[dep.name].get("env"),
|
|
971
|
-
}
|
|
972
|
-
aliases = self.mapper[dep.name].get("aliases") or {}
|
|
973
|
-
for alias, script in aliases.items():
|
|
974
|
-
binaries[alias] = {
|
|
975
|
-
"script": f"{package_name}:{script}",
|
|
976
|
-
"env": self.mapper[dep.name].get("env"),
|
|
977
|
-
}
|
|
978
|
-
else:
|
|
979
|
-
raise Exception(
|
|
980
|
-
f"Dependency {dep.name}@{version} not found in Wasmer"
|
|
981
|
-
)
|
|
982
|
-
else:
|
|
983
|
-
raise Exception(f"Dependency {dep.name} not found in Wasmer")
|
|
984
|
-
|
|
985
|
-
fs = table()
|
|
986
|
-
doc.add("fs", fs)
|
|
987
|
-
inner = cast(Any, self.inner_builder)
|
|
988
|
-
if serve.mounts:
|
|
989
|
-
for mount in serve.mounts:
|
|
990
|
-
fs.add(
|
|
991
|
-
str(mount.serve_path.absolute()),
|
|
992
|
-
str(self.inner_builder.get_serve_mount_path(mount.name).absolute()),
|
|
993
|
-
)
|
|
994
|
-
|
|
995
|
-
doc.add(nl())
|
|
996
|
-
if serve.commands:
|
|
997
|
-
commands = aot()
|
|
998
|
-
doc.add("command", commands)
|
|
999
|
-
for command_name, command_line in serve.commands.items():
|
|
1000
|
-
command = table()
|
|
1001
|
-
commands.append(command)
|
|
1002
|
-
parts = shlex.split(command_line)
|
|
1003
|
-
program = parts[0]
|
|
1004
|
-
command.add("name", command_name)
|
|
1005
|
-
program_binary = binaries[program]
|
|
1006
|
-
command.add("module", program_binary["script"])
|
|
1007
|
-
command.add("runner", "wasi")
|
|
1008
|
-
wasi_args = table()
|
|
1009
|
-
if serve.cwd:
|
|
1010
|
-
wasi_args.add("cwd", serve.cwd)
|
|
1011
|
-
wasi_args.add("main-args", array(parts[1:]).multiline(True))
|
|
1012
|
-
env = program_binary.get("env") or {}
|
|
1013
|
-
if serve.env:
|
|
1014
|
-
env.update(serve.env)
|
|
1015
|
-
if env:
|
|
1016
|
-
arr = array([f"{k}={v}" for k, v in env.items()]).multiline(True)
|
|
1017
|
-
wasi_args.add("env", arr)
|
|
1018
|
-
title = string("annotations.wasi", literal=False)
|
|
1019
|
-
command.add(title, wasi_args)
|
|
1020
|
-
|
|
1021
|
-
inner = cast(Any, self.inner_builder)
|
|
1022
|
-
self.wasmer_dir_path.mkdir(parents=True, exist_ok=True)
|
|
1023
|
-
|
|
1024
|
-
manifest = doc.as_string().replace(
|
|
1025
|
-
'[command."annotations.wasi"]', "[command.annotations.wasi]"
|
|
1026
|
-
)
|
|
1027
|
-
console.print(f"\n[bold]Created wasmer.toml manifest ✅[/bold]")
|
|
1028
|
-
manifest_panel = Panel(
|
|
1029
|
-
Syntax(
|
|
1030
|
-
manifest.strip(),
|
|
1031
|
-
"toml",
|
|
1032
|
-
theme="monokai",
|
|
1033
|
-
background_color="default",
|
|
1034
|
-
line_numbers=True,
|
|
1035
|
-
),
|
|
1036
|
-
box=box.SQUARE,
|
|
1037
|
-
border_style="bright_black",
|
|
1038
|
-
expand=False,
|
|
1039
|
-
)
|
|
1040
|
-
console.print(manifest_panel, markup=False, highlight=True)
|
|
1041
|
-
(self.wasmer_dir_path / "wasmer.toml").write_text(manifest)
|
|
1042
|
-
|
|
1043
|
-
original_app_yaml_path = self.src_dir / "app.yaml"
|
|
1044
|
-
if original_app_yaml_path.exists():
|
|
1045
|
-
console.print(
|
|
1046
|
-
f"[bold]Using original app.yaml found in source directory[/bold]"
|
|
1047
|
-
)
|
|
1048
|
-
yaml_config = yaml.safe_load(original_app_yaml_path.read_text())
|
|
1049
|
-
else:
|
|
1050
|
-
yaml_config = {
|
|
1051
|
-
"kind": "wasmer.io/App.v0",
|
|
1052
|
-
}
|
|
1053
|
-
# Update the app to use the new package
|
|
1054
|
-
yaml_config["package"] = "."
|
|
1055
|
-
if serve.services:
|
|
1056
|
-
capabilities = yaml_config.get("capabilities", {})
|
|
1057
|
-
has_mysql = any(service.provider == "mysql" for service in serve.services)
|
|
1058
|
-
# has_postgres = any(service.provider == "postgres" for service in serve.services)
|
|
1059
|
-
# has_redis = any(service.provider == "redis" for service in serve.services)
|
|
1060
|
-
if has_mysql:
|
|
1061
|
-
capabilities["database"] = {"engine": "mysql"}
|
|
1062
|
-
yaml_config["capabilities"] = capabilities
|
|
1063
|
-
|
|
1064
|
-
# Attach declared volumes to the app manifest (serve-time mounts)
|
|
1065
|
-
if serve.volumes:
|
|
1066
|
-
volumes_yaml = yaml_config.get("volumes", [])
|
|
1067
|
-
for vol in serve.volumes:
|
|
1068
|
-
volumes_yaml.append(
|
|
1069
|
-
{
|
|
1070
|
-
"name": vol.name,
|
|
1071
|
-
"mount": str(vol.serve_path),
|
|
1072
|
-
}
|
|
1073
|
-
)
|
|
1074
|
-
yaml_config["volumes"] = volumes_yaml
|
|
1075
|
-
|
|
1076
|
-
# If it has a php dependency, set the scaling mode to single_concurrency
|
|
1077
|
-
has_php = any(dep.name == "php" for dep in serve.deps)
|
|
1078
|
-
if has_php:
|
|
1079
|
-
scaling = yaml_config.get("scaling", {})
|
|
1080
|
-
scaling["mode"] = "single_concurrency"
|
|
1081
|
-
yaml_config["scaling"] = scaling
|
|
1082
|
-
|
|
1083
|
-
if "after_deploy" in serve.commands:
|
|
1084
|
-
jobs = yaml_config.get("jobs", [])
|
|
1085
|
-
jobs.append(
|
|
1086
|
-
{
|
|
1087
|
-
"name": "after_deploy",
|
|
1088
|
-
"trigger": "post-deployment",
|
|
1089
|
-
"action": {"execute": {"command": "after_deploy"}},
|
|
1090
|
-
}
|
|
1091
|
-
)
|
|
1092
|
-
yaml_config["jobs"] = jobs
|
|
1093
|
-
|
|
1094
|
-
app_yaml = yaml.dump(
|
|
1095
|
-
yaml_config,
|
|
1096
|
-
)
|
|
1097
|
-
|
|
1098
|
-
console.print(f"\n[bold]Created app.yaml manifest ✅[/bold]")
|
|
1099
|
-
app_yaml_panel = Panel(
|
|
1100
|
-
Syntax(
|
|
1101
|
-
app_yaml.strip(),
|
|
1102
|
-
"yaml",
|
|
1103
|
-
theme="monokai",
|
|
1104
|
-
background_color="default",
|
|
1105
|
-
line_numbers=True,
|
|
1106
|
-
),
|
|
1107
|
-
box=box.SQUARE,
|
|
1108
|
-
border_style="bright_black",
|
|
1109
|
-
expand=False,
|
|
1110
|
-
)
|
|
1111
|
-
console.print(app_yaml_panel, markup=False, highlight=True)
|
|
1112
|
-
(self.wasmer_dir_path / "app.yaml").write_text(app_yaml)
|
|
1113
|
-
|
|
1114
|
-
# self.inner_builder.build_serve(serve)
|
|
1115
|
-
|
|
1116
|
-
def run_serve_command(
|
|
1117
|
-
self, command: str, extra_args: Optional[List[str]] = None
|
|
1118
|
-
) -> None:
|
|
1119
|
-
console.print(f"\n[bold]Serving site[/bold]: running {command} command")
|
|
1120
|
-
extra_args = extra_args or []
|
|
1121
|
-
|
|
1122
|
-
if self.wasmer_registry:
|
|
1123
|
-
extra_args = [f"--registry={self.wasmer_registry}"] + extra_args
|
|
1124
|
-
self.run_command(
|
|
1125
|
-
self.bin,
|
|
1126
|
-
[
|
|
1127
|
-
"run",
|
|
1128
|
-
str(self.wasmer_dir_path.absolute()),
|
|
1129
|
-
"--net",
|
|
1130
|
-
f"--command={command}",
|
|
1131
|
-
*extra_args,
|
|
1132
|
-
],
|
|
1133
|
-
)
|
|
1134
|
-
|
|
1135
|
-
def run_command(
|
|
1136
|
-
self, command: str, extra_args: Optional[List[str]] | None = None
|
|
1137
|
-
) -> Any:
|
|
1138
|
-
sh.Command(command)(
|
|
1139
|
-
*(extra_args or []), _out=write_stdout, _err=write_stderr, _env=os.environ
|
|
1140
|
-
)
|
|
1141
|
-
|
|
1142
|
-
def deploy_config(self, config_path: Path) -> None:
|
|
1143
|
-
package_webc_path = self.wasmer_dir_path / "package.webc"
|
|
1144
|
-
app_yaml_path = self.wasmer_dir_path / "app.yaml"
|
|
1145
|
-
package_webc_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1146
|
-
self.run_command(
|
|
1147
|
-
self.bin,
|
|
1148
|
-
["package", "build", self.wasmer_dir_path, "--out", package_webc_path],
|
|
1149
|
-
)
|
|
1150
|
-
config_path.write_text(
|
|
1151
|
-
json.dumps(
|
|
1152
|
-
{
|
|
1153
|
-
"app_yaml_path": str(app_yaml_path.absolute()),
|
|
1154
|
-
"package_webc_path": str(package_webc_path.absolute()),
|
|
1155
|
-
"package_webc_size": package_webc_path.stat().st_size,
|
|
1156
|
-
"package_webc_sha256": hashlib.sha256(
|
|
1157
|
-
package_webc_path.read_bytes()
|
|
1158
|
-
).hexdigest(),
|
|
1159
|
-
}
|
|
1160
|
-
)
|
|
1161
|
-
)
|
|
1162
|
-
console.print(f"\n[bold]Saved deploy config to {config_path}[/bold]")
|
|
1163
|
-
|
|
1164
|
-
def deploy(
|
|
1165
|
-
self, app_owner: Optional[str] = None, app_name: Optional[str] = None
|
|
1166
|
-
) -> str:
|
|
1167
|
-
extra_args = []
|
|
1168
|
-
if self.wasmer_registry:
|
|
1169
|
-
extra_args += ["--registry", self.wasmer_registry]
|
|
1170
|
-
if self.wasmer_token:
|
|
1171
|
-
extra_args += ["--token", self.wasmer_token]
|
|
1172
|
-
if app_owner:
|
|
1173
|
-
extra_args += ["--owner", app_owner]
|
|
1174
|
-
if app_name:
|
|
1175
|
-
extra_args += ["--app-name", app_name]
|
|
1176
|
-
# self.run_command(
|
|
1177
|
-
# self.bin,
|
|
1178
|
-
# [
|
|
1179
|
-
# "package",
|
|
1180
|
-
# "push",
|
|
1181
|
-
# self.wasmer_dir_path,
|
|
1182
|
-
# "--namespace",
|
|
1183
|
-
# app_owner,
|
|
1184
|
-
# "--non-interactive",
|
|
1185
|
-
# *extra_args,
|
|
1186
|
-
# ],
|
|
1187
|
-
# )
|
|
1188
|
-
return self.run_command(
|
|
1189
|
-
self.bin,
|
|
1190
|
-
[
|
|
1191
|
-
"deploy",
|
|
1192
|
-
"--publish-package",
|
|
1193
|
-
"--dir",
|
|
1194
|
-
self.wasmer_dir_path,
|
|
1195
|
-
"--non-interactive",
|
|
1196
|
-
*extra_args,
|
|
1197
|
-
],
|
|
1198
|
-
)
|
|
49
|
+
serve_path: str
|
|
1199
50
|
|
|
1200
51
|
|
|
1201
52
|
class Ctx:
|
|
1202
|
-
def __init__(self,
|
|
1203
|
-
self.
|
|
53
|
+
def __init__(self, build_backend: BuildBackend, runner: Runner) -> None:
|
|
54
|
+
self.build_backend = build_backend
|
|
55
|
+
self.runner = runner
|
|
1204
56
|
self.packages: Dict[str, Package] = {}
|
|
1205
57
|
self.builds: List[Build] = []
|
|
1206
58
|
self.steps: List[Step] = []
|
|
@@ -1208,7 +60,6 @@ class Ctx:
|
|
|
1208
60
|
self.mounts: List[Mount] = []
|
|
1209
61
|
self.volumes: List[Volume] = []
|
|
1210
62
|
self.services: Dict[str, Service] = {}
|
|
1211
|
-
self.getenv_variables: Set[str] = set()
|
|
1212
63
|
|
|
1213
64
|
def add_package(self, package: Package) -> str:
|
|
1214
65
|
index = f"{package.name}@{package.version}" if package.version else package.name
|
|
@@ -1254,10 +105,6 @@ class Ctx:
|
|
|
1254
105
|
self.steps.append(step)
|
|
1255
106
|
return f"ref:step:{len(self.steps) - 1}"
|
|
1256
107
|
|
|
1257
|
-
def getenv(self, name: str) -> Optional[str]:
|
|
1258
|
-
self.getenv_variables.add(name)
|
|
1259
|
-
return self.builder.getenv(name)
|
|
1260
|
-
|
|
1261
108
|
def dep(
|
|
1262
109
|
self,
|
|
1263
110
|
name: str,
|
|
@@ -1306,7 +153,7 @@ class Ctx:
|
|
|
1306
153
|
commands=commands,
|
|
1307
154
|
prepare=prepare_steps,
|
|
1308
155
|
workers=workers,
|
|
1309
|
-
mounts=self.get_refs([mount
|
|
156
|
+
mounts=self.get_refs([mount.ref for mount in mounts])
|
|
1310
157
|
if mounts
|
|
1311
158
|
else None,
|
|
1312
159
|
volumes=self.get_refs([volume["ref"] for volume in volumes])
|
|
@@ -1337,10 +184,12 @@ class Ctx:
|
|
|
1337
184
|
def copy(
|
|
1338
185
|
self,
|
|
1339
186
|
source: str,
|
|
1340
|
-
target: str,
|
|
187
|
+
target: Optional[str] = None,
|
|
1341
188
|
ignore: Optional[List[str]] = None,
|
|
1342
189
|
base: Optional[Literal["source", "assets"]] = None,
|
|
1343
190
|
) -> Optional[str]:
|
|
191
|
+
if target is None:
|
|
192
|
+
target = source
|
|
1344
193
|
step = CopyStep(source, target, ignore, base or "source")
|
|
1345
194
|
return self.add_step(step)
|
|
1346
195
|
|
|
@@ -1353,15 +202,12 @@ class Ctx:
|
|
|
1353
202
|
return f"ref:mount:{len(self.mounts) - 1}"
|
|
1354
203
|
|
|
1355
204
|
def mount(self, name: str) -> Optional[str]:
|
|
1356
|
-
build_path = self.
|
|
1357
|
-
serve_path = self.
|
|
205
|
+
build_path = self.build_backend.get_build_mount_path(name)
|
|
206
|
+
serve_path = self.runner.get_serve_mount_path(name)
|
|
1358
207
|
mount = Mount(name, build_path, serve_path)
|
|
1359
208
|
ref = self.add_mount(mount)
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
"build": str(build_path.absolute()),
|
|
1363
|
-
"serve": str(serve_path.absolute()),
|
|
1364
|
-
}
|
|
209
|
+
|
|
210
|
+
return CtxMount(ref=ref, path=str(build_path.absolute()), serve_path=str(serve_path.absolute()))
|
|
1365
211
|
|
|
1366
212
|
def add_volume(self, volume: Volume) -> Optional[str]:
|
|
1367
213
|
self.volumes.append(volume)
|
|
@@ -1377,35 +223,76 @@ class Ctx:
|
|
|
1377
223
|
}
|
|
1378
224
|
|
|
1379
225
|
|
|
1380
|
-
def evaluate_shipit(
|
|
226
|
+
def evaluate_shipit(
|
|
227
|
+
shipit_file: Path,
|
|
228
|
+
build_backend: BuildBackend,
|
|
229
|
+
runner: Runner,
|
|
230
|
+
provider_config: Config,
|
|
231
|
+
) -> Tuple[Ctx, Serve]:
|
|
1381
232
|
source = shipit_file.read_text()
|
|
1382
|
-
ctx = Ctx(
|
|
1383
|
-
glb = sl.
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
dialect = sl.Dialect
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
233
|
+
ctx = Ctx(build_backend, runner)
|
|
234
|
+
glb = sl.GlobalsBuilder.standard()
|
|
235
|
+
|
|
236
|
+
glb.set("PORT", str(provider_config.port or "8080"))
|
|
237
|
+
glb.set("config", provider_config)
|
|
238
|
+
glb.set("service", ctx.service)
|
|
239
|
+
glb.set("dep", ctx.dep)
|
|
240
|
+
glb.set("serve", ctx.serve)
|
|
241
|
+
glb.set("run", ctx.run)
|
|
242
|
+
glb.set("mount", ctx.mount)
|
|
243
|
+
glb.set("volume", ctx.volume)
|
|
244
|
+
glb.set("workdir", ctx.workdir)
|
|
245
|
+
glb.set("copy", ctx.copy)
|
|
246
|
+
glb.set("path", ctx.path)
|
|
247
|
+
glb.set("env", ctx.env)
|
|
248
|
+
glb.set("use", ctx.use)
|
|
249
|
+
|
|
250
|
+
dialect = sl.Dialect(enable_keyword_only_arguments=True, enable_f_strings=True)
|
|
251
|
+
|
|
252
|
+
ast = sl.AstModule.parse("Shipit", source, dialect=dialect)
|
|
253
|
+
|
|
254
|
+
evaluator = sl.Evaluator()
|
|
255
|
+
evaluator.eval_module(ast, glb.build())
|
|
1405
256
|
if not ctx.serves:
|
|
1406
257
|
raise ValueError(f"No serve definition found in {shipit_file}")
|
|
1407
258
|
assert len(ctx.serves) <= 1, "Only one serve is allowed for now"
|
|
1408
259
|
serve = next(iter(ctx.serves.values()))
|
|
260
|
+
|
|
261
|
+
# Now we apply the custom commands (start, after_deploy, build, install)
|
|
262
|
+
if provider_config.commands.start:
|
|
263
|
+
serve.commands["start"] = provider_config.commands.start
|
|
264
|
+
|
|
265
|
+
if provider_config.commands.after_deploy:
|
|
266
|
+
serve.commands["after_deploy"] = provider_config.commands.after_deploy
|
|
267
|
+
|
|
268
|
+
if provider_config.commands.build or provider_config.commands.install:
|
|
269
|
+
new_build = []
|
|
270
|
+
has_done_build = False
|
|
271
|
+
has_done_install = False
|
|
272
|
+
for step in serve.build:
|
|
273
|
+
if isinstance(step, RunStep):
|
|
274
|
+
if step.group == "build" and not has_done_build and provider_config.commands.build:
|
|
275
|
+
new_build.append(RunStep(provider_config.commands.build, group="build"))
|
|
276
|
+
has_done_build = True
|
|
277
|
+
elif step.group == "install" and not has_done_install and provider_config.commands.install:
|
|
278
|
+
new_build.append(RunStep(provider_config.commands.install, group="install"))
|
|
279
|
+
has_done_install = True
|
|
280
|
+
else:
|
|
281
|
+
new_build.append(step)
|
|
282
|
+
else:
|
|
283
|
+
new_build.append(step)
|
|
284
|
+
if not has_done_install and provider_config.commands.install:
|
|
285
|
+
new_build.append(RunStep(provider_config.commands.install, group="install"))
|
|
286
|
+
if not has_done_build and provider_config.commands.build:
|
|
287
|
+
new_build.append(RunStep(provider_config.commands.build, group="build"))
|
|
288
|
+
serve.build = new_build
|
|
289
|
+
|
|
290
|
+
if serve.commands.get("start"):
|
|
291
|
+
serve.commands["start"] = serve.commands["start"].replace("$PORT", str(provider_config.port or "8080"))
|
|
292
|
+
|
|
293
|
+
if serve.commands.get("after_deploy"):
|
|
294
|
+
serve.commands["after_deploy"] = serve.commands["after_deploy"].replace("$PORT", str(provider_config.port or "8080"))
|
|
295
|
+
|
|
1409
296
|
return ctx, serve
|
|
1410
297
|
|
|
1411
298
|
|
|
@@ -1419,13 +306,6 @@ def print_help() -> None:
|
|
|
1419
306
|
console.print(panel)
|
|
1420
307
|
|
|
1421
308
|
|
|
1422
|
-
def download_file(url: str, path: Path) -> None:
|
|
1423
|
-
response = requests.get(url)
|
|
1424
|
-
response.raise_for_status()
|
|
1425
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1426
|
-
path.write_bytes(response.content)
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
309
|
@app.command(name="auto")
|
|
1430
310
|
def auto(
|
|
1431
311
|
path: Path = typer.Argument(
|
|
@@ -1497,10 +377,6 @@ def auto(
|
|
|
1497
377
|
None,
|
|
1498
378
|
help="Name of the Wasmer app.",
|
|
1499
379
|
),
|
|
1500
|
-
use_procfile: bool = typer.Option(
|
|
1501
|
-
True,
|
|
1502
|
-
help="Use the Procfile to generate the default custom commands (install, build, start, after_deploy).",
|
|
1503
|
-
),
|
|
1504
380
|
install_command: Optional[str] = typer.Option(
|
|
1505
381
|
None,
|
|
1506
382
|
help="The install command to use (overwrites the default)",
|
|
@@ -1517,10 +393,18 @@ def auto(
|
|
|
1517
393
|
None,
|
|
1518
394
|
help="The environment to use (defaults to `.env`, it will use .env.<env_name> if provided)",
|
|
1519
395
|
),
|
|
1520
|
-
|
|
396
|
+
provider: Optional[str] = typer.Option(
|
|
1521
397
|
None,
|
|
1522
398
|
help="Use a specific provider to build the project.",
|
|
1523
399
|
),
|
|
400
|
+
config: Optional[str] = typer.Option(
|
|
401
|
+
None,
|
|
402
|
+
help="The JSON content to use as input.",
|
|
403
|
+
),
|
|
404
|
+
serve_port: Optional[int] = typer.Option(
|
|
405
|
+
None,
|
|
406
|
+
help="The port to use (defaults to 8080).",
|
|
407
|
+
),
|
|
1524
408
|
):
|
|
1525
409
|
if not path.exists():
|
|
1526
410
|
raise Exception(f"The path {path} does not exist")
|
|
@@ -1543,16 +427,19 @@ def auto(
|
|
|
1543
427
|
generate(
|
|
1544
428
|
path,
|
|
1545
429
|
out=shipit_path,
|
|
1546
|
-
use_procfile=use_procfile,
|
|
1547
430
|
install_command=install_command,
|
|
1548
431
|
build_command=build_command,
|
|
1549
432
|
start_command=start_command,
|
|
1550
|
-
|
|
433
|
+
provider=provider,
|
|
434
|
+
config=config,
|
|
1551
435
|
)
|
|
1552
436
|
|
|
1553
437
|
build(
|
|
1554
438
|
path,
|
|
1555
439
|
shipit_path=shipit_path,
|
|
440
|
+
install_command=install_command,
|
|
441
|
+
build_command=build_command,
|
|
442
|
+
start_command=start_command,
|
|
1556
443
|
wasmer=(wasmer or wasmer_deploy),
|
|
1557
444
|
docker=docker,
|
|
1558
445
|
docker_client=docker_client,
|
|
@@ -1562,6 +449,9 @@ def auto(
|
|
|
1562
449
|
wasmer_bin=wasmer_bin,
|
|
1563
450
|
skip_prepare=skip_prepare,
|
|
1564
451
|
env_name=env_name,
|
|
452
|
+
serve_port=serve_port,
|
|
453
|
+
provider=provider,
|
|
454
|
+
config=config,
|
|
1565
455
|
)
|
|
1566
456
|
if start or wasmer_deploy or wasmer_deploy_config:
|
|
1567
457
|
serve(
|
|
@@ -1596,10 +486,6 @@ def generate(
|
|
|
1596
486
|
"--shipit-path",
|
|
1597
487
|
help="Output path (defaults to the Shipit file in the provided path).",
|
|
1598
488
|
),
|
|
1599
|
-
use_procfile: bool = typer.Option(
|
|
1600
|
-
True,
|
|
1601
|
-
help="Use the Procfile to generate the default custom commands (install, build, start, after_deploy).",
|
|
1602
|
-
),
|
|
1603
489
|
install_command: Optional[str] = typer.Option(
|
|
1604
490
|
None,
|
|
1605
491
|
help="The install command to use (overwrites the default)",
|
|
@@ -1612,41 +498,48 @@ def generate(
|
|
|
1612
498
|
None,
|
|
1613
499
|
help="The start command to use (overwrites the default)",
|
|
1614
500
|
),
|
|
1615
|
-
|
|
501
|
+
provider: Optional[str] = typer.Option(
|
|
1616
502
|
None,
|
|
1617
503
|
help="Use a specific provider to build the project.",
|
|
1618
504
|
),
|
|
505
|
+
config: Optional[str] = typer.Option(
|
|
506
|
+
None,
|
|
507
|
+
help="The JSON content to use as input.",
|
|
508
|
+
),
|
|
1619
509
|
):
|
|
1620
510
|
if not path.exists():
|
|
1621
511
|
raise Exception(f"The path {path} does not exist")
|
|
1622
512
|
|
|
1623
513
|
if out is None:
|
|
1624
514
|
out = path / "Shipit"
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
# with open(path / "Dockerfile", "r") as f:
|
|
1629
|
-
# cmd = None
|
|
1630
|
-
# for line in f:
|
|
1631
|
-
# if line.startswith("CMD "):
|
|
1632
|
-
# cmd = line[4:].strip()
|
|
1633
|
-
# cmd = json.loads(cmd)
|
|
1634
|
-
# # We get the last command
|
|
1635
|
-
# if cmd:
|
|
1636
|
-
# if isinstance(cmd, list):
|
|
1637
|
-
# cmd = " ".join(cmd)
|
|
1638
|
-
# custom_commands.start = cmd
|
|
1639
|
-
if use_procfile:
|
|
1640
|
-
if (path / "Procfile").exists():
|
|
1641
|
-
procfile = Procfile.loads((path / "Procfile").read_text())
|
|
1642
|
-
custom_commands.start = procfile.get_start_command()
|
|
515
|
+
|
|
516
|
+
base_config = Config()
|
|
517
|
+
base_config.commands.enrich_from_path(path)
|
|
1643
518
|
if start_command:
|
|
1644
|
-
|
|
519
|
+
base_config.commands.start = start_command
|
|
1645
520
|
if install_command:
|
|
1646
|
-
|
|
521
|
+
base_config.commands.install = install_command
|
|
1647
522
|
if build_command:
|
|
1648
|
-
|
|
1649
|
-
|
|
523
|
+
base_config.commands.build = build_command
|
|
524
|
+
provider_cls = load_provider(path, base_config, use_provider=provider)
|
|
525
|
+
provider_config = load_provider_config(provider_cls, path, base_config, config=config)
|
|
526
|
+
provider = provider_cls(path, provider_config)
|
|
527
|
+
content = generate_shipit(path, provider)
|
|
528
|
+
config_json = provider_config.model_dump_json(indent=2, exclude_defaults=True)
|
|
529
|
+
if config_json and config_json != "{}":
|
|
530
|
+
manifest_panel = Panel(
|
|
531
|
+
Syntax(
|
|
532
|
+
config_json,
|
|
533
|
+
"json",
|
|
534
|
+
theme="monokai",
|
|
535
|
+
background_color="default",
|
|
536
|
+
line_numbers=True,
|
|
537
|
+
),
|
|
538
|
+
box=box.SQUARE,
|
|
539
|
+
border_style="bright_black",
|
|
540
|
+
expand=False,
|
|
541
|
+
)
|
|
542
|
+
console.print(manifest_panel, markup=False, highlight=True)
|
|
1650
543
|
out.write_text(content)
|
|
1651
544
|
console.print(f"[bold]Generated Shipit[/bold] at {out.absolute()}")
|
|
1652
545
|
|
|
@@ -1726,22 +619,35 @@ def serve(
|
|
|
1726
619
|
if not path.exists():
|
|
1727
620
|
raise Exception(f"The path {path} does not exist")
|
|
1728
621
|
|
|
1729
|
-
builder: Builder
|
|
1730
622
|
if docker or docker_client:
|
|
1731
|
-
|
|
623
|
+
build_backend: BuildBackend = DockerBuildBackend(
|
|
624
|
+
path, ASSETS_PATH, docker_client
|
|
625
|
+
)
|
|
1732
626
|
else:
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
627
|
+
build_backend = LocalBuildBackend(path, ASSETS_PATH)
|
|
628
|
+
|
|
629
|
+
needs_wasmer = wasmer or wasmer_deploy or wasmer_deploy_config
|
|
630
|
+
if needs_wasmer:
|
|
631
|
+
runner: Runner = WasmerRunner(
|
|
632
|
+
build_backend,
|
|
633
|
+
path,
|
|
634
|
+
registry=wasmer_registry,
|
|
635
|
+
token=wasmer_token,
|
|
636
|
+
bin=wasmer_bin,
|
|
1737
637
|
)
|
|
638
|
+
else:
|
|
639
|
+
runner = LocalRunner(build_backend, path)
|
|
1738
640
|
|
|
1739
641
|
if wasmer_deploy_config:
|
|
1740
|
-
|
|
642
|
+
if not isinstance(runner, WasmerRunner):
|
|
643
|
+
raise RuntimeError("--wasmer-deploy-config requires the Wasmer runner")
|
|
644
|
+
runner.deploy_config(wasmer_deploy_config)
|
|
1741
645
|
elif wasmer_deploy:
|
|
1742
|
-
|
|
646
|
+
if not isinstance(runner, WasmerRunner):
|
|
647
|
+
raise RuntimeError("--wasmer-deploy requires the Wasmer runner")
|
|
648
|
+
runner.deploy(app_owner=wasmer_app_owner, app_name=wasmer_app_name)
|
|
1743
649
|
elif start:
|
|
1744
|
-
|
|
650
|
+
runner.run_serve_command("start")
|
|
1745
651
|
|
|
1746
652
|
|
|
1747
653
|
@app.command(name="plan")
|
|
@@ -1794,10 +700,6 @@ def plan(
|
|
|
1794
700
|
None,
|
|
1795
701
|
help="Use a specific Docker client (such as depot, podman, etc.)",
|
|
1796
702
|
),
|
|
1797
|
-
use_procfile: bool = typer.Option(
|
|
1798
|
-
True,
|
|
1799
|
-
help="Use the Procfile to generate the default custom commands (install, build, start, after_deploy).",
|
|
1800
|
-
),
|
|
1801
703
|
install_command: Optional[str] = typer.Option(
|
|
1802
704
|
None,
|
|
1803
705
|
help="The install command to use (overwrites the default)",
|
|
@@ -1810,10 +712,18 @@ def plan(
|
|
|
1810
712
|
None,
|
|
1811
713
|
help="The start command to use (overwrites the default)",
|
|
1812
714
|
),
|
|
1813
|
-
|
|
715
|
+
provider: Optional[str] = typer.Option(
|
|
1814
716
|
None,
|
|
1815
717
|
help="Use a specific provider to build the project.",
|
|
1816
718
|
),
|
|
719
|
+
config: Optional[str] = typer.Option(
|
|
720
|
+
None,
|
|
721
|
+
help="The JSON content to use as input.",
|
|
722
|
+
),
|
|
723
|
+
serve_port: Optional[int] = typer.Option(
|
|
724
|
+
None,
|
|
725
|
+
help="The port to use (defaults to 8080).",
|
|
726
|
+
),
|
|
1817
727
|
) -> None:
|
|
1818
728
|
if not path.exists():
|
|
1819
729
|
raise Exception(f"The path {path} does not exist")
|
|
@@ -1836,39 +746,46 @@ def plan(
|
|
|
1836
746
|
generate(
|
|
1837
747
|
path,
|
|
1838
748
|
out=shipit_path,
|
|
1839
|
-
use_procfile=use_procfile,
|
|
1840
749
|
install_command=install_command,
|
|
1841
750
|
build_command=build_command,
|
|
1842
751
|
start_command=start_command,
|
|
1843
|
-
|
|
752
|
+
provider=provider,
|
|
753
|
+
config=config,
|
|
1844
754
|
)
|
|
1845
755
|
|
|
1846
|
-
custom_commands = CustomCommands()
|
|
1847
|
-
procfile_path = path / "Procfile"
|
|
1848
|
-
if procfile_path.exists():
|
|
1849
|
-
try:
|
|
1850
|
-
procfile = Procfile.loads(procfile_path.read_text())
|
|
1851
|
-
custom_commands.start = procfile.get_start_command()
|
|
1852
|
-
except Exception:
|
|
1853
|
-
pass
|
|
1854
|
-
|
|
1855
756
|
shipit_file = get_shipit_path(path, shipit_path)
|
|
1856
757
|
|
|
1857
|
-
builder: Builder
|
|
1858
758
|
if docker or docker_client:
|
|
1859
|
-
|
|
759
|
+
build_backend: BuildBackend = DockerBuildBackend(
|
|
760
|
+
path, ASSETS_PATH, docker_client
|
|
761
|
+
)
|
|
1860
762
|
else:
|
|
1861
|
-
|
|
763
|
+
build_backend = LocalBuildBackend(path, ASSETS_PATH)
|
|
1862
764
|
if wasmer:
|
|
1863
|
-
|
|
1864
|
-
|
|
765
|
+
runner: Runner = WasmerRunner(
|
|
766
|
+
build_backend,
|
|
767
|
+
path,
|
|
768
|
+
registry=wasmer_registry,
|
|
769
|
+
token=wasmer_token,
|
|
770
|
+
bin=wasmer_bin,
|
|
1865
771
|
)
|
|
772
|
+
else:
|
|
773
|
+
runner = LocalRunner(build_backend, path)
|
|
1866
774
|
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
775
|
+
base_config = Config()
|
|
776
|
+
base_config.commands.enrich_from_path(path)
|
|
777
|
+
if install_command:
|
|
778
|
+
base_config.commands.install = install_command
|
|
779
|
+
if build_command:
|
|
780
|
+
base_config.commands.build = build_command
|
|
781
|
+
if start_command:
|
|
782
|
+
base_config.commands.start = start_command
|
|
783
|
+
if serve_port:
|
|
784
|
+
base_config.port = serve_port
|
|
785
|
+
provider_cls = load_provider(path, base_config, use_provider=provider)
|
|
786
|
+
provider_config = load_provider_config(provider_cls, path, base_config, config=config)
|
|
787
|
+
# provider_config = runner.prepare_config(provider_config)
|
|
788
|
+
ctx, serve = evaluate_shipit(shipit_file, build_backend, runner, provider_config)
|
|
1872
789
|
|
|
1873
790
|
def _collect_group_commands(group: str) -> Optional[str]:
|
|
1874
791
|
commands = [
|
|
@@ -1880,25 +797,23 @@ def plan(
|
|
|
1880
797
|
return None
|
|
1881
798
|
return " && ".join(commands)
|
|
1882
799
|
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
800
|
+
start_command = serve.commands.get("start")
|
|
801
|
+
after_deploy_command = serve.commands.get("after_deploy")
|
|
802
|
+
install_command = _collect_group_commands("install")
|
|
803
|
+
build_command = _collect_group_commands("build")
|
|
804
|
+
if start_command:
|
|
805
|
+
provider_config.commands.start = start_command
|
|
806
|
+
if after_deploy_command:
|
|
807
|
+
provider_config.commands.after_deploy = after_deploy_command
|
|
808
|
+
if install_command:
|
|
809
|
+
provider_config.commands.install = install_command
|
|
810
|
+
if build_command:
|
|
811
|
+
provider_config.commands.build = build_command
|
|
1895
812
|
plan_output = {
|
|
1896
|
-
"provider":
|
|
1897
|
-
"
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
},
|
|
1901
|
-
"config": sorted(ctx.getenv_variables),
|
|
813
|
+
"provider": provider_cls.name(),
|
|
814
|
+
"config": json.loads(
|
|
815
|
+
provider_config.model_dump_json(exclude_defaults=True)
|
|
816
|
+
),
|
|
1902
817
|
"services": [
|
|
1903
818
|
{"name": svc.name, "provider": svc.provider}
|
|
1904
819
|
for svc in (serve.services or [])
|
|
@@ -1925,6 +840,18 @@ def build(
|
|
|
1925
840
|
None,
|
|
1926
841
|
help="The path to the Shipit file (defaults to Shipit in the provided path).",
|
|
1927
842
|
),
|
|
843
|
+
start_command: Optional[str] = typer.Option(
|
|
844
|
+
None,
|
|
845
|
+
help="The start command to use (overwrites the default)",
|
|
846
|
+
),
|
|
847
|
+
install_command: Optional[str] = typer.Option(
|
|
848
|
+
None,
|
|
849
|
+
help="The install command to use (overwrites the default)",
|
|
850
|
+
),
|
|
851
|
+
build_command: Optional[str] = typer.Option(
|
|
852
|
+
None,
|
|
853
|
+
help="The build command to use (overwrites the default)",
|
|
854
|
+
),
|
|
1928
855
|
wasmer: bool = typer.Option(
|
|
1929
856
|
False,
|
|
1930
857
|
help="Use Wasmer to build and serve the project.",
|
|
@@ -1961,23 +888,59 @@ def build(
|
|
|
1961
888
|
None,
|
|
1962
889
|
help="The environment to use (defaults to `.env`, it will use .env.<env_name> if provided)",
|
|
1963
890
|
),
|
|
891
|
+
serve_port: Optional[int] = typer.Option(
|
|
892
|
+
None,
|
|
893
|
+
help="The port to use (defaults to 8080).",
|
|
894
|
+
),
|
|
895
|
+
provider: Optional[str] = typer.Option(
|
|
896
|
+
None,
|
|
897
|
+
help="Use a specific provider to build the project.",
|
|
898
|
+
),
|
|
899
|
+
config: Optional[str] = typer.Option(
|
|
900
|
+
None,
|
|
901
|
+
help="The JSON content to use as input.",
|
|
902
|
+
),
|
|
1964
903
|
) -> None:
|
|
1965
904
|
if not path.exists():
|
|
1966
905
|
raise Exception(f"The path {path} does not exist")
|
|
1967
906
|
|
|
1968
907
|
shipit_file = get_shipit_path(path, shipit_path)
|
|
1969
908
|
|
|
1970
|
-
builder: Builder
|
|
1971
909
|
if docker or docker_client:
|
|
1972
|
-
|
|
910
|
+
build_backend: BuildBackend = DockerBuildBackend(
|
|
911
|
+
path, ASSETS_PATH, docker_client
|
|
912
|
+
)
|
|
1973
913
|
else:
|
|
1974
|
-
|
|
914
|
+
build_backend = LocalBuildBackend(path, ASSETS_PATH)
|
|
1975
915
|
if wasmer:
|
|
1976
|
-
|
|
1977
|
-
|
|
916
|
+
runner: Runner = WasmerRunner(
|
|
917
|
+
build_backend,
|
|
918
|
+
path,
|
|
919
|
+
registry=wasmer_registry,
|
|
920
|
+
token=wasmer_token,
|
|
921
|
+
bin=wasmer_bin,
|
|
1978
922
|
)
|
|
923
|
+
else:
|
|
924
|
+
runner = LocalRunner(build_backend, path)
|
|
1979
925
|
|
|
1980
|
-
|
|
926
|
+
base_config = Config()
|
|
927
|
+
base_config.commands.enrich_from_path(path)
|
|
928
|
+
if start_command:
|
|
929
|
+
base_config.commands.start = start_command
|
|
930
|
+
if install_command:
|
|
931
|
+
base_config.commands.install = install_command
|
|
932
|
+
if build_command:
|
|
933
|
+
base_config.commands.build = build_command
|
|
934
|
+
serve_port = serve_port or os.environ.get("PORT")
|
|
935
|
+
if serve_port:
|
|
936
|
+
base_config.port = serve_port
|
|
937
|
+
|
|
938
|
+
provider_cls = load_provider(path, base_config, use_provider=provider)
|
|
939
|
+
provider_config = load_provider_config(provider_cls, path, base_config, config=config)
|
|
940
|
+
provider_config = runner.prepare_config(provider_config)
|
|
941
|
+
ctx, serve = evaluate_shipit(
|
|
942
|
+
shipit_file, build_backend, runner, provider_config
|
|
943
|
+
)
|
|
1981
944
|
env = {
|
|
1982
945
|
"PATH": "",
|
|
1983
946
|
"COLORTERM": os.environ.get("COLORTERM", ""),
|
|
@@ -1997,6 +960,9 @@ def build(
|
|
|
1997
960
|
return build(
|
|
1998
961
|
path,
|
|
1999
962
|
shipit_path=shipit_path,
|
|
963
|
+
install_command=install_command,
|
|
964
|
+
build_command=build_command,
|
|
965
|
+
start_command=start_command,
|
|
2000
966
|
wasmer=wasmer,
|
|
2001
967
|
skip_prepare=skip_prepare,
|
|
2002
968
|
wasmer_bin=wasmer_bin,
|
|
@@ -2006,6 +972,9 @@ def build(
|
|
|
2006
972
|
docker_client=None,
|
|
2007
973
|
skip_docker_if_safe_build=False,
|
|
2008
974
|
env_name=env_name,
|
|
975
|
+
serve_port=serve_port,
|
|
976
|
+
provider=provider,
|
|
977
|
+
config=config,
|
|
2009
978
|
)
|
|
2010
979
|
|
|
2011
980
|
serve.env = serve.env or {}
|
|
@@ -2017,14 +986,13 @@ def build(
|
|
|
2017
986
|
env_vars = dotenv_values(path / f".env.{env_name}")
|
|
2018
987
|
serve.env.update(env_vars)
|
|
2019
988
|
|
|
989
|
+
assert serve.commands.get("start"), "No start command could be found, please provide a start command"
|
|
990
|
+
|
|
2020
991
|
# Build and serve
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
builder.build_prepare(serve)
|
|
2024
|
-
builder.build_serve(serve)
|
|
2025
|
-
builder.finalize_build(serve)
|
|
992
|
+
build_backend.build(serve.name, env, serve.mounts or [], serve.build)
|
|
993
|
+
runner.build(serve)
|
|
2026
994
|
if serve.prepare and not skip_prepare:
|
|
2027
|
-
|
|
995
|
+
runner.prepare(env, serve.prepare)
|
|
2028
996
|
|
|
2029
997
|
|
|
2030
998
|
def get_shipit_path(path: Path, shipit_path: Optional[Path] = None) -> Path:
|