shipit-cli 0.14.0__py3-none-any.whl → 0.15.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.
- 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 +318 -1325
- shipit/generator.py +54 -36
- shipit/procfile.py +4 -72
- shipit/providers/base.py +49 -10
- shipit/providers/hugo.py +62 -12
- shipit/providers/jekyll.py +48 -21
- shipit/providers/laravel.py +38 -29
- shipit/providers/mkdocs.py +33 -18
- shipit/providers/node_static.py +205 -139
- shipit/providers/php.py +41 -37
- shipit/providers/python.py +282 -226
- shipit/providers/registry.py +0 -2
- shipit/providers/staticfile.py +44 -25
- shipit/providers/wordpress.py +25 -26
- 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.14.0.dist-info → shipit_cli-0.15.1.dist-info}/METADATA +6 -3
- shipit_cli-0.15.1.dist-info/RECORD +34 -0
- {shipit_cli-0.14.0.dist-info → shipit_cli-0.15.1.dist-info}/WHEEL +1 -1
- shipit_cli-0.14.0.dist-info/RECORD +0 -23
- {shipit_cli-0.14.0.dist-info → shipit_cli-0.15.1.dist-info}/entry_points.txt +0 -0
shipit/generator.py
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import json
|
|
3
2
|
from pathlib import Path
|
|
4
|
-
from typing import
|
|
3
|
+
from typing import List, Optional, Union
|
|
5
4
|
|
|
6
5
|
from shipit.providers.base import (
|
|
7
6
|
DependencySpec,
|
|
8
7
|
Provider,
|
|
9
8
|
ProviderPlan,
|
|
10
9
|
DetectResult,
|
|
11
|
-
|
|
12
|
-
VolumeSpec,
|
|
13
|
-
CustomCommands,
|
|
10
|
+
Config,
|
|
14
11
|
)
|
|
15
12
|
from shipit.providers.registry import providers as registry_providers
|
|
16
13
|
|
|
@@ -20,10 +17,10 @@ def _providers() -> list[type[Provider]]:
|
|
|
20
17
|
return registry_providers()
|
|
21
18
|
|
|
22
19
|
|
|
23
|
-
def detect_provider(path: Path,
|
|
20
|
+
def detect_provider(path: Path, base_config: Config) -> Provider:
|
|
24
21
|
matches: list[tuple[type[Provider], DetectResult]] = []
|
|
25
22
|
for provider_cls in _providers():
|
|
26
|
-
res = provider_cls.detect(path,
|
|
23
|
+
res = provider_cls.detect(path, base_config)
|
|
27
24
|
if res:
|
|
28
25
|
matches.append((provider_cls, res))
|
|
29
26
|
if not matches:
|
|
@@ -64,41 +61,56 @@ def _emit_dependencies_declarations(
|
|
|
64
61
|
declared.add(alias)
|
|
65
62
|
|
|
66
63
|
version_var = None
|
|
67
|
-
|
|
68
|
-
if dep.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
version_var = version_key
|
|
73
|
-
if dep.architecture_var:
|
|
74
|
-
architecture_key = alias + "_architecture"
|
|
75
|
-
lines.append(f'{architecture_key} = getenv("{dep.architecture_var}")')
|
|
76
|
-
architecture_var = architecture_key
|
|
64
|
+
architecture_env_var = None
|
|
65
|
+
if dep.var_name:
|
|
66
|
+
version_var = dep.var_name
|
|
67
|
+
if dep.architecture_var_name:
|
|
68
|
+
architecture_env_var = dep.architecture_var_name
|
|
77
69
|
vars = [f'"{dep.name}"']
|
|
78
70
|
if version_var:
|
|
79
71
|
vars.append(version_var)
|
|
80
|
-
if
|
|
81
|
-
vars.append(f"architecture={
|
|
72
|
+
if architecture_env_var:
|
|
73
|
+
vars.append(f"architecture={architecture_env_var}")
|
|
82
74
|
lines.append(f"{alias} = dep({', '.join(vars)})")
|
|
83
75
|
|
|
84
76
|
return "\n".join(lines), serve_vars, build_vars
|
|
85
77
|
|
|
86
78
|
|
|
87
|
-
def
|
|
79
|
+
def load_provider(
|
|
80
|
+
path: Path, base_config: Config, use_provider: Optional[str] = None
|
|
81
|
+
) -> type[Provider]:
|
|
88
82
|
provider_cls = None
|
|
89
83
|
if use_provider:
|
|
90
|
-
provider_cls = next(
|
|
84
|
+
provider_cls = next(
|
|
85
|
+
(p for p in _providers() if p.name().lower() == use_provider.lower()), None
|
|
86
|
+
)
|
|
91
87
|
if not provider_cls:
|
|
92
|
-
provider_cls = detect_provider(path,
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
provider_cls = detect_provider(path, base_config)
|
|
89
|
+
return provider_cls
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def load_provider_config(
|
|
93
|
+
provider_cls: type[Provider],
|
|
94
|
+
path: Path,
|
|
95
|
+
base_config: Config,
|
|
96
|
+
config: Optional[Union[dict, str]] = None,
|
|
97
|
+
) -> dict:
|
|
98
|
+
provider_config = provider_cls.load_config(path, base_config)
|
|
99
|
+
if config:
|
|
100
|
+
if isinstance(config, str):
|
|
101
|
+
config = json.loads(config)
|
|
102
|
+
assert isinstance(config, dict), "Config must be a dictionary, got %s" % type(config)
|
|
103
|
+
provider_config = provider_config.__class__.model_validate({**(provider_config.model_dump() | config)})
|
|
104
|
+
return provider_config
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def generate_shipit(path: Path, provider: Provider) -> str:
|
|
95
108
|
default_serve_name = path.absolute().name
|
|
96
109
|
|
|
97
110
|
# Collect parts
|
|
98
111
|
plan = ProviderPlan(
|
|
99
112
|
serve_name=provider.serve_name() or default_serve_name,
|
|
100
113
|
provider=provider.name(),
|
|
101
|
-
platform=provider.platform(),
|
|
102
114
|
mounts=provider.mounts(),
|
|
103
115
|
volumes=provider.volumes(),
|
|
104
116
|
declarations=provider.declarations(),
|
|
@@ -123,8 +135,12 @@ def generate_shipit(path: Path, custom_commands: CustomCommands, use_provider: O
|
|
|
123
135
|
|
|
124
136
|
build_steps_block = ",\n".join([f" {s}" for s in build_steps])
|
|
125
137
|
deps_array = ", ".join(serve_dep_vars)
|
|
138
|
+
|
|
139
|
+
def format_command(k: str, v: str) -> str:
|
|
140
|
+
return f' "{k}": {v}'
|
|
141
|
+
|
|
126
142
|
commands_lines = ",\n".join(
|
|
127
|
-
[
|
|
143
|
+
[format_command(k, v) for k, v in plan.commands.items()]
|
|
128
144
|
)
|
|
129
145
|
env_lines = None
|
|
130
146
|
if plan.env is not None:
|
|
@@ -152,8 +168,9 @@ def generate_shipit(path: Path, custom_commands: CustomCommands, use_provider: O
|
|
|
152
168
|
out.append(dep_block)
|
|
153
169
|
out.append("")
|
|
154
170
|
|
|
155
|
-
|
|
156
|
-
|
|
171
|
+
if plan.mounts:
|
|
172
|
+
for m in plan.mounts:
|
|
173
|
+
out.append(f'{m.name} = mount("{m.name}")')
|
|
157
174
|
out.append("")
|
|
158
175
|
|
|
159
176
|
if plan.volumes:
|
|
@@ -168,18 +185,16 @@ def generate_shipit(path: Path, custom_commands: CustomCommands, use_provider: O
|
|
|
168
185
|
)
|
|
169
186
|
out.append("")
|
|
170
187
|
|
|
171
|
-
out.append('PORT = getenv("PORT") or "8080"')
|
|
172
|
-
|
|
173
188
|
if plan.declarations:
|
|
174
189
|
out.append(plan.declarations)
|
|
190
|
+
out.append("")
|
|
175
191
|
|
|
176
|
-
out.append("")
|
|
177
192
|
out.append("serve(")
|
|
178
193
|
out.append(f' name="{plan.serve_name}",')
|
|
179
194
|
out.append(f' provider="{plan.provider}",')
|
|
180
195
|
# If app is mounted for serve, set cwd to the app serve path
|
|
181
196
|
if "app" in attach_serve_names:
|
|
182
|
-
out.append(' cwd=app
|
|
197
|
+
out.append(' cwd=app.serve_path,')
|
|
183
198
|
out.append(" build=[")
|
|
184
199
|
out.append(build_steps_block)
|
|
185
200
|
out.append(" ],")
|
|
@@ -196,9 +211,12 @@ def generate_shipit(path: Path, custom_commands: CustomCommands, use_provider: O
|
|
|
196
211
|
out.append(" env = {")
|
|
197
212
|
out.append(env_lines)
|
|
198
213
|
out.append(" },")
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
214
|
+
if commands_lines:
|
|
215
|
+
out.append(" commands = {")
|
|
216
|
+
out.append(commands_lines)
|
|
217
|
+
out.append(" },")
|
|
218
|
+
else:
|
|
219
|
+
out.append(" commands = {},")
|
|
202
220
|
if plan.services:
|
|
203
221
|
out.append(" services=[")
|
|
204
222
|
for s in plan.services:
|
shipit/procfile.py
CHANGED
|
@@ -1,33 +1,8 @@
|
|
|
1
1
|
# File forked from https://github.com/nickstenning/honcho/blob/main/honcho/environ.py
|
|
2
2
|
# MIT License
|
|
3
|
-
import os
|
|
4
3
|
import re
|
|
5
|
-
from collections import defaultdict, namedtuple
|
|
6
4
|
|
|
7
|
-
PROCFILE_LINE = re.compile(r
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Env(object):
|
|
11
|
-
|
|
12
|
-
def __init__(self, config):
|
|
13
|
-
self._c = config
|
|
14
|
-
|
|
15
|
-
@property
|
|
16
|
-
def port(self):
|
|
17
|
-
try:
|
|
18
|
-
return int(self._c['port'])
|
|
19
|
-
except ValueError:
|
|
20
|
-
raise ValueError(f"invalid value for port: '{self._c['port']}'")
|
|
21
|
-
|
|
22
|
-
@property
|
|
23
|
-
def procfile(self):
|
|
24
|
-
return os.path.join(self._c['app_root'], self._c['procfile'])
|
|
25
|
-
|
|
26
|
-
def load_procfile(self):
|
|
27
|
-
with open(self.procfile) as f:
|
|
28
|
-
content = f.read()
|
|
29
|
-
|
|
30
|
-
return parse_procfile(content)
|
|
5
|
+
PROCFILE_LINE = re.compile(r"^([A-Za-z0-9_-]+):\s*(.+)$")
|
|
31
6
|
|
|
32
7
|
|
|
33
8
|
class Procfile(object):
|
|
@@ -46,10 +21,11 @@ class Procfile(object):
|
|
|
46
21
|
return p
|
|
47
22
|
|
|
48
23
|
def add_process(self, name, command):
|
|
49
|
-
assert name not in self.processes,
|
|
24
|
+
assert name not in self.processes, (
|
|
50
25
|
"process names must be unique within a Procfile"
|
|
26
|
+
)
|
|
51
27
|
self.processes[name] = command
|
|
52
|
-
|
|
28
|
+
|
|
53
29
|
def get_start_command(self):
|
|
54
30
|
if "web" in self.processes:
|
|
55
31
|
return self.processes["web"]
|
|
@@ -60,47 +36,3 @@ class Procfile(object):
|
|
|
60
36
|
elif len(self.processes) == 1:
|
|
61
37
|
return list(self.processes.values())[0]
|
|
62
38
|
return None
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
ProcessParams = namedtuple("ProcessParams", "name cmd quiet env")
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def expand_processes(processes, concurrency=None, env=None, quiet=None, port=None):
|
|
69
|
-
"""
|
|
70
|
-
Get a list of the processes that need to be started given the specified
|
|
71
|
-
list of process types, concurrency, environment, quietness, and base port
|
|
72
|
-
number.
|
|
73
|
-
|
|
74
|
-
Returns a list of ProcessParams objects, which have `name`, `cmd`, `env`,
|
|
75
|
-
and `quiet` attributes, corresponding to the parameters to the constructor
|
|
76
|
-
of `honcho.process.Process`.
|
|
77
|
-
"""
|
|
78
|
-
if env is not None and env.get("PORT") is not None:
|
|
79
|
-
port = int(env.get("PORT"))
|
|
80
|
-
|
|
81
|
-
if quiet is None:
|
|
82
|
-
quiet = []
|
|
83
|
-
|
|
84
|
-
con = defaultdict(lambda: 1)
|
|
85
|
-
if concurrency is not None:
|
|
86
|
-
con.update(concurrency)
|
|
87
|
-
|
|
88
|
-
out = []
|
|
89
|
-
|
|
90
|
-
for name, cmd in processes.items():
|
|
91
|
-
for i in range(con[name]):
|
|
92
|
-
n = "{0}.{1}".format(name, i + 1)
|
|
93
|
-
c = cmd
|
|
94
|
-
q = name in quiet
|
|
95
|
-
e = {'SHIPIT_PROCESS_NAME': n}
|
|
96
|
-
if env is not None:
|
|
97
|
-
e.update(env)
|
|
98
|
-
if port is not None:
|
|
99
|
-
e['PORT'] = str(port + i)
|
|
100
|
-
|
|
101
|
-
params = ProcessParams(n, c, q, e)
|
|
102
|
-
out.append(params)
|
|
103
|
-
if port is not None:
|
|
104
|
-
port += 100
|
|
105
|
-
|
|
106
|
-
return out
|
shipit/providers/base.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from dataclasses import dataclass, field
|
|
4
2
|
from pathlib import Path
|
|
5
|
-
from typing import Dict, List, Optional, Protocol, Literal
|
|
3
|
+
from typing import Any, Dict, List, Optional, Protocol, Literal
|
|
4
|
+
from shipit.procfile import Procfile
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from pydantic_settings import SettingsConfigDict
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@dataclass
|
|
@@ -11,24 +12,62 @@ class DetectResult:
|
|
|
11
12
|
score: int # Higher score wins when multiple providers match
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
class CustomCommands(BaseModel):
|
|
16
|
+
model_config = SettingsConfigDict(extra="ignore")
|
|
17
|
+
|
|
16
18
|
install: Optional[str] = None
|
|
17
19
|
build: Optional[str] = None
|
|
18
20
|
start: Optional[str] = None
|
|
19
21
|
after_deploy: Optional[str] = None
|
|
20
22
|
|
|
23
|
+
# if (path / "Dockerfile").exists():
|
|
24
|
+
# # We get the start command from the Dockerfile
|
|
25
|
+
# with open(path / "Dockerfile", "r") as f:
|
|
26
|
+
# cmd = None
|
|
27
|
+
# for line in f:
|
|
28
|
+
# if line.startswith("CMD "):
|
|
29
|
+
# cmd = line[4:].strip()
|
|
30
|
+
# cmd = json.loads(cmd)
|
|
31
|
+
# # We get the last command
|
|
32
|
+
# if cmd:
|
|
33
|
+
# if isinstance(cmd, list):
|
|
34
|
+
# cmd = " ".join(cmd)
|
|
35
|
+
# custom_commands.start = cmd
|
|
36
|
+
|
|
37
|
+
def enrich_from_path(self, path: Path, use_procfile: bool = True) -> "CustomCommands":
|
|
38
|
+
if use_procfile:
|
|
39
|
+
procfile_path = path / "Procfile"
|
|
40
|
+
if procfile_path.exists():
|
|
41
|
+
try:
|
|
42
|
+
procfile = Procfile.loads(procfile_path.read_text())
|
|
43
|
+
self.start = procfile.get_start_command()
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Config(BaseModel):
|
|
50
|
+
model_config = SettingsConfigDict(extra="ignore", env_prefix="SHIPIT_")
|
|
51
|
+
|
|
52
|
+
port: Optional[int] = 8080
|
|
53
|
+
commands: CustomCommands = Field(default_factory=CustomCommands)
|
|
54
|
+
|
|
55
|
+
def __getattr__(self, name: str) -> Any:
|
|
56
|
+
return getattr(self.commands, name, None)
|
|
57
|
+
|
|
21
58
|
|
|
22
59
|
class Provider(Protocol):
|
|
23
60
|
def __init__(self, path: Path): ...
|
|
24
61
|
@classmethod
|
|
25
62
|
def name(cls) -> str: ...
|
|
26
63
|
@classmethod
|
|
27
|
-
def
|
|
28
|
-
|
|
64
|
+
def load_config(cls, path: Path, config: Config) -> Config: ...
|
|
65
|
+
@classmethod
|
|
66
|
+
def detect(
|
|
67
|
+
cls, path: Path, config: Config
|
|
68
|
+
) -> Optional[DetectResult]: ...
|
|
29
69
|
# Structured plan steps (no path args; use self.path)
|
|
30
70
|
def serve_name(self) -> Optional[str]: ...
|
|
31
|
-
def platform(self) -> Optional[str]: ...
|
|
32
71
|
def dependencies(self) -> list["DependencySpec"]: ...
|
|
33
72
|
def declarations(self) -> Optional[str]: ...
|
|
34
73
|
def build_steps(self) -> list[str]: ...
|
|
@@ -44,9 +83,9 @@ class Provider(Protocol):
|
|
|
44
83
|
@dataclass
|
|
45
84
|
class DependencySpec:
|
|
46
85
|
name: str
|
|
47
|
-
|
|
86
|
+
var_name: Optional[str] = None
|
|
48
87
|
default_version: Optional[str] = None
|
|
49
|
-
|
|
88
|
+
architecture_var_name: Optional[str] = None
|
|
50
89
|
alias: Optional[str] = None # Variable name in Shipit plan
|
|
51
90
|
use_in_build: bool = False
|
|
52
91
|
use_in_serve: bool = False
|
shipit/providers/hugo.py
CHANGED
|
@@ -1,19 +1,70 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from pathlib import Path
|
|
4
2
|
from typing import Dict, Optional
|
|
5
3
|
|
|
6
|
-
from .base import
|
|
7
|
-
|
|
4
|
+
from .base import (
|
|
5
|
+
DetectResult,
|
|
6
|
+
DependencySpec,
|
|
7
|
+
Provider,
|
|
8
|
+
_exists,
|
|
9
|
+
ServiceSpec,
|
|
10
|
+
VolumeSpec,
|
|
11
|
+
CustomCommands,
|
|
12
|
+
MountSpec,
|
|
13
|
+
Config,
|
|
14
|
+
)
|
|
15
|
+
from .staticfile import StaticFileProvider, StaticFileConfig
|
|
16
|
+
from pydantic_settings import SettingsConfigDict
|
|
17
|
+
import toml
|
|
18
|
+
import json
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HugoConfig(StaticFileConfig):
|
|
23
|
+
model_config = SettingsConfigDict(extra="ignore", env_prefix="SHIPIT_")
|
|
24
|
+
|
|
25
|
+
hugo_version: Optional[str] = "0.149.0"
|
|
26
|
+
static_dir: Optional[str] = "public"
|
|
27
|
+
|
|
8
28
|
|
|
9
29
|
class HugoProvider(StaticFileProvider):
|
|
30
|
+
def __init__(self, path: Path, config: HugoConfig):
|
|
31
|
+
super().__init__(path, config)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def load_config(cls, path: Path, base_config: Config) -> HugoConfig:
|
|
35
|
+
config = super().load_config(path, base_config)
|
|
36
|
+
if _exists(path, "hugo.toml"):
|
|
37
|
+
config_dict = toml.load(open(path / "hugo.toml"))
|
|
38
|
+
elif _exists(path, "hugo.json"):
|
|
39
|
+
config_dict = json.load(open(path / "hugo.json"))
|
|
40
|
+
elif _exists(path, "hugo.yaml"):
|
|
41
|
+
config_dict = yaml.safe_load(open(path / "hugo.yaml"))
|
|
42
|
+
elif _exists(path, "hugo.yml"):
|
|
43
|
+
config_dict = yaml.safe_load(open(path / "hugo.yml"))
|
|
44
|
+
else:
|
|
45
|
+
config_dict = {}
|
|
46
|
+
|
|
47
|
+
config = HugoConfig(**config.model_dump())
|
|
48
|
+
if not config.static_dir:
|
|
49
|
+
hugo_publish_dir = None
|
|
50
|
+
if isinstance(config_dict, dict):
|
|
51
|
+
hugo_publish_dir = config_dict.get("publishDir")
|
|
52
|
+
if not hugo_publish_dir:
|
|
53
|
+
# Use destination as fallback
|
|
54
|
+
hugo_publish_dir = config_dict.get("destination")
|
|
55
|
+
hugo_publish_dir = hugo_publish_dir or "public"
|
|
56
|
+
assert isinstance(hugo_publish_dir, str), "publishDir in hugo config must be a string"
|
|
57
|
+
config.static_dir = hugo_publish_dir
|
|
58
|
+
return config
|
|
10
59
|
|
|
11
60
|
@classmethod
|
|
12
61
|
def name(cls) -> str:
|
|
13
62
|
return "hugo"
|
|
14
63
|
|
|
15
64
|
@classmethod
|
|
16
|
-
def detect(
|
|
65
|
+
def detect(
|
|
66
|
+
cls, path: Path, config: Config
|
|
67
|
+
) -> Optional[DetectResult]:
|
|
17
68
|
if _exists(path, "hugo.toml", "hugo.json", "hugo.yaml", "hugo.yml"):
|
|
18
69
|
return DetectResult(cls.name(), 80)
|
|
19
70
|
if (
|
|
@@ -22,20 +73,18 @@ class HugoProvider(StaticFileProvider):
|
|
|
22
73
|
and (_exists(path, "static") or _exists(path, "themes"))
|
|
23
74
|
):
|
|
24
75
|
return DetectResult(cls.name(), 40)
|
|
76
|
+
if config.commands.build and config.commands.build.startswith("hugo "):
|
|
77
|
+
return DetectResult(cls.name(), 80)
|
|
25
78
|
return None
|
|
26
79
|
|
|
27
80
|
def serve_name(self) -> Optional[str]:
|
|
28
81
|
return None
|
|
29
82
|
|
|
30
|
-
def platform(self) -> Optional[str]:
|
|
31
|
-
return "hugo"
|
|
32
|
-
|
|
33
83
|
def dependencies(self) -> list[DependencySpec]:
|
|
34
84
|
return [
|
|
35
85
|
DependencySpec(
|
|
36
86
|
"hugo",
|
|
37
|
-
|
|
38
|
-
default_version="0.149.0",
|
|
87
|
+
var_name="config.hugo_version",
|
|
39
88
|
use_in_build=True,
|
|
40
89
|
),
|
|
41
90
|
*super().dependencies(),
|
|
@@ -43,9 +92,10 @@ class HugoProvider(StaticFileProvider):
|
|
|
43
92
|
|
|
44
93
|
def build_steps(self) -> list[str]:
|
|
45
94
|
return [
|
|
46
|
-
'workdir(temp
|
|
95
|
+
'workdir(temp.path)',
|
|
47
96
|
'copy(".", ".", ignore=[".git"])',
|
|
48
|
-
'run("hugo build
|
|
97
|
+
'run("hugo build", group="build")',
|
|
98
|
+
'run("cp -R {}/* {}/".format(config.static_dir, static_app.path))'
|
|
49
99
|
]
|
|
50
100
|
|
|
51
101
|
def mounts(self) -> list[MountSpec]:
|
shipit/providers/jekyll.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from pathlib import Path
|
|
4
2
|
from typing import Dict, Optional
|
|
3
|
+
import yaml
|
|
5
4
|
|
|
6
5
|
from .base import (
|
|
7
6
|
DetectResult,
|
|
@@ -12,13 +11,46 @@ from .base import (
|
|
|
12
11
|
ServiceSpec,
|
|
13
12
|
VolumeSpec,
|
|
14
13
|
CustomCommands,
|
|
14
|
+
Config,
|
|
15
15
|
)
|
|
16
|
-
from .staticfile import StaticFileProvider
|
|
16
|
+
from .staticfile import StaticFileProvider, StaticFileConfig
|
|
17
|
+
from pydantic_settings import SettingsConfigDict
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class JekyllConfig(StaticFileConfig):
|
|
21
|
+
model_config = SettingsConfigDict(extra="ignore", env_prefix="SHIPIT_")
|
|
22
|
+
|
|
23
|
+
ruby_version: Optional[str] = "3.4.7"
|
|
24
|
+
jekyll_version: Optional[str] = "4.3.0"
|
|
25
|
+
|
|
26
|
+
static_dir: Optional[str] = "_site"
|
|
17
27
|
|
|
18
28
|
|
|
19
29
|
class JekyllProvider(StaticFileProvider):
|
|
20
|
-
def __init__(self, path: Path,
|
|
30
|
+
def __init__(self, path: Path, config: JekyllConfig):
|
|
21
31
|
self.path = path
|
|
32
|
+
self.config = config
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def load_config(
|
|
36
|
+
cls, path: Path, base_config: Config
|
|
37
|
+
) -> JekyllConfig:
|
|
38
|
+
config = super().load_config(path, base_config)
|
|
39
|
+
config = JekyllConfig(**config.model_dump())
|
|
40
|
+
if not config.static_dir:
|
|
41
|
+
jekyll_static_dir = None
|
|
42
|
+
if _exists(path, "_config.yml"):
|
|
43
|
+
config_dict = yaml.safe_load(open(path / "_config.yml"))
|
|
44
|
+
elif _exists(path, "_config.yaml"):
|
|
45
|
+
config_dict = yaml.safe_load(open(path / "_config.yaml"))
|
|
46
|
+
else:
|
|
47
|
+
config_dict = {}
|
|
48
|
+
if config_dict and isinstance(config_dict, dict):
|
|
49
|
+
jekyll_static_dir = config_dict.get("destination")
|
|
50
|
+
jekyll_static_dir = jekyll_static_dir or "_site"
|
|
51
|
+
assert isinstance(jekyll_static_dir, str), "destination in Jekyll config must be a string"
|
|
52
|
+
config.static_dir = jekyll_static_dir
|
|
53
|
+
return config
|
|
22
54
|
|
|
23
55
|
@classmethod
|
|
24
56
|
def name(cls) -> str:
|
|
@@ -26,45 +58,36 @@ class JekyllProvider(StaticFileProvider):
|
|
|
26
58
|
|
|
27
59
|
@classmethod
|
|
28
60
|
def detect(
|
|
29
|
-
cls, path: Path,
|
|
61
|
+
cls, path: Path, config: Config
|
|
30
62
|
) -> Optional[DetectResult]:
|
|
31
63
|
if _exists(path, "_config.yml", "_config.yaml"):
|
|
32
64
|
if _exists(path, "Gemfile"):
|
|
33
65
|
return DetectResult(cls.name(), 85)
|
|
34
66
|
return DetectResult(cls.name(), 40)
|
|
35
|
-
if
|
|
67
|
+
if config.commands.build and config.commands.build.startswith("jekyll "):
|
|
36
68
|
return DetectResult(cls.name(), 85)
|
|
37
69
|
return None
|
|
38
70
|
|
|
39
|
-
def initialize(self) -> None:
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
71
|
def serve_name(self) -> Optional[str]:
|
|
43
72
|
return None
|
|
44
73
|
|
|
45
|
-
def platform(self) -> Optional[str]:
|
|
46
|
-
return None
|
|
47
|
-
|
|
48
74
|
def dependencies(self) -> list[DependencySpec]:
|
|
49
75
|
return [
|
|
50
76
|
DependencySpec(
|
|
51
77
|
"ruby",
|
|
52
|
-
|
|
78
|
+
var_name="config.ruby_version",
|
|
53
79
|
use_in_build=True,
|
|
54
80
|
use_in_serve=False,
|
|
55
81
|
),
|
|
56
82
|
*super().dependencies(),
|
|
57
83
|
]
|
|
58
84
|
|
|
59
|
-
def declarations(self) -> Optional[str]:
|
|
60
|
-
return 'jekyll_version = getenv("SHIPIT_JEKYLL_VERSION") or "1.6.1"\n'
|
|
61
|
-
|
|
62
85
|
def build_steps(self) -> list[str]:
|
|
63
86
|
if _exists(self.path, "Gemfile"):
|
|
64
87
|
install_deps = ["Gemfile"]
|
|
65
88
|
install_deps_str = ", ".join([f'"{dep}"' for dep in install_deps])
|
|
66
89
|
install_commands = [
|
|
67
|
-
f'run("bundle install", inputs=[{install_deps_str}], group="
|
|
90
|
+
f'run("bundle install", inputs=[{install_deps_str}], group="install")'
|
|
68
91
|
]
|
|
69
92
|
if _exists(self.path, "Gemfile.lock"):
|
|
70
93
|
install_commands = [
|
|
@@ -72,12 +95,16 @@ class JekyllProvider(StaticFileProvider):
|
|
|
72
95
|
*install_commands,
|
|
73
96
|
]
|
|
74
97
|
else:
|
|
75
|
-
install_commands = [
|
|
98
|
+
install_commands = [
|
|
99
|
+
'run("bundle init", group="install")',
|
|
100
|
+
'run("bundle add jekyll -v {}".format(config.jekyll_version), group="install")',
|
|
101
|
+
]
|
|
76
102
|
return [
|
|
77
|
-
'workdir(temp
|
|
78
|
-
'copy(".", ignore=[".git"])',
|
|
103
|
+
'workdir(temp.path)',
|
|
79
104
|
*install_commands,
|
|
80
|
-
'
|
|
105
|
+
'copy(".", ignore=[".git"])',
|
|
106
|
+
'run("jekyll build", group="build")',
|
|
107
|
+
'run("cp -R {}/* {}/".format(config.static_dir, static_app.path))'
|
|
81
108
|
]
|
|
82
109
|
|
|
83
110
|
def prepare_steps(self) -> Optional[list[str]]:
|