shipit-cli 0.1.3__tar.gz → 0.2.1__tar.gz

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.
Files changed (23) hide show
  1. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/PKG-INFO +1 -1
  2. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/pyproject.toml +1 -1
  3. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/cli.py +192 -80
  4. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/generator.py +22 -6
  5. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/base.py +11 -2
  6. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/gatsby.py +7 -3
  7. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/laravel.py +7 -2
  8. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/mkdocs.py +7 -4
  9. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/node_static.py +6 -2
  10. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/php.py +8 -3
  11. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/python.py +20 -11
  12. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/staticfile.py +9 -3
  13. shipit_cli-0.2.1/src/shipit/version.py +5 -0
  14. shipit_cli-0.2.1/tests/test_version.py +15 -0
  15. shipit_cli-0.1.3/src/shipit/version.py +0 -5
  16. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/.gitignore +0 -0
  17. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/README.md +0 -0
  18. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/__init__.py +0 -0
  19. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/assets/php/php.ini +0 -0
  20. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/hugo.py +0 -0
  21. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/src/shipit/providers/registry.py +0 -0
  22. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/tests/test_examples_build.py +0 -0
  23. {shipit_cli-0.1.3 → shipit_cli-0.2.1}/tests/test_generate_shipit_examples.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shipit-cli
3
- Version: 0.1.3
3
+ Version: 0.2.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shipit-cli"
3
- version = "0.1.3"
3
+ version = "0.2.1"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -40,6 +40,12 @@ app = typer.Typer(invoke_without_command=True)
40
40
  DIR_PATH = Path(__file__).resolve().parent
41
41
  ASSETS_PATH = DIR_PATH / "assets"
42
42
 
43
+ @dataclass
44
+ class Mount:
45
+ name: str
46
+ build_path: Path
47
+ serve_path: Path
48
+
43
49
 
44
50
  @dataclass
45
51
  class Serve:
@@ -51,7 +57,8 @@ class Serve:
51
57
  assets: Optional[Dict[str, str]] = None
52
58
  prepare: Optional[List["PrepareStep"]] = None
53
59
  workers: Optional[List[str]] = None
54
- mounts: Optional[Dict[str, str]] = None
60
+ mounts: Optional[List[Mount]] = None
61
+ env: Optional[Dict[str, str]] = None
55
62
 
56
63
 
57
64
  @dataclass
@@ -71,6 +78,11 @@ class RunStep:
71
78
  group: Optional[str] = None
72
79
 
73
80
 
81
+ @dataclass
82
+ class WorkdirStep:
83
+ path: Path
84
+
85
+
74
86
  @dataclass
75
87
  class CopyStep:
76
88
  source: str
@@ -96,7 +108,7 @@ class PathStep:
96
108
  path: str
97
109
 
98
110
 
99
- Step = Union[RunStep, CopyStep, EnvStep, PathStep, UseStep]
111
+ Step = Union[RunStep, CopyStep, EnvStep, PathStep, UseStep, WorkdirStep]
100
112
  PrepareStep = Union[RunStep]
101
113
 
102
114
 
@@ -122,7 +134,7 @@ class MapperItem(TypedDict):
122
134
 
123
135
 
124
136
  class Builder(Protocol):
125
- def build(self, env: Dict[str, str], steps: List[Step]) -> None: ...
137
+ def build(self, env: Dict[str, str], mounts: List[Mount], steps: List[Step]) -> None: ...
126
138
  def build_assets(self, assets: Dict[str, str]) -> None: ...
127
139
  def build_prepare(self, serve: Serve) -> None: ...
128
140
  def build_serve(self, serve: Serve) -> None: ...
@@ -135,6 +147,8 @@ class Builder(Protocol):
135
147
  ) -> Any: ...
136
148
  def serve_mount(self, name: str) -> str: ...
137
149
  def get_asset(self, name: str) -> str: ...
150
+ def get_build_mount_path(self, name: str) -> Path: ...
151
+ def get_serve_mount_path(self, name: str) -> Path: ...
138
152
 
139
153
 
140
154
  class DockerBuilder:
@@ -142,6 +156,7 @@ class DockerBuilder:
142
156
  self.src_dir = src_dir
143
157
  self.docker_file_contents = ""
144
158
  self.docker_path = self.src_dir / ".shipit" / "docker"
159
+ self.docker_out_path = self.docker_path / "out"
145
160
  self.depot_metadata = self.docker_path / "depot-build.json"
146
161
  self.docker_file_path = self.docker_path / "Dockerfile"
147
162
  self.docker_name_path = self.docker_path / "name"
@@ -151,7 +166,20 @@ class DockerBuilder:
151
166
  self.env = {
152
167
  "HOME": "/root",
153
168
  }
154
-
169
+
170
+ def get_mount_path(self, name: str) -> Path:
171
+ if name == "app":
172
+ return Path("app")
173
+ else:
174
+ return Path("opt") / name
175
+
176
+ def get_build_mount_path(self, name: str) -> Path:
177
+ path = Path("/") / self.get_mount_path(name)
178
+ return path
179
+
180
+ def get_serve_mount_path(self, name: str) -> Path:
181
+ return self.docker_out_path / self.get_mount_path(name)
182
+
155
183
  @property
156
184
  def is_depot(self) -> bool:
157
185
  return self.docker_client == "depot"
@@ -169,10 +197,10 @@ class DockerBuilder:
169
197
  self.docker_name_path.write_text(image_name)
170
198
  self.print_dockerfile()
171
199
  extra_args = []
172
- if self.is_depot:
173
- # We load the docker image back into the local docker daemon
174
- # extra_args += ["--load"]
175
- extra_args += ["--save", f"--metadata-file={self.depot_metadata.absolute()}"]
200
+ # if self.is_depot:
201
+ # # We load the docker image back into the local docker daemon
202
+ # # extra_args += ["--load"]
203
+ # extra_args += ["--save", f"--metadata-file={self.depot_metadata.absolute()}"]
176
204
  sh.Command(self.docker_client)(
177
205
  "build",
178
206
  "-f",
@@ -181,6 +209,8 @@ class DockerBuilder:
181
209
  image_name,
182
210
  "--platform",
183
211
  "linux/amd64",
212
+ "--output",
213
+ self.docker_out_path.absolute(),
184
214
  ".",
185
215
  *extra_args,
186
216
  _cwd=self.src_dir.absolute(),
@@ -188,25 +218,25 @@ class DockerBuilder:
188
218
  _out=write_stdout,
189
219
  _err=write_stderr,
190
220
  )
191
- if self.is_depot:
192
- json_text = self.depot_metadata.read_text()
193
- json_data = json.loads(json_text)
194
- build_data = json_data["depot.build"]
195
- image_id = build_data["buildID"]
196
- project = build_data["projectID"]
197
- sh.Command("depot")(
198
- "pull",
199
- "--platform",
200
- "linux/amd64",
201
- "--project",
202
- project,
203
- image_id,
204
- _cwd=self.src_dir.absolute(),
205
- _env=os.environ, # Pass the current environment variables to the Docker client
206
- _out=write_stdout,
207
- _err=write_stderr,
208
- )
209
- # console.print(f"[bold]Image ID:[/bold] {image_id}")
221
+ # if self.is_depot:
222
+ # json_text = self.depot_metadata.read_text()
223
+ # json_data = json.loads(json_text)
224
+ # build_data = json_data["depot.build"]
225
+ # image_id = build_data["buildID"]
226
+ # project = build_data["projectID"]
227
+ # sh.Command("depot")(
228
+ # "pull",
229
+ # "--platform",
230
+ # "linux/amd64",
231
+ # "--project",
232
+ # project,
233
+ # image_id,
234
+ # _cwd=self.src_dir.absolute(),
235
+ # _env=os.environ, # Pass the current environment variables to the Docker client
236
+ # _out=write_stdout,
237
+ # _err=write_stderr,
238
+ # )
239
+ # # console.print(f"[bold]Image ID:[/bold] {image_id}")
210
240
 
211
241
  def finalize_build(self, serve: Serve) -> None:
212
242
  console.print(f"\n[bold]Building Docker file[/bold]")
@@ -280,11 +310,11 @@ RUN chmod {oct(mode)[2:]} {path.absolute()}
280
310
  else:
281
311
  self.docker_file_contents += f"RUN pkgm install {dependency.name}\n"
282
312
 
283
- def build(self, env: Dict[str, str], steps: List[Step]) -> None:
313
+ def build(self, env: Dict[str, str], mounts: List[Mount], steps: List[Step]) -> None:
284
314
  base_path = self.docker_path
285
315
  shutil.rmtree(base_path, ignore_errors=True)
286
316
  base_path.mkdir(parents=True, exist_ok=True)
287
- self.docker_file_contents = "FROM debian:bookworm-slim\n"
317
+ self.docker_file_contents = "FROM debian:bookworm-slim AS build\n"
288
318
  self.docker_file_contents += """
289
319
  RUN apt-get update \\
290
320
  && apt-get -y --no-install-recommends install sudo curl ca-certificates locate git zip unzip \\
@@ -295,13 +325,17 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
295
325
  RUN curl https://pkgx.sh | sh
296
326
  """
297
327
  # docker_file_contents += "RUN curl https://mise.run | sh\n"
298
- self.docker_file_contents += """
299
- RUN curl https://get.wasmer.io -sSfL | sh -s "v6.1.0-rc.3"
300
- ENV PATH="/root/.wasmer/bin:${PATH}"
301
- """
302
- self.docker_file_contents += "WORKDIR /app\n"
328
+ # self.docker_file_contents += """
329
+ # RUN curl https://get.wasmer.io -sSfL | sh -s "v6.1.0-rc.3"
330
+ # ENV PATH="/root/.wasmer/bin:${PATH}"
331
+ # """
332
+ for mount in mounts:
333
+ self.docker_file_contents += f"RUN mkdir -p {mount.build_path.absolute()}\n"
334
+
303
335
  for step in steps:
304
- if isinstance(step, RunStep):
336
+ if isinstance(step, WorkdirStep):
337
+ self.docker_file_contents += f"WORKDIR {step.path.absolute()}\n"
338
+ elif isinstance(step, RunStep):
305
339
  if step.inputs:
306
340
  pre = "\\\n " + "".join(
307
341
  [
@@ -325,6 +359,12 @@ ENV PATH="/root/.wasmer/bin:${PATH}"
325
359
  for dependency in step.dependencies:
326
360
  self.add_dependency(dependency)
327
361
 
362
+ self.docker_file_contents += """
363
+ FROM scratch
364
+ """
365
+ for mount in mounts:
366
+ self.docker_file_contents += f"COPY --from=build {mount.build_path} {mount.build_path}\n"
367
+
328
368
  self.docker_ignore_path.write_text("""
329
369
  .shipit
330
370
  Shipit
@@ -384,14 +424,33 @@ class LocalBuilder:
384
424
  self.src_dir = src_dir
385
425
  self.local_path = self.src_dir / ".shipit" / "local"
386
426
  self.prepare_bash_script = self.local_path / "prepare" / "prepare.sh"
427
+ self.build_path = self.local_path / "build"
428
+ self.workdir = self.build_path
429
+
430
+ def get_mount_path(self, name: str) -> Path:
431
+ if name == "app":
432
+ return self.build_path / "app"
433
+ else:
434
+ return self.build_path / "opt" / name
435
+
436
+ def get_build_mount_path(self, name: str) -> Path:
437
+ return self.get_mount_path(name)
387
438
 
388
- def execute_step(self, step: Step, env: Dict[str, str], build_path: Path) -> None:
439
+ def get_serve_mount_path(self, name: str) -> Path:
440
+ return self.get_mount_path(name)
441
+
442
+ def execute_step(self, step: Step, env: Dict[str, str]) -> None:
443
+ build_path = self.workdir
389
444
  if isinstance(step, UseStep):
390
445
  console.print(f"[bold]Using dependencies:[/bold] {step.dependencies}")
446
+ elif isinstance(step, WorkdirStep):
447
+ console.print(f"[bold]Working in {step.path}[/bold]")
448
+ self.workdir = step.path
391
449
  elif isinstance(step, RunStep):
392
450
  extra = ""
393
451
  if step.inputs:
394
452
  for input in step.inputs:
453
+ print(f"Copying {input} to {build_path / input}")
395
454
  copy((self.src_dir / input), (build_path / input))
396
455
  all_inputs = ", ".join(step.inputs)
397
456
  extra = f" [bright_black]# using {all_inputs}[/bright_black]"
@@ -448,17 +507,17 @@ class LocalBuilder:
448
507
  else:
449
508
  raise Exception(f"Unknown step type: {type(step)}")
450
509
 
451
- def build(self, env: Dict[str, str], steps: List[Step]) -> None:
510
+ def build(self, env: Dict[str, str], mounts: List[Mount], steps: List[Step]) -> None:
452
511
  console.print(f"\n[bold]Building package[/bold]")
453
512
  base_path = self.local_path
454
513
  shutil.rmtree(base_path, ignore_errors=True)
455
514
  base_path.mkdir(parents=True, exist_ok=True)
456
- temp_path = base_path / "build"
457
- temp_path.mkdir(exist_ok=False)
458
- logging.info(f"Initialized temporary build path: {temp_path}")
515
+ self.build_path.mkdir(exist_ok=True)
516
+ for mount in mounts:
517
+ mount.build_path.mkdir(parents=True, exist_ok=True)
459
518
  for step in steps:
460
519
  console.print(Rule(characters="-", style="bright_black"))
461
- self.execute_step(step, env, temp_path)
520
+ self.execute_step(step, env)
462
521
 
463
522
  if "PATH" in env:
464
523
  path = base_path / ".path"
@@ -565,6 +624,15 @@ class LocalBuilder:
565
624
 
566
625
 
567
626
  class WasmerBuilder:
627
+ def get_build_mount_path(self, name: str) -> Path:
628
+ return self.inner_builder.get_build_mount_path(name)
629
+
630
+ def get_serve_mount_path(self, name: str) -> Path:
631
+ if name == "app":
632
+ return Path("/app")
633
+ else:
634
+ return Path("/opt") / name
635
+
568
636
  mapper: Dict[str, MapperItem] = {
569
637
  "python": {
570
638
  "dependencies": {
@@ -576,8 +644,6 @@ class WasmerBuilder:
576
644
  "env": {
577
645
  "PYTHONEXECUTABLE": "/bin/python",
578
646
  "PYTHONHOME": "/cpython",
579
- "PYTHONPATH": "/.venv/lib/python3.13/site-packages",
580
- "HOME": "/app",
581
647
  },
582
648
  },
583
649
  "php": {
@@ -616,24 +682,26 @@ class WasmerBuilder:
616
682
  src_dir: Path,
617
683
  registry: Optional[str] = None,
618
684
  token: Optional[str] = None,
685
+ bin: Optional[Path] = None,
619
686
  ) -> None:
620
687
  self.src_dir = src_dir
621
688
  self.inner_builder = inner_builder
622
689
  # The path where we store the directory of the wasmer app in the inner builder
623
- self.wasmer_dir_path = Path(self.src_dir / ".shipit" / "wasmer_dir")
690
+ self.wasmer_dir_path = Path(self.src_dir / ".shipit" / "wasmer")
624
691
  self.wasmer_registry = registry
625
692
  self.wasmer_token = token
693
+ self.bin = bin.absolute() if bin else "wasmer"
626
694
  self.default_env = {
627
695
  "SHIPIT_PYTHON_EXTRA_INDEX_URL": "https://pythonindex.wasix.org/simple",
628
696
  "SHIPIT_PYTHON_CROSS_PLATFORM": "wasix_wasm32",
629
- # "SHIPIT_PYTHON_PRECOMPILE": "true",
697
+ "SHIPIT_PYTHON_PRECOMPILE": "true",
630
698
  }
631
699
 
632
700
  def getenv(self, name: str) -> Optional[str]:
633
701
  return self.inner_builder.getenv(name) or self.default_env.get(name)
634
702
 
635
- def build(self, env: Dict[str, str], build: List[Step]) -> None:
636
- return self.inner_builder.build(env, build)
703
+ def build(self, env: Dict[str, str], mounts: List[Mount], build: List[Step]) -> None:
704
+ return self.inner_builder.build(env, mounts, build)
637
705
 
638
706
  def build_assets(self, assets: Dict[str, str]) -> None:
639
707
  return self.inner_builder.build_assets(assets)
@@ -643,9 +711,9 @@ class WasmerBuilder:
643
711
 
644
712
  def build_prepare(self, serve: Serve) -> None:
645
713
  print("Building prepare")
646
- inner = cast(Any, self.inner_builder)
647
- prepare_dir = inner.mkdir(Path("wasmer") / "prepare")
648
- env = {}
714
+ prepare_dir = (self.wasmer_dir_path / "prepare")
715
+ prepare_dir.mkdir(parents=True, exist_ok=True)
716
+ env = serve.env or {}
649
717
  for dep in serve.deps:
650
718
  if dep.name in self.mapper:
651
719
  dep_env = self.mapper[dep.name].get("env")
@@ -663,19 +731,17 @@ class WasmerBuilder:
663
731
  if isinstance(step, RunStep):
664
732
  commands.append(step.command)
665
733
  body = "\n".join(filter(None, [env_lines, *commands]))
666
- inner.create_file(
667
- Path(prepare_dir) / "prepare.sh",
734
+ (prepare_dir / "prepare.sh").write_text(
668
735
  f"#!/bin/bash\ncd /app\n{body}",
669
- mode=0o755,
670
736
  )
737
+ (prepare_dir / "prepare.sh").chmod(0o755)
671
738
 
672
739
  def finalize_build(self, serve: Serve) -> None:
673
740
  inner = cast(Any, self.inner_builder)
674
741
  inner.finalize_build(serve)
675
742
 
676
743
  def prepare(self, env: Dict[str, str], prepare: List[PrepareStep]) -> None:
677
- inner = cast(Any, self.inner_builder)
678
- prepare_dir = inner.mkdir(Path("wasmer") / "prepare")
744
+ prepare_dir = (self.wasmer_dir_path / "prepare")
679
745
  self.run_serve_command(
680
746
  "bash",
681
747
  extra_args=[
@@ -739,10 +805,10 @@ class WasmerBuilder:
739
805
  inner = cast(Any, self.inner_builder)
740
806
  if serve.assets:
741
807
  fs.add("/assets", str((inner.get_path() / "assets").absolute()))
742
- fs.add("/app", str(inner.get_build_path().absolute()))
808
+ # fs.add("/app", str(inner.get_build_path().absolute()))
743
809
  if serve.mounts:
744
810
  for mount in serve.mounts:
745
- fs.add(mount, serve.mounts[mount])
811
+ fs.add(str(mount.serve_path.absolute()), str(self.inner_builder.get_serve_mount_path(mount.name).absolute()))
746
812
 
747
813
  doc.add(nl())
748
814
  if serve.commands:
@@ -760,8 +826,10 @@ class WasmerBuilder:
760
826
  wasi_args = table()
761
827
  wasi_args.add("cwd", "/app")
762
828
  wasi_args.add("main-args", parts[1:])
763
- env = program_binary.get("env")
764
- if env is not None:
829
+ env = program_binary.get("env") or {}
830
+ if serve.env:
831
+ env.update(serve.env)
832
+ if env:
765
833
  wasi_args.add(
766
834
  "env",
767
835
  [f"{k}={v}" for k, v in env.items()],
@@ -770,9 +838,7 @@ class WasmerBuilder:
770
838
  command.add(title, wasi_args)
771
839
 
772
840
  inner = cast(Any, self.inner_builder)
773
- wasmer_dir = inner.mkdir(Path("wasmer"))
774
- # Dump the wasmer_dir path to a file
775
- self.wasmer_dir_path.write_text(str(wasmer_dir))
841
+ self.wasmer_dir_path.mkdir(parents=True, exist_ok=True)
776
842
 
777
843
  manifest = doc.as_string().replace(
778
844
  '[command."annotations.wasi"]', "[command.annotations.wasi]"
@@ -791,7 +857,7 @@ class WasmerBuilder:
791
857
  expand=False,
792
858
  )
793
859
  console.print(manifest_panel, markup=False, highlight=True)
794
- inner.create_file(Path(wasmer_dir) / "wasmer.toml", manifest)
860
+ (self.wasmer_dir_path / "wasmer.toml").write_text(manifest)
795
861
 
796
862
  # Crete app.yaml
797
863
  yaml_config = {
@@ -811,7 +877,7 @@ class WasmerBuilder:
811
877
  }
812
878
 
813
879
  app_yaml = yaml.dump(yaml_config)
814
- inner.create_file(Path(wasmer_dir) / "app.yaml", app_yaml)
880
+ (self.wasmer_dir_path / "app.yaml").write_text(app_yaml)
815
881
 
816
882
  # self.inner_builder.build_serve(serve)
817
883
 
@@ -823,14 +889,13 @@ class WasmerBuilder:
823
889
  self, command: str, extra_args: Optional[List[str]] = None
824
890
  ) -> None:
825
891
  console.print(f"\n[bold]Serving site[/bold]: running {command} command")
826
- wasmer_path = self.wasmer_dir_path.read_text()
827
892
  extra_args = extra_args or []
828
893
 
829
894
  if self.wasmer_registry:
830
895
  extra_args = [f"--registry={self.wasmer_registry}"] + extra_args
831
- self.inner_builder.run_command(
832
- "wasmer",
833
- ["run", str(wasmer_path), "--net", f"--command={command}", *extra_args],
896
+ self.run_command(
897
+ self.bin,
898
+ ["run", str(self.wasmer_dir_path.absolute()), "--net", f"--command={command}", *extra_args],
834
899
  )
835
900
 
836
901
  def serve_mount(self, name: str) -> str:
@@ -842,7 +907,7 @@ class WasmerBuilder:
842
907
  def run_command(
843
908
  self, command: str, extra_args: Optional[List[str]] | None = None
844
909
  ) -> Any:
845
- return self.inner_builder.run_command(command, extra_args or [])
910
+ sh.Command(command)(*(extra_args or []), _out=write_stdout, _err=write_stderr, _env=os.environ)
846
911
 
847
912
  def deploy(
848
913
  self, app_owner: Optional[str] = None, app_name: Optional[str] = None
@@ -854,8 +919,8 @@ class WasmerBuilder:
854
919
  extra_args += ["--registry", self.wasmer_registry]
855
920
  if self.wasmer_token:
856
921
  extra_args += ["--token", self.wasmer_token]
857
- self.inner_builder.run_command(
858
- "wasmer",
922
+ self.run_command(
923
+ self.bin,
859
924
  [
860
925
  "package",
861
926
  "push",
@@ -866,8 +931,8 @@ class WasmerBuilder:
866
931
  *extra_args,
867
932
  ],
868
933
  )
869
- return self.inner_builder.run_command(
870
- "wasmer",
934
+ return self.run_command(
935
+ self.bin,
871
936
  [
872
937
  "deploy",
873
938
  "--publish-package",
@@ -890,6 +955,7 @@ class Ctx:
890
955
  self.builds: List[Build] = []
891
956
  self.steps: List[Step] = []
892
957
  self.serves: Dict[str, Serve] = {}
958
+ self.mounts: List[Mount] = []
893
959
 
894
960
  def add_package(self, package: Package) -> str:
895
961
  index = f"{package.name}@{package.version}" if package.version else package.name
@@ -905,6 +971,8 @@ class Ctx:
905
971
  return self.serves[index[len("ref:serve:") :]]
906
972
  elif index.startswith("ref:step:"):
907
973
  return self.steps[int(index[len("ref:step:") :])]
974
+ elif index.startswith("ref:mount:"):
975
+ return self.mounts[int(index[len("ref:mount:") :])]
908
976
  else:
909
977
  raise Exception(f"Invalid reference: {index}")
910
978
 
@@ -945,7 +1013,8 @@ class Ctx:
945
1013
  assets: Optional[Dict[str, str]] = None,
946
1014
  prepare: Optional[List[str]] = None,
947
1015
  workers: Optional[List[str]] = None,
948
- mounts: Optional[Dict[str, str]] = None,
1016
+ mounts: Optional[List[Mount]] = None,
1017
+ env: Optional[Dict[str, str]] = None,
949
1018
  ) -> str:
950
1019
  build_refs = [cast(Step, r) for r in self.get_refs(build)]
951
1020
  prepare_steps: Optional[List[PrepareStep]] = None
@@ -965,7 +1034,8 @@ class Ctx:
965
1034
  commands=commands,
966
1035
  prepare=prepare_steps,
967
1036
  workers=workers,
968
- mounts=mounts,
1037
+ mounts=self.get_refs([mount["ref"] for mount in mounts]) if mounts else None,
1038
+ env=env,
969
1039
  )
970
1040
  return self.add_serve(serve)
971
1041
 
@@ -982,6 +1052,10 @@ class Ctx:
982
1052
  step = RunStep(*args, **kwargs)
983
1053
  return self.add_step(step)
984
1054
 
1055
+ def workdir(self, path: str) -> Optional[str]:
1056
+ step = WorkdirStep(Path(path))
1057
+ return self.add_step(step)
1058
+
985
1059
  def copy(
986
1060
  self, source: str, target: str, ignore: Optional[List[str]] = None
987
1061
  ) -> Optional[str]:
@@ -995,6 +1069,21 @@ class Ctx:
995
1069
  step = EnvStep(env_vars)
996
1070
  return self.add_step(step)
997
1071
 
1072
+ def add_mount(self, mount: Mount) -> Optional[str]:
1073
+ self.mounts.append(mount)
1074
+ return f"ref:mount:{len(self.mounts) - 1}"
1075
+
1076
+ def mount(self, name: str) -> Optional[str]:
1077
+ build_path = self.builder.get_build_mount_path(name)
1078
+ serve_path = self.builder.get_serve_mount_path(name)
1079
+ mount = Mount(name, build_path, serve_path)
1080
+ ref = self.add_mount(mount)
1081
+ return {
1082
+ "ref": ref,
1083
+ "build": str(build_path.absolute()),
1084
+ "serve": str(serve_path.absolute()),
1085
+ }
1086
+
998
1087
  def serve_mount(self, name: str) -> Optional[str]:
999
1088
  return self.builder.serve_mount(name)
1000
1089
 
@@ -1020,6 +1109,10 @@ def auto(
1020
1109
  False,
1021
1110
  help="Use Wasmer to build and serve the project.",
1022
1111
  ),
1112
+ wasmer_bin: Optional[Path] = typer.Option(
1113
+ None,
1114
+ help="The path to the Wasmer binary.",
1115
+ ),
1023
1116
  docker: bool = typer.Option(
1024
1117
  False,
1025
1118
  help="Use Docker to build the project.",
@@ -1064,11 +1157,12 @@ def auto(
1064
1157
  if not (path / "Shipit").exists() or regenerate or regenerate_path is not None:
1065
1158
  generate(path, out=regenerate_path)
1066
1159
 
1067
- build(path, wasmer=(wasmer or wasmer_deploy), docker=docker, docker_client=docker_client,)
1160
+ build(path, wasmer=(wasmer or wasmer_deploy), docker=docker, docker_client=docker_client, wasmer_registry=wasmer_registry, wasmer_token=wasmer_token, wasmer_bin=wasmer_bin)
1068
1161
  if start or wasmer_deploy:
1069
1162
  serve(
1070
1163
  path,
1071
1164
  wasmer=wasmer,
1165
+ wasmer_bin=wasmer_bin,
1072
1166
  docker=docker,
1073
1167
  docker_client=docker_client,
1074
1168
  start=start,
@@ -1130,6 +1224,10 @@ def serve(
1130
1224
  False,
1131
1225
  help="Use Wasmer to build and serve the project.",
1132
1226
  ),
1227
+ wasmer_bin: Optional[Path] = typer.Option(
1228
+ None,
1229
+ help="The path to the Wasmer binary.",
1230
+ ),
1133
1231
  docker: bool = typer.Option(
1134
1232
  False,
1135
1233
  help="Use Docker to build the project.",
@@ -1170,7 +1268,7 @@ def serve(
1170
1268
  builder = LocalBuilder(path)
1171
1269
  if wasmer or wasmer_deploy:
1172
1270
  builder = WasmerBuilder(
1173
- builder, path, registry=wasmer_registry, token=wasmer_token
1271
+ builder, path, registry=wasmer_registry, token=wasmer_token, bin=wasmer_bin
1174
1272
  )
1175
1273
  if start:
1176
1274
  builder.run_serve_command("start")
@@ -1193,6 +1291,18 @@ def build(
1193
1291
  False,
1194
1292
  help="Use Wasmer to build and serve the project.",
1195
1293
  ),
1294
+ wasmer_bin: Optional[Path] = typer.Option(
1295
+ None,
1296
+ help="The path to the Wasmer binary.",
1297
+ ),
1298
+ wasmer_registry: Optional[str] = typer.Option(
1299
+ None,
1300
+ help="Wasmer registry.",
1301
+ ),
1302
+ wasmer_token: Optional[str] = typer.Option(
1303
+ None,
1304
+ help="Wasmer token.",
1305
+ ),
1196
1306
  docker: bool = typer.Option(
1197
1307
  False,
1198
1308
  help="Use Docker to build the project.",
@@ -1214,7 +1324,7 @@ def build(
1214
1324
  else:
1215
1325
  builder = LocalBuilder(path)
1216
1326
  if wasmer:
1217
- builder = WasmerBuilder(builder, path)
1327
+ builder = WasmerBuilder(builder, path, registry=wasmer_registry, token=wasmer_token, bin=wasmer_bin)
1218
1328
 
1219
1329
  ctx = Ctx(builder)
1220
1330
  glb = sl.Globals.standard()
@@ -1224,13 +1334,15 @@ def build(
1224
1334
  mod.add_callable("dep", ctx.dep)
1225
1335
  mod.add_callable("serve", ctx.serve)
1226
1336
  mod.add_callable("run", ctx.run)
1227
- mod.add_callable("use", ctx.run)
1337
+ mod.add_callable("mount", ctx.mount)
1338
+ mod.add_callable("workdir", ctx.workdir)
1228
1339
  mod.add_callable("copy", ctx.copy)
1229
1340
  mod.add_callable("path", ctx.path)
1230
1341
  mod.add_callable("buildpath", ctx.buildpath)
1231
1342
  mod.add_callable("get_asset", ctx.get_asset)
1232
1343
  mod.add_callable("env", ctx.env)
1233
1344
  mod.add_callable("use", ctx.use)
1345
+ # REMOVE ME
1234
1346
  mod.add_callable("serve_mount", ctx.serve_mount)
1235
1347
 
1236
1348
  dialect = sl.Dialect.extended()
@@ -1252,7 +1364,7 @@ def build(
1252
1364
  serve = next(iter(ctx.serves.values()))
1253
1365
 
1254
1366
  # Build and serve
1255
- builder.build(env, serve.build)
1367
+ builder.build(env, serve.mounts, serve.build)
1256
1368
  if serve.prepare:
1257
1369
  builder.build_prepare(serve)
1258
1370
  if serve.assets:
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
  from typing import Dict, List, Optional
5
5
 
6
- from shipit.providers.base import DependencySpec, Provider, ProviderPlan, DetectResult
6
+ from shipit.providers.base import DependencySpec, Provider, ProviderPlan, DetectResult, MountSpec
7
7
  from shipit.providers.registry import providers as registry_providers
8
8
 
9
9
 
@@ -87,13 +87,13 @@ def generate_shipit(path: Path) -> str:
87
87
  plan = ProviderPlan(
88
88
  serve_name=provider.serve_name(path),
89
89
  provider=provider.provider_kind(path),
90
+ mounts=provider.mounts(path),
90
91
  declarations=provider.declarations(path),
91
92
  dependencies=provider.dependencies(path),
92
93
  build_steps=provider.build_steps(path),
93
94
  prepare=provider.prepare_steps(path),
94
95
  commands=provider.commands(path),
95
- assets=provider.assets(path),
96
- mounts=provider.mounts(path),
96
+ env=provider.env(path),
97
97
  )
98
98
 
99
99
  # Declare dependency variables (combined) and collect serve deps
@@ -110,15 +110,24 @@ def generate_shipit(path: Path) -> str:
110
110
  build_steps_block = ",\n".join([f" {s}" for s in build_steps])
111
111
  deps_array = ", ".join(serve_dep_vars)
112
112
  commands_lines = ",\n".join([f' "{k}": {v}' for k, v in plan.commands.items()])
113
+ env_lines = None
114
+ if plan.env is not None:
115
+ if len(plan.env) == 0:
116
+ env_lines = "{}"
117
+ else:
118
+ env_lines = ",\n".join([f' "{k}": {v}' for k, v in plan.env.items()])
113
119
  assets_block = _render_assets(plan.assets)
114
120
  mounts_block = None
115
121
  if plan.mounts:
116
- mounts_block = ",\n".join([f" {k}: {v}" for k, v in plan.mounts.items()])
122
+ mounts = filter(lambda m: m.attach_to_serve, plan.mounts)
123
+ mounts_block = ",\n".join([f" {m.name}" for m in mounts])
117
124
 
118
125
  out: List[str] = []
119
126
  if dep_block:
120
127
  out.append(dep_block)
121
128
  out.append("")
129
+ for m in plan.mounts:
130
+ out.append(f"{m.name} = mount(\"{m.name}\")")
122
131
  if plan.declarations:
123
132
  out.append(plan.declarations)
124
133
  out.append("")
@@ -136,13 +145,20 @@ def generate_shipit(path: Path) -> str:
136
145
  out.append(" prepare=[")
137
146
  out.append(prepare_steps_block)
138
147
  out.append(" ],")
148
+ if env_lines is not None:
149
+ if env_lines == "{}":
150
+ out.append(" env = {},")
151
+ else:
152
+ out.append(" env = {")
153
+ out.append(env_lines)
154
+ out.append(" },")
139
155
  out.append(" commands = {")
140
156
  out.append(commands_lines)
141
157
  out.append(" },")
142
158
  if mounts_block:
143
- out.append(" mounts={")
159
+ out.append(" mounts=[")
144
160
  out.append(mounts_block)
145
- out.append(" },")
161
+ out.append(" ],")
146
162
  out.append(")")
147
163
  out.append("")
148
164
  return "\n".join(out)
@@ -25,7 +25,8 @@ class Provider(Protocol):
25
25
  def prepare_steps(self, path: Path) -> Optional[List[str]]: ...
26
26
  def commands(self, path: Path) -> Dict[str, str]: ...
27
27
  def assets(self, path: Path) -> Optional[Dict[str, str]]: ...
28
- def mounts(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]]: ...
29
30
 
30
31
 
31
32
  @dataclass
@@ -38,17 +39,25 @@ class DependencySpec:
38
39
  use_in_serve: bool = False
39
40
 
40
41
 
42
+ @dataclass
43
+ class MountSpec:
44
+ name: str
45
+ attach_to_build: bool = True
46
+ attach_to_serve: bool = True
47
+
48
+
41
49
  @dataclass
42
50
  class ProviderPlan:
43
51
  serve_name: str
44
52
  provider: str
53
+ mounts: List[MountSpec]
45
54
  declarations: Optional[str] = None
46
55
  dependencies: List[DependencySpec] = field(default_factory=list)
47
56
  build_steps: List[str] = field(default_factory=list)
48
57
  prepare: Optional[List[str]] = None
49
58
  commands: Dict[str, str] = field(default_factory=dict)
50
59
  assets: Optional[Dict[str, str]] = None
51
- mounts: Optional[Dict[str, str]] = None
60
+ env: Optional[Dict[str, str]] = None
52
61
 
53
62
 
54
63
  def _exists(path: Path, *candidates: str) -> bool:
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
  from typing import Dict, Optional
5
5
 
6
- from .base import DetectResult, DependencySpec, Provider, _exists, _has_dependency
6
+ from .base import DetectResult, DependencySpec, Provider, _exists, _has_dependency, MountSpec
7
7
 
8
8
 
9
9
  class GatsbyProvider:
@@ -49,16 +49,20 @@ class GatsbyProvider:
49
49
  "run(\"npm install\", inputs=[\"package.json\", \"package-lock.json\"], group=\"install\")",
50
50
  "copy(\".\", \".\", ignore=[\"node_modules\", \".git\"])",
51
51
  "run(\"npm run build\", outputs=[\"public\"], group=\"build\")",
52
+ "run(\"cp -R public/* {}/\".format(app[\"build\"]))",
52
53
  ]
53
54
 
54
55
  def prepare_steps(self, path: Path) -> Optional[list[str]]:
55
56
  return None
56
57
 
57
58
  def commands(self, path: Path) -> Dict[str, str]:
58
- return {"start": '"static-web-server --root /app/public"'}
59
+ return {"start": '"static-web-server --root /app"'}
59
60
 
60
61
  def assets(self, path: Path) -> Optional[Dict[str, str]]:
61
62
  return None
62
63
 
63
- def mounts(self, path: Path) -> Optional[Dict[str, str]]:
64
+ def mounts(self, path: Path) -> list[MountSpec]:
65
+ return [MountSpec("app")]
66
+
67
+ def env(self, path: Path) -> Optional[Dict[str, str]]:
64
68
  return None
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
  from typing import Dict, Optional
5
5
 
6
- from .base import DetectResult, DependencySpec, Provider, _exists
6
+ from .base import DetectResult, DependencySpec, Provider, _exists, MountSpec
7
7
 
8
8
 
9
9
  class LaravelProvider:
@@ -45,6 +45,7 @@ class LaravelProvider:
45
45
  def build_steps(self, path: Path) -> list[str]:
46
46
  return [
47
47
  "env(HOME=HOME, COMPOSER_FUND=\"0\")",
48
+ "workdir(app[\"build\"])",
48
49
  "run(\"pie install php/pdo_pgsql\")",
49
50
  "run(\"composer install --optimize-autoloader --no-scripts --no-interaction\", inputs=[\"composer.json\", \"composer.lock\", \"artisan\"], outputs=[\".\"], group=\"install\")",
50
51
  "run(\"pnpm install\", inputs=[\"package.json\", \"package-lock.json\"], outputs=[\".\"], group=\"install\")",
@@ -54,6 +55,7 @@ class LaravelProvider:
54
55
 
55
56
  def prepare_steps(self, path: Path) -> Optional[list[str]]:
56
57
  return [
58
+ 'workdir(app["serve"])',
57
59
  'run("mkdir -p storage/framework/{sessions,views,cache,testing} storage/logs bootstrap/cache")',
58
60
  'run("php artisan config:cache")',
59
61
  'run("php artisan event:cache")',
@@ -70,5 +72,8 @@ class LaravelProvider:
70
72
  def assets(self, path: Path) -> Optional[Dict[str, str]]:
71
73
  return None
72
74
 
73
- def mounts(self, path: Path) -> Optional[Dict[str, str]]:
75
+ def mounts(self, path: Path) -> list[MountSpec]:
76
+ return [MountSpec("app")]
77
+
78
+ def env(self, path: Path) -> Optional[Dict[str, str]]:
74
79
  return None
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
  from typing import Dict, Optional
5
5
 
6
- from .base import DetectResult, DependencySpec, Provider, _exists
6
+ from .base import DetectResult, DependencySpec, Provider, _exists, MountSpec
7
7
 
8
8
 
9
9
  class MkdocsProvider:
@@ -65,17 +65,20 @@ class MkdocsProvider:
65
65
  return [
66
66
  *install_lines,
67
67
  "copy(\".\", \".\", ignore=[\".venv\", \".git\", \"__pycache__\"])",
68
- "run(\"uv run mkdocs build\", outputs=[\".\"], group=\"build\")",
68
+ "run(\"uv run mkdocs build --site-dir={}\".format(app[\"build\"]), outputs=[\".\"], group=\"build\")",
69
69
  ]
70
70
 
71
71
  def prepare_steps(self, path: Path) -> Optional[list[str]]:
72
72
  return None
73
73
 
74
74
  def commands(self, path: Path) -> Dict[str, str]:
75
- return {"start": '"static-web-server --root {}".format(buildpath("site"))'}
75
+ return {"start": '"static-web-server --root /app"'}
76
76
 
77
77
  def assets(self, path: Path) -> Optional[Dict[str, str]]:
78
78
  return None
79
79
 
80
- def mounts(self, path: Path) -> Optional[Dict[str, str]]:
80
+ def mounts(self, path: Path) -> list[MountSpec]:
81
+ return [MountSpec("app")]
82
+
83
+ def env(self, path: Path) -> Optional[Dict[str, str]]:
81
84
  return None
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
  from typing import Dict, Optional
5
5
 
6
- from .base import DetectResult, DependencySpec, Provider, _exists, _has_dependency
6
+ from .base import DetectResult, DependencySpec, Provider, _exists, _has_dependency, MountSpec
7
7
 
8
8
 
9
9
  class NodeStaticProvider:
@@ -49,6 +49,7 @@ class NodeStaticProvider:
49
49
  "run(\"npm install\", inputs=[\"package.json\", \"package-lock.json\"], group=\"install\")",
50
50
  "copy(\".\", \".\", ignore=[\"node_modules\", \".git\"])",
51
51
  f"run(\"npm run build\", outputs=[\"{output_dir}\"], group=\"build\")",
52
+ f"run(\"cp -R {output_dir}/* {{}}/\".format(app[\"build\"]))",
52
53
  ]
53
54
 
54
55
  def prepare_steps(self, path: Path) -> Optional[list[str]]:
@@ -61,5 +62,8 @@ class NodeStaticProvider:
61
62
  def assets(self, path: Path) -> Optional[Dict[str, str]]:
62
63
  return None
63
64
 
64
- def mounts(self, path: Path) -> Optional[Dict[str, str]]:
65
+ def mounts(self, path: Path) -> list[MountSpec]:
66
+ return [MountSpec("app")]
67
+
68
+ def env(self, path: Path) -> Optional[Dict[str, str]]:
65
69
  return None
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
  from typing import Dict, Optional
5
5
 
6
- from .base import DetectResult, DependencySpec, Provider, _exists
6
+ from .base import DetectResult, DependencySpec, Provider, _exists, MountSpec
7
7
 
8
8
 
9
9
  class PhpProvider:
@@ -48,7 +48,9 @@ class PhpProvider:
48
48
  return "HOME = getenv(\"HOME\")"
49
49
 
50
50
  def build_steps(self, path: Path) -> list[str]:
51
- steps = []
51
+ steps = [
52
+ "workdir(app[\"build\"])",
53
+ ]
52
54
 
53
55
  if self.has_composer(path):
54
56
  steps.append("env(HOME=HOME, COMPOSER_FUND=\"0\")")
@@ -69,5 +71,8 @@ class PhpProvider:
69
71
  def assets(self, path: Path) -> Optional[Dict[str, str]]:
70
72
  return {"php.ini": "get_asset(\"php/php.ini\")"}
71
73
 
72
- def mounts(self, path: Path) -> Optional[Dict[str, str]]:
74
+ def mounts(self, path: Path) -> list[MountSpec]:
75
+ return [MountSpec("app")]
76
+
77
+ def env(self, path: Path) -> Optional[Dict[str, str]]:
73
78
  return None
@@ -8,6 +8,7 @@ from .base import (
8
8
  DependencySpec,
9
9
  Provider,
10
10
  _exists,
11
+ MountSpec,
11
12
  )
12
13
 
13
14
 
@@ -57,29 +58,26 @@ class PythonProvider:
57
58
  return (
58
59
  "cross_platform = getenv(\"SHIPIT_PYTHON_CROSS_PLATFORM\")\n"
59
60
  "python_extra_index_url = getenv(\"SHIPIT_PYTHON_EXTRA_INDEX_URL\")\n"
60
- "python_cross_packages_serve_path = None\n"
61
- "python_cross_packages_path = None\n"
62
- "if cross_platform:\n"
63
- " python_cross_packages_path = serve_mount(\"python-cross-packages\")\n"
64
- " python_cross_packages_serve_path = f\"/.venv/lib/python{python_version}/site-packages\"\n"
65
61
  "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\""
66
63
  )
67
64
 
68
65
  def build_steps(self, path: Path) -> list[str]:
69
66
  return [
67
+ "workdir(app[\"build\"])",
68
+ "env(UV_PROJECT_ENVIRONMENT=local_venv[\"build\"] if cross_platform else venv[\"build\"])",
70
69
  "run(f\"uv sync --compile --python python{python_version} --locked --no-managed-python\", inputs=[\"pyproject.toml\", \"uv.lock\"], outputs=[\".\"], group=\"install\")",
71
70
  "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",
72
71
  "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\", outputs=[\".\"]) if cross_platform else None",
73
72
  "run(\"rm cross-requirements.txt\") if cross_platform else None",
74
- "path(\".venv/bin\")",
73
+ "path((local_venv[\"build\"] if cross_platform else venv[\"build\"]) + \"/bin\")",
75
74
  "copy(\".\", \".\", ignore=[\".venv\", \".git\", \"__pycache__\"])",
76
- "run(\"rm -rf .venv\") if cross_platform else None",
77
75
  ]
78
76
 
79
77
  def prepare_steps(self, path: Path) -> Optional[list[str]]:
80
78
  return [
81
- 'run("echo \\\"Precompiling Python code...\\\"") if precompile_python and python_cross_packages_serve_path else None',
82
- 'run(f"python -m compileall -o 2 {python_cross_packages_serve_path}") if precompile_python and python_cross_packages_serve_path else None',
79
+ 'run("echo \\\"Precompiling Python code...\\\"") if precompile_python else None',
80
+ 'run("python -m compileall -o 2 $PYTHONPATH") if precompile_python else None',
83
81
  'run("echo \\\"Precompiling package code...\\\"") if precompile_python else None',
84
82
  'run("python -m compileall -o 2 .") if precompile_python else None',
85
83
  ]
@@ -100,5 +98,16 @@ class PythonProvider:
100
98
  def assets(self, path: Path) -> Optional[Dict[str, str]]:
101
99
  return None
102
100
 
103
- def mounts(self, path: Path) -> Optional[Dict[str, str]]:
104
- return {"python_cross_packages_serve_path": "python_cross_packages_path"}
101
+ def mounts(self, path: Path) -> list[MountSpec]:
102
+ return [
103
+ MountSpec("app"),
104
+ MountSpec("venv"),
105
+ MountSpec("local_venv", attach_to_serve=False),
106
+ ]
107
+
108
+ def env(self, path: Path) -> Optional[Dict[str, str]]:
109
+ # For Django projects, generate an empty env dict to surface the field
110
+ # in the Shipit file. Other Python projects omit it by default.
111
+ return {
112
+ "PYTHONPATH": "\"{}/lib/python{}/site-packages\".format(venv[\"serve\"], python_version)"
113
+ }
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
  from typing import Dict, Optional
5
5
 
6
- from .base import DetectResult, DependencySpec, Provider, _exists
6
+ from .base import DetectResult, DependencySpec, Provider, _exists, MountSpec
7
7
 
8
8
 
9
9
  class StaticFileProvider:
@@ -41,7 +41,10 @@ class StaticFileProvider:
41
41
  ]
42
42
 
43
43
  def build_steps(self, path: Path) -> list[str]:
44
- return ['copy(".", ".", ignore=[".git"])']
44
+ return [
45
+ 'workdir(app["build"])',
46
+ 'copy(".", ".", ignore=[".git"])'
47
+ ]
45
48
 
46
49
  def prepare_steps(self, path: Path) -> Optional[list[str]]:
47
50
  return None
@@ -57,5 +60,8 @@ class StaticFileProvider:
57
60
  def assets(self, path: Path) -> Optional[Dict[str, str]]:
58
61
  return None
59
62
 
60
- def mounts(self, path: Path) -> Optional[Dict[str, str]]:
63
+ def mounts(self, path: Path) -> list[MountSpec]:
64
+ return [MountSpec("app")]
65
+
66
+ def env(self, path: Path) -> Optional[Dict[str, str]]:
61
67
  return None
@@ -0,0 +1,5 @@
1
+ __all__ = ["version", "version_info"]
2
+
3
+
4
+ version = "0.2.1"
5
+ version_info = (0, 2, 1, "final", 0)
@@ -0,0 +1,15 @@
1
+ from pathlib import Path
2
+
3
+ import tomlkit
4
+
5
+ from shipit.version import version as module_version
6
+
7
+
8
+ def test_module_version_matches_pyproject() -> None:
9
+ pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
10
+ data = tomlkit.parse(pyproject_path.read_text())
11
+ project_version = data["project"]["version"]
12
+ assert (
13
+ module_version == project_version
14
+ ), f"module version {module_version} != pyproject version {project_version}"
15
+
@@ -1,5 +0,0 @@
1
- __all__ = ["version", "version_info"]
2
-
3
-
4
- version = "0.1.0"
5
- version_info = (0, 1, 0, "final", 0)
File without changes
File without changes