shipit-cli 0.21.2__tar.gz → 0.21.4__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.4}/PKG-INFO +17 -7
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/README.md +16 -6
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/pyproject.toml +1 -1
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/assets/wordpress/install.sh +11 -7
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/builders/docker.py +1 -1
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/cli.py +1 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/node.py +103 -7
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/wordpress.py +33 -5
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/runners/wasmer.py +15 -15
- shipit_cli-0.21.4/src/shipit/version.py +12 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_e2e.py +77 -1
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_node_provider.py +32 -7
- shipit_cli-0.21.2/src/shipit/version.py +0 -5
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/.gitignore +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/__init__.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/assets/node/optimize-node-modules.sh +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/assets/php/php.ini +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/assets/wordpress/.htaccess +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/assets/wordpress/start.php +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/assets/wordpress/wp-config.php +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/builders/__init__.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/builders/base.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/builders/local.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/generator.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/procfile.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/base.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/go.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/hugo.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/install_context.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/jekyll.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/laravel.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/mkdocs.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/node_static.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/php.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/python.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/registry.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/providers/staticfile.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/runners/__init__.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/runners/base.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/runners/local.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/shipit_types.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/ui.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/utils.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/src/shipit/volumes.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_cli_after_deploy.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_generate_shipit_examples.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_install_context.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_node_static_provider.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_php_provider.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_staticfile_provider.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_subdir.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_version.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_volumes.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/tests/test_wasmer_annotations.py +0 -0
- {shipit_cli-0.21.2 → shipit_cli-0.21.4}/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.4
|
|
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
|
|
@@ -26,6 +27,7 @@ from .install_context import (
|
|
|
26
27
|
|
|
27
28
|
NODE_MODULES_OPTIMIZER_ASSET = "node/optimize-node-modules.sh"
|
|
28
29
|
OPTIMIZE_DEPS_VERSION = "0.1.1"
|
|
30
|
+
NEXT_BUNDLE_VERSION = "1.0.0"
|
|
29
31
|
|
|
30
32
|
class PackageManager(Enum):
|
|
31
33
|
NPM = "npm"
|
|
@@ -394,7 +396,8 @@ class NodeFramework(Enum):
|
|
|
394
396
|
if self == NodeFramework.NEXT:
|
|
395
397
|
quoted_command = shlex.quote(build_command)
|
|
396
398
|
return package_manager.dlx_command(
|
|
397
|
-
f"next-bundle@
|
|
399
|
+
f"next-bundle@{NEXT_BUNDLE_VERSION} "
|
|
400
|
+
f"--build-command {quoted_command}"
|
|
398
401
|
)
|
|
399
402
|
return build_command
|
|
400
403
|
|
|
@@ -483,6 +486,46 @@ class NodeProvider:
|
|
|
483
486
|
"src/server.js",
|
|
484
487
|
"src/index.js",
|
|
485
488
|
)
|
|
489
|
+
NODE_BUILTIN_MODULES: ClassVar[tuple[str, ...]] = (
|
|
490
|
+
"assert",
|
|
491
|
+
"buffer",
|
|
492
|
+
"child_process",
|
|
493
|
+
"cluster",
|
|
494
|
+
"crypto",
|
|
495
|
+
"dgram",
|
|
496
|
+
"dns",
|
|
497
|
+
"events",
|
|
498
|
+
"fs",
|
|
499
|
+
"http",
|
|
500
|
+
"http2",
|
|
501
|
+
"https",
|
|
502
|
+
"module",
|
|
503
|
+
"net",
|
|
504
|
+
"os",
|
|
505
|
+
"path",
|
|
506
|
+
"perf_hooks",
|
|
507
|
+
"process",
|
|
508
|
+
"querystring",
|
|
509
|
+
"readline",
|
|
510
|
+
"stream",
|
|
511
|
+
"timers",
|
|
512
|
+
"tls",
|
|
513
|
+
"tty",
|
|
514
|
+
"url",
|
|
515
|
+
"util",
|
|
516
|
+
"v8",
|
|
517
|
+
"vm",
|
|
518
|
+
"worker_threads",
|
|
519
|
+
"zlib",
|
|
520
|
+
)
|
|
521
|
+
NODE_ENTRY_MARKER_PATTERN: ClassVar[re.Pattern[str]] = re.compile(
|
|
522
|
+
r"\bmodule\.exports\b"
|
|
523
|
+
r"|\bexports\."
|
|
524
|
+
r"|\b__dirname\b"
|
|
525
|
+
r"|\b__filename\b"
|
|
526
|
+
r"|\bprocess\.(?:env|argv|exit|cwd|platform|version)\b"
|
|
527
|
+
r"|\b[A-Za-z_$][\w$]*\.listen\s*\("
|
|
528
|
+
)
|
|
486
529
|
START_PROGRAMS = {
|
|
487
530
|
"edge",
|
|
488
531
|
"node",
|
|
@@ -598,12 +641,62 @@ class NodeProvider:
|
|
|
598
641
|
if (path / "package.json").is_file():
|
|
599
642
|
return DetectResult(cls.name(), 30)
|
|
600
643
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
644
|
+
if cls._common_entry_file(path, require_node_evidence=True):
|
|
645
|
+
return DetectResult(cls.name(), 30)
|
|
646
|
+
|
|
647
|
+
return None
|
|
604
648
|
|
|
649
|
+
@classmethod
|
|
650
|
+
def _common_entry_file(
|
|
651
|
+
cls,
|
|
652
|
+
path: Path,
|
|
653
|
+
*,
|
|
654
|
+
require_node_evidence: bool,
|
|
655
|
+
) -> Optional[str]:
|
|
656
|
+
for entry_file in cls.COMMON_ENTRY_FILES:
|
|
657
|
+
entry_path = path / entry_file
|
|
658
|
+
if not entry_path.is_file():
|
|
659
|
+
continue
|
|
660
|
+
if not require_node_evidence:
|
|
661
|
+
return entry_file
|
|
662
|
+
if cls._looks_like_node_entry(entry_path):
|
|
663
|
+
return entry_file
|
|
605
664
|
return None
|
|
606
665
|
|
|
666
|
+
@classmethod
|
|
667
|
+
def _looks_like_node_entry(cls, entry_path: Path) -> bool:
|
|
668
|
+
try:
|
|
669
|
+
source = entry_path.read_text(errors="ignore")
|
|
670
|
+
except OSError:
|
|
671
|
+
return False
|
|
672
|
+
|
|
673
|
+
first_line = source.splitlines()[0] if source else ""
|
|
674
|
+
has_node_shebang = (
|
|
675
|
+
first_line.startswith("#!")
|
|
676
|
+
and re.search(r"\bnode(?:js)?\b", first_line)
|
|
677
|
+
)
|
|
678
|
+
if has_node_shebang:
|
|
679
|
+
return True
|
|
680
|
+
|
|
681
|
+
if cls.NODE_ENTRY_MARKER_PATTERN.search(source):
|
|
682
|
+
return True
|
|
683
|
+
|
|
684
|
+
module_ref = cls._node_builtin_module_ref_pattern()
|
|
685
|
+
module_patterns = (
|
|
686
|
+
rf"\brequire\s*\(\s*['\"]{module_ref}['\"]\s*\)",
|
|
687
|
+
rf"\bimport\s*\(\s*['\"]{module_ref}['\"]\s*\)",
|
|
688
|
+
rf"\bfrom\s*['\"]{module_ref}['\"]",
|
|
689
|
+
rf"\bimport\s*['\"]{module_ref}['\"]",
|
|
690
|
+
)
|
|
691
|
+
return any(re.search(pattern, source) for pattern in module_patterns)
|
|
692
|
+
|
|
693
|
+
@classmethod
|
|
694
|
+
def _node_builtin_module_ref_pattern(cls) -> str:
|
|
695
|
+
modules = "|".join(
|
|
696
|
+
re.escape(module) for module in cls.NODE_BUILTIN_MODULES
|
|
697
|
+
)
|
|
698
|
+
return rf"(?:node:)?(?:{modules})(?:/[^'\"\s)]*)?"
|
|
699
|
+
|
|
607
700
|
@classmethod
|
|
608
701
|
def detect_package_manager(cls, path: Path) -> PackageManager:
|
|
609
702
|
package_json = cls.parse_package_json(path) or {}
|
|
@@ -921,9 +1014,12 @@ class NodeProvider:
|
|
|
921
1014
|
if isinstance(main, str) and main.strip():
|
|
922
1015
|
return cls._node_entry_command(main.strip())
|
|
923
1016
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1017
|
+
entry_file = cls._common_entry_file(
|
|
1018
|
+
path,
|
|
1019
|
+
require_node_evidence=not (path / "package.json").is_file(),
|
|
1020
|
+
)
|
|
1021
|
+
if entry_file:
|
|
1022
|
+
return cls._node_entry_command(entry_file)
|
|
927
1023
|
|
|
928
1024
|
return None
|
|
929
1025
|
|
|
@@ -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
|
[
|
|
@@ -200,7 +218,7 @@ def test_node_provider_detects_nextjs_runtime_app(tmp_path: Path) -> None:
|
|
|
200
218
|
assert load_provider(tmp_path, Config()) is NodeProvider
|
|
201
219
|
assert provider_config.framework == NodeFramework.NEXT
|
|
202
220
|
assert provider_config.build_command == (
|
|
203
|
-
"npx -y next-bundle@0.
|
|
221
|
+
"npx -y next-bundle@1.0.0 --build-command 'npm run build'"
|
|
204
222
|
)
|
|
205
223
|
assert provider_config.commands.start == "node server.mjs"
|
|
206
224
|
|
|
@@ -210,19 +228,19 @@ def test_node_provider_detects_nextjs_runtime_app(tmp_path: Path) -> None:
|
|
|
210
228
|
[
|
|
211
229
|
(
|
|
212
230
|
"package-lock.json",
|
|
213
|
-
"npx -y next-bundle@0.
|
|
231
|
+
"npx -y next-bundle@1.0.0 --build-command 'npm run build'",
|
|
214
232
|
),
|
|
215
233
|
(
|
|
216
234
|
"pnpm-lock.yaml",
|
|
217
|
-
"pnpm dlx next-bundle@0.
|
|
235
|
+
"pnpm dlx next-bundle@1.0.0 --build-command 'pnpm run build'",
|
|
218
236
|
),
|
|
219
237
|
(
|
|
220
238
|
"yarn.lock",
|
|
221
|
-
"yarn dlx next-bundle@0.
|
|
239
|
+
"yarn dlx next-bundle@1.0.0 --build-command 'yarn run build'",
|
|
222
240
|
),
|
|
223
241
|
(
|
|
224
242
|
"bun.lockb",
|
|
225
|
-
"bunx next-bundle@0.
|
|
243
|
+
"bunx next-bundle@1.0.0 --build-command 'bun run build'",
|
|
226
244
|
),
|
|
227
245
|
],
|
|
228
246
|
)
|
|
@@ -264,7 +282,7 @@ def test_nextjs_build_command_wraps_explicit_build_command(
|
|
|
264
282
|
provider_config = NodeProvider.load_config(tmp_path, base_config)
|
|
265
283
|
|
|
266
284
|
assert provider_config.build_command == (
|
|
267
|
-
"npx -y next-bundle@0.
|
|
285
|
+
"npx -y next-bundle@1.0.0 --build-command 'next build --debug'"
|
|
268
286
|
)
|
|
269
287
|
assert provider_config.commands.build == provider_config.build_command
|
|
270
288
|
|
|
@@ -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
|