shipit-cli 0.21.2__tar.gz → 0.21.3__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.
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/PKG-INFO +17 -7
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/README.md +16 -6
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/pyproject.toml +1 -1
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/assets/wordpress/install.sh +11 -7
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/builders/docker.py +1 -1
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/cli.py +1 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/node.py +100 -6
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/wordpress.py +33 -5
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/runners/wasmer.py +15 -15
- shipit_cli-0.21.3/src/shipit/version.py +12 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_e2e.py +77 -1
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_node_provider.py +26 -1
- shipit_cli-0.21.2/src/shipit/version.py +0 -5
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/.gitignore +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/__init__.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/assets/node/optimize-node-modules.sh +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/assets/php/php.ini +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/assets/wordpress/.htaccess +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/assets/wordpress/start.php +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/assets/wordpress/wp-config.php +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/builders/__init__.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/builders/base.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/builders/local.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/generator.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/procfile.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/base.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/go.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/hugo.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/install_context.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/jekyll.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/laravel.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/mkdocs.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/node_static.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/php.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/python.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/registry.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/providers/staticfile.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/runners/__init__.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/runners/base.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/runners/local.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/shipit_types.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/ui.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/utils.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/src/shipit/volumes.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_cli_after_deploy.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_generate_shipit_examples.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_install_context.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_node_static_provider.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_php_provider.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_staticfile_provider.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_subdir.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_version.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_volumes.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_wasmer_annotations.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.3}/tests/test_wordpress_phpix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shipit-cli
|
|
3
|
-
Version: 0.21.
|
|
3
|
+
Version: 0.21.3
|
|
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
|
|
@@ -145,16 +145,26 @@ The e2e tests will:
|
|
|
145
145
|
* Run the project (locally or with Wasmer)
|
|
146
146
|
* Test that the project output (via http requests) is the correct one
|
|
147
147
|
|
|
148
|
-
###
|
|
148
|
+
### Release Automation
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
Releases are automated with Release Please and PyPI Trusted Publishing.
|
|
151
151
|
|
|
152
152
|
Requirements:
|
|
153
153
|
|
|
154
|
-
*
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
* Enable GitHub Actions workflow permissions for creating pull requests in the
|
|
155
|
+
repository settings. Release Please uses the built-in `GITHUB_TOKEN`.
|
|
156
|
+
* Configure PyPI Trusted Publishing for the `shipit-cli` project:
|
|
157
|
+
* owner: `wasmerio`
|
|
158
|
+
* repository: `shipit`
|
|
159
|
+
* workflow: `pypi-publish.yml`
|
|
160
|
+
* environment: `pypi`
|
|
161
|
+
* Create a GitHub environment named `pypi`. Add reviewers there if releases
|
|
162
|
+
should require manual approval before publishing.
|
|
163
|
+
|
|
164
|
+
On each push to `main`, Release Please opens or updates a release PR based on
|
|
165
|
+
Conventional Commits. When that PR is merged, it creates a GitHub release with
|
|
166
|
+
a `vX.Y.Z` tag and dispatches the PyPI workflow for that tag with the built-in
|
|
167
|
+
`GITHUB_TOKEN`. The PyPI workflow also supports manual dispatch for reruns. It:
|
|
158
168
|
|
|
159
169
|
* Runs regular test and e2e workflows.
|
|
160
170
|
* Validates that the tag version without the leading `v` matches:
|
|
@@ -122,16 +122,26 @@ The e2e tests will:
|
|
|
122
122
|
* Run the project (locally or with Wasmer)
|
|
123
123
|
* Test that the project output (via http requests) is the correct one
|
|
124
124
|
|
|
125
|
-
###
|
|
125
|
+
### Release Automation
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
Releases are automated with Release Please and PyPI Trusted Publishing.
|
|
128
128
|
|
|
129
129
|
Requirements:
|
|
130
130
|
|
|
131
|
-
*
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
* Enable GitHub Actions workflow permissions for creating pull requests in the
|
|
132
|
+
repository settings. Release Please uses the built-in `GITHUB_TOKEN`.
|
|
133
|
+
* Configure PyPI Trusted Publishing for the `shipit-cli` project:
|
|
134
|
+
* owner: `wasmerio`
|
|
135
|
+
* repository: `shipit`
|
|
136
|
+
* workflow: `pypi-publish.yml`
|
|
137
|
+
* environment: `pypi`
|
|
138
|
+
* Create a GitHub environment named `pypi`. Add reviewers there if releases
|
|
139
|
+
should require manual approval before publishing.
|
|
140
|
+
|
|
141
|
+
On each push to `main`, Release Please opens or updates a release PR based on
|
|
142
|
+
Conventional Commits. When that PR is merged, it creates a GitHub release with
|
|
143
|
+
a `vX.Y.Z` tag and dispatches the PyPI workflow for that tag with the built-in
|
|
144
|
+
`GITHUB_TOKEN`. The PyPI workflow also supports manual dispatch for reruns. It:
|
|
135
145
|
|
|
136
146
|
* Runs regular test and e2e workflows.
|
|
137
147
|
* Validates that the tag version without the leading `v` matches:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Needed to get the WP-CLI commands to avoid asking for the TTY size
|
|
2
2
|
IFS=$'\n\t'
|
|
3
|
+
set -e
|
|
3
4
|
|
|
4
5
|
export COLUMNS=80 # Prevent WP-CLI from asking for TTY size
|
|
5
6
|
export PAGER="cat"
|
|
@@ -11,18 +12,21 @@ WP_DEFAULT_LOCALE=${WP_DEFAULT_LOCALE:-"en_US"}
|
|
|
11
12
|
WP_LOCALE=${WP_LOCALE:-"$WP_DEFAULT_LOCALE"}
|
|
12
13
|
WP_SITEURL=${WP_SITEURL:-"http://localhost"}
|
|
13
14
|
WP_SITE_TITLE=${WP_SITE_TITLE:-"WordPress"}
|
|
15
|
+
WP_CONTENT_DIR=${WP_CONTENT_DIR:-"wp-content"}
|
|
14
16
|
|
|
15
17
|
echo "📁 Initializing wp-content..."
|
|
16
|
-
mkdir -p
|
|
17
|
-
mkdir -p
|
|
18
|
-
mkdir -p
|
|
18
|
+
mkdir -p "${WP_CONTENT_DIR}/plugins"
|
|
19
|
+
mkdir -p "${WP_CONTENT_DIR}/themes"
|
|
20
|
+
mkdir -p "${WP_CONTENT_DIR}/upgrade"
|
|
19
21
|
|
|
20
22
|
if [ -n "${WPCONTENT_BASE_PATH:-}" ] && [ -d "${WPCONTENT_BASE_PATH}" ]; then
|
|
21
23
|
shopt -s dotglob nullglob
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
cp -R "${WPCONTENT_BASE_PATH}"/plugins/* "${WP_CONTENT_DIR}/plugins" || true
|
|
25
|
+
if [ -n "${WP_DEFAULT_THEME:-}" ]; then
|
|
26
|
+
cp -R "${WPCONTENT_BASE_PATH}"/themes/* "${WP_CONTENT_DIR}/themes" || true
|
|
27
|
+
else
|
|
28
|
+
cp -R "${WPCONTENT_BASE_PATH}"/themes/twentytwenty* "${WP_CONTENT_DIR}/themes" || true
|
|
29
|
+
fi
|
|
26
30
|
shopt -u dotglob nullglob
|
|
27
31
|
fi
|
|
28
32
|
|
|
@@ -144,7 +144,7 @@ RUN apt-get update \\
|
|
|
144
144
|
&& apt-get -y --no-install-recommends install \\
|
|
145
145
|
build-essential gcc make autoconf libtool bison \\
|
|
146
146
|
dpkg-dev pkg-config re2c locate \\
|
|
147
|
-
libmariadb-dev libmariadb-dev-compat libpq-dev \\
|
|
147
|
+
libmariadb-dev libmariadb-dev-compat libpq-dev libsqlite3-dev \\
|
|
148
148
|
libvips-dev default-libmysqlclient-dev libmagickwand-dev \\
|
|
149
149
|
libicu-dev libxml2-dev libxslt-dev libyaml-dev \\
|
|
150
150
|
sudo curl ca-certificates unzip git \\
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import re
|
|
2
3
|
import shlex
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from pathlib import Path
|
|
@@ -483,6 +484,46 @@ class NodeProvider:
|
|
|
483
484
|
"src/server.js",
|
|
484
485
|
"src/index.js",
|
|
485
486
|
)
|
|
487
|
+
NODE_BUILTIN_MODULES: ClassVar[tuple[str, ...]] = (
|
|
488
|
+
"assert",
|
|
489
|
+
"buffer",
|
|
490
|
+
"child_process",
|
|
491
|
+
"cluster",
|
|
492
|
+
"crypto",
|
|
493
|
+
"dgram",
|
|
494
|
+
"dns",
|
|
495
|
+
"events",
|
|
496
|
+
"fs",
|
|
497
|
+
"http",
|
|
498
|
+
"http2",
|
|
499
|
+
"https",
|
|
500
|
+
"module",
|
|
501
|
+
"net",
|
|
502
|
+
"os",
|
|
503
|
+
"path",
|
|
504
|
+
"perf_hooks",
|
|
505
|
+
"process",
|
|
506
|
+
"querystring",
|
|
507
|
+
"readline",
|
|
508
|
+
"stream",
|
|
509
|
+
"timers",
|
|
510
|
+
"tls",
|
|
511
|
+
"tty",
|
|
512
|
+
"url",
|
|
513
|
+
"util",
|
|
514
|
+
"v8",
|
|
515
|
+
"vm",
|
|
516
|
+
"worker_threads",
|
|
517
|
+
"zlib",
|
|
518
|
+
)
|
|
519
|
+
NODE_ENTRY_MARKER_PATTERN: ClassVar[re.Pattern[str]] = re.compile(
|
|
520
|
+
r"\bmodule\.exports\b"
|
|
521
|
+
r"|\bexports\."
|
|
522
|
+
r"|\b__dirname\b"
|
|
523
|
+
r"|\b__filename\b"
|
|
524
|
+
r"|\bprocess\.(?:env|argv|exit|cwd|platform|version)\b"
|
|
525
|
+
r"|\b[A-Za-z_$][\w$]*\.listen\s*\("
|
|
526
|
+
)
|
|
486
527
|
START_PROGRAMS = {
|
|
487
528
|
"edge",
|
|
488
529
|
"node",
|
|
@@ -598,12 +639,62 @@ class NodeProvider:
|
|
|
598
639
|
if (path / "package.json").is_file():
|
|
599
640
|
return DetectResult(cls.name(), 30)
|
|
600
641
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
642
|
+
if cls._common_entry_file(path, require_node_evidence=True):
|
|
643
|
+
return DetectResult(cls.name(), 30)
|
|
644
|
+
|
|
645
|
+
return None
|
|
604
646
|
|
|
647
|
+
@classmethod
|
|
648
|
+
def _common_entry_file(
|
|
649
|
+
cls,
|
|
650
|
+
path: Path,
|
|
651
|
+
*,
|
|
652
|
+
require_node_evidence: bool,
|
|
653
|
+
) -> Optional[str]:
|
|
654
|
+
for entry_file in cls.COMMON_ENTRY_FILES:
|
|
655
|
+
entry_path = path / entry_file
|
|
656
|
+
if not entry_path.is_file():
|
|
657
|
+
continue
|
|
658
|
+
if not require_node_evidence:
|
|
659
|
+
return entry_file
|
|
660
|
+
if cls._looks_like_node_entry(entry_path):
|
|
661
|
+
return entry_file
|
|
605
662
|
return None
|
|
606
663
|
|
|
664
|
+
@classmethod
|
|
665
|
+
def _looks_like_node_entry(cls, entry_path: Path) -> bool:
|
|
666
|
+
try:
|
|
667
|
+
source = entry_path.read_text(errors="ignore")
|
|
668
|
+
except OSError:
|
|
669
|
+
return False
|
|
670
|
+
|
|
671
|
+
first_line = source.splitlines()[0] if source else ""
|
|
672
|
+
has_node_shebang = (
|
|
673
|
+
first_line.startswith("#!")
|
|
674
|
+
and re.search(r"\bnode(?:js)?\b", first_line)
|
|
675
|
+
)
|
|
676
|
+
if has_node_shebang:
|
|
677
|
+
return True
|
|
678
|
+
|
|
679
|
+
if cls.NODE_ENTRY_MARKER_PATTERN.search(source):
|
|
680
|
+
return True
|
|
681
|
+
|
|
682
|
+
module_ref = cls._node_builtin_module_ref_pattern()
|
|
683
|
+
module_patterns = (
|
|
684
|
+
rf"\brequire\s*\(\s*['\"]{module_ref}['\"]\s*\)",
|
|
685
|
+
rf"\bimport\s*\(\s*['\"]{module_ref}['\"]\s*\)",
|
|
686
|
+
rf"\bfrom\s*['\"]{module_ref}['\"]",
|
|
687
|
+
rf"\bimport\s*['\"]{module_ref}['\"]",
|
|
688
|
+
)
|
|
689
|
+
return any(re.search(pattern, source) for pattern in module_patterns)
|
|
690
|
+
|
|
691
|
+
@classmethod
|
|
692
|
+
def _node_builtin_module_ref_pattern(cls) -> str:
|
|
693
|
+
modules = "|".join(
|
|
694
|
+
re.escape(module) for module in cls.NODE_BUILTIN_MODULES
|
|
695
|
+
)
|
|
696
|
+
return rf"(?:node:)?(?:{modules})(?:/[^'\"\s)]*)?"
|
|
697
|
+
|
|
607
698
|
@classmethod
|
|
608
699
|
def detect_package_manager(cls, path: Path) -> PackageManager:
|
|
609
700
|
package_json = cls.parse_package_json(path) or {}
|
|
@@ -921,9 +1012,12 @@ class NodeProvider:
|
|
|
921
1012
|
if isinstance(main, str) and main.strip():
|
|
922
1013
|
return cls._node_entry_command(main.strip())
|
|
923
1014
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1015
|
+
entry_file = cls._common_entry_file(
|
|
1016
|
+
path,
|
|
1017
|
+
require_node_evidence=not (path / "package.json").is_file(),
|
|
1018
|
+
)
|
|
1019
|
+
if entry_file:
|
|
1020
|
+
return cls._node_entry_command(entry_file)
|
|
927
1021
|
|
|
928
1022
|
return None
|
|
929
1023
|
|
|
@@ -47,6 +47,9 @@ class WordPressProvider(PhpProvider):
|
|
|
47
47
|
THEME_HEADER_RE = re.compile(
|
|
48
48
|
r"^[ \t/*#@]*Theme Name\s*:", re.I | re.M
|
|
49
49
|
)
|
|
50
|
+
TEXT_DOMAIN_RE = re.compile(
|
|
51
|
+
r"^[ \t/*#@]*Text Domain\s*:\s*([^\s*/#@]+)", re.I | re.M
|
|
52
|
+
)
|
|
50
53
|
|
|
51
54
|
@classmethod
|
|
52
55
|
def name(cls) -> str:
|
|
@@ -93,7 +96,9 @@ class WordPressProvider(PhpProvider):
|
|
|
93
96
|
for plugin_file in plugin_files:
|
|
94
97
|
if not cls._file_has_header(plugin_file, cls.PLUGIN_HEADER_RE):
|
|
95
98
|
continue
|
|
96
|
-
slug = cls._slugify(
|
|
99
|
+
slug = cls._detect_text_domain_slug(plugin_file) or cls._slugify(
|
|
100
|
+
path.name
|
|
101
|
+
)
|
|
97
102
|
return WordPressExtension(
|
|
98
103
|
kind="plugin",
|
|
99
104
|
slug=slug,
|
|
@@ -113,7 +118,9 @@ class WordPressProvider(PhpProvider):
|
|
|
113
118
|
or (path / "theme.json").is_file()
|
|
114
119
|
):
|
|
115
120
|
return None
|
|
116
|
-
slug = cls._slugify(
|
|
121
|
+
slug = cls._detect_text_domain_slug(style_css) or cls._slugify(
|
|
122
|
+
path.name
|
|
123
|
+
)
|
|
117
124
|
return WordPressExtension(
|
|
118
125
|
kind="theme",
|
|
119
126
|
slug=slug,
|
|
@@ -121,15 +128,36 @@ class WordPressProvider(PhpProvider):
|
|
|
121
128
|
source_file="style.css",
|
|
122
129
|
)
|
|
123
130
|
|
|
131
|
+
@classmethod
|
|
132
|
+
def _detect_text_domain_slug(cls, header_file: Path) -> Optional[str]:
|
|
133
|
+
text_domain = cls._file_header_value(
|
|
134
|
+
header_file,
|
|
135
|
+
cls.TEXT_DOMAIN_RE,
|
|
136
|
+
)
|
|
137
|
+
if not text_domain:
|
|
138
|
+
return None
|
|
139
|
+
return cls._slugify(text_domain)
|
|
140
|
+
|
|
124
141
|
@classmethod
|
|
125
142
|
def _file_has_header(cls, path: Path, pattern: re.Pattern[str]) -> bool:
|
|
143
|
+
return cls._file_header_value(path, pattern) is not None
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def _file_header_value(
|
|
147
|
+
cls, path: Path, pattern: re.Pattern[str]
|
|
148
|
+
) -> Optional[str]:
|
|
126
149
|
if not path.is_file():
|
|
127
|
-
return
|
|
150
|
+
return None
|
|
128
151
|
try:
|
|
129
152
|
contents = path.read_text(errors="ignore")[: cls.HEADER_SCAN_BYTES]
|
|
130
153
|
except OSError:
|
|
131
|
-
return
|
|
132
|
-
|
|
154
|
+
return None
|
|
155
|
+
match = pattern.search(contents)
|
|
156
|
+
if not match:
|
|
157
|
+
return None
|
|
158
|
+
if match.lastindex:
|
|
159
|
+
return match.group(1).strip()
|
|
160
|
+
return match.group(0).strip()
|
|
133
161
|
|
|
134
162
|
@staticmethod
|
|
135
163
|
def _slugify(value: str) -> str:
|
|
@@ -167,28 +167,28 @@ class WasmerRunner:
|
|
|
167
167
|
},
|
|
168
168
|
"phpix": {
|
|
169
169
|
"dependencies": {
|
|
170
|
-
"latest": "phpix/phpix-83-32bit@=0.2.
|
|
171
|
-
"8.3": "phpix/phpix-83-32bit@=0.2.
|
|
172
|
-
"8.3.29": "phpix/phpix-83-32bit@=0.2.
|
|
173
|
-
"8.2": "phpix/phpix-82-32bit@=0.2.
|
|
174
|
-
"8.1": "phpix/phpix-81-32bit@=0.2.
|
|
170
|
+
"latest": "phpix/phpix-83-32bit@=0.2.2",
|
|
171
|
+
"8.3": "phpix/phpix-83-32bit@=0.2.2",
|
|
172
|
+
"8.3.29": "phpix/phpix-83-32bit@=0.2.2",
|
|
173
|
+
"8.2": "phpix/phpix-82-32bit@=0.2.2",
|
|
174
|
+
"8.1": "phpix/phpix-81-32bit@=0.2.2",
|
|
175
175
|
"7.4": "php/php-32@=7.4.3301", # Note, we don't have PHPix + PHP 7.4 and never will
|
|
176
176
|
},
|
|
177
177
|
"architecture_dependencies": {
|
|
178
178
|
"64-bit": {
|
|
179
|
-
"latest": "phpix/phpix-83-64bit@=0.2.
|
|
180
|
-
"8.3": "phpix/phpix-83-64bit@=0.2.
|
|
181
|
-
"8.3.29": "phpix/phpix-83-64bit@=0.2.
|
|
182
|
-
"8.2": "phpix/phpix-82-64bit@=0.2.
|
|
183
|
-
"8.1": "phpix/phpix-81-64bit@=0.2.
|
|
179
|
+
"latest": "phpix/phpix-83-64bit@=0.2.2",
|
|
180
|
+
"8.3": "phpix/phpix-83-64bit@=0.2.2",
|
|
181
|
+
"8.3.29": "phpix/phpix-83-64bit@=0.2.2",
|
|
182
|
+
"8.2": "phpix/phpix-82-64bit@=0.2.2",
|
|
183
|
+
"8.1": "phpix/phpix-81-64bit@=0.2.2",
|
|
184
184
|
"7.4": "php/php-64@=7.4.3301",
|
|
185
185
|
},
|
|
186
186
|
"32-bit": {
|
|
187
|
-
"latest": "phpix/phpix-83-32bit@=0.2.
|
|
188
|
-
"8.3": "phpix/phpix-83-32bit@=0.2.
|
|
189
|
-
"8.3.29": "phpix/phpix-83-32bit@=0.2.
|
|
190
|
-
"8.2": "phpix/phpix-82-32bit@=0.2.
|
|
191
|
-
"8.1": "phpix/phpix-81-32bit@=0.2.
|
|
187
|
+
"latest": "phpix/phpix-83-32bit@=0.2.2",
|
|
188
|
+
"8.3": "phpix/phpix-83-32bit@=0.2.2",
|
|
189
|
+
"8.3.29": "phpix/phpix-83-32bit@=0.2.2",
|
|
190
|
+
"8.2": "phpix/phpix-82-32bit@=0.2.2",
|
|
191
|
+
"8.1": "phpix/phpix-81-32bit@=0.2.2",
|
|
192
192
|
"7.4": "php/php-32@=7.4.3301",
|
|
193
193
|
},
|
|
194
194
|
},
|
|
@@ -65,6 +65,7 @@ class E2ECase:
|
|
|
65
65
|
http: List[HTTPRequest]
|
|
66
66
|
path: str | None = None
|
|
67
67
|
download: str | None = None
|
|
68
|
+
name: str | None = None
|
|
68
69
|
use_random_port: bool = True
|
|
69
70
|
env: dict[str, str] | None = None
|
|
70
71
|
extra_env: dict[str, str] | None = None
|
|
@@ -77,6 +78,8 @@ class E2ECase:
|
|
|
77
78
|
build_modes: tuple[BuildMode, ...] | None = None
|
|
78
79
|
|
|
79
80
|
def __str__(self):
|
|
81
|
+
if self.name:
|
|
82
|
+
return self.name
|
|
80
83
|
if self.path:
|
|
81
84
|
return self.path
|
|
82
85
|
assert self.download is not None
|
|
@@ -86,6 +89,12 @@ class E2ECase:
|
|
|
86
89
|
return str(self)
|
|
87
90
|
|
|
88
91
|
|
|
92
|
+
BRO_BARBERSHOP_ARCHIVE_URL = (
|
|
93
|
+
"https://github.com/motopress/bro-barbershop/archive/"
|
|
94
|
+
"refs/heads/master.zip"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
89
98
|
@pytest.mark.e2e
|
|
90
99
|
@pytest.mark.asyncio
|
|
91
100
|
@pytest.mark.parametrize(
|
|
@@ -203,6 +212,41 @@ class E2ECase:
|
|
|
203
212
|
],
|
|
204
213
|
build_modes=(BuildMode.Wasmer,),
|
|
205
214
|
),
|
|
215
|
+
# Real WordPress theme from GitHub; validates custom theme activation.
|
|
216
|
+
E2ECase(
|
|
217
|
+
download=BRO_BARBERSHOP_ARCHIVE_URL,
|
|
218
|
+
name="wordpress_bro_barbershop_theme",
|
|
219
|
+
serve_pattern=(
|
|
220
|
+
r"listening addr"
|
|
221
|
+
),
|
|
222
|
+
http=[
|
|
223
|
+
HTTPRequest(
|
|
224
|
+
path="/",
|
|
225
|
+
expected_status=200,
|
|
226
|
+
)
|
|
227
|
+
],
|
|
228
|
+
use_random_port=False,
|
|
229
|
+
env={
|
|
230
|
+
"DB_NAME": "test",
|
|
231
|
+
"DB_USERNAME": "root",
|
|
232
|
+
"DB_HOST": "127.0.0.1",
|
|
233
|
+
"DB_PORT": "3306",
|
|
234
|
+
"DB_PASSWORD": "",
|
|
235
|
+
"SHIPIT_PHPIX": "true",
|
|
236
|
+
"SHIPIT_WP_VERSION": "6.9.4",
|
|
237
|
+
},
|
|
238
|
+
create_db=True,
|
|
239
|
+
create_wp_content_volume=True,
|
|
240
|
+
run_after_deploy=True,
|
|
241
|
+
commands=[
|
|
242
|
+
RunCommand("wp theme is-active bro-barbershop"),
|
|
243
|
+
RunCommand(
|
|
244
|
+
"wp option get stylesheet",
|
|
245
|
+
stdout_match=r"^bro-barbershop\s*$",
|
|
246
|
+
),
|
|
247
|
+
],
|
|
248
|
+
build_modes=(BuildMode.Wasmer,),
|
|
249
|
+
),
|
|
206
250
|
# WordPress skeleton in phpix mode (Wasmer only), validate memory cap.
|
|
207
251
|
E2ECase(
|
|
208
252
|
path="examples/php-wordpress",
|
|
@@ -230,6 +274,13 @@ class E2ECase:
|
|
|
230
274
|
serve_pattern=r"server is listening on",
|
|
231
275
|
http=[HTTPRequest(path="/", body_match=r"Test")],
|
|
232
276
|
),
|
|
277
|
+
# Static HTML app with browser JavaScript should not be treated as Node.
|
|
278
|
+
E2ECase(
|
|
279
|
+
path="examples/static-htmlwithjs",
|
|
280
|
+
serve_pattern=r"server is listening on",
|
|
281
|
+
http=[HTTPRequest(path="/", body_match=r"Static HTML with JS")],
|
|
282
|
+
build_modes=(BuildMode.Wasmer,),
|
|
283
|
+
),
|
|
233
284
|
# Staticfile provider serving content under site/
|
|
234
285
|
E2ECase(
|
|
235
286
|
path="examples/staticfile",
|
|
@@ -641,11 +692,16 @@ class E2ECase:
|
|
|
641
692
|
serve_pattern=r"Uvicorn running on .*",
|
|
642
693
|
http=[HTTPRequest(path="/", body_match=r"Welcome to Flask")],
|
|
643
694
|
),
|
|
644
|
-
# Python Django via Uvicorn WSGI
|
|
695
|
+
# Python Django via Uvicorn WSGI. Docker covers collectstatic.
|
|
645
696
|
E2ECase(
|
|
646
697
|
path="examples/python-django",
|
|
647
698
|
serve_pattern=r"Uvicorn running on .*",
|
|
648
699
|
http=[HTTPRequest(path="/", body_match=r"Django")],
|
|
700
|
+
build_modes=(
|
|
701
|
+
BuildMode.Local,
|
|
702
|
+
BuildMode.Wasmer,
|
|
703
|
+
BuildMode.WasmerAndDocker,
|
|
704
|
+
),
|
|
649
705
|
),
|
|
650
706
|
# Python ffmpeg demo (FastAPI), homepage is static HTML form
|
|
651
707
|
E2ECase(
|
|
@@ -776,6 +832,7 @@ async def test_end_to_end(
|
|
|
776
832
|
env=env,
|
|
777
833
|
timeout=180,
|
|
778
834
|
)
|
|
835
|
+
_print_run_command_output(command, cmd, result)
|
|
779
836
|
_assert_run_command(command, cmd, result)
|
|
780
837
|
return
|
|
781
838
|
|
|
@@ -1276,6 +1333,25 @@ async def _stop_process(proc: asyncio.subprocess.Process) -> None:
|
|
|
1276
1333
|
await proc.wait()
|
|
1277
1334
|
|
|
1278
1335
|
|
|
1336
|
+
def _print_run_command_output(
|
|
1337
|
+
command: RunCommand,
|
|
1338
|
+
cmd: list[str],
|
|
1339
|
+
result: CompletedCommand,
|
|
1340
|
+
) -> None:
|
|
1341
|
+
print(f"[run-command] {shlex.join(cmd)}")
|
|
1342
|
+
print(
|
|
1343
|
+
"[run-command] "
|
|
1344
|
+
f"expected_returncode={command.expected_returncode} "
|
|
1345
|
+
f"returncode={result.returncode}"
|
|
1346
|
+
)
|
|
1347
|
+
if result.stdout:
|
|
1348
|
+
print("[run-command stdout]")
|
|
1349
|
+
print(result.stdout, end="" if result.stdout.endswith("\n") else "\n")
|
|
1350
|
+
if result.stderr:
|
|
1351
|
+
print("[run-command stderr]")
|
|
1352
|
+
print(result.stderr, end="" if result.stderr.endswith("\n") else "\n")
|
|
1353
|
+
|
|
1354
|
+
|
|
1279
1355
|
def _assert_run_command(
|
|
1280
1356
|
command: RunCommand,
|
|
1281
1357
|
cmd: list[str],
|
|
@@ -123,6 +123,24 @@ def test_node_provider_detects_generic_node_example() -> None:
|
|
|
123
123
|
assert load_provider(path, Config()) is NodeProvider
|
|
124
124
|
|
|
125
125
|
|
|
126
|
+
def test_common_entry_without_package_json_needs_node_evidence(
|
|
127
|
+
tmp_path: Path,
|
|
128
|
+
) -> None:
|
|
129
|
+
(tmp_path / "app.js").write_text(
|
|
130
|
+
"""import http from "node:http";
|
|
131
|
+
|
|
132
|
+
http.createServer((_req, res) => {
|
|
133
|
+
res.end("ok");
|
|
134
|
+
}).listen(process.env.PORT || 8080);
|
|
135
|
+
"""
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
provider_config = NodeProvider.load_config(tmp_path, Config())
|
|
139
|
+
|
|
140
|
+
assert load_provider(tmp_path, Config()) is NodeProvider
|
|
141
|
+
assert provider_config.commands.start == "node app.js"
|
|
142
|
+
|
|
143
|
+
|
|
126
144
|
@pytest.mark.parametrize(
|
|
127
145
|
("example", "framework"),
|
|
128
146
|
[
|
|
@@ -332,7 +350,14 @@ def test_node_start_command_uses_package_main(tmp_path: Path) -> None:
|
|
|
332
350
|
|
|
333
351
|
|
|
334
352
|
def test_node_start_command_uses_common_entry_file(tmp_path: Path) -> None:
|
|
335
|
-
(tmp_path / "server.js").write_text(
|
|
353
|
+
(tmp_path / "server.js").write_text(
|
|
354
|
+
"""const http = require("http");
|
|
355
|
+
|
|
356
|
+
http.createServer((_req, res) => {
|
|
357
|
+
res.end("ok");
|
|
358
|
+
}).listen(8080);
|
|
359
|
+
"""
|
|
360
|
+
)
|
|
336
361
|
|
|
337
362
|
provider_config = NodeProvider.load_config(tmp_path, Config())
|
|
338
363
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|