shipit-cli 0.10.0__py3-none-any.whl → 0.11.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/assets/php/php.ini CHANGED
@@ -22,6 +22,8 @@ error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
22
22
  display_errors = Off
23
23
  display_startup_errors = Off
24
24
  log_errors = On
25
+ access_log = /dev/stdout
26
+ error_log = /dev/stderr
25
27
  ignore_repeated_errors = Off
26
28
  ignore_repeated_source = Off
27
29
  report_memleaks = On
@@ -1,15 +1,20 @@
1
1
  # Needed to get the WP-CLI commands to avoid asking for the TTY size
2
- export COLUMNS=80
2
+ IFS=$'\n\t'
3
+
4
+ export COLUMNS=80 # Prevent WP-CLI from asking for TTY size
5
+ export PAGER="cat"
6
+
7
+ echo "🚀 Starting WordPress setup..."
3
8
 
4
9
  echo "Creating required directories..."
5
10
 
6
11
  mkdir -p wp-content/plugins
7
12
  mkdir -p wp-content/upgrade
8
13
 
9
- echo "Installing WordPress core..."
14
+ echo "Installing WordPress core"
10
15
 
11
16
  wp core install \
12
- --url="$WP_SITE_URL" \
17
+ --url="$WP_SITEURL" \
13
18
  --title="$WP_SITE_TITLE" \
14
19
  --admin_user="$WP_ADMIN_USERNAME" \
15
20
  --admin_password="$WP_ADMIN_PASSWORD" \
@@ -17,9 +22,63 @@ wp core install \
17
22
  --locale="$WP_LOCALE"
18
23
 
19
24
 
20
- if [ -z "$WP_UPDATE_DB" ]; then
25
+ if [ "${WP_UPDATE_DB:-false}" = "true" ]; then
21
26
  echo "Updating database..."
22
27
  wp core update-db
23
28
  fi
24
29
 
25
- echo "Installation complete"
30
+ # Install plugins from WP_PLUGINS environment variable
31
+ if [ -n "${WP_PLUGINS:-}" ]; then
32
+ echo "Installing plugins from WP_PLUGINS: $WP_PLUGINS"
33
+
34
+ IFS=',' # Split by commas
35
+ for PLUGIN_ENTRY in $WP_PLUGINS; do
36
+ if [[ "$PLUGIN_ENTRY" =~ ^https?:// ]]; then
37
+ echo "Installing plugin from URL: $PLUGIN_ENTRY"
38
+ wp plugin install "$PLUGIN_ENTRY" --activate
39
+ else
40
+ # Extract name and version using parameter expansion
41
+ PLUGIN_NAME="${PLUGIN_ENTRY%%:*}"
42
+ PLUGIN_VERSION="${PLUGIN_ENTRY#*:}"
43
+
44
+ if [[ "$PLUGIN_NAME" == "$PLUGIN_VERSION" ]]; then
45
+ echo "Installing plugin '${PLUGIN_NAME}' (latest version)..."
46
+ wp plugin install "$PLUGIN_NAME" --activate
47
+ else
48
+ echo "Installing plugin '${PLUGIN_NAME}' (version: ${PLUGIN_VERSION})..."
49
+ wp plugin install "$PLUGIN_NAME" --version="$PLUGIN_VERSION" --activate
50
+ fi
51
+ fi
52
+ done
53
+ fi
54
+
55
+ # Install themes from WP_THEMES environment variable
56
+ if [ -n "${WP_THEMES:-}" ]; then
57
+ echo "🎨 Installing themes from WP_THEMES: $WP_THEMES"
58
+ IFS=','
59
+
60
+ for THEME_ENTRY in $WP_THEMES; do
61
+ if [[ "$THEME_ENTRY" =~ ^https?:// ]]; then
62
+ echo "Installing theme from URL: $THEME_ENTRY"
63
+ wp theme install "$THEME_ENTRY"
64
+ else
65
+ THEME_NAME="${THEME_ENTRY%%:*}"
66
+ THEME_VERSION="${THEME_ENTRY#*:}"
67
+
68
+ if [[ "$THEME_NAME" == "$THEME_VERSION" ]]; then
69
+ echo "Installing theme '${THEME_NAME}' (latest version)..."
70
+ wp theme install "$THEME_NAME"
71
+ else
72
+ echo "Installing theme '${THEME_NAME}' (version: ${THEME_VERSION})..."
73
+ wp theme install "$THEME_NAME" --version="$THEME_VERSION"
74
+ fi
75
+ fi
76
+ done
77
+ fi
78
+
79
+ if [ -n "${WP_DEFAULT_THEME:-}" ]; then
80
+ echo "Activating default theme: $WP_DEFAULT_THEME"
81
+ wp theme activate "$WP_DEFAULT_THEME"
82
+ fi
83
+
84
+ echo "✅ WordPress Installation complete"
@@ -21,7 +21,6 @@ define( 'WP_AUTO_UPDATE_CORE', false); // Disable automatic aupdates and checks
21
21
  * @package WordPress
22
22
  */
23
23
 
24
-
25
24
  function get_env_var(string $name, string $default = ''): string
26
25
  {
27
26
  if (isset($_ENV[$name])) {
@@ -29,14 +28,17 @@ function get_env_var(string $name, string $default = ''): string
29
28
  }
30
29
 
31
30
  if ($default === '') {
32
- $stderr = fopen("php://stderr", "wb");
33
- fwrite($stderr, "Configuration error: environment variable " . $name . " not provided. Using default value: " . $default . PHP_EOL);
34
- fclose($stderr);
31
+ error_log("Configuration error: environment variable " . $name . " not provided.");
35
32
  }
36
33
 
37
34
  return $default;
38
35
  }
39
36
 
37
+ function get_env_var_bool(string $name, bool $default = false): bool
38
+ {
39
+ return in_array(get_env_var($name, $default ? "1" : "0"), ["1", "true", "yes", "on", "y"], true);
40
+ }
41
+
40
42
  // ** Database settings - You can get this info from your web host ** //
41
43
  /** The name of the database for WordPress */
42
44
  define( 'DB_NAME', get_env_var('DB_NAME', 'wordpress') );
@@ -87,14 +89,14 @@ define('NONCE_SALT', get_env_var('NONCE_SALT', 'no secret provided'));
87
89
  $scheme = isset( $_SERVER['HTTPS'] ) && '1' === (string) $_SERVER['HTTPS'] ? "https://" : "http://";
88
90
 
89
91
  if (!defined('WP_HOME')) {
90
- define( 'WP_HOME', isset($_SERVER['HTTP_HOST']) ? ($scheme . $_SERVER['HTTP_HOST'] ): "http://localhost");
92
+ define( 'WP_HOME', get_env_var('WP_HOME', isset($_SERVER['HTTP_HOST']) ? ($scheme . $_SERVER['HTTP_HOST'] ): "http://localhost"));
91
93
  }
92
94
 
93
- define( 'WP_SITEURL', WP_HOME . '/' );
95
+ define( 'WP_SITEURL', get_env_var('WP_SITEURL', WP_HOME . '/') );
94
96
 
95
- define( 'WP_MEMORY_LIMIT', '256M' );
96
- define( 'WP_MAX_MEMORY_LIMIT', '256M' );
97
- define( 'WP_POST_REVISIONS', false );
97
+ define( 'WP_MEMORY_LIMIT', get_env_var('WP_MEMORY_LIMIT', '256M') );
98
+ define( 'WP_MAX_MEMORY_LIMIT', get_env_var('WP_MAX_MEMORY_LIMIT', '256M') );
99
+ define( 'WP_POST_REVISIONS', get_env_var_bool('WP_POST_REVISIONS', false));
98
100
 
99
101
  /**#@-*/
100
102
 
@@ -118,11 +120,20 @@ $table_prefix = 'wp_';
118
120
  *
119
121
  * @link https://wordpress.org/support/article/debugging-in-wordpress/
120
122
  */
121
- define( 'WP_DEBUG', false );
123
+ define( 'WP_DEBUG', get_env_var_bool('WP_DEBUG', false) );
122
124
 
123
125
  /* Add any custom values between this line and the "stop editing" line. */
124
126
 
127
+ // Optionally include an additional wp-config.php file if defined
128
+ if ( getenv('WP_ADDITIONAL_CONFIG') ) {
129
+ $extra_config_path = getenv('WP_ADDITIONAL_CONFIG');
125
130
 
131
+ if ( file_exists( $extra_config_path ) ) {
132
+ require_once $extra_config_path;
133
+ } else {
134
+ error_log( "WP_ADDITIONAL_CONFIG defined but file not found: {$extra_config_path}" );
135
+ }
136
+ }
126
137
 
127
138
  /* That's all, stop editing! Happy publishing. */
128
139
 
shipit/cli.py CHANGED
@@ -16,6 +16,7 @@ from typing import (
16
16
  Optional,
17
17
  Protocol,
18
18
  Set,
19
+ Tuple,
19
20
  TypedDict,
20
21
  Union,
21
22
  Literal,
@@ -33,7 +34,7 @@ from rich.rule import Rule
33
34
  from rich.syntax import Syntax
34
35
 
35
36
  from shipit.version import version as shipit_version
36
- from shipit.generator import generate_shipit
37
+ from shipit.generator import generate_shipit, detect_provider
37
38
  from shipit.providers.base import CustomCommands
38
39
  from shipit.procfile import Procfile
39
40
  from dotenv import dotenv_values
@@ -88,11 +89,13 @@ class Serve:
88
89
  class Package:
89
90
  name: str
90
91
  version: Optional[str] = None
92
+ architecture: Optional[Literal["64-bit", "32-bit"]] = None
91
93
 
92
94
  def __str__(self) -> str: # pragma: no cover - simple representation
95
+ name = f"{self.name}({self.architecture})" if self.architecture else self.name
93
96
  if self.version is None:
94
- return self.name
95
- return f"{self.name}@{self.version}"
97
+ return name
98
+ return f"{name}@{self.version}"
96
99
 
97
100
 
98
101
  @dataclass
@@ -780,6 +783,25 @@ class WasmerBuilder:
780
783
  "dependencies": {
781
784
  "latest": "php/php-32@=8.3.2102",
782
785
  "8.3": "php/php-32@=8.3.2102",
786
+ "8.2": "php/php-32@=8.2.2801",
787
+ "8.1": "php/php-32@=8.1.3201",
788
+ "7.4": "php/php-32@=7.4.3301",
789
+ },
790
+ "architecture_dependencies": {
791
+ "64-bit": {
792
+ "latest": "php/php-64@=8.3.2102",
793
+ "8.3": "php/php-64@=8.3.2102",
794
+ "8.2": "php/php-64@=8.2.2801",
795
+ "8.1": "php/php-64@=8.1.3201",
796
+ "7.4": "php/php-64@=7.4.3301",
797
+ },
798
+ "32-bit": {
799
+ "latest": "php/php-32@=8.3.2102",
800
+ "8.3": "php/php-32@=8.3.2102",
801
+ "8.2": "php/php-32@=8.2.2801",
802
+ "8.1": "php/php-32@=8.1.3201",
803
+ "7.4": "php/php-32@=7.4.3301",
804
+ },
783
805
  },
784
806
  "scripts": {"php"},
785
807
  "aliases": {},
@@ -925,13 +947,20 @@ class WasmerBuilder:
925
947
  for dep in deps:
926
948
  if dep.name in self.mapper:
927
949
  version = dep.version or "latest"
928
- if version in self.mapper[dep.name]["dependencies"]:
950
+ mapped_dependencies = self.mapper[dep.name]["dependencies"]
951
+ if dep.architecture:
952
+ architecture_dependencies = (
953
+ self.mapper[dep.name]
954
+ .get("architecture_dependencies", {})
955
+ .get(dep.architecture, {})
956
+ )
957
+ if architecture_dependencies:
958
+ mapped_dependencies = architecture_dependencies
959
+ if version in mapped_dependencies:
929
960
  console.print(
930
961
  f"* {dep.name}@{version} mapped to {self.mapper[dep.name]['dependencies'][version]}"
931
962
  )
932
- package_name, version = self.mapper[dep.name]["dependencies"][
933
- version
934
- ].split("@")
963
+ package_name, version = mapped_dependencies[version].split("@")
935
964
  dependencies.add(package_name, version)
936
965
  scripts = self.mapper[dep.name].get("scripts") or []
937
966
  for script in scripts:
@@ -1156,6 +1185,7 @@ class Ctx:
1156
1185
  self.mounts: List[Mount] = []
1157
1186
  self.volumes: List[Volume] = []
1158
1187
  self.services: Dict[str, Service] = {}
1188
+ self.getenv_variables: Set[str] = set()
1159
1189
 
1160
1190
  def add_package(self, package: Package) -> str:
1161
1191
  index = f"{package.name}@{package.version}" if package.version else package.name
@@ -1202,10 +1232,16 @@ class Ctx:
1202
1232
  return f"ref:step:{len(self.steps) - 1}"
1203
1233
 
1204
1234
  def getenv(self, name: str) -> Optional[str]:
1235
+ self.getenv_variables.add(name)
1205
1236
  return self.builder.getenv(name)
1206
1237
 
1207
- def dep(self, name: str, version: Optional[str] = None) -> str:
1208
- package = Package(name, version)
1238
+ def dep(
1239
+ self,
1240
+ name: str,
1241
+ version: Optional[str] = None,
1242
+ architecture: Optional[Literal["64-bit", "32-bit"]] = None,
1243
+ ) -> str:
1244
+ package = Package(name, version, architecture)
1209
1245
  return self.add_package(package)
1210
1246
 
1211
1247
  def service(
@@ -1318,6 +1354,43 @@ class Ctx:
1318
1354
  }
1319
1355
 
1320
1356
 
1357
+ def evaluate_shipit(path: Path, builder: Builder) -> Tuple[Ctx, Serve]:
1358
+ shipit_file = path / "Shipit"
1359
+ if not shipit_file.exists():
1360
+ raise FileNotFoundError(
1361
+ f"Shipit file not found at {shipit_file}. Run `shipit generate {path}` to create it."
1362
+ )
1363
+ source = shipit_file.read_text()
1364
+ ctx = Ctx(builder)
1365
+ glb = sl.Globals.standard()
1366
+ mod = sl.Module()
1367
+
1368
+ mod.add_callable("service", ctx.service)
1369
+ mod.add_callable("getenv", ctx.getenv)
1370
+ mod.add_callable("dep", ctx.dep)
1371
+ mod.add_callable("serve", ctx.serve)
1372
+ mod.add_callable("run", ctx.run)
1373
+ mod.add_callable("mount", ctx.mount)
1374
+ mod.add_callable("volume", ctx.volume)
1375
+ mod.add_callable("workdir", ctx.workdir)
1376
+ mod.add_callable("copy", ctx.copy)
1377
+ mod.add_callable("path", ctx.path)
1378
+ mod.add_callable("env", ctx.env)
1379
+ mod.add_callable("use", ctx.use)
1380
+
1381
+ dialect = sl.Dialect.extended()
1382
+ dialect.enable_f_strings = True
1383
+
1384
+ ast = sl.parse("shipit", source, dialect=dialect)
1385
+
1386
+ sl.eval(mod, ast, glb)
1387
+ if not ctx.serves:
1388
+ raise ValueError(f"No serve definition found in {shipit_file}")
1389
+ assert len(ctx.serves) <= 1, "Only one serve is allowed for now"
1390
+ serve = next(iter(ctx.serves.values()))
1391
+ return ctx, serve
1392
+
1393
+
1321
1394
  def print_help() -> None:
1322
1395
  panel = Panel(
1323
1396
  f"Shipit {shipit_version}",
@@ -1528,7 +1601,8 @@ def generate(
1528
1601
  context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
1529
1602
  )
1530
1603
  def _default(ctx: typer.Context) -> None:
1531
- print_help()
1604
+ if ctx.invoked_subcommand is None:
1605
+ print_help()
1532
1606
 
1533
1607
 
1534
1608
  @app.command(name="deploy")
@@ -1612,6 +1686,103 @@ def serve(
1612
1686
  raise Exception("Wasmer deploy is only supported for Wasmer builders")
1613
1687
 
1614
1688
 
1689
+ @app.command(name="plan")
1690
+ def plan(
1691
+ path: Path = typer.Argument(
1692
+ Path("."),
1693
+ help="Project path (defaults to current directory).",
1694
+ show_default=False,
1695
+ ),
1696
+ wasmer: bool = typer.Option(
1697
+ False,
1698
+ help="Use Wasmer to evaluate the project.",
1699
+ ),
1700
+ wasmer_bin: Optional[Path] = typer.Option(
1701
+ None,
1702
+ help="The path to the Wasmer binary.",
1703
+ ),
1704
+ wasmer_registry: Optional[str] = typer.Option(
1705
+ None,
1706
+ help="Wasmer registry.",
1707
+ ),
1708
+ wasmer_token: Optional[str] = typer.Option(
1709
+ None,
1710
+ help="Wasmer token.",
1711
+ ),
1712
+ docker: bool = typer.Option(
1713
+ False,
1714
+ help="Use Docker to evaluate the project.",
1715
+ ),
1716
+ docker_client: Optional[str] = typer.Option(
1717
+ None,
1718
+ help="Use a specific Docker client (such as depot, podman, etc.)",
1719
+ ),
1720
+ ) -> None:
1721
+ if not path.exists():
1722
+ raise Exception(f"The path {path} does not exist")
1723
+
1724
+ custom_commands = CustomCommands()
1725
+ procfile_path = path / "Procfile"
1726
+ if procfile_path.exists():
1727
+ try:
1728
+ procfile = Procfile.loads(procfile_path.read_text())
1729
+ custom_commands.start = procfile.get_start_command()
1730
+ except Exception:
1731
+ pass
1732
+
1733
+ builder: Builder
1734
+ if docker or docker_client:
1735
+ builder = DockerBuilder(path, docker_client)
1736
+ else:
1737
+ builder = LocalBuilder(path)
1738
+ if wasmer:
1739
+ builder = WasmerBuilder(
1740
+ builder, path, registry=wasmer_registry, token=wasmer_token, bin=wasmer_bin
1741
+ )
1742
+
1743
+ ctx, serve = evaluate_shipit(path, builder)
1744
+ metadata_commands: Dict[str, Optional[str]] = {
1745
+ "start": serve.commands.get("start"),
1746
+ "after_deploy": serve.commands.get("after_deploy"),
1747
+ }
1748
+
1749
+ def _collect_group_commands(group: str) -> Optional[str]:
1750
+ commands = [
1751
+ step.command
1752
+ for step in serve.build
1753
+ if isinstance(step, RunStep) and step.group == group
1754
+ ]
1755
+ if not commands:
1756
+ return None
1757
+ return " && ".join(commands)
1758
+
1759
+ metadata_install = _collect_group_commands("install")
1760
+ metadata_build = _collect_group_commands("build")
1761
+ metadata_commands["install"] = metadata_install
1762
+ metadata_commands["build"] = metadata_build
1763
+ platform: Optional[str]
1764
+ try:
1765
+ provider_cls = detect_provider(path, custom_commands)
1766
+ provider_instance = provider_cls(path, custom_commands)
1767
+ provider_instance.initialize()
1768
+ platform = provider_instance.platform()
1769
+ except Exception:
1770
+ platform = None
1771
+ plan_output = {
1772
+ "provider": serve.provider,
1773
+ "metadata": {
1774
+ "platform": platform,
1775
+ "commands": metadata_commands,
1776
+ },
1777
+ "config": sorted(ctx.getenv_variables),
1778
+ "services": [
1779
+ {"name": svc.name, "provider": svc.provider}
1780
+ for svc in (serve.services or [])
1781
+ ],
1782
+ }
1783
+ print(json.dumps(plan_output, indent=4))
1784
+
1785
+
1615
1786
  @app.command(name="build")
1616
1787
  def build(
1617
1788
  path: Path = typer.Argument(
@@ -1659,12 +1830,6 @@ def build(
1659
1830
  if not path.exists():
1660
1831
  raise Exception(f"The path {path} does not exist")
1661
1832
 
1662
- ab_file = path / "Shipit"
1663
- if not ab_file.exists():
1664
- raise FileNotFoundError(
1665
- f"Shipit file not found at {ab_file}. Run `shipit generate {path}` to create it."
1666
- )
1667
- source = open(ab_file).read()
1668
1833
  builder: Builder
1669
1834
  if docker or docker_client:
1670
1835
  builder = DockerBuilder(path, docker_client)
@@ -1675,30 +1840,7 @@ def build(
1675
1840
  builder, path, registry=wasmer_registry, token=wasmer_token, bin=wasmer_bin
1676
1841
  )
1677
1842
 
1678
- ctx = Ctx(builder)
1679
- glb = sl.Globals.standard()
1680
- mod = sl.Module()
1681
-
1682
- mod.add_callable("service", ctx.service)
1683
- mod.add_callable("getenv", ctx.getenv)
1684
- mod.add_callable("dep", ctx.dep)
1685
- mod.add_callable("serve", ctx.serve)
1686
- mod.add_callable("run", ctx.run)
1687
- mod.add_callable("mount", ctx.mount)
1688
- mod.add_callable("volume", ctx.volume)
1689
- mod.add_callable("workdir", ctx.workdir)
1690
- mod.add_callable("copy", ctx.copy)
1691
- mod.add_callable("path", ctx.path)
1692
- mod.add_callable("env", ctx.env)
1693
- mod.add_callable("use", ctx.use)
1694
-
1695
- dialect = sl.Dialect.extended()
1696
- dialect.enable_f_strings = True
1697
-
1698
- ast = sl.parse("shipit", source, dialect=dialect)
1699
-
1700
- sl.eval(mod, ast, glb)
1701
- assert len(ctx.serves) <= 1, "Only one serve is allowed for now"
1843
+ ctx, serve = evaluate_shipit(path, builder)
1702
1844
  env = {
1703
1845
  "PATH": "",
1704
1846
  "COLORTERM": os.environ.get("COLORTERM", ""),
@@ -1706,7 +1848,6 @@ def build(
1706
1848
  "LS_COLORS": os.environ.get("LS_COLORS", "0"),
1707
1849
  "CLICOLOR": os.environ.get("CLICOLOR", "0"),
1708
1850
  }
1709
- serve = next(iter(ctx.serves.values()))
1710
1851
 
1711
1852
  if skip_docker_if_safe_build and serve.build and len(serve.build) > 0:
1712
1853
  # If it doesn't have a run step, then it's safe to skip Docker and run all the
shipit/generator.py CHANGED
@@ -64,15 +64,22 @@ def _emit_dependencies_declarations(
64
64
  declared.add(alias)
65
65
 
66
66
  version_var = None
67
+ architecture_var = None
67
68
  if dep.env_var:
68
69
  default = f' or "{dep.default_version}"' if dep.default_version else ""
69
70
  version_key = alias + "_version"
70
71
  lines.append(f'{version_key} = getenv("{dep.env_var}"){default}')
71
72
  version_var = version_key
73
+ if dep.architecture_var:
74
+ architecture_key = alias + "_architecture"
75
+ lines.append(f'{architecture_key} = getenv("{dep.architecture_var}")')
76
+ architecture_var = architecture_key
77
+ vars = [f'"{dep.name}"']
72
78
  if version_var:
73
- lines.append(f'{alias} = dep("{dep.name}", {version_var})')
74
- else:
75
- lines.append(f'{alias} = dep("{dep.name}")')
79
+ vars.append(version_var)
80
+ if architecture_var:
81
+ vars.append(f"architecture={architecture_var}")
82
+ lines.append(f"{alias} = dep({', '.join(vars)})")
76
83
 
77
84
  return "\n".join(lines), serve_vars, build_vars
78
85
 
@@ -85,6 +92,7 @@ def generate_shipit(path: Path, custom_commands: CustomCommands) -> str:
85
92
  plan = ProviderPlan(
86
93
  serve_name=provider.serve_name(),
87
94
  provider=provider.provider_kind(),
95
+ platform=provider.platform(),
88
96
  mounts=provider.mounts(),
89
97
  volumes=provider.volumes(),
90
98
  declarations=provider.declarations(),
@@ -109,7 +117,9 @@ def generate_shipit(path: Path, custom_commands: CustomCommands) -> str:
109
117
 
110
118
  build_steps_block = ",\n".join([f" {s}" for s in build_steps])
111
119
  deps_array = ", ".join(serve_dep_vars)
112
- commands_lines = ",\n".join([f' "{k}": {v}.replace("$PORT", PORT)' for k, v in plan.commands.items()])
120
+ commands_lines = ",\n".join(
121
+ [f' "{k}": {v}.replace("$PORT", PORT)' for k, v in plan.commands.items()]
122
+ )
113
123
  env_lines = None
114
124
  if plan.env is not None:
115
125
  if len(plan.env) == 0:
@@ -137,24 +147,26 @@ def generate_shipit(path: Path, custom_commands: CustomCommands) -> str:
137
147
  out.append("")
138
148
 
139
149
  for m in plan.mounts:
140
- out.append(f"{m.name} = mount(\"{m.name}\")")
150
+ out.append(f'{m.name} = mount("{m.name}")')
141
151
  out.append("")
142
152
 
143
153
  if plan.volumes:
144
154
  for v in plan.volumes:
145
- out.append(f"{v.var_name or v.name} = volume(\"{v.name}\", {v.serve_path})")
155
+ out.append(f'{v.var_name or v.name} = volume("{v.name}", {v.serve_path})')
146
156
  out.append("")
147
157
 
148
158
  if plan.services:
149
159
  for s in plan.services:
150
- out.append(f"{s.name} = service(\n name=\"{s.name}\",\n provider=\"{s.provider}\"\n)")
160
+ out.append(
161
+ f'{s.name} = service(\n name="{s.name}",\n provider="{s.provider}"\n)'
162
+ )
151
163
  out.append("")
152
164
 
153
- out.append("PORT = getenv(\"PORT\") or\"8080\"")
165
+ out.append('PORT = getenv("PORT") or "8080"')
154
166
 
155
167
  if plan.declarations:
156
168
  out.append(plan.declarations)
157
-
169
+
158
170
  out.append("")
159
171
  out.append("serve(")
160
172
  out.append(f' name="{plan.serve_name}",')
shipit/providers/base.py CHANGED
@@ -29,6 +29,7 @@ class Provider(Protocol):
29
29
  # Structured plan steps (no path args; use self.path)
30
30
  def serve_name(self) -> str: ...
31
31
  def provider_kind(self) -> str: ...
32
+ def platform(self) -> Optional[str]: ...
32
33
  def dependencies(self) -> list["DependencySpec"]: ...
33
34
  def declarations(self) -> Optional[str]: ...
34
35
  def build_steps(self) -> list[str]: ...
@@ -46,6 +47,7 @@ class DependencySpec:
46
47
  name: str
47
48
  env_var: Optional[str] = None
48
49
  default_version: Optional[str] = None
50
+ architecture_var: Optional[str] = None
49
51
  alias: Optional[str] = None # Variable name in Shipit plan
50
52
  use_in_build: bool = False
51
53
  use_in_serve: bool = False
@@ -77,6 +79,7 @@ class ProviderPlan:
77
79
  serve_name: str
78
80
  provider: str
79
81
  mounts: List[MountSpec]
82
+ platform: Optional[str] = None
80
83
  volumes: List[VolumeSpec] = field(default_factory=list)
81
84
  declarations: Optional[str] = None
82
85
  dependencies: List[DependencySpec] = field(default_factory=list)
@@ -45,6 +45,9 @@ class GatsbyProvider:
45
45
  def provider_kind(self) -> str:
46
46
  return "staticsite"
47
47
 
48
+ def platform(self) -> Optional[str]:
49
+ return "gatsby"
50
+
48
51
  def declarations(self) -> Optional[str]:
49
52
  return None
50
53
 
shipit/providers/hugo.py CHANGED
@@ -30,6 +30,9 @@ class HugoProvider(StaticFileProvider):
30
30
  def provider_kind(self) -> str:
31
31
  return "staticsite"
32
32
 
33
+ def platform(self) -> Optional[str]:
34
+ return "hugo"
35
+
33
36
  def dependencies(self) -> list[DependencySpec]:
34
37
  return [
35
38
  DependencySpec(
@@ -39,6 +39,9 @@ class LaravelProvider:
39
39
  def provider_kind(self) -> str:
40
40
  return "php"
41
41
 
42
+ def platform(self) -> Optional[str]:
43
+ return "laravel"
44
+
42
45
  def dependencies(self) -> list[DependencySpec]:
43
46
  return [
44
47
  DependencySpec(
@@ -43,6 +43,9 @@ class MkdocsProvider(StaticFileProvider):
43
43
  def provider_kind(self) -> str:
44
44
  return "mkdocs-site"
45
45
 
46
+ def platform(self) -> Optional[str]:
47
+ return "mkdocs"
48
+
46
49
  def dependencies(self) -> list[DependencySpec]:
47
50
  return [
48
51
  *self.python_provider.dependencies(),
@@ -122,6 +122,7 @@ class NodeStaticProvider(StaticFileProvider):
122
122
 
123
123
  def __init__(self, path: Path, custom_commands: CustomCommands):
124
124
  super().__init__(path, custom_commands)
125
+ self.static_generator: Optional[StaticGenerator] = None
125
126
  if (path / "package-lock.json").exists():
126
127
  self.package_manager = PackageManager.NPM
127
128
  elif (path / "pnpm-lock.yaml").exists():
@@ -233,6 +234,9 @@ class NodeStaticProvider(StaticFileProvider):
233
234
  def provider_kind(self) -> str:
234
235
  return "staticsite"
235
236
 
237
+ def platform(self) -> Optional[str]:
238
+ return self.static_generator.value if self.static_generator else None
239
+
236
240
  def dependencies(self) -> list[DependencySpec]:
237
241
  package_manager_dep = self.package_manager.as_dependency(self.path)
238
242
  package_manager_dep.use_in_build = True
shipit/providers/php.py CHANGED
@@ -20,16 +20,23 @@ class PhpProvider:
20
20
  def __init__(self, path: Path, custom_commands: CustomCommands):
21
21
  self.path = path
22
22
  self.custom_commands = custom_commands
23
-
23
+ self.has_composer = _exists(self.path, "composer.json", "composer.lock")
24
+
24
25
  @classmethod
25
26
  def name(cls) -> str:
26
27
  return "php"
27
28
 
28
29
  @classmethod
29
- def detect(cls, path: Path, custom_commands: CustomCommands) -> Optional[DetectResult]:
30
+ def detect(
31
+ cls, path: Path, custom_commands: CustomCommands
32
+ ) -> Optional[DetectResult]:
30
33
  if _exists(path, "composer.json") and _exists(path, "public/index.php"):
31
34
  return DetectResult(cls.name(), 60)
32
- if _exists(path, "index.php") or _exists(path, "public/index.php") or _exists(path, "app/index.php"):
35
+ if (
36
+ _exists(path, "index.php")
37
+ or _exists(path, "public/index.php")
38
+ or _exists(path, "app/index.php")
39
+ ):
33
40
  return DetectResult(cls.name(), 10)
34
41
  if custom_commands.start and custom_commands.start.startswith("php "):
35
42
  return DetectResult(cls.name(), 70)
@@ -44,8 +51,8 @@ class PhpProvider:
44
51
  def provider_kind(self) -> str:
45
52
  return "php"
46
53
 
47
- def has_composer(self) -> bool:
48
- return _exists(self.path, "composer.json", "composer.lock")
54
+ def platform(self) -> Optional[str]:
55
+ return None
49
56
 
50
57
  def dependencies(self) -> list[DependencySpec]:
51
58
  deps = [
@@ -53,32 +60,39 @@ class PhpProvider:
53
60
  "php",
54
61
  env_var="SHIPIT_PHP_VERSION",
55
62
  default_version="8.3",
63
+ architecture_var="SHIPIT_PHP_ARCHITECTURE",
56
64
  use_in_build=True,
57
65
  use_in_serve=True,
58
66
  ),
59
67
  ]
60
- if self.has_composer():
68
+ if self.has_composer:
61
69
  deps.append(DependencySpec("composer", use_in_build=True))
62
70
  deps.append(DependencySpec("bash", use_in_serve=True))
63
71
  return deps
64
72
 
65
73
  def declarations(self) -> Optional[str]:
66
- return "HOME = getenv(\"HOME\")\n"
74
+ if self.has_composer:
75
+ return 'HOME = getenv("HOME")\n'
76
+ return None
67
77
 
68
78
  def build_steps(self) -> list[str]:
69
79
  steps = [
70
- "workdir(app[\"build\"])",
80
+ 'workdir(app["build"])',
71
81
  ]
72
82
  if _exists(self.path, "php.ini"):
73
83
  steps.append('copy("php.ini", "{}/php.ini".format(assets["build"]))')
74
84
  else:
75
- steps.append('copy("php/php.ini", "{}/php.ini".format(assets["build"]), base="assets")')
85
+ steps.append(
86
+ 'copy("php/php.ini", "{}/php.ini".format(assets["build"]), base="assets")'
87
+ )
76
88
 
77
- if self.has_composer():
78
- steps.append("env(HOME=HOME, COMPOSER_FUND=\"0\")")
79
- steps.append("run(\"composer install --optimize-autoloader --no-scripts --no-interaction\", inputs=[\"composer.json\", \"composer.lock\"], outputs=[\".\"], group=\"install\")")
89
+ if self.has_composer:
90
+ steps.append('env(HOME=HOME, COMPOSER_FUND="0")')
91
+ steps.append(
92
+ 'run("composer install --optimize-autoloader --no-scripts --no-interaction", inputs=["composer.json", "composer.lock"], outputs=["."], group="install")'
93
+ )
80
94
 
81
- steps.append("copy(\".\", \".\", ignore=[\".git\"])")
95
+ steps.append('copy(".", ".", ignore=[".git"])')
82
96
  return steps
83
97
 
84
98
  def prepare_steps(self) -> Optional[list[str]]:
@@ -92,9 +106,13 @@ class PhpProvider:
92
106
 
93
107
  def base_commands(self) -> Dict[str, str]:
94
108
  if _exists(self.path, "public/index.php"):
95
- return {"start": '"php -S localhost:{} -t {}/public".format(PORT, app["serve"])'}
109
+ return {
110
+ "start": '"php -S localhost:{} -t {}/public".format(PORT, app["serve"])'
111
+ }
96
112
  elif _exists(self.path, "app/index.php"):
97
- return {"start": '"php -S localhost:{} -t {}/app".format(PORT, app["serve"])'}
113
+ return {
114
+ "start": '"php -S localhost:{} -t {}/app".format(PORT, app["serve"])'
115
+ }
98
116
  elif _exists(self.path, "index.php"):
99
117
  return {"start": '"php -S localhost:{} -t {}".format(PORT, app["serve"])'}
100
118
  return {}
@@ -112,6 +130,6 @@ class PhpProvider:
112
130
  return {
113
131
  "PHP_INI_SCAN_DIR": '"{}".format(assets["serve"])',
114
132
  }
115
-
133
+
116
134
  def services(self) -> list[ServiceSpec]:
117
135
  return []
@@ -229,6 +229,9 @@ class PythonProvider:
229
229
  def provider_kind(self) -> str:
230
230
  return "python"
231
231
 
232
+ def platform(self) -> Optional[str]:
233
+ return self.framework.value if self.framework else None
234
+
232
235
  def dependencies(self) -> list[DependencySpec]:
233
236
  deps = [
234
237
  DependencySpec(
@@ -307,9 +310,9 @@ class PythonProvider:
307
310
  # Join inputs
308
311
  inputs = ", ".join([f'"{input}"' for input in input_files])
309
312
  steps += [
310
- 'env(UV_PROJECT_ENVIRONMENT=local_venv["build"] if cross_platform else venv["build"])',
313
+ 'env(UV_PROJECT_ENVIRONMENT=local_venv["build"] if cross_platform else venv["build"], UV_PYTHON_PREFERENCE="only-system", UV_PYTHON=f"python{python_version}")',
311
314
  'copy(".", ".")' if self.install_requires_all_files else None,
312
- f'run(f"uv sync --compile --python python{{python_version}} --no-managed-python{extra_args}", inputs=[{inputs}], group="install")',
315
+ f'run(f"uv sync{extra_args}", inputs=[{inputs}], group="install")',
313
316
  'copy("pyproject.toml", "pyproject.toml")'
314
317
  if not self.install_requires_all_files
315
318
  else None,
@@ -317,14 +320,14 @@ class PythonProvider:
317
320
  ]
318
321
  if not self.only_build:
319
322
  steps += [
320
- '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 --no-deps -o cross-requirements.txt", outputs=["cross-requirements.txt"]) if cross_platform else None',
323
+ 'run(f"uv pip compile pyproject.toml --universal --extra-index-url {python_extra_index_url} --index-url=https://pypi.org/simple --emit-index-url --no-deps -o cross-requirements.txt", outputs=["cross-requirements.txt"]) if cross_platform else None',
321
324
  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',
322
325
  'run("rm cross-requirements.txt") if cross_platform else None',
323
326
  ]
324
327
  elif has_requirements or extra_deps:
325
328
  steps += [
326
329
  'env(UV_PROJECT_ENVIRONMENT=local_venv["build"] if cross_platform else venv["build"])',
327
- 'run(f"uv init --no-workspace --no-managed-python --python python{python_version}", inputs=[], outputs=["uv.lock"], group="install")',
330
+ 'run(f"uv init --no-workspace", inputs=[], outputs=["uv.lock"], group="install")',
328
331
  'copy(".", ".")' if self.install_requires_all_files else None,
329
332
  ]
330
333
  if has_requirements:
@@ -351,6 +354,10 @@ class PythonProvider:
351
354
  'run("mkdir -p {}/bin".format(venv["build"])) if cross_platform else None',
352
355
  'run("cp {}/bin/mcp {}/bin/mcp".format(local_venv["build"], venv["build"])) if cross_platform else None',
353
356
  ]
357
+ if self.framework == PythonFramework.Django:
358
+ steps += [
359
+ 'run("python manage.py collectstatic --noinput", group="build")',
360
+ ]
354
361
  return list(filter(None, steps))
355
362
 
356
363
  def prepare_steps(self) -> Optional[list[str]]:
@@ -61,6 +61,9 @@ class StaticFileProvider:
61
61
  def provider_kind(self) -> str:
62
62
  return "staticfile"
63
63
 
64
+ def platform(self) -> Optional[str]:
65
+ return None
66
+
64
67
  def dependencies(self) -> list[DependencySpec]:
65
68
  return [
66
69
  DependencySpec(
@@ -19,8 +19,7 @@ from .php import PhpProvider
19
19
 
20
20
  class WordPressProvider(PhpProvider):
21
21
  def __init__(self, path: Path, custom_commands: CustomCommands):
22
- self.path = path
23
- self.custom_commands = custom_commands
22
+ super().__init__(path, custom_commands)
24
23
 
25
24
  @classmethod
26
25
  def name(cls) -> str:
@@ -47,6 +46,9 @@ class WordPressProvider(PhpProvider):
47
46
  def provider_kind(self) -> str:
48
47
  return "php"
49
48
 
49
+ def platform(self) -> Optional[str]:
50
+ return "wordpress"
51
+
50
52
  def dependencies(self) -> list[DependencySpec]:
51
53
  return [
52
54
  *super().dependencies(),
@@ -54,7 +56,7 @@ class WordPressProvider(PhpProvider):
54
56
  ]
55
57
 
56
58
  def declarations(self) -> Optional[str]:
57
- return super().declarations() + (
59
+ return (super().declarations() or "") + (
58
60
  'wp_cli_version = getenv("SHIPIT_WPCLI_VERSION")\n'
59
61
  "if wp_cli_version:\n"
60
62
  ' wp_cli_download_url = f"https://github.com/wp-cli/wp-cli/releases/download/v{wp_cli_version}/wp-cli-{wp_cli_version}.phar"\n'
@@ -65,7 +67,7 @@ class WordPressProvider(PhpProvider):
65
67
  def build_steps(self) -> list[str]:
66
68
  steps = [
67
69
  'copy(wp_cli_download_url, "{}/wp-cli.phar".format(assets["build"]))',
68
- 'copy("wordpress/install.sh", "{}/wordpress-install.sh".format(assets["build"]), base="assets")',
70
+ 'copy("wordpress/install.sh", "{}/setup-wp.sh".format(assets["build"]), base="assets")',
69
71
  ]
70
72
  if not _exists(self.path, "wp-config.php"):
71
73
  steps.append(
@@ -80,7 +82,7 @@ class WordPressProvider(PhpProvider):
80
82
  commands = super().commands()
81
83
  return {
82
84
  "wp": '"php {}/wp-cli.phar --allow-root --path={}".format(assets["serve"], app["serve"])',
83
- "after_deploy": '"bash {}/wordpress-install.sh".format(assets["serve"])',
85
+ "after_deploy": '"bash {}/setup-wp.sh".format(assets["serve"])',
84
86
  **commands,
85
87
  }
86
88
 
shipit/version.py CHANGED
@@ -1,5 +1,5 @@
1
1
  __all__ = ["version", "version_info"]
2
2
 
3
3
 
4
- version = "0.10.0"
5
- version_info = (0, 10, 0, "final", 0)
4
+ version = "0.11.0"
5
+ version_info = (0, 11, 0, "final", 0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shipit-cli
3
- Version: 0.10.0
3
+ Version: 0.11.0
4
4
  Summary: Shipit CLI is the best way to build, serve and deploy your projects anywhere.
5
5
  Project-URL: homepage, https://wasmer.io
6
6
  Project-URL: repository, https://github.com/wasmerio/shipit
@@ -0,0 +1,23 @@
1
+ shipit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ shipit/cli.py,sha256=3M-wn_X_z-MTep_Yoz5P8s404uJLr5GpTd--my2-DuA,65664
3
+ shipit/generator.py,sha256=37Y40KKrmVpQ6XMA3VqhdvMJFDsIqCuQy_aVUVJqA20,6945
4
+ shipit/procfile.py,sha256=GlfdwzFUr0GWGKaaiXlLKNFInWaRNMy_wN14UEyU_5Q,2974
5
+ shipit/version.py,sha256=guCYg8LK1bf7KxJBZzbM2RwQBLcGyAlvj5rF5tNz6TI,97
6
+ shipit/assets/php/php.ini,sha256=SaR3wvssSROtxTY_CQ5-BspM_47_I971V5X2dsqe8sU,2579
7
+ shipit/assets/wordpress/install.sh,sha256=h_Pbuj1nUqC0dE91yAYxeBiWf2oFbBWn0oCUkkosUsE,2498
8
+ shipit/assets/wordpress/wp-config.php,sha256=8M4C6xLI5ziX1udM7B-Ef3jd9xuMGVRve6cCuOgDT4o,4786
9
+ shipit/providers/base.py,sha256=bA1CgOpQ3dUQkXx_LPJvFj_CbZ77xNh5l2GI8Qh3Owo,3117
10
+ shipit/providers/gatsby.py,sha256=Yxzq-xYwgLGGxTA5X9AE6g6w4v8ruXFcg3U6QlJwqkE,2459
11
+ shipit/providers/hugo.py,sha256=ZMG54hbtdrTem-YUg9gp75Qu-pd1XzhaoNYOihU7HOg,1872
12
+ shipit/providers/laravel.py,sha256=v7xDNipE72CDCZtzkpmpHktBp5n2kmgduSYTmmKjo2Y,3098
13
+ shipit/providers/mkdocs.py,sha256=SRbTkRtzUbFiqP3KrPckD_IQF7nE1-FUkQk7FswaEBs,2266
14
+ shipit/providers/node_static.py,sha256=7vrz1lh_L-Op62r6HLcxy2RQypfQjFbJh7e9b46xNLM,12542
15
+ shipit/providers/php.py,sha256=_kXQ7ja9skvC9mOxW9pKqQzdcXaixXBGTdAAEbT4tsY,4086
16
+ shipit/providers/python.py,sha256=ZLzqv3P0R93u7_i_PBQ_VhVZEt9iTp1bvyjLlMJE-_0,21155
17
+ shipit/providers/registry.py,sha256=JCuQaYTvJcWK1nS-om9TIQgGW6pT5BuNLIRzChLLFWE,731
18
+ shipit/providers/staticfile.py,sha256=Zpt7s-R17Fow-f4ObXfCRDiLEM8gHp8dC7hyi2DUV6o,2802
19
+ shipit/providers/wordpress.py,sha256=IHRWcb1K3taD30eOruJMn5Cp_t8_PAQhdcY7KB4qBIw,3293
20
+ shipit_cli-0.11.0.dist-info/METADATA,sha256=NSATh2qlUnYhDArV5QbYqESLAIipD2SEYfZ3SWTYZhI,616
21
+ shipit_cli-0.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
+ shipit_cli-0.11.0.dist-info/entry_points.txt,sha256=7AE1NjSrHaSDfbfsRRO50KKnHFTbB0Imsccd1WynzAQ,72
23
+ shipit_cli-0.11.0.dist-info/RECORD,,
@@ -1,23 +0,0 @@
1
- shipit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- shipit/cli.py,sha256=k60mGxpBoyxYgdsFfosc3HuSm5m7Nus5I9RnAaP8X0E,60820
3
- shipit/generator.py,sha256=W4MynSFwId4PRWWrF0R3NsANye_Zv8TwXCUXai94pW0,6553
4
- shipit/procfile.py,sha256=GlfdwzFUr0GWGKaaiXlLKNFInWaRNMy_wN14UEyU_5Q,2974
5
- shipit/version.py,sha256=kFcU-e4oDxwCFdfvjlVkQgvkQaOBmtFeg-EaPmeM9c0,97
6
- shipit/assets/php/php.ini,sha256=9STxIKCYaku3rbfWP9VwUGaG40zuS6bCd_psNpx9BQk,2530
7
- shipit/assets/wordpress/install.sh,sha256=YyYzX43I_vlzAKcm7nSOSflxbEaLQgovnrle9oCkEyU,557
8
- shipit/assets/wordpress/wp-config.php,sha256=bqtT3ep1SfG9H2zdyYHACN4eehLBzYs4ZP0L-uiCWyQ,4179
9
- shipit/providers/base.py,sha256=-lraLhnXtc2SfYNIUiyry7xD2NL4q0n6voNjr6qfRis,2994
10
- shipit/providers/gatsby.py,sha256=kzfS-z040GaJ0a9u2_6S6K-ykGSX2yPG17VpjUWBOBA,2393
11
- shipit/providers/hugo.py,sha256=l3IZ14LGYc3wQ7Uk0iQMDNN9Syd1G4HPzm0ePCGFyzE,1808
12
- shipit/providers/laravel.py,sha256=2rCuOi2kC5OYQfAG7C0NMhG7KEgzfudwUT_CvVFz4hM,3031
13
- shipit/providers/mkdocs.py,sha256=mDJpT3rzYAr5Vw-yt5fCpV0QgBZyksn9MrkPd4nzROU,2200
14
- shipit/providers/node_static.py,sha256=tkttFRB2KEfhZYK_qzcdf7ulEepZEJq9EqkAUo2KYWs,12358
15
- shipit/providers/php.py,sha256=B5CEvYX51IXWrleNNa6eq-d8xGI8xP4k0Pl8Rpi5cLo,3786
16
- shipit/providers/python.py,sha256=kQpaaQi8ajHM_SFk2xI5lRIeOc6PXSKumb-Tv_5n6m0,20954
17
- shipit/providers/registry.py,sha256=JCuQaYTvJcWK1nS-om9TIQgGW6pT5BuNLIRzChLLFWE,731
18
- shipit/providers/staticfile.py,sha256=hs8ER8rATTho8pBJ6b6ibaOqAfbNZKmlRqbalSdeYY0,2740
19
- shipit/providers/wordpress.py,sha256=6wd0wPBlE9DvNQgmcEKnH0TArGDkHfRvT9XhqnfOWTU,3258
20
- shipit_cli-0.10.0.dist-info/METADATA,sha256=94O3cZZT3pBMq20dhD7ph1-vljYQP--Wc6F8g83TId0,616
21
- shipit_cli-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
- shipit_cli-0.10.0.dist-info/entry_points.txt,sha256=7AE1NjSrHaSDfbfsRRO50KKnHFTbB0Imsccd1WynzAQ,72
23
- shipit_cli-0.10.0.dist-info/RECORD,,