shipit-cli 0.3.3__py3-none-any.whl → 0.4.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/cli.py CHANGED
@@ -55,6 +55,7 @@ class Serve:
55
55
  build: List["Step"]
56
56
  deps: List["Package"]
57
57
  commands: Dict[str, str]
58
+ cwd: Optional[str] = None
58
59
  assets: Optional[Dict[str, str]] = None
59
60
  prepare: Optional[List["PrepareStep"]] = None
60
61
  workers: Optional[List[str]] = None
@@ -577,18 +578,33 @@ class LocalBuilder:
577
578
  self.create_file(asset_path, assets[asset])
578
579
 
579
580
  def build_prepare(self, serve: Serve) -> None:
580
- app_dir = self.get_build_path()
581
581
  self.prepare_bash_script.parent.mkdir(parents=True, exist_ok=True)
582
582
  commands: List[str] = []
583
+ if serve.cwd:
584
+ commands.append(f"cd {serve.cwd}")
583
585
  if serve.prepare:
584
586
  for step in serve.prepare:
585
587
  if isinstance(step, RunStep):
586
588
  commands.append(step.command)
587
589
  elif isinstance(step, WorkdirStep):
588
590
  commands.append(f"cd {step.path}")
589
- content = "#!/bin/bash\ncd {app_dir}\n{body}".format(
590
- app_dir=app_dir, body="\n".join(commands)
591
+ content = "#!/bin/bash\n{body}".format(
592
+ body="\n".join(commands)
591
593
  )
594
+ console.print(f"\n[bold]Created prepare.sh script to run before packaging ✅[/bold]")
595
+ manifest_panel = Panel(
596
+ Syntax(
597
+ content,
598
+ "bash",
599
+ theme="monokai",
600
+ background_color="default",
601
+ line_numbers=True,
602
+ ),
603
+ box=box.SQUARE,
604
+ border_style="bright_black",
605
+ expand=False,
606
+ )
607
+ console.print(manifest_panel, markup=False, highlight=True)
592
608
  self.prepare_bash_script.write_text(content)
593
609
  self.prepare_bash_script.chmod(0o755)
594
610
 
@@ -739,15 +755,35 @@ class WasmerBuilder:
739
755
  env_lines = ""
740
756
 
741
757
  commands: List[str] = []
758
+ if serve.cwd:
759
+ commands.append(f"cd {serve.cwd}")
760
+
742
761
  if serve.prepare:
743
762
  for step in serve.prepare:
744
763
  if isinstance(step, RunStep):
745
764
  commands.append(step.command)
746
765
  elif isinstance(step, WorkdirStep):
747
766
  commands.append(f"cd {step.path}")
767
+
748
768
  body = "\n".join(filter(None, [env_lines, *commands]))
769
+ content = f"#!/bin/bash\n\n{body}"
770
+ console.print(f"\n[bold]Created prepare.sh script to run before packaging ✅[/bold]")
771
+ manifest_panel = Panel(
772
+ Syntax(
773
+ content,
774
+ "bash",
775
+ theme="monokai",
776
+ background_color="default",
777
+ line_numbers=True,
778
+ ),
779
+ box=box.SQUARE,
780
+ border_style="bright_black",
781
+ expand=False,
782
+ )
783
+ console.print(manifest_panel, markup=False, highlight=True)
784
+
749
785
  (prepare_dir / "prepare.sh").write_text(
750
- f"#!/bin/bash\n\n{body}",
786
+ content,
751
787
  )
752
788
  (prepare_dir / "prepare.sh").chmod(0o755)
753
789
 
@@ -842,7 +878,8 @@ class WasmerBuilder:
842
878
  command.add("module", program_binary["script"])
843
879
  command.add("runner", "wasi")
844
880
  wasi_args = table()
845
- wasi_args.add("cwd", "/app")
881
+ if serve.cwd:
882
+ wasi_args.add("cwd", serve.cwd)
846
883
  wasi_args.add("main-args", parts[1:])
847
884
  env = program_binary.get("env") or {}
848
885
  if serve.env:
@@ -1024,6 +1061,7 @@ class Ctx:
1024
1061
  build: List[str],
1025
1062
  deps: List[str],
1026
1063
  commands: Dict[str, str],
1064
+ cwd: Optional[str] = None,
1027
1065
  assets: Optional[Dict[str, str]] = None,
1028
1066
  prepare: Optional[List[str]] = None,
1029
1067
  workers: Optional[List[str]] = None,
@@ -1043,6 +1081,7 @@ class Ctx:
1043
1081
  name=name,
1044
1082
  provider=provider,
1045
1083
  build=build_refs,
1084
+ cwd=cwd,
1046
1085
  assets=assets,
1047
1086
  deps=dep_refs,
1048
1087
  commands=commands,
@@ -1433,7 +1472,7 @@ def main() -> None:
1433
1472
  app()
1434
1473
  except Exception as e:
1435
1474
  console.print(f"[bold red]{type(e).__name__}[/bold red]: {e}")
1436
- raise e
1475
+ # raise e
1437
1476
 
1438
1477
 
1439
1478
  if __name__ == "__main__":
shipit/generator.py CHANGED
@@ -7,22 +7,19 @@ from shipit.providers.base import DependencySpec, Provider, ProviderPlan, Detect
7
7
  from shipit.providers.registry import providers as registry_providers
8
8
 
9
9
 
10
- def _providers() -> list[Provider]:
11
- # Load providers from modular registry
10
+ def _providers() -> list[type[Provider]]:
11
+ # Load provider classes from modular registry
12
12
  return registry_providers()
13
13
 
14
14
 
15
15
  def detect_provider(path: Path) -> Provider:
16
- matches: list[tuple[Provider, DetectResult]] = []
17
- for p in _providers():
18
- res = p.detect(path)
16
+ matches: list[tuple[type[Provider], DetectResult]] = []
17
+ for provider_cls in _providers():
18
+ res = provider_cls.detect(path)
19
19
  if res:
20
- matches.append((p, res))
20
+ matches.append((provider_cls, res))
21
21
  if not matches:
22
- # Default to static site as the safest fallback
23
- from shipit.providers.staticfile import StaticFileProvider
24
-
25
- return StaticFileProvider()
22
+ raise Exception("Shipit could not detect a provider for this project")
26
23
  # Highest score wins; tie-breaker by order
27
24
  matches.sort(key=lambda x: x[1].score, reverse=True)
28
25
  return matches[0][0]
@@ -80,20 +77,20 @@ def _render_assets(assets: Optional[Dict[str, str]]) -> Optional[str]:
80
77
 
81
78
 
82
79
  def generate_shipit(path: Path) -> str:
83
- provider = detect_provider(path)
84
- provider.initialize(path)
80
+ provider_cls = detect_provider(path)
81
+ provider = provider_cls(path)
85
82
 
86
83
  # Collect parts
87
84
  plan = ProviderPlan(
88
- serve_name=provider.serve_name(path),
89
- provider=provider.provider_kind(path),
90
- mounts=provider.mounts(path),
91
- declarations=provider.declarations(path),
92
- dependencies=provider.dependencies(path),
93
- build_steps=provider.build_steps(path),
94
- prepare=provider.prepare_steps(path),
95
- commands=provider.commands(path),
96
- env=provider.env(path),
85
+ serve_name=provider.serve_name(),
86
+ provider=provider.provider_kind(),
87
+ mounts=provider.mounts(),
88
+ declarations=provider.declarations(),
89
+ dependencies=provider.dependencies(),
90
+ build_steps=provider.build_steps(),
91
+ prepare=provider.prepare_steps(),
92
+ commands=provider.commands(),
93
+ env=provider.env(),
97
94
  )
98
95
 
99
96
  # Declare dependency variables (combined) and collect serve deps
@@ -118,8 +115,10 @@ def generate_shipit(path: Path) -> str:
118
115
  env_lines = ",\n".join([f' "{k}": {v}' for k, v in plan.env.items()])
119
116
  assets_block = _render_assets(plan.assets)
120
117
  mounts_block = None
118
+ attach_serve_names: list[str] = []
121
119
  if plan.mounts:
122
- mounts = filter(lambda m: m.attach_to_serve, plan.mounts)
120
+ mounts = list(filter(lambda m: m.attach_to_serve, plan.mounts))
121
+ attach_serve_names = [m.name for m in mounts]
123
122
  mounts_block = ",\n".join([f" {m.name}" for m in mounts])
124
123
 
125
124
  out: List[str] = []
@@ -134,6 +133,9 @@ def generate_shipit(path: Path) -> str:
134
133
  out.append("serve(")
135
134
  out.append(f' name="{plan.serve_name}",')
136
135
  out.append(f' provider="{plan.provider}",')
136
+ # If app is mounted for serve, set cwd to the app serve path
137
+ if "app" in attach_serve_names:
138
+ out.append(' cwd=app["serve"],')
137
139
  out.append(" build=[")
138
140
  out.append(build_steps_block)
139
141
  out.append(" ],")
shipit/providers/base.py CHANGED
@@ -12,21 +12,24 @@ class DetectResult:
12
12
 
13
13
 
14
14
  class Provider(Protocol):
15
- def name(self) -> str: ...
16
- def detect(self, path: Path) -> Optional[DetectResult]: ...
17
- def initialize(self, path: Path) -> None: ...
18
- # Structured plan steps
19
- def serve_name(self, path: Path) -> str: ...
20
- def provider_kind(self, path: Path) -> str: ...
21
- def dependencies(self, path: Path) -> list["DependencySpec"]: ...
22
- def declarations(self, path: Path) -> Optional[str]: ...
23
- def build_steps(self, path: Path) -> list[str]: ...
15
+ def __init__(self, path: Path): ...
16
+ @classmethod
17
+ def name(cls) -> str: ...
18
+ @classmethod
19
+ def detect(cls, path: Path) -> Optional[DetectResult]: ...
20
+ def initialize(self) -> None: ...
21
+ # Structured plan steps (no path args; use self.path)
22
+ def serve_name(self) -> str: ...
23
+ def provider_kind(self) -> str: ...
24
+ def dependencies(self) -> list["DependencySpec"]: ...
25
+ def declarations(self) -> Optional[str]: ...
26
+ def build_steps(self) -> list[str]: ...
24
27
  # Prepare: list of Starlark step calls (currently only run(...))
25
- def prepare_steps(self, path: Path) -> Optional[List[str]]: ...
26
- def commands(self, path: Path) -> Dict[str, str]: ...
27
- def assets(self, path: Path) -> Optional[Dict[str, str]]: ...
28
- def mounts(self, path: Path) -> List["MountSpec"]: ...
29
- def env(self, path: Path) -> Optional[Dict[str, str]]: ...
28
+ def prepare_steps(self) -> Optional[List[str]]: ...
29
+ def commands(self) -> Dict[str, str]: ...
30
+ def assets(self) -> Optional[Dict[str, str]]: ...
31
+ def mounts(self) -> List["MountSpec"]: ...
32
+ def env(self) -> Optional[Dict[str, str]]: ...
30
33
 
31
34
 
32
35
  @dataclass
@@ -7,32 +7,36 @@ from .base import DetectResult, DependencySpec, Provider, _exists, _has_dependen
7
7
 
8
8
 
9
9
  class GatsbyProvider:
10
- def name(self) -> str:
10
+ def __init__(self, path: Path):
11
+ self.path = path
12
+ @classmethod
13
+ def name(cls) -> str:
11
14
  return "gatsby"
12
15
 
13
- def detect(self, path: Path) -> Optional[DetectResult]:
16
+ @classmethod
17
+ def detect(cls, path: Path) -> Optional[DetectResult]:
14
18
  pkg = path / "package.json"
15
19
  if not pkg.exists():
16
20
  return None
17
21
  if _exists(path, "gatsby-config.js", "gatsby-config.ts") or _has_dependency(
18
22
  pkg, "gatsby"
19
23
  ):
20
- return DetectResult(self.name(), 90)
24
+ return DetectResult(cls.name(), 90)
21
25
  return None
22
26
 
23
- def initialize(self, path: Path) -> None:
27
+ def initialize(self) -> None:
24
28
  pass
25
29
 
26
- def serve_name(self, path: Path) -> str:
27
- return path.name
30
+ def serve_name(self) -> str:
31
+ return self.path.name
28
32
 
29
- def provider_kind(self, path: Path) -> str:
33
+ def provider_kind(self) -> str:
30
34
  return "staticsite"
31
35
 
32
- def declarations(self, path: Path) -> Optional[str]:
36
+ def declarations(self) -> Optional[str]:
33
37
  return None
34
38
 
35
- def dependencies(self, path: Path) -> list[DependencySpec]:
39
+ def dependencies(self) -> list[DependencySpec]:
36
40
  return [
37
41
  DependencySpec(
38
42
  "node",
@@ -44,7 +48,7 @@ class GatsbyProvider:
44
48
  DependencySpec("static-web-server", env_var="SHIPIT_SWS_VERSION", use_in_serve=True),
45
49
  ]
46
50
 
47
- def build_steps(self, path: Path) -> list[str]:
51
+ def build_steps(self) -> list[str]:
48
52
  return [
49
53
  "run(\"npm install\", inputs=[\"package.json\", \"package-lock.json\"], group=\"install\")",
50
54
  "copy(\".\", \".\", ignore=[\"node_modules\", \".git\"])",
@@ -52,17 +56,17 @@ class GatsbyProvider:
52
56
  "run(\"cp -R public/* {}/\".format(app[\"build\"]))",
53
57
  ]
54
58
 
55
- def prepare_steps(self, path: Path) -> Optional[list[str]]:
59
+ def prepare_steps(self) -> Optional[list[str]]:
56
60
  return None
57
61
 
58
- def commands(self, path: Path) -> Dict[str, str]:
62
+ def commands(self) -> Dict[str, str]:
59
63
  return {"start": '"static-web-server --root /app"'}
60
64
 
61
- def assets(self, path: Path) -> Optional[Dict[str, str]]:
65
+ def assets(self) -> Optional[Dict[str, str]]:
62
66
  return None
63
67
 
64
- def mounts(self, path: Path) -> list[MountSpec]:
68
+ def mounts(self) -> list[MountSpec]:
65
69
  return [MountSpec("app")]
66
70
 
67
- def env(self, path: Path) -> Optional[Dict[str, str]]:
71
+ def env(self) -> Optional[Dict[str, str]]:
68
72
  return None
shipit/providers/hugo.py CHANGED
@@ -7,27 +7,29 @@ from .base import DetectResult, DependencySpec, Provider, _exists
7
7
  from .staticfile import StaticFileProvider
8
8
 
9
9
  class HugoProvider(StaticFileProvider):
10
- def name(self) -> str:
10
+ @classmethod
11
+ def name(cls) -> str:
11
12
  return "hugo"
12
13
 
13
- def detect(self, path: Path) -> Optional[DetectResult]:
14
+ @classmethod
15
+ def detect(cls, path: Path) -> Optional[DetectResult]:
14
16
  if _exists(path, "hugo.toml", "hugo.json", "hugo.yaml", "hugo.yml"):
15
- return DetectResult(self.name(), 80)
17
+ return DetectResult(cls.name(), 80)
16
18
  if (
17
19
  _exists(path, "config.toml", "config.json", "config.yaml", "config.yml")
18
20
  and _exists(path, "content")
19
21
  and (_exists(path, "static") or _exists(path, "themes"))
20
22
  ):
21
- return DetectResult(self.name(), 40)
23
+ return DetectResult(cls.name(), 40)
22
24
  return None
23
25
 
24
- def serve_name(self, path: Path) -> str:
25
- return path.name
26
+ def serve_name(self) -> str:
27
+ return self.path.name
26
28
 
27
- def provider_kind(self, path: Path) -> str:
29
+ def provider_kind(self) -> str:
28
30
  return "staticsite"
29
31
 
30
- def dependencies(self, path: Path) -> list[DependencySpec]:
32
+ def dependencies(self) -> list[DependencySpec]:
31
33
  return [
32
34
  DependencySpec(
33
35
  "hugo",
@@ -35,10 +37,10 @@ class HugoProvider(StaticFileProvider):
35
37
  default_version="0.149.0",
36
38
  use_in_build=True,
37
39
  ),
38
- *super().dependencies(path),
40
+ *super().dependencies(),
39
41
  ]
40
42
 
41
- def build_steps(self, path: Path) -> list[str]:
43
+ def build_steps(self) -> list[str]:
42
44
  return [
43
45
  'copy(".", ".", ignore=[".git"])',
44
46
  'run("hugo build --destination={}".format(app["build"]), group="build")',
@@ -7,24 +7,28 @@ from .base import DetectResult, DependencySpec, Provider, _exists, MountSpec
7
7
 
8
8
 
9
9
  class LaravelProvider:
10
- def name(self) -> str:
10
+ def __init__(self, path: Path):
11
+ self.path = path
12
+ @classmethod
13
+ def name(cls) -> str:
11
14
  return "laravel"
12
15
 
13
- def detect(self, path: Path) -> Optional[DetectResult]:
16
+ @classmethod
17
+ def detect(cls, path: Path) -> Optional[DetectResult]:
14
18
  if _exists(path, "artisan") and _exists(path, "composer.json"):
15
- return DetectResult(self.name(), 95)
19
+ return DetectResult(cls.name(), 95)
16
20
  return None
17
21
 
18
- def initialize(self, path: Path) -> None:
22
+ def initialize(self) -> None:
19
23
  pass
20
24
 
21
- def serve_name(self, path: Path) -> str:
22
- return path.name
25
+ def serve_name(self) -> str:
26
+ return self.path.name
23
27
 
24
- def provider_kind(self, path: Path) -> str:
28
+ def provider_kind(self) -> str:
25
29
  return "php"
26
30
 
27
- def dependencies(self, path: Path) -> list[DependencySpec]:
31
+ def dependencies(self) -> list[DependencySpec]:
28
32
  return [
29
33
  DependencySpec(
30
34
  "php",
@@ -39,10 +43,10 @@ class LaravelProvider:
39
43
  DependencySpec("bash", use_in_serve=True),
40
44
  ]
41
45
 
42
- def declarations(self, path: Path) -> Optional[str]:
46
+ def declarations(self) -> Optional[str]:
43
47
  return "HOME = getenv(\"HOME\")"
44
48
 
45
- def build_steps(self, path: Path) -> list[str]:
49
+ def build_steps(self) -> list[str]:
46
50
  return [
47
51
  "env(HOME=HOME, COMPOSER_FUND=\"0\")",
48
52
  "workdir(app[\"build\"])",
@@ -53,7 +57,7 @@ class LaravelProvider:
53
57
  "run(\"pnpm run build\", outputs=[\".\"], group=\"build\")",
54
58
  ]
55
59
 
56
- def prepare_steps(self, path: Path) -> Optional[list[str]]:
60
+ def prepare_steps(self) -> Optional[list[str]]:
57
61
  return [
58
62
  'workdir(app["serve"])',
59
63
  'run("mkdir -p storage/framework/{sessions,views,cache,testing} storage/logs bootstrap/cache")',
@@ -63,17 +67,17 @@ class LaravelProvider:
63
67
  'run("php artisan view:cache")',
64
68
  ]
65
69
 
66
- def commands(self, path: Path) -> Dict[str, str]:
70
+ def commands(self) -> Dict[str, str]:
67
71
  return {
68
72
  "start": '"php -S localhost:8080 -t public"',
69
73
  "after_deploy": '"php artisan migrate"',
70
74
  }
71
75
 
72
- def assets(self, path: Path) -> Optional[Dict[str, str]]:
76
+ def assets(self) -> Optional[Dict[str, str]]:
73
77
  return None
74
78
 
75
- def mounts(self, path: Path) -> list[MountSpec]:
79
+ def mounts(self) -> list[MountSpec]:
76
80
  return [MountSpec("app")]
77
81
 
78
- def env(self, path: Path) -> Optional[Dict[str, str]]:
82
+ def env(self) -> Optional[Dict[str, str]]:
79
83
  return None
@@ -7,24 +7,28 @@ from .base import DetectResult, DependencySpec, Provider, _exists, MountSpec
7
7
 
8
8
 
9
9
  class MkdocsProvider:
10
- def name(self) -> str:
10
+ def __init__(self, path: Path):
11
+ self.path = path
12
+ @classmethod
13
+ def name(cls) -> str:
11
14
  return "mkdocs"
12
15
 
13
- def detect(self, path: Path) -> Optional[DetectResult]:
16
+ @classmethod
17
+ def detect(cls, path: Path) -> Optional[DetectResult]:
14
18
  if _exists(path, "mkdocs.yml", "mkdocs.yaml"):
15
- return DetectResult(self.name(), 85)
19
+ return DetectResult(cls.name(), 85)
16
20
  return None
17
21
 
18
- def initialize(self, path: Path) -> None:
22
+ def initialize(self) -> None:
19
23
  pass
20
24
 
21
- def serve_name(self, path: Path) -> str:
22
- return path.name
25
+ def serve_name(self) -> str:
26
+ return self.path.name
23
27
 
24
- def provider_kind(self, path: Path) -> str:
28
+ def provider_kind(self) -> str:
25
29
  return "mkdocs-site"
26
30
 
27
- def dependencies(self, path: Path) -> list[DependencySpec]:
31
+ def dependencies(self) -> list[DependencySpec]:
28
32
  return [
29
33
  DependencySpec(
30
34
  "python",
@@ -46,11 +50,11 @@ class MkdocsProvider:
46
50
  ),
47
51
  ]
48
52
 
49
- def declarations(self, path: Path) -> Optional[str]:
53
+ def declarations(self) -> Optional[str]:
50
54
  return None
51
55
 
52
- def build_steps(self, path: Path) -> list[str]:
53
- has_requirements = _exists(path, "requirements.txt")
56
+ def build_steps(self) -> list[str]:
57
+ has_requirements = _exists(self.path, "requirements.txt")
54
58
  if has_requirements:
55
59
  install_lines = [
56
60
  "run(\"uv init --no-managed-python\", inputs=[], outputs=[\".\"], group=\"install\")",
@@ -68,17 +72,17 @@ class MkdocsProvider:
68
72
  "run(\"uv run mkdocs build --site-dir={}\".format(app[\"build\"]), outputs=[\".\"], group=\"build\")",
69
73
  ]
70
74
 
71
- def prepare_steps(self, path: Path) -> Optional[list[str]]:
75
+ def prepare_steps(self) -> Optional[list[str]]:
72
76
  return None
73
77
 
74
- def commands(self, path: Path) -> Dict[str, str]:
78
+ def commands(self) -> Dict[str, str]:
75
79
  return {"start": '"static-web-server --root /app"'}
76
80
 
77
- def assets(self, path: Path) -> Optional[Dict[str, str]]:
81
+ def assets(self) -> Optional[Dict[str, str]]:
78
82
  return None
79
83
 
80
- def mounts(self, path: Path) -> list[MountSpec]:
84
+ def mounts(self) -> list[MountSpec]:
81
85
  return [MountSpec("app")]
82
86
 
83
- def env(self, path: Path) -> Optional[Dict[str, str]]:
87
+ def env(self) -> Optional[Dict[str, str]]:
84
88
  return None
@@ -7,28 +7,32 @@ from .base import DetectResult, DependencySpec, Provider, _exists, _has_dependen
7
7
 
8
8
 
9
9
  class NodeStaticProvider:
10
- def name(self) -> str:
10
+ def __init__(self, path: Path):
11
+ self.path = path
12
+ @classmethod
13
+ def name(cls) -> str:
11
14
  return "node-static"
12
15
 
13
- def detect(self, path: Path) -> Optional[DetectResult]:
16
+ @classmethod
17
+ def detect(cls, path: Path) -> Optional[DetectResult]:
14
18
  pkg = path / "package.json"
15
19
  if not pkg.exists():
16
20
  return None
17
21
  static_generators = ["astro", "vite", "next", "nuxt"]
18
22
  if any(_has_dependency(pkg, dep) for dep in static_generators):
19
- return DetectResult(self.name(), 40)
23
+ return DetectResult(cls.name(), 40)
20
24
  return None
21
25
 
22
- def initialize(self, path: Path) -> None:
26
+ def initialize(self) -> None:
23
27
  pass
24
28
 
25
- def serve_name(self, path: Path) -> str:
26
- return path.name
29
+ def serve_name(self) -> str:
30
+ return self.path.name
27
31
 
28
- def provider_kind(self, path: Path) -> str:
32
+ def provider_kind(self) -> str:
29
33
  return "staticsite"
30
34
 
31
- def dependencies(self, path: Path) -> list[DependencySpec]:
35
+ def dependencies(self) -> list[DependencySpec]:
32
36
  return [
33
37
  DependencySpec(
34
38
  "node",
@@ -40,11 +44,11 @@ class NodeStaticProvider:
40
44
  DependencySpec("static-web-server", use_in_serve=True),
41
45
  ]
42
46
 
43
- def declarations(self, path: Path) -> Optional[str]:
47
+ def declarations(self) -> Optional[str]:
44
48
  return None
45
49
 
46
- def build_steps(self, path: Path) -> list[str]:
47
- output_dir = "dist" if (path / "dist").exists() else "public"
50
+ def build_steps(self) -> list[str]:
51
+ output_dir = "dist" if (self.path / "dist").exists() else "public"
48
52
  return [
49
53
  "run(\"npm install\", inputs=[\"package.json\", \"package-lock.json\"], group=\"install\")",
50
54
  "copy(\".\", \".\", ignore=[\"node_modules\", \".git\"])",
@@ -52,18 +56,18 @@ class NodeStaticProvider:
52
56
  f"run(\"cp -R {output_dir}/* {{}}/\".format(app[\"build\"]))",
53
57
  ]
54
58
 
55
- def prepare_steps(self, path: Path) -> Optional[list[str]]:
59
+ def prepare_steps(self) -> Optional[list[str]]:
56
60
  return None
57
61
 
58
- def commands(self, path: Path) -> Dict[str, str]:
59
- output_dir = "dist" if (path / "dist").exists() else "public"
62
+ def commands(self) -> Dict[str, str]:
63
+ output_dir = "dist" if (self.path / "dist").exists() else "public"
60
64
  return {"start": f'"static-web-server --root /app/{output_dir}"'}
61
65
 
62
- def assets(self, path: Path) -> Optional[Dict[str, str]]:
66
+ def assets(self) -> Optional[Dict[str, str]]:
63
67
  return None
64
68
 
65
- def mounts(self, path: Path) -> list[MountSpec]:
69
+ def mounts(self) -> list[MountSpec]:
66
70
  return [MountSpec("app")]
67
71
 
68
- def env(self, path: Path) -> Optional[Dict[str, str]]:
72
+ def env(self) -> Optional[Dict[str, str]]:
69
73
  return None
shipit/providers/php.py CHANGED
@@ -7,29 +7,33 @@ from .base import DetectResult, DependencySpec, Provider, _exists, MountSpec
7
7
 
8
8
 
9
9
  class PhpProvider:
10
- def name(self) -> str:
10
+ def __init__(self, path: Path):
11
+ self.path = path
12
+ @classmethod
13
+ def name(cls) -> str:
11
14
  return "php"
12
15
 
13
- def detect(self, path: Path) -> Optional[DetectResult]:
16
+ @classmethod
17
+ def detect(cls, path: Path) -> Optional[DetectResult]:
14
18
  if _exists(path, "composer.json") and _exists(path, "public/index.php"):
15
- return DetectResult(self.name(), 60)
19
+ return DetectResult(cls.name(), 60)
16
20
  if _exists(path, "index.php") and not _exists(path, "composer.json"):
17
- return DetectResult(self.name(), 10)
21
+ return DetectResult(cls.name(), 10)
18
22
  return None
19
23
 
20
- def initialize(self, path: Path) -> None:
24
+ def initialize(self) -> None:
21
25
  pass
22
26
 
23
- def serve_name(self, path: Path) -> str:
24
- return path.name
27
+ def serve_name(self) -> str:
28
+ return self.path.name
25
29
 
26
- def provider_kind(self, path: Path) -> str:
30
+ def provider_kind(self) -> str:
27
31
  return "php"
28
32
 
29
- def has_composer(self, path: Path) -> bool:
30
- return _exists(path, "composer.json", "composer.lock")
33
+ def has_composer(self) -> bool:
34
+ return _exists(self.path, "composer.json", "composer.lock")
31
35
 
32
- def dependencies(self, path: Path) -> list[DependencySpec]:
36
+ def dependencies(self) -> list[DependencySpec]:
33
37
  deps = [
34
38
  DependencySpec(
35
39
  "php",
@@ -39,40 +43,40 @@ class PhpProvider:
39
43
  use_in_serve=True,
40
44
  ),
41
45
  ]
42
- if self.has_composer(path):
46
+ if self.has_composer():
43
47
  deps.append(DependencySpec("composer", use_in_build=True))
44
48
  deps.append(DependencySpec("bash", use_in_serve=True))
45
49
  return deps
46
50
 
47
- def declarations(self, path: Path) -> Optional[str]:
51
+ def declarations(self) -> Optional[str]:
48
52
  return "HOME = getenv(\"HOME\")"
49
53
 
50
- def build_steps(self, path: Path) -> list[str]:
54
+ def build_steps(self) -> list[str]:
51
55
  steps = [
52
56
  "workdir(app[\"build\"])",
53
57
  ]
54
58
 
55
- if self.has_composer(path):
59
+ if self.has_composer():
56
60
  steps.append("env(HOME=HOME, COMPOSER_FUND=\"0\")")
57
61
  steps.append("run(\"composer install --optimize-autoloader --no-scripts --no-interaction\", inputs=[\"composer.json\", \"composer.lock\"], outputs=[\".\"], group=\"install\")")
58
62
 
59
63
  steps.append("copy(\".\", \".\", ignore=[\".git\"])")
60
64
  return steps
61
65
 
62
- def prepare_steps(self, path: Path) -> Optional[list[str]]:
66
+ def prepare_steps(self) -> Optional[list[str]]:
63
67
  return None
64
68
 
65
- def commands(self, path: Path) -> Dict[str, str]:
66
- if _exists(path, "public/index.php"):
69
+ def commands(self) -> Dict[str, str]:
70
+ if _exists(self.path, "public/index.php"):
67
71
  return {"start": '"php -S localhost:8080 -t public"'}
68
- elif _exists(path, "index.php"):
72
+ elif _exists(self.path, "index.php"):
69
73
  return {"start": '"php -S localhost:8080" -t .'}
70
74
 
71
- def assets(self, path: Path) -> Optional[Dict[str, str]]:
75
+ def assets(self) -> Optional[Dict[str, str]]:
72
76
  return {"php.ini": "get_asset(\"php/php.ini\")"}
73
77
 
74
- def mounts(self, path: Path) -> list[MountSpec]:
78
+ def mounts(self) -> list[MountSpec]:
75
79
  return [MountSpec("app")]
76
80
 
77
- def env(self, path: Path) -> Optional[Dict[str, str]]:
81
+ def env(self) -> Optional[Dict[str, str]]:
78
82
  return None
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import re
3
4
  from pathlib import Path
4
- from typing import Dict, Optional
5
+ from typing import Dict, Optional, Set
6
+ from enum import Enum
5
7
 
6
8
  from .base import (
7
9
  DetectResult,
@@ -12,37 +14,167 @@ from .base import (
12
14
  )
13
15
 
14
16
 
17
+ class PythonFramework(Enum):
18
+ Django = "django"
19
+ FastAPI = "fastapi"
20
+ Flask = "flask"
21
+ FastHTML = "python-fasthtml"
22
+
23
+
24
+ class PythonServer(Enum):
25
+ Hypercorn = "hypercorn"
26
+ Uvicorn = "uvicorn"
27
+ # Gunicorn = "gunicorn"
28
+ Daphne = "daphne"
29
+
30
+ class DatabaseType(Enum):
31
+ MySQL = "mysql"
32
+ PostgreSQL = "postgresql"
33
+
34
+
15
35
  class PythonProvider:
16
- def name(self) -> str:
36
+ framework: Optional[PythonFramework] = None
37
+ server: Optional[PythonServer] = None
38
+ database: Optional[DatabaseType] = None
39
+ extra_dependencies: Set[str]
40
+ asgi_application: Optional[str] = None
41
+ wsgi_application: Optional[str] = None
42
+
43
+ def __init__(self, path: Path):
44
+ self.path = path
45
+ if _exists(self.path, ".python-version"):
46
+ python_version = (self.path / ".python-version").read_text().strip()
47
+ else:
48
+ python_version = "3.13"
49
+ self.default_python_version = python_version
50
+ self.extra_dependencies = set()
51
+
52
+ pg_deps = {
53
+ "asyncpg",
54
+ "aiopg",
55
+ "psycopg",
56
+ "psycopg2",
57
+ "psycopg-binary",
58
+ "psycopg2-binary"}
59
+ mysql_deps = {"mysqlclient", "pymysql", "mysql-connector-python", "aiomysql"}
60
+ found_deps = self.check_deps(
61
+ "django",
62
+ "fastapi",
63
+ "flask",
64
+ "python-fasthtml",
65
+ "daphne",
66
+ "hypercorn",
67
+ "uvicorn",
68
+ # "gunicorn",
69
+ *mysql_deps,
70
+ *pg_deps,
71
+ )
72
+
73
+ # ASGI/WSGI Server
74
+ if "uvicorn" in found_deps:
75
+ server = PythonServer.Uvicorn
76
+ elif "hypercorn" in found_deps:
77
+ server = PythonServer.Hypercorn
78
+ # elif "gunicorn" in found_deps:
79
+ # server = PythonServer.Gunicorn
80
+ elif "daphne" in found_deps:
81
+ server = PythonServer.Daphne
82
+ else:
83
+ server = None
84
+ self.server = server
85
+
86
+ # Set framework
87
+ if _exists(self.path, "manage.py") and ("django" in found_deps):
88
+ framework = PythonFramework.Django
89
+ # Find the settings.py file using glob
90
+ settings_file = next(self.path.glob( "**/settings.py"))
91
+ if settings_file:
92
+ asgi_match = re.search(r"ASGI_APPLICATION\s*=\s*['\"](.*)['\"]", settings_file.read_text())
93
+ if asgi_match:
94
+ self.asgi_application = asgi_match.group(1)
95
+ else:
96
+ wsgi_match = re.search(r"WSGI_APPLICATION\s*=\s*['\"](.*)['\"]", settings_file.read_text())
97
+ if wsgi_match:
98
+ self.wsgi_application = wsgi_match.group(1)
99
+
100
+ if not self.server:
101
+ if self.asgi_application:
102
+ self.extra_dependencies = {"uvicorn"}
103
+ self.server = PythonServer.Uvicorn
104
+ elif self.wsgi_application:
105
+ # gunicorn can't run with Wasmer atm
106
+ self.extra_dependencies = {"uvicorn"}
107
+ self.server = PythonServer.Uvicorn
108
+ elif "fastapi" in found_deps:
109
+ framework = PythonFramework.FastAPI
110
+ if not self.server:
111
+ self.extra_dependencies = {"uvicorn"}
112
+ self.server = PythonServer.Uvicorn
113
+ elif "flask" in found_deps:
114
+ framework = PythonFramework.Flask
115
+ elif "fastapi" in found_deps:
116
+ framework = PythonFramework.FastAPI
117
+ elif "flask" in found_deps:
118
+ framework = PythonFramework.Flask
119
+ elif "python-fasthtml" in found_deps:
120
+ framework = PythonFramework.FastHTML
121
+ else:
122
+ framework = None
123
+ self.framework = framework
124
+
125
+ # Database
126
+ if mysql_deps & found_deps:
127
+ database = DatabaseType.MySQL
128
+ elif pg_deps & found_deps:
129
+ database = DatabaseType.PostgreSQL
130
+ else:
131
+ database = None
132
+ self.database = database
133
+
134
+ def check_deps(self, *deps: str) -> Set[str]:
135
+ deps = set([dep.lower() for dep in deps])
136
+ initial_deps = set(deps)
137
+ for file in ["requirements.txt", "pyproject.toml"]:
138
+ if _exists(self.path, file):
139
+ for line in (self.path / file).read_text().splitlines():
140
+ for dep in set(deps):
141
+ if dep in line.lower():
142
+ deps.remove(dep)
143
+ if not deps:
144
+ break
145
+ if not deps:
146
+ break
147
+ if not deps:
148
+ break
149
+ return initial_deps-deps
150
+
151
+ @classmethod
152
+ def name(cls) -> str:
17
153
  return "python"
18
154
 
19
- def detect(self, path: Path) -> Optional[DetectResult]:
155
+ @classmethod
156
+ def detect(cls, path: Path) -> Optional[DetectResult]:
20
157
  if _exists(path, "pyproject.toml", "requirements.txt"):
21
158
  if _exists(path, "manage.py"):
22
- return DetectResult(self.name(), 70)
23
- return DetectResult(self.name(), 50)
159
+ return DetectResult(cls.name(), 70)
160
+ return DetectResult(cls.name(), 50)
24
161
  return None
25
162
 
26
- def initialize(self, path: Path) -> None:
163
+ def initialize(self) -> None:
27
164
  pass
28
165
 
29
- def serve_name(self, path: Path) -> str:
30
- return path.name
166
+ def serve_name(self) -> str:
167
+ return self.path.name
31
168
 
32
- def provider_kind(self, path: Path) -> str:
169
+ def provider_kind(self) -> str:
33
170
  return "python"
34
171
 
35
- def dependencies(self, path: Path) -> list[DependencySpec]:
36
- if _exists(path, ".python-version"):
37
- python_version = (path / ".python-version").read_text().strip()
38
- else:
39
- python_version = "3.13"
40
-
172
+ def dependencies(self) -> list[DependencySpec]:
41
173
  return [
42
174
  DependencySpec(
43
175
  "python",
44
176
  env_var="SHIPIT_PYTHON_VERSION",
45
- default_version=python_version,
177
+ default_version=self.default_python_version,
46
178
  use_in_build=True,
47
179
  use_in_serve=True,
48
180
  ),
@@ -54,32 +186,37 @@ class PythonProvider:
54
186
  ),
55
187
  ]
56
188
 
57
- def declarations(self, path: Path) -> Optional[str]:
189
+ def declarations(self) -> Optional[str]:
58
190
  return (
59
191
  "cross_platform = getenv(\"SHIPIT_PYTHON_CROSS_PLATFORM\")\n"
60
192
  "python_extra_index_url = getenv(\"SHIPIT_PYTHON_EXTRA_INDEX_URL\")\n"
61
193
  "precompile_python = getenv(\"SHIPIT_PYTHON_PRECOMPILE\") in [\"true\", \"True\", \"TRUE\", \"1\", \"on\", \"yes\", \"y\", \"Y\", \"YES\", \"On\", \"ON\"]\n"
62
- "python_cross_packages_path = venv[\"build\"] + f\"/lib/python{python_version}/site-packages\""
194
+ "python_cross_packages_path = venv[\"build\"] + f\"/lib/python{python_version}/site-packages\"\n"
195
+ "python_serve_path = \"{}/lib/python{}/site-packages\".format(venv[\"serve\"], python_version)\n"
63
196
  )
64
197
 
65
- def build_steps(self, path: Path) -> list[str]:
198
+ def build_steps(self) -> list[str]:
66
199
  steps = [
67
200
  "workdir(app[\"build\"])"
68
201
  ]
69
202
 
70
- if _exists(path, "pyproject.toml"):
203
+ if _exists(self.path, "pyproject.toml"):
71
204
  input_files = ["pyproject.toml"]
72
- if _exists(path, "uv.lock"):
205
+ extra_args = ""
206
+ if _exists(self.path, "uv.lock"):
73
207
  input_files.append("uv.lock")
208
+ extra_args = " --locked"
74
209
  inputs = ", ".join([f"\"{input}\"" for input in input_files])
75
- steps += [
210
+ extra_deps = ", ".join([f"{dep}" for dep in self.extra_dependencies])
211
+ steps += list(filter(None, [
76
212
  "env(UV_PROJECT_ENVIRONMENT=local_venv[\"build\"] if cross_platform else venv[\"build\"])",
77
- "run(f\"uv sync --compile --python python{python_version} --locked --no-managed-python\", inputs=[" + inputs + "], group=\"install\")",
213
+ "run(f\"uv sync --compile --python python{python_version} --no-managed-python" + extra_args + "\", inputs=[" + inputs + "], group=\"install\")",
214
+ f"run(\"uv add {extra_deps}\", inputs=[\"pyproject.toml\"], group=\"install\")" if extra_deps else None,
78
215
  "run(f\"uv pip compile pyproject.toml --python-version={python_version} --universal --extra-index-url {python_extra_index_url} --index-url=https://pypi.org/simple --emit-index-url --only-binary :all: -o cross-requirements.txt\", inputs=[\"pyproject.toml\"], outputs=[\"cross-requirements.txt\"]) if cross_platform else None",
79
- "run(f\"uvx pip install -r cross-requirements.txt --target {python_cross_packages_path} --platform {cross_platform} --only-binary=:all: --python-version={python_version} --compile\") if cross_platform else None",
216
+ f"run(f\"uvx pip install -r cross-requirements.txt {extra_deps} --target {{python_cross_packages_path}} --platform {{cross_platform}} --only-binary=:all: --python-version={{python_version}} --compile\") if cross_platform else None",
80
217
  "run(\"rm cross-requirements.txt\") if cross_platform else None",
81
- ]
82
- if _exists(path, "requirements.txt"):
218
+ ]))
219
+ if _exists(self.path, "requirements.txt"):
83
220
  steps += [
84
221
  "env(UV_PROJECT_ENVIRONMENT=local_venv[\"build\"] if cross_platform else venv[\"build\"])",
85
222
  "run(f\"uv init --no-managed-python --python python{python_version}\", inputs=[], outputs=[\"uv.lock\"], group=\"install\")",
@@ -95,41 +232,78 @@ class PythonProvider:
95
232
  ]
96
233
  return steps
97
234
 
98
- def prepare_steps(self, path: Path) -> Optional[list[str]]:
235
+ def prepare_steps(self) -> Optional[list[str]]:
99
236
  return [
100
- 'workdir(app["serve"])',
101
237
  'run("echo \\\"Precompiling Python code...\\\"") if precompile_python else None',
102
- 'run("python -m compileall -o 2 $PYTHONPATH") if precompile_python else None',
238
+ 'run(f"python -m compileall -o 2 {python_serve_path}") if precompile_python else None',
103
239
  'run("echo \\\"Precompiling package code...\\\"") if precompile_python else None',
104
- 'run("python -m compileall -o 2 .") if precompile_python else None',
240
+ 'run("python -m compileall -o 2 {}".format(app["serve"])) if precompile_python else None',
105
241
  ]
106
242
 
107
- def commands(self, path: Path) -> Dict[str, str]:
108
- if _exists(path, "manage.py"):
109
- start_cmd = '"python manage.py runserver 0.0.0.0:8000"'
243
+ def commands(self) -> Dict[str, str]:
244
+ if self.framework == PythonFramework.Django:
245
+ start_cmd = None
246
+ if self.server == PythonServer.Daphne and self.asgi_application:
247
+ asgi_application = format_app_import(self.asgi_application)
248
+ start_cmd = f'"python -m daphne {asgi_application} --bind 0.0.0.0 --port 8000"'
249
+ elif self.server == PythonServer.Uvicorn:
250
+ if self.asgi_application:
251
+ asgi_application = format_app_import(self.asgi_application)
252
+ start_cmd = f'"python -m uvicorn {asgi_application} --host 0.0.0.0 --port 8000"'
253
+ elif self.wsgi_application:
254
+ wsgi_application = format_app_import(self.wsgi_application)
255
+ start_cmd = f'"python -m uvicorn {wsgi_application} --interface=wsgi --host 0.0.0.0 --port 8000"'
256
+ # elif self.server == PythonServer.Gunicorn:
257
+ # start_cmd = f'"python -m gunicorn {self.wsgi_application} --bind 0.0.0.0 --port 8000"'
258
+ if not start_cmd:
259
+ # We run the default runserver command if no server is specified
260
+ start_cmd = '"python manage.py runserver 0.0.0.0:8000"'
110
261
  migrate_cmd = '"python manage.py migrate"'
111
262
  return {"start": start_cmd, "after_deploy": migrate_cmd}
112
- elif _exists(path, "main.py"):
263
+ elif self.framework == PythonFramework.FastAPI:
264
+ if _exists(self.path, "main.py"):
265
+ path = "main:app"
266
+ elif _exists(self.path, "src/main.py"):
267
+ path = "src.main:app"
268
+
269
+ if self.server == PythonServer.Uvicorn:
270
+ start_cmd = f'"python -m uvicorn {path} --host 0.0.0.0 --port 8000"'
271
+ elif self.server == PythonServer.Hypercorn:
272
+ start_cmd = f'"python -m hypercorn {path} --bind 0.0.0.0:8000"'
273
+ else:
274
+ start_cmd = '"python -c \'print(\\\"No start command detected, please provide a start command manually\\\")\'"'
275
+ return {"start": start_cmd}
276
+ elif self.framework == PythonFramework.FastHTML:
277
+ if _exists(self.path, "main.py"):
278
+ path = "main:app"
279
+ elif _exists(self.path, "src/main.py"):
280
+ path = "src.main:app"
281
+ start_cmd = f'"python -m uvicorn {path} --host 0.0.0.0 --port 8000"'
282
+ elif _exists(self.path, "main.py"):
113
283
  start_cmd = '"python main.py"'
114
- elif _exists(path, "src/main.py"):
284
+ elif _exists(self.path, "src/main.py"):
115
285
  start_cmd = '"python src/main.py"'
116
286
  else:
117
- start_cmd = '"python -c \'print(\\\"Hello, World!\\\")\'"'
287
+ start_cmd = '"python -c \'print(\\\"No start command detected, please provide a start command manually\\\")\'"'
118
288
  return {"start": start_cmd}
119
289
 
120
- def assets(self, path: Path) -> Optional[Dict[str, str]]:
290
+ def assets(self) -> Optional[Dict[str, str]]:
121
291
  return None
122
292
 
123
- def mounts(self, path: Path) -> list[MountSpec]:
293
+ def mounts(self) -> list[MountSpec]:
124
294
  return [
125
295
  MountSpec("app"),
126
296
  MountSpec("venv"),
127
297
  MountSpec("local_venv", attach_to_serve=False),
128
298
  ]
129
299
 
130
- def env(self, path: Path) -> Optional[Dict[str, str]]:
300
+ def env(self) -> Optional[Dict[str, str]]:
131
301
  # For Django projects, generate an empty env dict to surface the field
132
302
  # in the Shipit file. Other Python projects omit it by default.
133
303
  return {
134
- "PYTHONPATH": "\"{}/lib/python{}/site-packages\".format(venv[\"serve\"], python_version)"
304
+ "PYTHONPATH": "python_serve_path"
135
305
  }
306
+
307
+ def format_app_import(asgi_application: str) -> str:
308
+ # Transform "mysite.asgi.application" to "mysite.asgi:application" using regex
309
+ return re.sub(r"\.([^.]+)$", r":\1", asgi_application)
@@ -11,16 +11,15 @@ from .python import PythonProvider
11
11
  from .staticfile import StaticFileProvider
12
12
 
13
13
 
14
- def providers() -> list[Provider]:
14
+ def providers() -> list[type[Provider]]:
15
15
  # Order matters: more specific providers first
16
16
  return [
17
- LaravelProvider(),
18
- GatsbyProvider(),
19
- HugoProvider(),
20
- MkdocsProvider(),
21
- PythonProvider(),
22
- PhpProvider(),
23
- NodeStaticProvider(),
24
- StaticFileProvider(),
17
+ LaravelProvider,
18
+ GatsbyProvider,
19
+ HugoProvider,
20
+ MkdocsProvider,
21
+ PythonProvider,
22
+ PhpProvider,
23
+ NodeStaticProvider,
24
+ StaticFileProvider,
25
25
  ]
26
-
@@ -7,28 +7,33 @@ from .base import DetectResult, DependencySpec, Provider, _exists, MountSpec
7
7
 
8
8
 
9
9
  class StaticFileProvider:
10
- def name(self) -> str:
10
+ def __init__(self, path: Path):
11
+ self.path = path
12
+
13
+ @classmethod
14
+ def name(cls) -> str:
11
15
  return "staticfile"
12
16
 
13
- def detect(self, path: Path) -> Optional[DetectResult]:
17
+ @classmethod
18
+ def detect(cls, path: Path) -> Optional[DetectResult]:
14
19
  if _exists(path, "Staticfile"):
15
- return DetectResult(self.name(), 50)
20
+ return DetectResult(cls.name(), 50)
16
21
  if _exists(path, "index.html") and not _exists(
17
22
  path, "package.json", "pyproject.toml", "composer.json"
18
23
  ):
19
- return DetectResult(self.name(), 10)
24
+ return DetectResult(cls.name(), 10)
20
25
  return None
21
26
 
22
- def initialize(self, path: Path) -> None:
27
+ def initialize(self) -> None:
23
28
  pass
24
29
 
25
- def serve_name(self, path: Path) -> str:
26
- return path.name
30
+ def serve_name(self) -> str:
31
+ return self.path.name
27
32
 
28
- def provider_kind(self, path: Path) -> str:
33
+ def provider_kind(self) -> str:
29
34
  return "staticfile"
30
35
 
31
- def dependencies(self, path: Path) -> list[DependencySpec]:
36
+ def dependencies(self) -> list[DependencySpec]:
32
37
  return [
33
38
  DependencySpec(
34
39
  "static-web-server",
@@ -38,28 +43,28 @@ class StaticFileProvider:
38
43
  )
39
44
  ]
40
45
 
41
- def build_steps(self, path: Path) -> list[str]:
46
+ def build_steps(self) -> list[str]:
42
47
  return [
43
48
  'workdir(app["build"])',
44
49
  'copy(".", ".", ignore=[".git"])'
45
50
  ]
46
51
 
47
- def prepare_steps(self, path: Path) -> Optional[list[str]]:
52
+ def prepare_steps(self) -> Optional[list[str]]:
48
53
  return None
49
54
 
50
- def declarations(self, path: Path) -> Optional[str]:
55
+ def declarations(self) -> Optional[str]:
51
56
  return None
52
57
 
53
- def commands(self, path: Path) -> Dict[str, str]:
58
+ def commands(self) -> Dict[str, str]:
54
59
  return {
55
60
  "start": '"static-web-server --root={} --log-level=info".format(app["serve"])'
56
61
  }
57
62
 
58
- def assets(self, path: Path) -> Optional[Dict[str, str]]:
63
+ def assets(self) -> Optional[Dict[str, str]]:
59
64
  return None
60
65
 
61
- def mounts(self, path: Path) -> list[MountSpec]:
66
+ def mounts(self) -> list[MountSpec]:
62
67
  return [MountSpec("app")]
63
68
 
64
- def env(self, path: Path) -> Optional[Dict[str, str]]:
69
+ def env(self) -> Optional[Dict[str, str]]:
65
70
  return None
shipit/version.py CHANGED
@@ -1,5 +1,5 @@
1
1
  __all__ = ["version", "version_info"]
2
2
 
3
3
 
4
- version = "0.3.3"
5
- version_info = (0, 3, 3, "final", 0)
4
+ version = "0.4.0"
5
+ version_info = (0, 4, 0, "final", 0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shipit-cli
3
- Version: 0.3.3
3
+ Version: 0.4.0
4
4
  Summary: Add your description here
5
5
  Project-URL: homepage, https://wasmer.io
6
6
  Project-URL: repository, https://github.com/wasmerio/shipit
@@ -0,0 +1,19 @@
1
+ shipit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ shipit/cli.py,sha256=5b5e1H-SscXBraZXxJ5cVs_IJSNqjWUt2IjcZtOdALE,49615
3
+ shipit/generator.py,sha256=4pezEo4OzpDFSFqiFpGCpdwo72VkItS490R_f2rIz2k,5755
4
+ shipit/version.py,sha256=zmMUF3ZPl6yX0INjIEDY59a3aBdXE4OLXagX09Ji9cE,95
5
+ shipit/assets/php/php.ini,sha256=f4irndAjB4GuuouEImRkNV22Q-yw1KqR-43jAMDw730,2531
6
+ shipit/providers/base.py,sha256=a_5VA1tV4_QbH83yjPCTHsNR23EJT2CiKUpWA_pu_lo,2373
7
+ shipit/providers/gatsby.py,sha256=uwNjIJloS9JwKXqkbhihgdTTpJL4iL4bLCZ5kuzqqNs,2138
8
+ shipit/providers/hugo.py,sha256=tbn_1t_1AwXHk3-J6mGA2C0D7-3Wpzr-fKpDNhqHy2Q,1439
9
+ shipit/providers/laravel.py,sha256=rDpfx7RyF4sK0xxDAWefX0IiguU2xdgEXP2jJp1Jdzo,2777
10
+ shipit/providers/mkdocs.py,sha256=QJFNt7QWMvYbWeo7WjOgFVKGVcdUnUmUNfFbe4c8ThM,2812
11
+ shipit/providers/node_static.py,sha256=K55BXkNz4QXSXR2wdg5diP6HLpmksC70ph16zox7v6Y,2309
12
+ shipit/providers/php.py,sha256=Hmtv47K5qtYbQ3v9SSk-_KTNlhXedStg2MxhTTOK9ac,2594
13
+ shipit/providers/python.py,sha256=KnDH7nB1Ipz_oAqXPBz7FbEo3s7kwvQOhOZSHKCpltw,13430
14
+ shipit/providers/registry.py,sha256=UisII1dr24ZxmDD8GnpTsyNwPN9W8MnAHQ1Px1iJ-OQ,661
15
+ shipit/providers/staticfile.py,sha256=Y4oqw6dNDU2crzcWQ5SEgnXHoDy0CXRntABwlgdf1mo,1827
16
+ shipit_cli-0.4.0.dist-info/METADATA,sha256=_B-0rn-0ASJDMFL4faoV5mTzOSpi5ge5bdJuFjio3tc,462
17
+ shipit_cli-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ shipit_cli-0.4.0.dist-info/entry_points.txt,sha256=7AE1NjSrHaSDfbfsRRO50KKnHFTbB0Imsccd1WynzAQ,72
19
+ shipit_cli-0.4.0.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- shipit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- shipit/cli.py,sha256=extuf-zJzjddNXbQR5m-e_SfpdtJmL7WaFHSsBtZZqM,48402
3
- shipit/generator.py,sha256=iBkVcs44dd_xYKitM_zNVLnpiZ3KrV__xVswPMCZ97Y,5570
4
- shipit/version.py,sha256=m-Vgs5jnC0nfsmDfrP6scrIRgwcnv_Lkot6BIgxrNmw,95
5
- shipit/assets/php/php.ini,sha256=f4irndAjB4GuuouEImRkNV22Q-yw1KqR-43jAMDw730,2531
6
- shipit/providers/base.py,sha256=bqh1k7TSPJo7hOnxgdI6PIJmrqzQkZhgUoV0bbYIWrw,2403
7
- shipit/providers/gatsby.py,sha256=VUGhE7xtQJHsYzEzdkXi3n5mbpgg868wbUVOU4MWN5s,2173
8
- shipit/providers/hugo.py,sha256=CpkDw9LQWcUfWb1K64eEB1fTtj7OESoA1o3MqFhux7E,1456
9
- shipit/providers/laravel.py,sha256=4wSa0ByLrq87WhrAf04mOGVKz_xn8xtCaSYHpx0l7-0,2812
10
- shipit/providers/mkdocs.py,sha256=YIbSAaL2jDQtr8YteZmKjIbRMDWdoQgy6G2D6dfH1ws,2842
11
- shipit/providers/node_static.py,sha256=Zpq4fRCMBzGkObdsfPVAoYUAnZSqE9C1D0aaJyI30Fc,2334
12
- shipit/providers/php.py,sha256=HxxgfXmA0U6PeTLyFMbyXWm05G_IQqdFz4Liq1d_VBM,2635
13
- shipit/providers/python.py,sha256=p4hIE0vXmNiqR2gbneUhYjtQVJ2HqKuirwudSuEQm9A,6311
14
- shipit/providers/registry.py,sha256=V6CAOK5gEX0RhWhr-lcAkvlwRuMom7YY2ZeAyRy1Eck,672
15
- shipit/providers/staticfile.py,sha256=DDA4dMHQesYu0AfZEWBHqfk4Ib8GFrkpmvcsnwJkqH4,1862
16
- shipit_cli-0.3.3.dist-info/METADATA,sha256=zILxyeA1OSaxuXMFfFoNieT_khB1YYi8xfd8MwmDB9c,462
17
- shipit_cli-0.3.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- shipit_cli-0.3.3.dist-info/entry_points.txt,sha256=7AE1NjSrHaSDfbfsRRO50KKnHFTbB0Imsccd1WynzAQ,72
19
- shipit_cli-0.3.3.dist-info/RECORD,,