shipit-cli 0.3.4__py3-none-any.whl → 0.4.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/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
@@ -115,8 +115,10 @@ def generate_shipit(path: Path) -> str:
115
115
  env_lines = ",\n".join([f' "{k}": {v}' for k, v in plan.env.items()])
116
116
  assets_block = _render_assets(plan.assets)
117
117
  mounts_block = None
118
+ attach_serve_names: list[str] = []
118
119
  if plan.mounts:
119
- 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]
120
122
  mounts_block = ",\n".join([f" {m.name}" for m in mounts])
121
123
 
122
124
  out: List[str] = []
@@ -131,6 +133,9 @@ def generate_shipit(path: Path) -> str:
131
133
  out.append("serve(")
132
134
  out.append(f' name="{plan.serve_name}",')
133
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"],')
134
139
  out.append(" build=[")
135
140
  out.append(build_steps_block)
136
141
  out.append(" ],")
@@ -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,9 +14,140 @@ 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:
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
+
16
43
  def __init__(self, path: Path):
17
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
+
18
151
  @classmethod
19
152
  def name(cls) -> str:
20
153
  return "python"
@@ -37,16 +170,11 @@ class PythonProvider:
37
170
  return "python"
38
171
 
39
172
  def dependencies(self) -> list[DependencySpec]:
40
- if _exists(self.path, ".python-version"):
41
- python_version = (self.path / ".python-version").read_text().strip()
42
- else:
43
- python_version = "3.13"
44
-
45
173
  return [
46
174
  DependencySpec(
47
175
  "python",
48
176
  env_var="SHIPIT_PYTHON_VERSION",
49
- default_version=python_version,
177
+ default_version=self.default_python_version,
50
178
  use_in_build=True,
51
179
  use_in_serve=True,
52
180
  ),
@@ -63,7 +191,8 @@ class PythonProvider:
63
191
  "cross_platform = getenv(\"SHIPIT_PYTHON_CROSS_PLATFORM\")\n"
64
192
  "python_extra_index_url = getenv(\"SHIPIT_PYTHON_EXTRA_INDEX_URL\")\n"
65
193
  "precompile_python = getenv(\"SHIPIT_PYTHON_PRECOMPILE\") in [\"true\", \"True\", \"TRUE\", \"1\", \"on\", \"yes\", \"y\", \"Y\", \"YES\", \"On\", \"ON\"]\n"
66
- "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"
67
196
  )
68
197
 
69
198
  def build_steps(self) -> list[str]:
@@ -78,13 +207,16 @@ class PythonProvider:
78
207
  input_files.append("uv.lock")
79
208
  extra_args = " --locked"
80
209
  inputs = ", ".join([f"\"{input}\"" for input in input_files])
81
- steps += [
210
+ extra_deps = ", ".join([f"{dep}" for dep in self.extra_dependencies])
211
+ steps += list(filter(None, [
82
212
  "env(UV_PROJECT_ENVIRONMENT=local_venv[\"build\"] if cross_platform else venv[\"build\"])",
83
213
  "run(f\"uv sync --compile --python python{python_version} --no-managed-python" + extra_args + "\", inputs=[" + inputs + "], group=\"install\")",
84
- "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",
85
- "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",
214
+ "copy(\"pyproject.toml\", \"pyproject.toml\")",
215
+ f"run(\"uv add {extra_deps}\", group=\"install\")" if extra_deps else None,
216
+ "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\", outputs=[\"cross-requirements.txt\"]) if cross_platform else None",
217
+ 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",
86
218
  "run(\"rm cross-requirements.txt\") if cross_platform else None",
87
- ]
219
+ ]))
88
220
  if _exists(self.path, "requirements.txt"):
89
221
  steps += [
90
222
  "env(UV_PROJECT_ENVIRONMENT=local_venv[\"build\"] if cross_platform else venv[\"build\"])",
@@ -103,24 +235,57 @@ class PythonProvider:
103
235
 
104
236
  def prepare_steps(self) -> Optional[list[str]]:
105
237
  return [
106
- 'workdir(app["serve"])',
107
238
  'run("echo \\\"Precompiling Python code...\\\"") if precompile_python else None',
108
- 'run("python -m compileall -o 2 $PYTHONPATH") if precompile_python else None',
239
+ 'run(f"python -m compileall -o 2 {python_serve_path}") if precompile_python else None',
109
240
  'run("echo \\\"Precompiling package code...\\\"") if precompile_python else None',
110
- 'run("python -m compileall -o 2 .") if precompile_python else None',
241
+ 'run("python -m compileall -o 2 {}".format(app["serve"])) if precompile_python else None',
111
242
  ]
112
243
 
113
244
  def commands(self) -> Dict[str, str]:
114
- if _exists(self.path, "manage.py"):
115
- start_cmd = '"python manage.py runserver 0.0.0.0:8000"'
245
+ if self.framework == PythonFramework.Django:
246
+ start_cmd = None
247
+ if self.server == PythonServer.Daphne and self.asgi_application:
248
+ asgi_application = format_app_import(self.asgi_application)
249
+ start_cmd = f'"python -m daphne {asgi_application} --bind 0.0.0.0 --port 8000"'
250
+ elif self.server == PythonServer.Uvicorn:
251
+ if self.asgi_application:
252
+ asgi_application = format_app_import(self.asgi_application)
253
+ start_cmd = f'"python -m uvicorn {asgi_application} --host 0.0.0.0 --port 8000"'
254
+ elif self.wsgi_application:
255
+ wsgi_application = format_app_import(self.wsgi_application)
256
+ start_cmd = f'"python -m uvicorn {wsgi_application} --interface=wsgi --host 0.0.0.0 --port 8000"'
257
+ # elif self.server == PythonServer.Gunicorn:
258
+ # start_cmd = f'"python -m gunicorn {self.wsgi_application} --bind 0.0.0.0 --port 8000"'
259
+ if not start_cmd:
260
+ # We run the default runserver command if no server is specified
261
+ start_cmd = '"python manage.py runserver 0.0.0.0:8000"'
116
262
  migrate_cmd = '"python manage.py migrate"'
117
263
  return {"start": start_cmd, "after_deploy": migrate_cmd}
264
+ elif self.framework == PythonFramework.FastAPI:
265
+ if _exists(self.path, "main.py"):
266
+ path = "main:app"
267
+ elif _exists(self.path, "src/main.py"):
268
+ path = "src.main:app"
269
+
270
+ if self.server == PythonServer.Uvicorn:
271
+ start_cmd = f'"python -m uvicorn {path} --host 0.0.0.0 --port 8000"'
272
+ elif self.server == PythonServer.Hypercorn:
273
+ start_cmd = f'"python -m hypercorn {path} --bind 0.0.0.0:8000"'
274
+ else:
275
+ start_cmd = '"python -c \'print(\\\"No start command detected, please provide a start command manually\\\")\'"'
276
+ return {"start": start_cmd}
277
+ elif self.framework == PythonFramework.FastHTML:
278
+ if _exists(self.path, "main.py"):
279
+ path = "main:app"
280
+ elif _exists(self.path, "src/main.py"):
281
+ path = "src.main:app"
282
+ start_cmd = f'"python -m uvicorn {path} --host 0.0.0.0 --port 8000"'
118
283
  elif _exists(self.path, "main.py"):
119
284
  start_cmd = '"python main.py"'
120
285
  elif _exists(self.path, "src/main.py"):
121
286
  start_cmd = '"python src/main.py"'
122
287
  else:
123
- start_cmd = '"python -c \'print(\\\"Hello, World!\\\")\'"'
288
+ start_cmd = '"python -c \'print(\\\"No start command detected, please provide a start command manually\\\")\'"'
124
289
  return {"start": start_cmd}
125
290
 
126
291
  def assets(self) -> Optional[Dict[str, str]]:
@@ -137,5 +302,9 @@ class PythonProvider:
137
302
  # For Django projects, generate an empty env dict to surface the field
138
303
  # in the Shipit file. Other Python projects omit it by default.
139
304
  return {
140
- "PYTHONPATH": "\"{}/lib/python{}/site-packages\".format(venv[\"serve\"], python_version)"
305
+ "PYTHONPATH": "python_serve_path"
141
306
  }
307
+
308
+ def format_app_import(asgi_application: str) -> str:
309
+ # Transform "mysite.asgi.application" to "mysite.asgi:application" using regex
310
+ return re.sub(r"\.([^.]+)$", r":\1", asgi_application)
shipit/version.py CHANGED
@@ -1,5 +1,5 @@
1
1
  __all__ = ["version", "version_info"]
2
2
 
3
3
 
4
- version = "0.3.4"
5
- version_info = (0, 3, 4, "final", 0)
4
+ version = "0.4.1"
5
+ version_info = (0, 4, 1, "final", 0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shipit-cli
3
- Version: 0.3.4
3
+ Version: 0.4.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,7 +1,7 @@
1
1
  shipit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- shipit/cli.py,sha256=extuf-zJzjddNXbQR5m-e_SfpdtJmL7WaFHSsBtZZqM,48402
3
- shipit/generator.py,sha256=qU619Qa17nqERTC2kf0-TDUFUz4x5BQw9Wf6_wuJ0ow,5513
4
- shipit/version.py,sha256=YaMLXH-p3IyMxUWoEEPfD6W-m8ydr8DTML_Nv8fy5HM,95
2
+ shipit/cli.py,sha256=5b5e1H-SscXBraZXxJ5cVs_IJSNqjWUt2IjcZtOdALE,49615
3
+ shipit/generator.py,sha256=4pezEo4OzpDFSFqiFpGCpdwo72VkItS490R_f2rIz2k,5755
4
+ shipit/version.py,sha256=snMorrRb7vcsXgkcL5T056NoRLUWbc6L5Mope2nDAbU,95
5
5
  shipit/assets/php/php.ini,sha256=f4irndAjB4GuuouEImRkNV22Q-yw1KqR-43jAMDw730,2531
6
6
  shipit/providers/base.py,sha256=a_5VA1tV4_QbH83yjPCTHsNR23EJT2CiKUpWA_pu_lo,2373
7
7
  shipit/providers/gatsby.py,sha256=uwNjIJloS9JwKXqkbhihgdTTpJL4iL4bLCZ5kuzqqNs,2138
@@ -10,10 +10,10 @@ shipit/providers/laravel.py,sha256=rDpfx7RyF4sK0xxDAWefX0IiguU2xdgEXP2jJp1Jdzo,2
10
10
  shipit/providers/mkdocs.py,sha256=QJFNt7QWMvYbWeo7WjOgFVKGVcdUnUmUNfFbe4c8ThM,2812
11
11
  shipit/providers/node_static.py,sha256=K55BXkNz4QXSXR2wdg5diP6HLpmksC70ph16zox7v6Y,2309
12
12
  shipit/providers/php.py,sha256=Hmtv47K5qtYbQ3v9SSk-_KTNlhXedStg2MxhTTOK9ac,2594
13
- shipit/providers/python.py,sha256=h6QZ9gDwQu2e-FaIVLrogHXpr6FwofhQZNFdZK0aw8A,6393
13
+ shipit/providers/python.py,sha256=nupUuxNV4n-F-vaDbJLZd_O7cChs2ZNZhdAYR611VCg,13436
14
14
  shipit/providers/registry.py,sha256=UisII1dr24ZxmDD8GnpTsyNwPN9W8MnAHQ1Px1iJ-OQ,661
15
15
  shipit/providers/staticfile.py,sha256=Y4oqw6dNDU2crzcWQ5SEgnXHoDy0CXRntABwlgdf1mo,1827
16
- shipit_cli-0.3.4.dist-info/METADATA,sha256=6EE4IOFyNKDiXZW6aN1Gww_ZUT_fz-heyN8GFMBdca4,462
17
- shipit_cli-0.3.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- shipit_cli-0.3.4.dist-info/entry_points.txt,sha256=7AE1NjSrHaSDfbfsRRO50KKnHFTbB0Imsccd1WynzAQ,72
19
- shipit_cli-0.3.4.dist-info/RECORD,,
16
+ shipit_cli-0.4.1.dist-info/METADATA,sha256=tSa032WmxWchuT6sbwTZPH3xeO5mH5ZWZgReEj780eE,462
17
+ shipit_cli-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ shipit_cli-0.4.1.dist-info/entry_points.txt,sha256=7AE1NjSrHaSDfbfsRRO50KKnHFTbB0Imsccd1WynzAQ,72
19
+ shipit_cli-0.4.1.dist-info/RECORD,,