| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,6 +25,7 @@ pn --help | |||
| 25 | 25 | ||
| 26 | 26 | # create a new sample app from the bundled templates | |
| 27 | 27 | pn init my_app | |
| 28 | + cd my_app | ||
| 28 | 29 | ||
| 29 | 30 | # run the Hello World example | |
| 30 | 31 | cd examples/hello-world && pn run android | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,8 +7,10 @@ the documented behavior never drifts from the code. | |||
| 7 | 7 | ## Subcommands | |
| 8 | 8 | ||
| 9 | 9 | - `pn init [name]`: scaffold a new project (creates `app/`, | |
| 10 | - `pythonnative.toml`, `.gitignore`). Flag: `--force` to overwrite | ||
| 11 | - existing files. See [Configuration](../guides/configuration.md). | ||
| 10 | + `pythonnative.toml`, `.gitignore`). With a name it creates `./<name>/` | ||
| 11 | + and scaffolds into it; without one it uses the current directory. | ||
| 12 | + Flag: `--force` to overwrite existing files or scaffold into a | ||
| 13 | + non-empty directory. See [Configuration](../guides/configuration.md). | ||
| 12 | 14 | - `pn doctor [android|ios]`: diagnose the local toolchain and validate | |
| 13 | 15 | `pythonnative.toml`. Exits non-zero when something will block a build. | |
| 14 | 16 | - `pn preview [component]`: render the app in a desktop (Tkinter) window | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,16 +9,20 @@ pn --help | |||
| 9 | 9 | ||
| 10 | 10 | ```bash | |
| 11 | 11 | pn init MyApp | |
| 12 | + cd MyApp | ||
| 12 | 13 | ``` | |
| 13 | 14 | ||
| 14 | - This scaffolds: | ||
| 15 | + This creates a `MyApp/` directory containing: | ||
| 15 | 16 | ||
| 16 | 17 | - `app/` with a minimal `main.py` | |
| 17 | 18 | - `pythonnative.toml`: your project configuration (app id, version, | |
| 18 | 19 | permissions, assets, and signing). See | |
| 19 | 20 | [Configuration](guides/configuration.md). | |
| 20 | 21 | - `.gitignore` | |
| 21 | 22 | ||
| 23 | + Run `pn init` without a name to scaffold into the current directory | ||
| 24 | + instead, named after it. | ||
| 25 | + | ||
| 22 | 26 | A minimal `app/main.py` looks like: | |
| 23 | 27 | ||
| 24 | 28 | ```python | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -28,6 +28,34 @@ is finding a different Python. Reactivate the venv or run | |||
| 28 | 28 | `pn init` won't clobber existing project files. Pass `--force` or | |
| 29 | 29 | remove the listed files first. | |
| 30 | 30 | ||
| 31 | + ### `Refusing to overwrite existing non-empty directory: my_app/` | ||
| 32 | + | ||
| 33 | + `pn init my_app` creates `my_app/` and scaffolds into it, so it stops | ||
| 34 | + when that directory already holds files. Pass `--force` to scaffold over | ||
| 35 | + it, choose a different name, or run `pn init` from inside the directory | ||
| 36 | + to use it as is. An existing but empty directory is fine. | ||
| 37 | + | ||
| 38 | + ### `Refusing to overwrite existing file: my_app` | ||
| 39 | + | ||
| 40 | + Something other than a directory is already at `./my_app`. `--force` | ||
| 41 | + won't help here, since `pn init` can't turn a file into a directory. | ||
| 42 | + Remove or rename it, or choose a different project name. | ||
| 43 | + | ||
| 44 | + ### `Refusing to treat a path as a project name: '../app'` | ||
| 45 | + | ||
| 46 | + `pn init` takes a single directory name, not a path, so the project always | ||
| 47 | + lands inside the current directory. Pass a plain name like `my_app`, or | ||
| 48 | + `cd` to the directory you want the project in and run `pn init` with no | ||
| 49 | + name at all. `--force` doesn't lift this one. | ||
| 50 | + | ||
| 51 | + ### `Refusing to scaffold through a link or outside the current directory: link` | ||
| 52 | + | ||
| 53 | + The name resolves somewhere other than a directory directly inside the | ||
| 54 | + current one, so `pn init` stops rather than writing through it. A symlink | ||
| 55 | + at `./link` is the usual cause. Scaffold into a real directory, or `cd` to | ||
| 56 | + the directory the link points at and run `pn init` with no name. `--force` | ||
| 57 | + doesn't lift this one either. | ||
| 58 | + | ||
| 31 | 59 | ### `Do not list 'pythonnative' in [requirements].packages` | |
| 32 | 60 | ||
| 33 | 61 | The CLI bundles the installed `pythonnative` package directly into | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,9 @@ | |||
| 2 | 2 | ||
| 3 | 3 | The console script `pn` (declared in `pyproject.toml`) dispatches to: | |
| 4 | 4 | ||
| 5 | - - `pn init [name]`: scaffold a new project (``pythonnative.toml`` + ``app/``). | ||
| 5 | + - `pn init [name]`: scaffold a new project (``pythonnative.toml`` + | ||
| 6 | + ``app/``) into ``./name/``, or into the current directory when no name | ||
| 7 | + is given. | ||
| 6 | 8 | - `pn doctor [platform]`: diagnose the local toolchain and config. | |
| 7 | 9 | - `pn preview [component]`: render the app in a desktop (Tkinter) window | |
| 8 | 10 | with Fast Refresh, the fast inner dev loop, no device required. | |
@@ -104,21 +106,62 @@ def _app_id_from_name(name: str) -> str: | |||
| 104 | 106 | ||
| 105 | 107 | ||
| 106 | 108 | def init_project(args: argparse.Namespace) -> None: | |
| 107 | - """Scaffold a new PythonNative project in the current directory. | ||
| 109 | + """Scaffold a new PythonNative project. | ||
| 108 | 110 | ||
| 109 | - Creates ``app/main.py``, ``pythonnative.toml``, and ``.gitignore``. | ||
| 110 | - Refuses to overwrite existing files unless ``--force`` is passed. | ||
| 111 | + Given a name, this creates ``./<name>/`` and scaffolds into it. Without | ||
| 112 | + one, it scaffolds into the current directory and names the project after | ||
| 113 | + it. Either way it writes ``app/main.py``, ``pythonnative.toml``, and | ||
| 114 | + ``.gitignore``. | ||
| 115 | + | ||
| 116 | + The name has to be a single directory name, so the project always lands | ||
| 117 | + inside the current directory. Anything that reads as a path, such as | ||
| 118 | + ``a/b``, ``..``, or ``/tmp/app``, is refused, and so is a name that | ||
| 119 | + resolves somewhere else, such as a symlink to another directory. | ||
| 120 | + | ||
| 121 | + It won't scaffold into a target directory that already holds files, and it | ||
| 122 | + won't overwrite any of the three paths above; pass ``--force`` to override | ||
| 123 | + both. An existing but empty target directory is fine. A plain file at | ||
| 124 | + ``./<name>`` is always refused, since ``--force`` can't turn it into a | ||
| 125 | + directory, and ``--force`` lifts neither of the rules above. | ||
| 111 | 126 | ||
| 112 | 127 | Args: | |
| 113 | 128 | args: Parsed namespace with ``name`` (optional) and ``force``. | |
| 114 | 129 | """ | |
| 115 | - cwd = Path.cwd() | ||
| 116 | - project_name: str = getattr(args, "name", None) or cwd.name | ||
| 130 | + name: Optional[str] = getattr(args, "name", None) | ||
| 117 | 131 | force: bool = getattr(args, "force", False) | |
| 118 | 132 | ||
| 119 | - app_dir = cwd / "app" | ||
| 120 | - config_path = cwd / CONFIG_FILENAME | ||
| 121 | - gitignore_path = cwd / ".gitignore" | ||
| 133 | + # Lexical check, before anything reads the filesystem. ``Path("..").name`` | ||
| 134 | + # is "..", so ".." needs naming explicitly; the rest (absolute, nested, | ||
| 135 | + # trailing separator, ".") fall out of the name check. | ||
| 136 | + if name and (name in (os.curdir, os.pardir) or Path(name).name != name): | ||
| 137 | + print(f"Refusing to treat a path as a project name: {name!r}. Use a single directory name like my_app.") | ||
| 138 | + sys.exit(1) | ||
| 139 | + | ||
| 140 | + cwd = Path.cwd() | ||
| 141 | + target = cwd / name if name else cwd | ||
| 142 | + project_name: str = name or cwd.name | ||
| 143 | + | ||
| 144 | + # A lexically clean name can still resolve elsewhere, and ``exists()`` and | ||
| 145 | + # ``is_dir()`` below follow symlinks. Check containment rather than just | ||
| 146 | + # ``is_symlink()`` so the whole class is closed, not one spelling of it. | ||
| 147 | + if name and (target.is_symlink() or target.resolve().parent != cwd.resolve()): | ||
| 148 | + print( | ||
| 149 | + f"Refusing to scaffold through a link or outside the current directory: {name}. " | ||
| 150 | + "Use a plain directory name." | ||
| 151 | + ) | ||
| 152 | + sys.exit(1) | ||
| 153 | + | ||
| 154 | + app_dir = target / "app" | ||
| 155 | + config_path = target / CONFIG_FILENAME | ||
| 156 | + gitignore_path = target / ".gitignore" | ||
| 157 | + | ||
| 158 | + if name and target.exists(): | ||
| 159 | + if not target.is_dir(): | ||
| 160 | + print(f"Refusing to overwrite existing file: {name}. Remove it or choose a different name.") | ||
| 161 | + sys.exit(1) | ||
| 162 | + if any(target.iterdir()) and not force: | ||
| 163 | + print(f"Refusing to overwrite existing non-empty directory: {name}/. Use --force to overwrite.") | ||
| 164 | + sys.exit(1) | ||
| 122 | 165 | ||
| 123 | 166 | if not force: | |
| 124 | 167 | existing = [ | |
@@ -142,8 +185,11 @@ def init_project(args: argparse.Namespace) -> None: | |||
| 142 | 185 | if force or not gitignore_path.exists(): | |
| 143 | 186 | gitignore_path.write_text(_GITIGNORE, encoding="utf-8") | |
| 144 | 187 | ||
| 145 | - print(f"Initialized PythonNative project in {cwd}.") | ||
| 146 | - print("Next: pn preview (desktop) | pn run android | pn run ios") | ||
| 188 | + print(f"Initialized PythonNative project in {target}.") | ||
| 189 | + next_steps = "pn preview (desktop) | pn run android | pn run ios" | ||
| 190 | + if name: | ||
| 191 | + next_steps = f"cd {name} | {next_steps}" | ||
| 192 | + print(f"Next: {next_steps}") | ||
| 147 | 193 | ||
| 148 | 194 | ||
| 149 | 195 | # ====================================================================== | |
@@ -873,8 +919,8 @@ def _build_parser() -> argparse.ArgumentParser: | |||
| 873 | 919 | subparsers = parser.add_subparsers() | |
| 874 | 920 | ||
| 875 | 921 | parser_init = subparsers.add_parser("init", help="Scaffold a new project") | |
| 876 | - parser_init.add_argument("name", nargs="?", help="Project name (defaults to current directory name)") | ||
| 877 | - parser_init.add_argument("--force", action="store_true", help="Overwrite existing files if present") | ||
| 922 | + parser_init.add_argument("name", nargs="?", help="Project name; creates ./<name>/ (default: current directory)") | ||
| 923 | + parser_init.add_argument("--force", action="store_true", help="Overwrite existing files or a non-empty directory") | ||
| 878 | 924 | parser_init.set_defaults(func=init_project) | |
| 879 | 925 | ||
| 880 | 926 | parser_doctor = subparsers.add_parser("doctor", help="Diagnose the local toolchain and config") | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,21 +36,22 @@ def test_cli_init_and_clean() -> None: | |||
| 36 | 36 | try: | |
| 37 | 37 | result = run_pn(["init", "MyApp"], tmpdir) | |
| 38 | 38 | assert result.returncode == 0, result.stderr | |
| 39 | - assert os.path.isdir(os.path.join(tmpdir, "app")) | ||
| 39 | + project_dir = os.path.join(tmpdir, "MyApp") | ||
| 40 | + assert os.path.isdir(os.path.join(project_dir, "app")) | ||
| 40 | 41 | ||
| 41 | - main_path = os.path.join(tmpdir, "app", "main.py") | ||
| 42 | + main_path = os.path.join(project_dir, "app", "main.py") | ||
| 42 | 43 | assert os.path.isfile(main_path) | |
| 43 | 44 | content = Path(main_path).read_text(encoding="utf-8") | |
| 44 | 45 | assert "def App(" in content | |
| 45 | 46 | assert "Stack.Navigator" in content | |
| 46 | 47 | ||
| 47 | - config_path = os.path.join(tmpdir, "pythonnative.toml") | ||
| 48 | + config_path = os.path.join(project_dir, "pythonnative.toml") | ||
| 48 | 49 | assert os.path.isfile(config_path) | |
| 49 | 50 | toml_text = Path(config_path).read_text(encoding="utf-8") | |
| 50 | 51 | assert 'id = "com.example.myapp"' in toml_text | |
| 51 | - assert os.path.isfile(os.path.join(tmpdir, ".gitignore")) | ||
| 52 | + assert os.path.isfile(os.path.join(project_dir, ".gitignore")) | ||
| 52 | 53 | # The legacy JSON config and requirements.txt are no longer scaffolded. | |
| 53 | - assert not os.path.exists(os.path.join(tmpdir, "pythonnative.json")) | ||
| 54 | + assert not os.path.exists(os.path.join(project_dir, "pythonnative.json")) | ||
| 54 | 55 | ||
| 55 | 56 | # clean on empty build is a no-op | |
| 56 | 57 | result = run_pn(["clean"], tmpdir) | |
@@ -76,6 +77,136 @@ def test_cli_init_refuses_overwrite() -> None: | |||
| 76 | 77 | shutil.rmtree(tmpdir, ignore_errors=True) | |
| 77 | 78 | ||
| 78 | 79 | ||
| 80 | + def test_cli_init_creates_named_directory(tmp_path: Path) -> None: | ||
| 81 | + result = run_pn(["init", "my_app"], str(tmp_path)) | ||
| 82 | + assert result.returncode == 0, result.stderr | ||
| 83 | + assert "cd my_app" in result.stdout | ||
| 84 | + | ||
| 85 | + project_dir = os.path.join(str(tmp_path), "my_app") | ||
| 86 | + assert os.path.isfile(os.path.join(project_dir, "app", "main.py")) | ||
| 87 | + assert os.path.isfile(os.path.join(project_dir, "pythonnative.toml")) | ||
| 88 | + assert os.path.isfile(os.path.join(project_dir, ".gitignore")) | ||
| 89 | + toml_text = Path(os.path.join(project_dir, "pythonnative.toml")).read_text(encoding="utf-8") | ||
| 90 | + assert 'id = "com.example.my_app"' in toml_text | ||
| 91 | + # Nothing is scaffolded beside the project directory. | ||
| 92 | + assert os.listdir(str(tmp_path)) == ["my_app"] | ||
| 93 | + | ||
| 94 | + | ||
| 95 | + def test_cli_init_without_name_uses_cwd(tmp_path: Path) -> None: | ||
| 96 | + project_dir = tmp_path / "widgets" | ||
| 97 | + project_dir.mkdir() | ||
| 98 | + | ||
| 99 | + result = run_pn(["init"], str(project_dir)) | ||
| 100 | + assert result.returncode == 0, result.stderr | ||
| 101 | + assert "cd " not in result.stdout | ||
| 102 | + | ||
| 103 | + assert os.path.isfile(os.path.join(str(project_dir), "app", "main.py")) | ||
| 104 | + assert os.path.isfile(os.path.join(str(project_dir), ".gitignore")) | ||
| 105 | + toml_text = Path(os.path.join(str(project_dir), "pythonnative.toml")).read_text(encoding="utf-8") | ||
| 106 | + assert 'id = "com.example.widgets"' in toml_text | ||
| 107 | + assert 'name = "widgets"' in toml_text | ||
| 108 | + | ||
| 109 | + | ||
| 110 | + def test_cli_init_refuses_non_empty_directory(tmp_path: Path) -> None: | ||
| 111 | + project_dir = tmp_path / "my_app" | ||
| 112 | + project_dir.mkdir() | ||
| 113 | + keeper = project_dir / "README.md" | ||
| 114 | + keeper.write_text("keep me\n", encoding="utf-8") | ||
| 115 | + | ||
| 116 | + result = run_pn(["init", "my_app"], str(tmp_path)) | ||
| 117 | + assert result.returncode != 0 | ||
| 118 | + assert "Refusing to overwrite" in result.stdout | ||
| 119 | + assert "non-empty directory" in result.stdout | ||
| 120 | + assert not os.path.exists(os.path.join(str(project_dir), "app")) | ||
| 121 | + assert keeper.read_text(encoding="utf-8") == "keep me\n" | ||
| 122 | + | ||
| 123 | + result = run_pn(["init", "my_app", "--force"], str(tmp_path)) | ||
| 124 | + assert result.returncode == 0, result.stderr | ||
| 125 | + assert os.path.isfile(os.path.join(str(project_dir), "app", "main.py")) | ||
| 126 | + # --force scaffolds over the directory; it doesn't empty it first. | ||
| 127 | + assert keeper.read_text(encoding="utf-8") == "keep me\n" | ||
| 128 | + | ||
| 129 | + | ||
| 130 | + def test_cli_init_accepts_existing_empty_directory(tmp_path: Path) -> None: | ||
| 131 | + project_dir = tmp_path / "my_app" | ||
| 132 | + project_dir.mkdir() | ||
| 133 | + | ||
| 134 | + result = run_pn(["init", "my_app"], str(tmp_path)) | ||
| 135 | + assert result.returncode == 0, result.stderr | ||
| 136 | + assert os.path.isfile(os.path.join(str(project_dir), "app", "main.py")) | ||
| 137 | + assert os.path.isfile(os.path.join(str(project_dir), "pythonnative.toml")) | ||
| 138 | + | ||
| 139 | + | ||
| 140 | + def test_cli_init_refuses_existing_file(tmp_path: Path) -> None: | ||
| 141 | + blocker = tmp_path / "my_app" | ||
| 142 | + blocker.write_text("not a project\n", encoding="utf-8") | ||
| 143 | + | ||
| 144 | + result = run_pn(["init", "my_app"], str(tmp_path)) | ||
| 145 | + assert result.returncode != 0 | ||
| 146 | + assert "Refusing to overwrite existing file" in result.stdout | ||
| 147 | + | ||
| 148 | + # --force can't turn a file into a directory, so it is refused too. | ||
| 149 | + result = run_pn(["init", "my_app", "--force"], str(tmp_path)) | ||
| 150 | + assert result.returncode != 0 | ||
| 151 | + assert "Refusing to overwrite existing file" in result.stdout | ||
| 152 | + assert blocker.read_text(encoding="utf-8") == "not a project\n" | ||
| 153 | + | ||
| 154 | + | ||
| 155 | + @pytest.mark.parametrize("name", ["{absolute}", "nested/my_app", "my_app/", ".", "..", "../", "a/.."]) | ||
| 156 | + def test_cli_init_rejects_path_like_names(tmp_path: Path, name: str) -> None: | ||
| 157 | + work_dir = tmp_path / "work" | ||
| 158 | + work_dir.mkdir() | ||
| 159 | + | ||
| 160 | + result = run_pn(["init", name.format(absolute=str(tmp_path / "elsewhere"))], str(work_dir)) | ||
| 161 | + assert result.returncode != 0 | ||
| 162 | + assert "Refusing to" in result.stdout | ||
| 163 | + assert "project name" in result.stdout | ||
| 164 | + # Nothing was created in the working directory or anywhere above it. | ||
| 165 | + assert os.listdir(str(work_dir)) == [] | ||
| 166 | + assert os.listdir(str(tmp_path)) == ["work"] | ||
| 167 | + | ||
| 168 | + | ||
| 169 | + def test_cli_init_force_does_not_escape_to_parent(tmp_path: Path) -> None: | ||
| 170 | + parent_config = tmp_path / "pythonnative.toml" | ||
| 171 | + parent_config.write_text("# hand-written\n", encoding="utf-8") | ||
| 172 | + work_dir = tmp_path / "work" | ||
| 173 | + work_dir.mkdir() | ||
| 174 | + | ||
| 175 | + result = run_pn(["init", "..", "--force"], str(work_dir)) | ||
| 176 | + assert result.returncode != 0 | ||
| 177 | + assert "Refusing to" in result.stdout | ||
| 178 | + # --force does not lift the single-name rule, so the parent is untouched. | ||
| 179 | + assert parent_config.read_text(encoding="utf-8") == "# hand-written\n" | ||
| 180 | + assert not os.path.exists(os.path.join(str(tmp_path), "app")) | ||
| 181 | + assert not os.path.exists(os.path.join(str(tmp_path), ".gitignore")) | ||
| 182 | + assert os.listdir(str(work_dir)) == [] | ||
| 183 | + | ||
| 184 | + | ||
| 185 | + @pytest.mark.parametrize("extra_args", [[], ["--force"]]) | ||
| 186 | + @pytest.mark.parametrize("populated", [True, False]) | ||
| 187 | + def test_cli_init_rejects_symlinked_target(tmp_path: Path, populated: bool, extra_args: List[str]) -> None: | ||
| 188 | + outside = tmp_path / "outside" | ||
| 189 | + outside.mkdir() | ||
| 190 | + outside_config = outside / "pythonnative.toml" | ||
| 191 | + if populated: | ||
| 192 | + outside_config.write_text("# hand-written\n", encoding="utf-8") | ||
| 193 | + before = sorted(os.listdir(str(outside))) | ||
| 194 | + | ||
| 195 | + work_dir = tmp_path / "work" | ||
| 196 | + work_dir.mkdir() | ||
| 197 | + os.symlink(str(outside), os.path.join(str(work_dir), "link")) | ||
| 198 | + | ||
| 199 | + result = run_pn(["init", "link"] + extra_args, str(work_dir)) | ||
| 200 | + assert result.returncode != 0 | ||
| 201 | + assert "Refusing to" in result.stdout | ||
| 202 | + # exists() and is_dir() follow symlinks, so the destination must stay untouched: | ||
| 203 | + # no new entries, and no rewrite of a config that was already there. | ||
| 204 | + assert sorted(os.listdir(str(outside))) == before | ||
| 205 | + if populated: | ||
| 206 | + assert outside_config.read_text(encoding="utf-8") == "# hand-written\n" | ||
| 207 | + assert os.listdir(str(work_dir)) == ["link"] | ||
| 208 | + | ||
| 209 | + | ||
| 79 | 210 | def test_cli_run_help_lists_flags() -> None: | |
| 80 | 211 | tmpdir = tempfile.mkdtemp(prefix="pn_cli_test_") | |
| 81 | 212 | try: | |
@@ -119,15 +250,16 @@ def test_cli_run_without_config_errors() -> None: | |||
| 119 | 250 | ||
| 120 | 251 | def test_cli_app_id_resolves(tmp_path: Path) -> None: | |
| 121 | 252 | assert run_pn(["init", "MyApp"], str(tmp_path)).returncode == 0 | |
| 122 | - result = run_pn(["app-id", "android"], str(tmp_path)) | ||
| 253 | + project_dir = str(tmp_path / "MyApp") | ||
| 254 | + result = run_pn(["app-id", "android"], project_dir) | ||
| 123 | 255 | assert result.returncode == 0, result.stderr | |
| 124 | 256 | assert result.stdout.strip() == "com.example.myapp" | |
| 125 | - assert run_pn(["app-id", "ios"], str(tmp_path)).stdout.strip() == "com.example.myapp" | ||
| 257 | + assert run_pn(["app-id", "ios"], project_dir).stdout.strip() == "com.example.myapp" | ||
| 126 | 258 | ||
| 127 | 259 | ||
| 128 | 260 | def test_cli_doctor_runs(tmp_path: Path) -> None: | |
| 129 | 261 | assert run_pn(["init", "MyApp"], str(tmp_path)).returncode == 0 | |
| 130 | - result = run_pn(["doctor", "android"], str(tmp_path)) | ||
| 262 | + result = run_pn(["doctor", "android"], str(tmp_path / "MyApp")) | ||
| 131 | 263 | assert "PythonNative doctor" in result.stdout | |
| 132 | 264 | # android-only doctor on a CI box without adb still produces warnings, not errors. | |
| 133 | 265 | assert result.returncode in (0, 1) | |
@@ -137,10 +269,11 @@ def test_cli_run_prepare_only_android_and_ios() -> None: | |||
| 137 | 269 | tmpdir = tempfile.mkdtemp(prefix="pn_cli_test_") | |
| 138 | 270 | try: | |
| 139 | 271 | assert run_pn(["init", "MyApp"], tmpdir).returncode == 0 | |
| 272 | + project_dir = os.path.join(tmpdir, "MyApp") | ||
| 140 | 273 | ||
| 141 | - result = run_pn(["run", "android", "--prepare-only", "--no-logs"], tmpdir) | ||
| 274 | + result = run_pn(["run", "android", "--prepare-only", "--no-logs"], project_dir) | ||
| 142 | 275 | assert result.returncode == 0, result.stderr | |
| 143 | - android_root = os.path.join(tmpdir, "build", "android", "android_template") | ||
| 276 | + android_root = os.path.join(project_dir, "build", "android", "android_template") | ||
| 144 | 277 | assert os.path.isdir(android_root) | |
| 145 | 278 | # Package relocated to the configured application id. | |
| 146 | 279 | relocated = os.path.join( | |
@@ -154,9 +287,9 @@ def test_cli_run_prepare_only_android_and_ios() -> None: | |||
| 154 | 287 | gradle = Path(os.path.join(android_root, "app", "build.gradle")).read_text(encoding="utf-8") | |
| 155 | 288 | assert "com.example.myapp" in gradle | |
| 156 | 289 | ||
| 157 | - result = run_pn(["run", "ios", "--prepare-only", "--no-logs"], tmpdir) | ||
| 290 | + result = run_pn(["run", "ios", "--prepare-only", "--no-logs"], project_dir) | ||
| 158 | 291 | assert result.returncode == 0, result.stderr | |
| 159 | - ios_root = os.path.join(tmpdir, "build", "ios", "ios_template") | ||
| 292 | + ios_root = os.path.join(project_dir, "build", "ios", "ios_template") | ||
| 160 | 293 | assert os.path.isdir(ios_root) | |
| 161 | 294 | info_plist = Path(os.path.join(ios_root, "ios_template", "Info.plist")).read_bytes() | |
| 162 | 295 | assert b"CFBundleDisplayName" in info_plist | |
| Back | FazBrowse Home | New Git URL |
0 commit comments