fujin-cli 0.7.1__py3-none-any.whl → 0.9.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.

Potentially problematic release.


This version of fujin-cli might be problematic. Click here for more details.

fujin/commands/_base.py CHANGED
@@ -33,6 +33,14 @@ class BaseCommand:
33
33
  def app_dir(self) -> str:
34
34
  return self.config.host.get_app_dir(app_name=self.config.app_name)
35
35
 
36
+ @cached_property
37
+ def hook_manager(self) -> HookManager:
38
+ return HookManager(
39
+ hooks=self.config.hooks,
40
+ app_name=self.config.app_name,
41
+ local_config_dir=self.config.local_config_dir,
42
+ )
43
+
36
44
  @contextmanager
37
45
  def connection(self):
38
46
  with host_connection(host=self.config.host) as conn:
@@ -70,8 +78,3 @@ class BaseCommand:
70
78
 
71
79
  def create_process_manager(self, conn: Connection) -> ProcessManager:
72
80
  return self.process_manager_class.create(conn=conn, config=self.config)
73
-
74
- def create_hook_manager(self, conn: Connection) -> HookManager:
75
- return HookManager(
76
- conn=conn, hooks=self.config.hooks, app_name=self.config.app_name
77
- )
fujin/commands/config.py CHANGED
@@ -37,9 +37,17 @@ class ConfigCMD(BaseCommand):
37
37
  )
38
38
  )
39
39
 
40
+ host_config = {
41
+ f: getattr(self.config.host, f) for f in self.config.host.__struct_fields__
42
+ }
43
+ host_config.pop("_key_filename")
44
+ host_config.pop("_env_file")
45
+ host_config.pop("env_content")
46
+ if self.config.host.key_filename:
47
+ host_config["key_filename"] = self.config.host.key_filename
48
+ host_config["env_content"] = self.config.host.env_content
40
49
  host_config_text = "\n".join(
41
- f"[dim]{key}:[/dim] {value}"
42
- for key, value in self.config.host.to_dict().items()
50
+ f"[dim]{key}:[/dim] {value}" for key, value in host_config.items()
43
51
  )
44
52
  console.print(
45
53
  Panel(
fujin/commands/deploy.py CHANGED
@@ -16,15 +16,15 @@ from fujin.secrets import resolve_secrets
16
16
  )
17
17
  class Deploy(BaseCommand):
18
18
  def __call__(self):
19
+ self.hook_manager.pre_build()
19
20
  parsed_env = self.parse_envfile()
20
21
  self.build_app()
21
-
22
+ self.hook_manager.pre_deploy()
22
23
  with self.connection() as conn:
23
24
  process_manager = self.create_process_manager(conn)
24
25
  conn.run(f"mkdir -p {self.app_dir}")
25
26
  conn.run(f"mkdir -p {self.versioned_assets_dir}")
26
27
  with conn.cd(self.app_dir):
27
- self.create_hook_manager(conn).pre_deploy()
28
28
  self.transfer_files(conn, env=parsed_env)
29
29
  self.install_project(conn)
30
30
  with self.app_environment() as app_conn:
@@ -35,7 +35,7 @@ class Deploy(BaseCommand):
35
35
  self.create_web_proxy(app_conn).setup()
36
36
  self.update_version_history(app_conn)
37
37
  self.prune_assets(app_conn)
38
- self.create_hook_manager(conn).post_deploy()
38
+ self.hook_manager.post_deploy()
39
39
  self.stdout.output("[green]Project deployment completed successfully![/green]")
40
40
  self.stdout.output(
41
41
  f"[blue]Access the deployed project at: https://{self.config.host.domain_name}[/blue]"
@@ -52,12 +52,12 @@ class Deploy(BaseCommand):
52
52
  return f"{self.app_dir}/v{self.config.version}"
53
53
 
54
54
  def parse_envfile(self) -> str:
55
- if not self.config.host.envfile.exists():
56
- raise cappa.Exit(f"{self.config.host.envfile} not found", code=1)
57
55
  if self.config.secret_config:
58
56
  self.stdout.output("[blue]Reading secrets....[/blue]")
59
- return resolve_secrets(self.config.host.envfile, self.config.secret_config)
60
- return self.config.host.envfile.read_text()
57
+ return resolve_secrets(
58
+ self.config.host.env_content, self.config.secret_config
59
+ )
60
+ return self.config.host.env_content
61
61
 
62
62
  def transfer_files(
63
63
  self, conn: Connection, env: str, skip_requirements: bool = False
fujin/commands/down.py CHANGED
@@ -33,8 +33,6 @@ class Down(BaseCommand):
33
33
  if not confirm:
34
34
  return
35
35
  with self.connection() as conn:
36
- hook_manager = self.create_hook_manager(conn)
37
- hook_manager.pre_teardown()
38
36
  process_manager = self.create_process_manager(conn)
39
37
  conn.run(f"rm -rf {self.app_dir}")
40
38
  self.create_web_proxy(conn).teardown()
@@ -42,7 +40,6 @@ class Down(BaseCommand):
42
40
  process_manager.reload_configuration()
43
41
  if self.full:
44
42
  self.create_web_proxy(conn).uninstall()
45
- hook_manager.post_teardown()
46
43
  self.stdout.output(
47
44
  "[green]Project teardown completed successfully![/green]"
48
45
  )
fujin/commands/init.py CHANGED
@@ -31,7 +31,7 @@ class Init(BaseCommand):
31
31
  }
32
32
  app_name = Path().resolve().stem.replace("-", "_").replace(" ", "_").lower()
33
33
  config = profile_to_func[self.profile](app_name)
34
- fujin_toml.write_text(tomli_w.dumps(config))
34
+ fujin_toml.write_text(tomli_w.dumps(config, multiline_strings=True))
35
35
  self.stdout.output(
36
36
  "[green]Sample configuration file generated successfully![/green]"
37
37
  )
@@ -57,7 +57,7 @@ def simple_config(app_name) -> dict:
57
57
  "host": {
58
58
  "user": "root",
59
59
  "domain_name": f"{app_name}.com",
60
- "envfile": ".env.prod",
60
+ "env_content": f"DEBUG=False\nALLOWED_HOSTS={app_name}.com\n",
61
61
  },
62
62
  }
63
63
  if not Path(".python-version").exists():
@@ -11,8 +11,8 @@ class Printenv(BaseCommand):
11
11
  def __call__(self):
12
12
  if self.config.secret_config:
13
13
  result = resolve_secrets(
14
- self.config.host.envfile, self.config.secret_config
14
+ self.config.host.env_content, self.config.secret_config
15
15
  )
16
16
  else:
17
- result = self.config.host.envfile.read_text()
17
+ result = self.config.host.env_content
18
18
  self.stdout.output(result)
@@ -5,22 +5,21 @@ from pathlib import Path
5
5
 
6
6
  import cappa
7
7
 
8
- from .deploy import Deploy
9
8
  from fujin.commands import BaseCommand
10
9
  from fujin.config import InstallationMode
11
10
  from fujin.connection import Connection
11
+ from .deploy import Deploy
12
12
 
13
13
 
14
14
  @cappa.command(help="Redeploy the application to apply code and environment changes")
15
15
  class Redeploy(BaseCommand):
16
16
  def __call__(self):
17
17
  deploy = Deploy()
18
+ self.hook_manager.pre_build()
18
19
  parsed_env = deploy.parse_envfile()
19
20
  deploy.build_app()
20
-
21
+ self.hook_manager.pre_deploy()
21
22
  with self.app_environment() as conn:
22
- hook_manager = self.create_hook_manager(conn)
23
- hook_manager.pre_deploy()
24
23
  conn.run(f"mkdir -p {deploy.versioned_assets_dir}")
25
24
  requirements_copied = self._copy_requirements_if_needed(conn)
26
25
  deploy.transfer_files(
@@ -30,8 +29,8 @@ class Redeploy(BaseCommand):
30
29
  deploy.release(conn)
31
30
  self.create_process_manager(conn).restart_services()
32
31
  deploy.update_version_history(conn)
33
- hook_manager.post_deploy()
34
- self.stdout.output("[green]Redeployment completed successfully![/green]")
32
+ self.hook_manager.post_deploy()
33
+ self.stdout.output("[green]Redeployment completed successfully![/green]")
35
34
 
36
35
  def _copy_requirements_if_needed(self, conn: Connection) -> bool:
37
36
  if (
fujin/commands/server.py CHANGED
@@ -23,8 +23,6 @@ class Server(BaseCommand):
23
23
  @cappa.command(help="Setup uv, web proxy, and install necessary dependencies")
24
24
  def bootstrap(self):
25
25
  with self.connection() as conn:
26
- hook_manager = self.create_hook_manager(conn)
27
- hook_manager.pre_bootstrap()
28
26
  conn.run("sudo apt update && sudo apt upgrade -y", pty=True)
29
27
  conn.run("sudo apt install -y sqlite3 curl rsync", pty=True)
30
28
  result = conn.run("command -v uv", warn=True)
@@ -33,7 +31,6 @@ class Server(BaseCommand):
33
31
  conn.run("uv tool update-shell")
34
32
  conn.run("uv tool install fastfetch-bin-edge")
35
33
  self.create_web_proxy(conn).install()
36
- hook_manager.post_bootstrap()
37
34
  self.stdout.output(
38
35
  "[green]Server bootstrap completed successfully![/green]"
39
36
  )
fujin/config.py CHANGED
@@ -136,10 +136,18 @@ The login user for running remote tasks. Should have passwordless sudo access fo
136
136
 
137
137
  You can create a user with these requirements using the ``fujin server create-user`` command.
138
138
 
139
- envfile
140
- ~~~~~~~
139
+ env_file
140
+ ~~~~~~~~
141
141
  Path to the production environment file that will be copied to the host.
142
142
 
143
+ env_content
144
+ ~~~~~~~~~~~
145
+ A string containing the production environment variables, ideal for scenarios where most variables are retrieved from secrets and you prefer not to use a separate file.
146
+
147
+ .. important::
148
+
149
+ ``env_file`` and ``env_content`` are mutually exclusive—you can define only one.
150
+
143
151
  apps_dir
144
152
  ~~~~~~~~
145
153
 
@@ -176,6 +184,9 @@ Example:
176
184
  dbconsole = "app exec -i dbshell" # open an interactive django database shell
177
185
  shell = "server exec --appenv -i bash" # SSH into the project directory with environment variables loaded
178
186
 
187
+ hooks
188
+ -----
189
+ Run custom scripts at specific points with hooks. Check out the `hooks </hooks.html>`_ page for more information.
179
190
 
180
191
  """
181
192
 
@@ -268,28 +279,27 @@ class HostConfig(msgspec.Struct, kw_only=True):
268
279
  ip: str | None = None
269
280
  domain_name: str
270
281
  user: str
271
- _envfile: str = msgspec.field(name="envfile")
282
+ _env_file: str = msgspec.field(name="envfile", default="")
283
+ env_content: str = ""
272
284
  apps_dir: str = ".local/share/fujin"
273
285
  password_env: str | None = None
274
286
  ssh_port: int = 22
275
287
  _key_filename: str | None = msgspec.field(name="key_filename", default=None)
276
288
 
277
289
  def __post_init__(self):
290
+ if self._env_file and self.env_content:
291
+ raise ImproperlyConfiguredError(
292
+ "Cannot set both 'env_content' and 'env_file' properties."
293
+ )
294
+ if not self.env_content:
295
+ envfile = Path(self._env_file)
296
+ if not envfile.exists():
297
+ raise ImproperlyConfiguredError(f"{self._env_file} not found")
298
+ self.env_content = envfile.read_text()
299
+ self.env_content = self.env_content.strip()
278
300
  self.apps_dir = f"/home/{self.user}/{self.apps_dir}"
279
301
  self.ip = self.ip or self.domain_name
280
302
 
281
- def to_dict(self):
282
- d = {f: getattr(self, f) for f in self.__struct_fields__}
283
- d.pop("_key_filename")
284
- d.pop("_envfile")
285
- d["key_filename"] = self.key_filename
286
- d["envfile"] = self.envfile
287
- return d
288
-
289
- @property
290
- def envfile(self) -> Path:
291
- return Path(self._envfile)
292
-
293
303
  @property
294
304
  def key_filename(self) -> Path | None:
295
305
  if self._key_filename:
fujin/hooks.py CHANGED
@@ -1,9 +1,10 @@
1
+ import subprocess
1
2
  from dataclasses import dataclass
3
+ from pathlib import Path
2
4
 
5
+ import cappa
3
6
  from rich import print as rich_print
4
7
 
5
- from fujin.connection import Connection
6
-
7
8
  try:
8
9
  from enum import StrEnum
9
10
  except ImportError:
@@ -14,43 +15,45 @@ except ImportError:
14
15
 
15
16
 
16
17
  class Hook(StrEnum):
18
+ PRE_BUILD = "pre_build"
17
19
  PRE_DEPLOY = "pre_deploy"
18
20
  POST_DEPLOY = "post_deploy"
19
- PRE_BOOTSTRAP = "pre_bootstrap"
20
- POST_BOOTSTRAP = "post_bootstrap"
21
- PRE_TEARDOWN = "pre_teardown"
22
- POST_TEARDOWN = "post_teardown"
23
21
 
24
22
 
25
- HooksDict = dict[Hook, dict]
23
+ HooksDict = dict[Hook, str]
26
24
 
27
25
 
28
- @dataclass(frozen=True, slots=True)
26
+ @dataclass(slots=True)
29
27
  class HookManager:
30
28
  app_name: str
31
29
  hooks: HooksDict
32
- conn: Connection
30
+ local_config_dir: Path
31
+
32
+ def __post_init__(self):
33
+ if self.hooks:
34
+ return
35
+ hooks_folder = self.local_config_dir / "hooks"
36
+ if not hooks_folder.exists():
37
+ return
38
+ self.hooks = {
39
+ h.value: f"./{hooks_folder / h.value}" # noqa
40
+ for h in Hook
41
+ if (hooks_folder / h.value).exists() # noqa
42
+ }
33
43
 
34
44
  def _run_hook(self, type_: Hook) -> None:
35
- if hooks := self.hooks.get(type_):
36
- for name, command in hooks.items():
37
- rich_print(f"[blue]Running {type_} hook {name} [/blue]")
38
- self.conn.run(command, pty=True)
45
+ if cmd := self.hooks.get(type_):
46
+ rich_print(f"[blue]Running {type_} hook[/blue]")
47
+ result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
48
+ if result.returncode != 0:
49
+ raise cappa.Exit(result.stderr)
50
+ rich_print(result.stdout)
51
+
52
+ def pre_build(self) -> None:
53
+ self._run_hook(Hook.PRE_BUILD)
39
54
 
40
55
  def pre_deploy(self) -> None:
41
56
  self._run_hook(Hook.PRE_DEPLOY)
42
57
 
43
58
  def post_deploy(self) -> None:
44
59
  self._run_hook(Hook.POST_DEPLOY)
45
-
46
- def pre_bootstrap(self) -> None:
47
- self._run_hook(Hook.PRE_BOOTSTRAP)
48
-
49
- def post_bootstrap(self) -> None:
50
- self._run_hook(Hook.POST_BOOTSTRAP)
51
-
52
- def pre_teardown(self) -> None:
53
- self._run_hook(Hook.PRE_TEARDOWN)
54
-
55
- def post_teardown(self) -> None:
56
- self._run_hook(Hook.POST_TEARDOWN)
fujin/secrets/__init__.py CHANGED
@@ -1,19 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
- from pathlib import Path
4
- from typing import Callable
3
+ from contextlib import closing
4
+ from io import StringIO
5
+ from typing import Callable, ContextManager
5
6
 
6
7
  import gevent
7
8
  from dotenv import dotenv_values
8
9
 
9
- from .bitwarden import bitwarden
10
- from .onepassword import one_password
11
10
  from fujin.config import SecretAdapter
12
11
  from fujin.config import SecretConfig
13
-
12
+ from .bitwarden import bitwarden
13
+ from .onepassword import one_password
14
14
 
15
15
  secret_reader = Callable[[str], str]
16
- secret_adapter_context = Callable[[SecretConfig], secret_reader]
16
+ secret_adapter_context = Callable[[SecretConfig], ContextManager[secret_reader]]
17
17
 
18
18
  adapter_to_context: dict[SecretAdapter, secret_adapter_context] = {
19
19
  SecretAdapter.BITWARDEN: bitwarden,
@@ -21,14 +21,17 @@ adapter_to_context: dict[SecretAdapter, secret_adapter_context] = {
21
21
  }
22
22
 
23
23
 
24
- def resolve_secrets(envfile: Path, secret_config: SecretConfig) -> str:
25
- env_dict = dotenv_values(envfile)
24
+ def resolve_secrets(env_content: str, secret_config: SecretConfig) -> str:
25
+ with closing(StringIO(env_content)) as buffer:
26
+ env_dict = dotenv_values(stream=buffer)
26
27
  secrets = {key: value for key, value in env_dict.items() if value.startswith("$")}
28
+ if not secrets:
29
+ return env_content
27
30
  adapter_context = adapter_to_context[secret_config.adapter]
28
31
  parsed_secrets = {}
29
- with adapter_context(secret_config) as secret_reader:
32
+ with adapter_context(secret_config) as reader:
30
33
  for key, secret in secrets.items():
31
- parsed_secrets[key] = gevent.spawn(secret_reader, secret[1:])
34
+ parsed_secrets[key] = gevent.spawn(reader, secret[1:])
32
35
  gevent.joinall(parsed_secrets.values())
33
36
  env_dict.update({key: thread.value for key, thread in parsed_secrets.items()})
34
37
  return "\n".join(f'{key}="{value}"' for key, value in env_dict.items())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fujin-cli
3
- Version: 0.7.1
3
+ Version: 0.9.0
4
4
  Summary: Get your project up and running in a few minutes on your own vps.
5
5
  Project-URL: Documentation, https://github.com/falcopackages/fujin#readme
6
6
  Project-URL: Issues, https://github.com/falcopackages/fujin/issues
@@ -1,23 +1,23 @@
1
1
  fujin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  fujin/__main__.py,sha256=VJMBzuQuxkQaAKNEySktGnms944bkRsrIAjj-XeaWR8,1813
3
- fujin/config.py,sha256=pdWK4fM3BITpz0MAv_ydCXRcQbgC3eLz--APdOpehiQ,10412
3
+ fujin/config.py,sha256=LjgOTBumgOQTHkgXLJ0Rb2ERz_WBO-8rDj1hTYo-z0s,11027
4
4
  fujin/connection.py,sha256=LL7LhX9p0X9FmiGdlSroD3Ht216QY0Kd51xkSrXmM3s,2479
5
5
  fujin/errors.py,sha256=74Rh-Sgql1YspPdR_akQ2G3xZ48zecyafYCptpaFo1A,73
6
- fujin/hooks.py,sha256=nTn2PHpFuKbl_iAXUxagkrGcuwT2Exx8zIq4YfleX8A,1351
6
+ fujin/hooks.py,sha256=EDVYNozlDJ5kc1xHtZrXgtuKplUMEMPTp65TLMP02Ek,1449
7
7
  fujin/commands/__init__.py,sha256=g0b13vzidPUbxne_Zo_Wv5jpEmwCiSK4AokCinH9uo4,39
8
- fujin/commands/_base.py,sha256=NfBdRBx_l7tYnVg0L8A3tPFC_XmtuHq-toU66KT5CiE,2553
8
+ fujin/commands/_base.py,sha256=Rtf7kcO_Pg2JxPNKsXEx0UdhqfzYU6g7IiezC8cqEPQ,2610
9
9
  fujin/commands/app.py,sha256=mazb4dCTdR5juh79bL3a9b68Nd6O8u_nR9IgYqQlqWE,5279
10
- fujin/commands/config.py,sha256=xdfd1OZLxw2YZldiAbW5rq5EBXEaXbUC-I7FKLRfzIQ,2387
11
- fujin/commands/deploy.py,sha256=PfEdLS-rkdF12BfjtLEyqbnn1K4dEYRh7h6CFtWb2zs,5934
10
+ fujin/commands/config.py,sha256=WymGla-H2yduhLcYE1Nb6IJaFIj0S0KIhV9fBDCKsAw,2779
11
+ fujin/commands/deploy.py,sha256=AylcVNX47ekf-AFfQrYcSh86I3qltADEgjPtV9lQAfE,5831
12
12
  fujin/commands/docs.py,sha256=b5FZ8AgoAfn4q4BueEQvM2w5HCuh8-rwBqv_CRFVU8E,349
13
- fujin/commands/down.py,sha256=v1lAq70ApktjeHRB_1sCzjmKH8t6EXqyL4RTt7OE-f0,1716
14
- fujin/commands/init.py,sha256=zREfkIQyC6etqqQ6hgvDqpNNWQT4bk_8IOPBBT5-YUE,3983
15
- fujin/commands/printenv.py,sha256=bpGmOfc1t_dKWb8gy7EILYtwEyI9pIwhKg2XPKyJ9cQ,527
13
+ fujin/commands/down.py,sha256=aw_mxl_TMC66plKTXwlYP1W2XQBmHeROltQqOpQssyE,1577
14
+ fujin/commands/init.py,sha256=AHAZeYkSk-zShF288Zs4rCfVwalCUqeMXWR6GTks2v0,4046
15
+ fujin/commands/printenv.py,sha256=bS2mIgk7zd_w3yDhZLTa1PkHIzuSbBWjnYyM8CUjX0k,523
16
16
  fujin/commands/proxy.py,sha256=ajXwboS0gDDiMWW7b9rtWU6WPF1h7JYYeycDyU-hQfg,3053
17
17
  fujin/commands/prune.py,sha256=C2aAN6AUS84jgRg1eiCroyiuZyaZDmf5yvGAQY9xkcg,1517
18
- fujin/commands/redeploy.py,sha256=z1giY9SpINdJt8UagPlvUkJu30c8fgqapNqOxG4Jfuo,2378
18
+ fujin/commands/redeploy.py,sha256=491Mzz0qiaHqcI7BFUtZq-M34WQkiBd2ZgQbLRLp8T8,2355
19
19
  fujin/commands/rollback.py,sha256=JsocJzQcdQelSnYD94klhjBh8UKkkdiRD9shfUfo4FI,2032
20
- fujin/commands/server.py,sha256=0N_P_Luj31t56riZ8GfgRqW3vRHiw0cDrlp3PFoyWn8,3453
20
+ fujin/commands/server.py,sha256=-3-PyBNR0fGm-RYE3fz50kP-LSDYGU9BzUxrbGZEghc,3312
21
21
  fujin/commands/up.py,sha256=OEK_n-6-mnnIUffFpR7QtVunr1V1F04pxlAAS1U62BY,419
22
22
  fujin/process_managers/__init__.py,sha256=MhhfTBhm64zWRAKgjvsZRIToOUJus60vGScbAjqpQ6Y,994
23
23
  fujin/process_managers/systemd.py,sha256=qG_4Ew8SEWtaTFOAW_XZXsMO2WjFWZ4dp5nBwAPBObk,5603
@@ -25,14 +25,14 @@ fujin/proxies/__init__.py,sha256=UuWYU175tkdaz1WWRCDDpQgGfFVYYNR9PBxA3lTCNr0,695
25
25
  fujin/proxies/caddy.py,sha256=dzLD8s664_kIK-1hCE3y50JIwBd8kK9yS1LynUDRVSE,7908
26
26
  fujin/proxies/dummy.py,sha256=qBKSn8XNEA9SVwB7GzRNX2l9Iw6tUjo2CFqZjWi0FjY,465
27
27
  fujin/proxies/nginx.py,sha256=BNJNLxLLRVAmBIGVCk8pb16iiSJsOI9jXOZhdSQGtX8,4151
28
- fujin/secrets/__init__.py,sha256=p4rY4J7yRoEEz6OXkomJ_Ov2AaaQ37-Zd_TJGpUDPgQ,1217
28
+ fujin/secrets/__init__.py,sha256=hiNZIBtOwM0WQbvAF3GhuMMCsQCjXjiSr5gTLAGN-VI,1375
29
29
  fujin/secrets/bitwarden.py,sha256=01GZL5hYwZzL6yXy5ab3L3kgBFBeOT8i3Yg9GC8YwFU,2008
30
30
  fujin/secrets/onepassword.py,sha256=6Xj3XWttKfcjMbcoMZvXVpJW1KHxlD785DysmX_mqvk,654
31
31
  fujin/templates/simple.service,sha256=-lyKjmSyfHGucP4O_vRQE1NNaHq0Qjsc0twdwoRLgI0,321
32
32
  fujin/templates/web.service,sha256=NZ7ZeaFvV_MZTBn8QqRQeu8PIrWHf3aWYWNzjOQeqCw,685
33
33
  fujin/templates/web.socket,sha256=2lJsiOHlMJL0YlN7YBLLnr5zqsytPEt81yP34nk0dmc,173
34
- fujin_cli-0.7.1.dist-info/METADATA,sha256=kqTIyfQ_EtbpdoaSkBmrF5Wy6y838HLaqLJHbLko3L4,4576
35
- fujin_cli-0.7.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
36
- fujin_cli-0.7.1.dist-info/entry_points.txt,sha256=Y_TBtKt3j11qhwquMexZR5yqnDEqOBDACtresqQFE-s,46
37
- fujin_cli-0.7.1.dist-info/licenses/LICENSE.txt,sha256=0QF8XfuH0zkIHhSet6teXfiCze6JSdr8inRkmLLTDyo,1099
38
- fujin_cli-0.7.1.dist-info/RECORD,,
34
+ fujin_cli-0.9.0.dist-info/METADATA,sha256=0OCkg_YffO4PLhTp1AKrk7OmncaCbZOO3IyTYkjGXxI,4576
35
+ fujin_cli-0.9.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
36
+ fujin_cli-0.9.0.dist-info/entry_points.txt,sha256=Y_TBtKt3j11qhwquMexZR5yqnDEqOBDACtresqQFE-s,46
37
+ fujin_cli-0.9.0.dist-info/licenses/LICENSE.txt,sha256=0QF8XfuH0zkIHhSet6teXfiCze6JSdr8inRkmLLTDyo,1099
38
+ fujin_cli-0.9.0.dist-info/RECORD,,